所以唯一的選擇是欺騙操作系統。從Kernel創建一個Process實例,但改變誰是創建者。讓我詳細說明一下。
由於Windows Vista及更高版本......微軟認爲將Windows服務作爲服務與用戶GUI進行交互是一個壞主意(我同意在某些時候),因爲它可能會運行一種病毒每次在啓動。所以他們創建了一個叫做Session 0的東西。所有的服務都在這個Session中,這樣他們就不能和你的用戶(或Session 1 +)GUI交互。這意味着Windows服務無法訪問cmd.exe,VBoxManage.exe以及任何其他具有GUI交互功能的應用程序。
因此...解決該問題的方法是操縱操作系統,從平臺調用(Win 32 API)的內核創建進程,這對於C#中的日常開發人員來說並不常見。 從KernelDLL創建進程時,您可以訪問更改用戶或創建者。在這種情況下,我沒有將會話0創建爲進程,而是將其更改爲當前會話ID或當前用戶。這使得我的Windows服務工作成爲可能,就像我想要的一樣。
對於這個想法的工作,你必須閱讀很多關於KernelDll,advapi32.dll,主要是他們的方法和枚舉聲明,因爲它不是你可以引用到你的項目。這兩個需要進行P/Invoke才能使用它們。
我創建的以下類使您可以創建一個作爲當前用戶而不是作爲會話0的進程。因此解決了我原來的問題。
//Just use the Class Method no need to instantiate it:
ApplicationLoader.CreateProcessAsUser(string filename, string args)
[SuppressUnmanagedCodeSecurity]
class ApplicationLoader
{
/// <summary>
/// No Need to create the class.
/// </summary>
private ApplicationLoader() { }
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
}
[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("advapi32.dll", EntryPoint = "ImpersonateLoggedOnUser", SetLastError = true,
CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr ImpersonateLoggedOnUser(IntPtr hToken);
[
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);
[DllImport("Kernel32.dll", SetLastError = true)]
//[return: MarshalAs(UnmanagedType.U4)]
public static extern IntPtr WTSGetActiveConsoleSessionId();
[DllImport("advapi32.dll")]
public static extern IntPtr SetTokenInformation(IntPtr TokenHandle, IntPtr TokenInformationClass, IntPtr TokenInformation, IntPtr TokenInformationLength);
[DllImport("wtsapi32.dll", SetLastError = true)]
public static extern bool WTSQueryUserToken(uint sessionId, out IntPtr Token);
private static int getCurrentUserSessionID()
{
uint dwSessionId = (uint)WTSGetActiveConsoleSessionId();
//Gets the ID of the User logged in with WinLogOn
Process[] processes = Process.GetProcessesByName("winlogon");
foreach (Process p in processes)
{
if ((uint)p.SessionId == dwSessionId)
{
//this is the process controlled by the same sessionID
return p.SessionId;
}
}
return -1;
}
/// <summary>
/// Actually calls and creates the application.
/// </summary>
/// <param name="filename"></param>
/// <param name="args"></param>
/// <returns></returns>
public static Process CreateProcessAsUser(string filename, string args)
{
//var replaces IntPtr
var hToken = WindowsIdentity.GetCurrent().Token; //gets Security Token of Current User.
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);
//Testing
uint curSessionid = (uint)ApplicationLoader.getCurrentUserSessionID();
if (!WTSQueryUserToken(curSessionid,out hDupedToken))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
// 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, CREATE_NO_WINDOW, 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);
}
}
}
按照您的意願修改班級。如果你不知道如何工作,請小心不要觸及很多最初的枚舉聲明或外部方法。
當你可以這樣做時,這從來就不是一個好主意。你應該這樣做的方式是編寫一些在登錄時運行並從服務請求它需要的東西,在該用戶的權限下運行,或者以管理員身份運行,因爲你需要處理該問題。 –
根據白皮書,通過使用客戶機/服務器機制(如RPC或命名管道),仍可以從會話0中的服務與用戶會話中的UI機制進行通信。我解釋這意味着你的情況你需要爲cmd創建一個包裝器(用戶代理),它只需傳遞需要通過命名管道以字符串形式運行的命令。請參閱第6頁:http://msdn.microsoft.com/en-us/windows/hardware/gg463353.aspx –
我閱讀了有關命名管道的工作...但未找到具體的解決方案。 我需要它沒有用戶,因爲我無法隨時瞭解所有管理員用戶。因此,本地系統是完美的 –