我正在使用Windows服务中的CreateProcessAsUser(我们可以保持主题并假设我有充分的理由这样做)。与其他人在这里问的问题相反,我在我的活动终端会话(会话1)中获得了一个窗口,而不是与服务(会话0)相同的会话 - 这是不可取的。
我挪用了Scott Allen's code;并想出了以下内容。值得注意的变化是“恢复自我”,“CREATE_NO_WINDOW”和命令行args支持。
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 :(得分:6)
正如您可能已经知道的那样,会话0的隔离是出于安全原因,您可以在此处阅读更多相关信息 http://msdn.microsoft.com/en-us/windows/hardware/gg463353.aspx
关于为什么您的控制台应用程序是在活动会话中创建的(例如会话1),这实际上直接链接回您的用户令牌。当您请求当前用户令牌时,此令牌会自动携带会话ID信息 - 在这种情况下,它是登录终端服务会话(会话1)。此会话标识由标记引用,然后在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
的更多信息答案 1 :(得分:1)
我能够将最初的帖子作为工作解决方案实现,但是,我似乎无法找到隐藏控制台窗口的方法。我尝试了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);
}
}
答案 2 :(得分:0)
尝试使用MarshalAs
,StructLayout
和DllImport
的CharSet命名参数进行处理。您可能需要将MarshalAs
添加到各种字符串才能执行此操作。不要打扰Unicode:你没有使用它。我建议先将它们全部设置为CharSet.Ansi
。运行您已经尝试过的所有测试 - 即设置桌面和所有有趣的东西。如果崩溃,请将它们全部切换为自动。如果它仍然无效,请将它们全部删除。
假设这一切都不起作用,请切换到CreateUserProcessW
和CharSet.Unicode
,这样您就知道自己得到了什么。再想一想,就跳到这一步。
如果您需要为UnmanagedType
设置MarshalAs
字符串,则需要UnmanagedType.LPStr
代表安西,UnmanagedType.LPTStr
代表自动,UnmanagedType.LPWStr
代表Unicode。实际上,无论如何都要为你的所有字符串执行此操作。