CreateProcessAsUser在活动会话中创建窗口

我正在使用Windows服务中的CreateProcessAsUser( 我们可以保持主题并假设我有充分的理由这样做 )。 与其他人在这里问的问题相反,我在我的活动终端会话(会话1)中获得了一个窗口,而不是与服务(会话0)相同的会话 – 这是不可取的。

我挪用了斯科特艾伦的代码 ; 并想出以下内容。 值得注意的变化是“恢复自我”,“CREATE_NO_WINDOW”和命令行参数支持。

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Security; using System.Runtime.InteropServices; using System.Diagnostics; using System.Security.Principal; using System.ComponentModel; using System.IO; namespace SourceCode.Runtime.ChildProcessService { [SuppressUnmanagedCodeSecurity] class NativeMethods { [StructLayout(LayoutKind.Sequential)] public struct STARTUPINFO { public Int32 cb; public string lpReserved; public string lpDesktop; public string lpTitle; public Int32 dwX; public Int32 dwY; public Int32 dwXSize; public Int32 dwXCountChars; public Int32 dwYCountChars; public Int32 dwFillAttribute; public Int32 dwFlags; public Int16 wShowWindow; public Int16 cbReserved2; public IntPtr lpReserved2; public IntPtr hStdInput; public IntPtr hStdOutput; public IntPtr hStdError; } [StructLayout(LayoutKind.Sequential)] public struct PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; public Int32 dwProcessID; public Int32 dwThreadID; } [StructLayout(LayoutKind.Sequential)] public struct SECURITY_ATTRIBUTES { public Int32 Length; public IntPtr lpSecurityDescriptor; public bool bInheritHandle; } public enum SECURITY_IMPERSONATION_LEVEL { SecurityAnonymous, SecurityIdentification, SecurityImpersonation, SecurityDelegation } public enum TOKEN_TYPE { TokenPrimary = 1, TokenImpersonation } public const int GENERIC_ALL_ACCESS = 0x10000000; public const int CREATE_NO_WINDOW = 0x08000000; [ DllImport("kernel32.dll", EntryPoint = "CloseHandle", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall) ] public static extern bool CloseHandle(IntPtr handle); [ DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall) ] public static extern bool CreateProcessAsUser(IntPtr hToken, string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandle, Int32 dwCreationFlags, IntPtr lpEnvrionment, string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, ref PROCESS_INFORMATION lpProcessInformation); [ DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx") ] public static extern bool DuplicateTokenEx(IntPtr hExistingToken, Int32 dwDesiredAccess, ref SECURITY_ATTRIBUTES lpThreadAttributes, Int32 ImpersonationLevel, Int32 dwTokenType, ref IntPtr phNewToken); public static Process CreateProcessAsUser(string filename, string args) { var hToken = WindowsIdentity.GetCurrent().Token; var hDupedToken = IntPtr.Zero; var pi = new PROCESS_INFORMATION(); var sa = new SECURITY_ATTRIBUTES(); sa.Length = Marshal.SizeOf(sa); try { if (!DuplicateTokenEx( hToken, GENERIC_ALL_ACCESS, ref sa, (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int)TOKEN_TYPE.TokenPrimary, ref hDupedToken )) throw new Win32Exception(Marshal.GetLastWin32Error()); var si = new STARTUPINFO(); si.cb = Marshal.SizeOf(si); si.lpDesktop = ""; var path = Path.GetFullPath(filename); var dir = Path.GetDirectoryName(path); // Revert to self to create the entire process; not doing this might // require that the currently impersonated user has "Replace a process // level token" rights - we only want our service account to need // that right. using (var ctx = WindowsIdentity.Impersonate(IntPtr.Zero)) { if (!CreateProcessAsUser( hDupedToken, path, string.Format("\"{0}\" {1}", filename.Replace("\"", "\"\""), args), ref sa, ref sa, false, 0, IntPtr.Zero, dir, ref si, ref pi )) throw new Win32Exception(Marshal.GetLastWin32Error()); } return Process.GetProcessById(pi.dwProcessID); } finally { if (pi.hProcess != IntPtr.Zero) CloseHandle(pi.hProcess); if (pi.hThread != IntPtr.Zero) CloseHandle(pi.hThread); if (hDupedToken != IntPtr.Zero) CloseHandle(hDupedToken); } } } } 

现在假设该服务在“Domain \ MyService”下运行,我当前以“Domain \ Administrator”身份登录 – 我正在将控制台应用程序作为工作进程启动。 当我使用客户端应用程序访问服务(服务未在控制台模式下启动,即它在会话0中)并执行调用CreateProcessAsUser的方法时,工作进程出现在我的桌面上。

现在我可以使它成为一个没有窗口的Windows应用程序来支持创建控制台窗口; 然而,在一天结束时,它仍然在第1阶段创建。

有没有为什么控制台应用程序不在与服务相同的会话中创建的想法?

您可能已经意识到,会话0的隔离是出于安全原因,您可以在此处阅读更多相关信息http://msdn.microsoft.com/en-us/windows/hardware/gg463353.aspx

关于为什么您的控制台应用程序是在活动会话中创建的(例如会话1),这实际上直接链接回您的用户令牌。 当您请求当前用户令牌时,此令牌会自动携带会话ID信息 – 在这种情况下,它是登录终端服务会话(会话1)。 此会话ID由令牌引用,然后在DuplicateTokenEx中复制,然后在CreateProcessAsUser调用中使用。 为了强制在会话0中创建控制台应用程序,您需要显式调用SetTokenInformation API(advapi32.dll),在调用CreateProcessAsUser之前传入hDupedToken,如下所示

 .................. UInt32 dwSessionId = 0; // set it to session 0 SetTokenInformation(hDupedToken, TokenInformationClass.TokenSessionId, ref dwSessionId, (UInt32) IntPtr.Size); ................. CreateProcessAsUser(hDupedToken, ....) 

以下是有关SetTokenInformation的更多信息http://msdn.microsoft.com/en-us/library/windows/desktop/aa379591(v=vs.85).aspx

我能够将最初的post作为工作解决方案实现,但是,我似乎无法找到隐藏控制台窗口的方法。 我尝试了STARTF_USESHOWWINDOW和SW_HIDE,但我的命令窗口仍然弹出。 知道为什么吗?

  public const int STARTF_USESHOWWINDOW = 0x0000000; public const int SW_HIDE = 0; public static Process CreateProcessAsUser(string filename, string args) { var hToken = WindowsIdentity.GetCurrent().Token; var hDupedToken = IntPtr.Zero; var pi = new PROCESS_INFORMATION(); var sa = new SECURITY_ATTRIBUTES(); sa.Length = Marshal.SizeOf(sa); try { if (!DuplicateTokenEx( hToken, GENERIC_ALL_ACCESS, ref sa, (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int)TOKEN_TYPE.TokenPrimary, ref hDupedToken )) throw new Win32Exception(Marshal.GetLastWin32Error()); var si = new STARTUPINFO(); si.cb = Marshal.SizeOf(si); si.lpDesktop = String.Empty; si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE; var path = Path.GetFullPath(filename); var dir = Path.GetDirectoryName(path); // Revert to self to create the entire process; not doing this might // require that the currently impersonated user has "Replace a process // level token" rights - we only want our service account to need // that right. using (var ctx = WindowsIdentity.Impersonate(IntPtr.Zero)) { UInt32 dwSessionId = 1; // set it to session 0 SetTokenInformation(hDupedToken, TOKEN_INFORMATION_CLASS.TokenSessionId, ref dwSessionId, (UInt32)IntPtr.Size); if (!CreateProcessAsUser( hDupedToken, path, string.Format("\"{0}\" {1}", filename.Replace("\"", "\"\""), args), ref sa, ref sa, false, 0, IntPtr.Zero, dir, ref si, ref pi )) throw new Win32Exception(Marshal.GetLastWin32Error()); } return Process.GetProcessById(pi.dwProcessID); } finally { if (pi.hProcess != IntPtr.Zero) CloseHandle(pi.hProcess); if (pi.hThread != IntPtr.Zero) CloseHandle(pi.hThread); if (hDupedToken != IntPtr.Zero) CloseHandle(hDupedToken); } } 

尝试使用MarshalAsStructLayoutDllImport的CharSet命名参数。 您可能需要将MarshalAs添加到各种字符串才能执行此操作。 不要打扰Unicode:你没有使用它。 我建议首先将它们全部设置为CharSet.Ansi 。 运行您已经尝试过的所有测试 – 即设置桌面和所有有趣的东西。 如果崩溃,请将它们全部切换为自动。 如果仍然无效,请将它们全部删除。

假设这些都CharSet.Unicode ,请切换到CreateUserProcessWCharSet.Unicode这样您就知道自己得到了什么。 再想一想,跳到这一步。

如果需要为字符串设置带有MarshalAsUnmanagedType ,则需要UnmanagedType.LPStr用于Ansi, UnmanagedType.LPTStr用于Auto, UnmanagedType.LPWStr用于Unicode。 实际上,无论如何,请为所有字符串执行此操作。