使用Process.Start()从Windows服务中以不同的用户身份启动进程

我想定期从一个Windows服务指定的用户帐户运行一个任意的.NET EXE文件。

到目前为止,我已经得到了我的Windows服务与逻辑运行,以确定目标进程是什么,以及什么时候运行它。 目标进程以下列方式启动:

  1. Windows服务使用“pipe理员”凭据启动。
  2. 当时间到了,一个中间.NET进程被执行,参数详细说明哪个进程应该被启动(文件名,用户名,域名,密码)。
  3. 此进程创build一个新的System.Diagnostics.Process,将一个ProcessStartInfo对象与传递给它的参数进行关联,然后调用该进程对象上的Start()。

第一次发生这种情况时, 目标进程正常执行,然后正常closures 。 然而,随后的每一次,只要目标进程开始,它就会抛出错误“Application failed to initalize properly(0xc0000142)”。 重新启动Windows服务将允许进程再次成功运行(对于第一次执行)。

自然,目标是每次成功执行目标进程。

关于上面的步骤2:以不同的用户身份运行进程.NET调用win32函数CreateProcessWithLogonW。 此function需要一个窗口句柄才能login指定的用户。由于Windows服务未以交互模式运行,因此它没有窗口句柄。 这个中间过程解决了这个问题,因为它有一个窗口句柄可以传递给目标进程。

请不要使用psexec或Windows任务计划程序的build议。 我已经接受了我的生活,包括以上述方式解决问题。

我似乎有一个工作实现(Works On My Machine(TM))在以下情况下:

batch file,.NET控制台程序集,.NET Windows窗体应用程序。

就是这样:

我有一个Windows服务作为pipe理员用户运行。 我将以下策略添加到pipe理员用户:

  • 作为服务login
  • 作为操作系统的一部分
  • 调整进程的内存配额
  • replace进程级别令牌

这些策略可以通过打开控制面板/pipe理工具/本地安全策略/用户权限分配来添加。 一旦设置,策略在下次login之前不会生效。 您可以使用另一个用户,而不是pipe理员,这可能会使事情有点安全:)

现在,我的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. login用户
  2. 将给定的标记转换为主标记
  3. 使用此令牌执行该过程
  4. 完成后closures手柄。

这是我的机器的新鲜开发代码,没有办法在生产环境中使用。 这里的代码仍然是越野车 – 对于初学者来说:我不确定手柄是否在正确的位置closures,并且上面定义的一些互操作函数是不需要的。 单独的启动过程也真的让我恼火。 理想情况下,我想所有这个工作的东西包装在一个程序集使用我们的API以及这项服务。 如果有人在这里有任何build议,他们将不胜感激。

我不会build议psexec和任务计划者。 但是,你看过Sudowin吗?

它几乎完全符合你的要求,除了在执行过程之前要求input密码。

另外,作为开放源代码和全部,您可以看到它是如何从关联的服务中一次又一次地执行进程的。

第一次调用失败的原因很可能是因为它使用了“默认”安全描述符(不pipe是什么)。

来自msdn :

lpProcessAttributes [in,可选]

指向SECURITY_ATTRIBUTES结构的指针,用于指定新进程对象的安全描述符,并确定subprocess是否可以inheritance进程返回的句柄。 如果lpProcessAttributes为NULL或lpSecurityDescriptor为NULL,则进程将获取默认安全描述符,并且该句柄不能被inheritance。 默认安全描述符是hToken参数中引用的用户的安全描述符。 此安全描述符可能不允许调用者访问, 在这种情况下,进程运行后可能不会再次打开。 进程句柄是有效的,并将继续有完整的访问权限。

我猜CreateProcessWithLogonW正在创build这个默认的安全描述符(在任何情况下,我没有指定一个)。

时间开始Interopping …

只是一个猜测 – 你使用LoadUserProfile = true与启动信息? CreateProcessWithLogonW默认情况下不加载用户registryconfiguration单元,除非您告诉它。

你不需要一个窗口句柄来使用CreateProcessWithLogonW,我不知道你的信息来自哪里。

应用程序未能初始化错误有很多原因,但它几乎总是与安全或耗尽的用户资源有关。 如果没有更多关于你正在运行的东西以及你正在运行的环境的信息,诊断这是非常困难的。但是要看的东西是:用户提供的是否具有访问可执行文件目录的正确权限,用户有权访问它启动的窗口工作站和桌面,它是否具有它需要在初始化时加载的任何dll文件的正确权限等。

我刚刚阅读了这个评论在MSDN( http://msdn.microsoft.com/en-us/library/ms682431 ( VS.85 ) .aspx ):

不要使用此function调用用户应用程序! ChristianWimmer |
编辑| 显示历史请稍候如果您打算打电话给提供文档编辑和类似的东西(如Word)的用户模式应用程序,所有未保存的数据将会丢失。 这是因为通常的closures顺序不适用于使用CreateProcessWithLogonW启动的进程。 通过这种方式,启动的应用程序不会获取WM_QUERYENDSESSION,WM_ENDSESSION和最重要的WM_QUIT消息。 所以他们不要求保存数据或清理他们的东西。 他们会毫不留情地退出。 此function不是用户友好的,应谨慎使用。

这只是“糟糕的用户体验”。 没有人期待。

这可以解释我所观察到的:第一次工作。 每一次都失败 这加强了我的信念,即内部没有正确清理某些东西

您说“Windows服务是使用”pipe理员“凭据启动的”

你是指实际的“pipe理员”帐户,还是“pipe理员”组中的用户? 以pipe理员身份启动服务为我解决了这个问题。

我有类似的问题,当我试图启动PhantomJS二进制与“runas” – 在Windows服务的动词。 我现在已经用下面的程序解决了这个问题:

  • 模拟用户
  • 启动过程(无UI)
  • 模仿回来

您可以使用模仿类模仿。 在ProcessStartInfo中设置以下属性也很重要,所以应用程序不会尝试访问Windows UI:

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