c# - visual - windows service教學




使用Process.Start()以Windows服務中的不同用戶身份啟動進程 (6)

我想定期在Windows服務的指定用戶帳戶下運行任意.NET exe。

到目前為止,我已經讓我的Windows服務運行邏輯來決定目標進程是什麼,以及何時運行它。 目標進程以下列方式啟動:

  1. Windows服務使用“管理員”憑據啟動。
  2. 到時候,執行一個中間的.NET進程,其參數詳細說明應該啟動哪個進程(文件名,用戶名,域名,密碼)。
  3. 此過程創建一個新的System.Diagnostics.Process,將ProcessStartInfo對象與傳遞給它的參數相關聯,然後在進程對像上調用Start()。

第一次發生這種情況時, 目標進程執行正常,然後正常關閉 。 然而,每次後續時間,只要目標進程啟動,它就會拋出錯誤“應用程序無法正常運行(0xc0000142)”。 重新啟動Windows服務將允許進程再次成功運行(第一次執行)。

當然,目標是每次都成功執行目標進程。

關於上面的步驟2:要以不同的用戶身份運行進程.NET調用win32函數CreateProcessWithLogonW。 此函數需要一個窗口句柄來記錄指定的用戶。由於Windows服務未在交互模式下運行,因此它沒有窗口句柄。 這個中間過程解決了這個問題,因為它有一個可以傳遞給目標進程的窗口句柄。

請不要使用psexec或windows任務規劃器的建議。 我已經接受了我的生活,包括以上述方式解決問題。


你說“Windows服務是使用”管理員“憑據啟動的”

您是指實際的“管理員”帳戶,還是“管理員”組中的用戶? 以管理員身份啟動服務為我解決了這個問題。


只是一個猜測 - 您是否在啟動信息中使用LoadUserProfile = true? 默認情況下,CreateProcessWithLogonW不會加載用戶註冊表配置單元,除非您告訴它。


我不會建議psexec和任務規劃師。 但是,你看過Sudowin嗎?

它幾乎完全符合您的要求,但在執行流程之前需要輸入密碼。

此外,作為開源和所有,你可以看到它如何從相關的服務時間和次數執行進程。


我似乎有一個工作實現(Works On My Machine(TM))用於以下場景:

批處理文件,.NET控制台程序集,.NET Windows窗體應用程序。

就是這樣:

我有一個以管理員用戶身份運行的Windows服務。 我將以下策略添加到管理員用戶:

  • 以服務身份登錄
  • 作為操作系統的一部分
  • 調整進程的內存配額
  • 替換進程級令牌

可以通過打開“控制面板/管理工具/本地安全策略/用戶權限分配”來添加這些策略。 設置完成後,策略在下次登錄後才會生效。 您可以使用其他用戶而不是管理員,這可能會使事情更安全:)

現在,我的Windows服務具有與其他用戶一樣啟動作業所需的權限。 當需要啟動一個作業時,該服務會執行一個單獨的程序集(“Starter”.NET控制台程序集),它會為我啟動該程序。

位於Windows服務中的以下代碼執行我的“Starter”控制台程序集:

Process proc = null;
System.Diagnostics.ProcessStartInfo info;
string domain = string.IsNullOrEmpty(row.Domain) ? "." : row.Domain;
info = new ProcessStartInfo("Starter.exe");
info.Arguments = cmd + " " + domain + " " + username + " " + password + " " + args;
info.WorkingDirectory = Path.GetDirectoryName(cmd);
info.UseShellExecute = false;
info.RedirectStandardError = true;
info.RedirectStandardOutput = true;
proc = System.Diagnostics.Process.Start(info);

然後控制台程序集通過互操作調用啟動目標進程:

class Program
{
    #region Interop

    [StructLayout(LayoutKind.Sequential)]
    public struct LUID
    {
        public UInt32 LowPart;
        public Int32 HighPart;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct LUID_AND_ATTRIBUTES
    {
        public LUID Luid;
        public UInt32 Attributes;
    }

    public struct TOKEN_PRIVILEGES
    {
        public UInt32 PrivilegeCount;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
        public LUID_AND_ATTRIBUTES[] Privileges;
    }

    enum TOKEN_INFORMATION_CLASS
    {
        TokenUser = 1,
        TokenGroups,
        TokenPrivileges,
        TokenOwner,
        TokenPrimaryGroup,
        TokenDefaultDacl,
        TokenSource,
        TokenType,
        TokenImpersonationLevel,
        TokenStatistics,
        TokenRestrictedSids,
        TokenSessionId,
        TokenGroupsAndPrivileges,
        TokenSessionReference,
        TokenSandBoxInert,
        TokenAuditPolicy,
        TokenOrigin,
        TokenElevationType,
        TokenLinkedToken,
        TokenElevation,
        TokenHasRestrictions,
        TokenAccessInformation,
        TokenVirtualizationAllowed,
        TokenVirtualizationEnabled,
        TokenIntegrityLevel,
        TokenUIAccess,
        TokenMandatoryPolicy,
        TokenLogonSid,
        MaxTokenInfoClass
    }

    [Flags]
    enum CreationFlags : uint
    {
        CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
        CREATE_DEFAULT_ERROR_MODE = 0x04000000,
        CREATE_NEW_CONSOLE = 0x00000010,
        CREATE_NEW_PROCESS_GROUP = 0x00000200,
        CREATE_NO_WINDOW = 0x08000000,
        CREATE_PROTECTED_PROCESS = 0x00040000,
        CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000,
        CREATE_SEPARATE_WOW_VDM = 0x00001000,
        CREATE_SUSPENDED = 0x00000004,
        CREATE_UNICODE_ENVIRONMENT = 0x00000400,
        DEBUG_ONLY_THIS_PROCESS = 0x00000002,
        DEBUG_PROCESS = 0x00000001,
        DETACHED_PROCESS = 0x00000008,
        EXTENDED_STARTUPINFO_PRESENT = 0x00080000
    }

    public enum TOKEN_TYPE
    {
        TokenPrimary = 1,
        TokenImpersonation
    }

    public enum SECURITY_IMPERSONATION_LEVEL
    {
        SecurityAnonymous,
        SecurityIdentification,
        SecurityImpersonation,
        SecurityDelegation
    }

    [Flags]
    enum LogonFlags
    {
        LOGON_NETCREDENTIALS_ONLY = 2,
        LOGON_WITH_PROFILE = 1
    }

    enum LOGON_TYPE
    {
        LOGON32_LOGON_INTERACTIVE = 2,
        LOGON32_LOGON_NETWORK,
        LOGON32_LOGON_BATCH,
        LOGON32_LOGON_SERVICE,
        LOGON32_LOGON_UNLOCK = 7,
        LOGON32_LOGON_NETWORK_CLEARTEXT,
        LOGON32_LOGON_NEW_CREDENTIALS
    }

    enum LOGON_PROVIDER
    {
        LOGON32_PROVIDER_DEFAULT,
        LOGON32_PROVIDER_WINNT35,
        LOGON32_PROVIDER_WINNT40,
        LOGON32_PROVIDER_WINNT50
    }

    #region _SECURITY_ATTRIBUTES
    //typedef struct _SECURITY_ATTRIBUTES {  
    //    DWORD nLength;  
    //    LPVOID lpSecurityDescriptor;  
    //    BOOL bInheritHandle;
    //} SECURITY_ATTRIBUTES,  *PSECURITY_ATTRIBUTES,  *LPSECURITY_ATTRIBUTES;
    #endregion
    struct SECURITY_ATTRIBUTES
    {
        public uint Length;
        public IntPtr SecurityDescriptor;
        public bool InheritHandle;
    }

    [Flags] enum SECURITY_INFORMATION : uint
    {
        OWNER_SECURITY_INFORMATION        = 0x00000001,
        GROUP_SECURITY_INFORMATION        = 0x00000002,
        DACL_SECURITY_INFORMATION         = 0x00000004,
        SACL_SECURITY_INFORMATION         = 0x00000008,
        UNPROTECTED_SACL_SECURITY_INFORMATION = 0x10000000,
        UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000,
        PROTECTED_SACL_SECURITY_INFORMATION   = 0x40000000,
        PROTECTED_DACL_SECURITY_INFORMATION   = 0x80000000
    }

    #region _SECURITY_DESCRIPTOR
    //typedef struct _SECURITY_DESCRIPTOR {
    //  UCHAR  Revision;
    //  UCHAR  Sbz1;
    //  SECURITY_DESCRIPTOR_CONTROL  Control;
    //  PSID  Owner;
    //  PSID  Group;
    //  PACL  Sacl;
    //  PACL  Dacl;
    //} SECURITY_DESCRIPTOR, *PISECURITY_DESCRIPTOR;
    #endregion
    [StructLayoutAttribute(LayoutKind.Sequential)]
    struct SECURITY_DESCRIPTOR
    {
        public byte revision;
        public byte size;
        public short control; // public SECURITY_DESCRIPTOR_CONTROL control;
        public IntPtr owner;
        public IntPtr group;
        public IntPtr sacl;
        public IntPtr dacl;
    }

    #region _STARTUPINFO
    //typedef struct _STARTUPINFO {  
    //    DWORD cb;  
    //    LPTSTR lpReserved;  
    //    LPTSTR lpDesktop;  
    //    LPTSTR lpTitle;  
    //    DWORD dwX;  
    //    DWORD dwY;  
    //    DWORD dwXSize;  
    //    DWORD dwYSize;  
    //    DWORD dwXCountChars;  
    //    DWORD dwYCountChars;  
    //    DWORD dwFillAttribute;  
    //    DWORD dwFlags;  
    //    WORD wShowWindow;  
    //    WORD cbReserved2;  
    //    LPBYTE lpReserved2;  
    //    HANDLE hStdInput;  
    //    HANDLE hStdOutput;  
    //    HANDLE hStdError; 
    //} STARTUPINFO,  *LPSTARTUPINFO;
    #endregion
    struct STARTUPINFO
    {
        public uint cb;
        [MarshalAs(UnmanagedType.LPTStr)]
        public string Reserved;
        [MarshalAs(UnmanagedType.LPTStr)]
        public string Desktop;
        [MarshalAs(UnmanagedType.LPTStr)]
        public string Title;
        public uint X;
        public uint Y;
        public uint XSize;
        public uint YSize;
        public uint XCountChars;
        public uint YCountChars;
        public uint FillAttribute;
        public uint Flags;
        public ushort ShowWindow;
        public ushort Reserverd2;
        public byte bReserverd2;
        public IntPtr StdInput;
        public IntPtr StdOutput;
        public IntPtr StdError;
    }

    #region _PROCESS_INFORMATION
    //typedef struct _PROCESS_INFORMATION {  
    //  HANDLE hProcess;  
    //  HANDLE hThread;  
    //  DWORD dwProcessId;  
    //  DWORD dwThreadId; } 
    //  PROCESS_INFORMATION,  *LPPROCESS_INFORMATION;
    #endregion
    [StructLayout(LayoutKind.Sequential)]
    struct PROCESS_INFORMATION
    {
        public IntPtr Process;
        public IntPtr Thread;
        public uint ProcessId;
        public uint ThreadId;
    }

    [DllImport("advapi32.dll", SetLastError = true)]
    static extern bool InitializeSecurityDescriptor(IntPtr pSecurityDescriptor, uint dwRevision);
    const uint SECURITY_DESCRIPTOR_REVISION = 1;

    [DllImport("advapi32.dll", SetLastError = true)]
    static extern bool SetSecurityDescriptorDacl(ref SECURITY_DESCRIPTOR sd, bool daclPresent, IntPtr dacl, bool daclDefaulted);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    extern static bool DuplicateTokenEx(
        IntPtr hExistingToken,
        uint dwDesiredAccess,
        ref SECURITY_ATTRIBUTES lpTokenAttributes,
        SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
        TOKEN_TYPE TokenType,
        out IntPtr phNewToken);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool LogonUser(
        string lpszUsername,
        string lpszDomain,
        string lpszPassword,
        int dwLogonType,
        int dwLogonProvider,
        out IntPtr phToken
        );

    #region GetTokenInformation
    //BOOL WINAPI GetTokenInformation(
    //  __in       HANDLE TokenHandle,
    //  __in       TOKEN_INFORMATION_CLASS TokenInformationClass,
    //  __out_opt  LPVOID TokenInformation,
    //  __in       DWORD TokenInformationLength,
    //  __out      PDWORD ReturnLength
    //);
    #endregion
    [DllImport("advapi32.dll", SetLastError = true)]
    static extern bool GetTokenInformation(
        IntPtr TokenHandle,
        TOKEN_INFORMATION_CLASS TokenInformationClass,
        IntPtr TokenInformation,
        int TokenInformationLength,
        out int ReturnLength
        );


    #region CreateProcessAsUser
    //        BOOL WINAPI CreateProcessAsUser(
    //  __in_opt     HANDLE hToken,
    //  __in_opt     LPCTSTR lpApplicationName,
    //  __inout_opt  LPTSTR lpCommandLine,
    //  __in_opt     LPSECURITY_ATTRIBUTES lpProcessAttributes,
    //  __in_opt     LPSECURITY_ATTRIBUTES lpThreadAttributes,
    //  __in         BOOL bInheritHandles,
    //  __in         DWORD dwCreationFlags,
    //  __in_opt     LPVOID lpEnvironment,
    //  __in_opt     LPCTSTR lpCurrentDirectory,
    //  __in         LPSTARTUPINFO lpStartupInfo,
    //  __out        LPPROCESS_INFORMATION lpProcessInformation);
    #endregion
    [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    static extern bool CreateProcessAsUser(
        IntPtr Token, 
        [MarshalAs(UnmanagedType.LPTStr)] string ApplicationName,
        [MarshalAs(UnmanagedType.LPTStr)] string CommandLine,
        ref SECURITY_ATTRIBUTES ProcessAttributes, 
        ref SECURITY_ATTRIBUTES ThreadAttributes, 
        bool InheritHandles,
        uint CreationFlags, 
        IntPtr Environment, 
        [MarshalAs(UnmanagedType.LPTStr)] string CurrentDirectory, 
        ref STARTUPINFO StartupInfo, 
        out PROCESS_INFORMATION ProcessInformation);

    #region CloseHandle
    //BOOL WINAPI CloseHandle(
    //      __in          HANDLE hObject
    //        );
    #endregion
    [DllImport("Kernel32.dll")]
    extern static int CloseHandle(IntPtr handle);

    [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
    internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);

    [DllImport("advapi32.dll", SetLastError = true)]
    internal static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    internal struct TokPriv1Luid
    {
        public int Count;
        public long Luid;
        public int Attr;
    }

    //static internal const int TOKEN_QUERY = 0x00000008;
    internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
    //static internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;

    internal const int TOKEN_QUERY = 0x00000008;
    internal const int TOKEN_DUPLICATE = 0x0002;
    internal const int TOKEN_ASSIGN_PRIMARY = 0x0001;

    #endregion

    [STAThread]
    static void Main(string[] args)
    {
        string username, domain, password, applicationName;
        username = args[2];
        domain = args[1];
        password = args[3];
        applicationName = @args[0];

        IntPtr token = IntPtr.Zero;
        IntPtr primaryToken = IntPtr.Zero;
        try
        {
            bool result = false;

            result = LogonUser(username, domain, password, (int)LOGON_TYPE.LOGON32_LOGON_NETWORK, (int)LOGON_PROVIDER.LOGON32_PROVIDER_DEFAULT, out token);
            if (!result)
            {
                int winError = Marshal.GetLastWin32Error();
            }

            string commandLine = null;

            #region security attributes
            SECURITY_ATTRIBUTES processAttributes = new SECURITY_ATTRIBUTES();

            SECURITY_DESCRIPTOR sd = new SECURITY_DESCRIPTOR();
            IntPtr ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(sd));
            Marshal.StructureToPtr(sd, ptr, false);
            InitializeSecurityDescriptor(ptr, SECURITY_DESCRIPTOR_REVISION);
            sd = (SECURITY_DESCRIPTOR)Marshal.PtrToStructure(ptr, typeof(SECURITY_DESCRIPTOR));

            result = SetSecurityDescriptorDacl(ref sd, true, IntPtr.Zero, false);
            if (!result)
            {
                int winError = Marshal.GetLastWin32Error();
            }

            primaryToken = new IntPtr();
            result = DuplicateTokenEx(token, 0, ref processAttributes, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, out primaryToken);
            if (!result)
            {
                int winError = Marshal.GetLastWin32Error();
            }
            processAttributes.SecurityDescriptor = ptr;
            processAttributes.Length = (uint)Marshal.SizeOf(sd);
            processAttributes.InheritHandle = true;
            #endregion

            SECURITY_ATTRIBUTES threadAttributes = new SECURITY_ATTRIBUTES();
            threadAttributes.SecurityDescriptor = IntPtr.Zero;
            threadAttributes.Length = 0;
            threadAttributes.InheritHandle = false;

            bool inheritHandles = true;
            //CreationFlags creationFlags = CreationFlags.CREATE_DEFAULT_ERROR_MODE;
            IntPtr environment = IntPtr.Zero;
            string currentDirectory = currdir;

            STARTUPINFO startupInfo = new STARTUPINFO();
            startupInfo.Desktop = "";

            PROCESS_INFORMATION processInformation;

            result = CreateProcessAsUser(primaryToken, applicationName, commandLine, ref processAttributes, ref threadAttributes, inheritHandles, 16, environment, currentDirectory, ref startupInfo, out processInformation);
            if (!result)
            {
                int winError = Marshal.GetLastWin32Error();
                File.AppendAllText(logfile, DateTime.Now.ToLongTimeString() + " " + winError + Environment.NewLine);
            }
        }
        catch
        {
            int winError = Marshal.GetLastWin32Error();
            File.AppendAllText(logfile, DateTime.Now.ToLongTimeString() + " " + winError + Environment.NewLine);
        }
        finally
        {
            if (token != IntPtr.Zero)
            {
                int x = CloseHandle(token);
                if (x == 0)
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                x = CloseHandle(primaryToken);
                if (x == 0)
                    throw new Win32Exception(Marshal.GetLastWin32Error());
            }
        }
    }

基本程序是:

  1. 記錄用戶
  2. 將給定的令牌轉換為主令牌
  3. 使用此令牌,執行該過程
  4. 完成後關閉手柄。

這是我的機器新鮮的開發代碼,並沒有準備好在生產環境中使用。 這裡的代碼仍然是錯誤的 - 對於初學者:我不確定句柄是否在正確的位置關閉,並且上面定義了一些不需要的互操作函數。 單獨的啟動程序也讓我很煩。 理想情況下,我希望所有這些Job包裝在一個程序集中,以便從我們的API以及此服務中使用。 如果有人在這裡有任何建議,他們將不勝感激。


當我嘗試在Windows服務中使用“runas”-verb啟動PhantomJS-binary時,我遇到了類似的問題。 我現在使用以下過程解決了問題:

  • 冒充用戶
  • 啟動流程(無UI)
  • 冒充回來

您可以使用Impersonator-Class進行模擬。 在ProcessStartInfo中設置以下屬性也很重要,因此應用程序不會嘗試訪問Windows UI:

var processStartInfo = new ProcessStartInfo()
{
    FileName = [email protected]"{assemblyFolder}\PhantomJS\phantomjs.exe",
    Arguments = $"--webdriver={port}",
    RedirectStandardOutput = true,
    RedirectStandardError = true,
    RedirectStandardInput = true,
    UseShellExecute = false,
    CreateNoWindow = true,
    ErrorDialog = false,
    WindowStyle = ProcessWindowStyle.Hidden
};

第一次調用失敗的原因很可能是因為它使用了“默認”安全描述符(無論是什麼)。

來自msdn

lpProcessAttributes [in,optional]

指向SECURITY_ATTRIBUTES結構的指針,該結構指定新進程對象的安全描述符,並確定子進程是否可以繼承返回的進程句柄。 如果lpProcessAttributes為NULL或lpSecurityDescriptor為NULL,則進程將獲取默認安全描述符,並且不能繼承句柄。 默認安全描述符是hToken參數中引用的用戶的安全描述符。 此安全描述符可能不允許調用者訪問, 在這種情況下,該進程在運行後可能不會再次打開。 進程句柄有效,並將繼續具有完全訪問權限。

我猜CreateProcessWithLogonW正在創建這個默認的安全描述符(無論如何,我沒有指定一個)。

是時候開始互操作......





service