所以我检查了很多网站,现在研究了几天。而且我还没有找到或想出一个解决这个问题的方法。
我知道,自Windows Vista以来,Windows服务自第0会话创建以来,它无法与最初考虑的GUI可执行文件(如控制台应用程序和其他不属于会话0的其他会话的软件)交互。
根据微软的说法,这样做的服务将是潜在的“病毒”。我明白了他们思考的原因。但这是解决我们问题的唯一方法。
//This is how I am calling the process.
public void startVM(string vmname) {
string cmdline = startvm --type headless VM2000";
ProcessStartInfo startInfo = new ProcessStartInfo("cmd.exe");
startInfo.WindowStyle = ProcessWindowStyle.Minimized;
startInfo.Arguments = string.Format(@"c:\vms\vboxmanage startvm {0}",vmname);
Process.Start(startInfo);
}
所以这就是:
我创建一个Windows服务,启动时该服务将启动一个进程。在这种情况下“cmd.exe”。我已经检查了很多次,我确信这个过程实际上已经创建了。但是我希望cmd.exe执行的参数,实际命令......它们被忽略了。它们永远不会发生。我在其他地方测试了代码,作为一个库,作为一个Windows表单应用程序,它像发条一样工作。但是,作为一项服务,它将无法运作。
我尝试了与桌面交互的解决方案。甚至来自Registry Key。我甚至试过调用不同的可执行文件,它发生了同样的事情:它创建了进程,但它不执行命令或参数。
我看过很多人都遇到过这个问题...但是我找不到所有这些网站的解决方案。甚至来自StackOverflow的用户。
//Located in the service class inheriting from ServiceBase
protected override void OnStart(string[] args)
{
//System.Diagnostics.Debugger.Launch();
IVBoxCom vBox = new VBoxCom();
//This method calls the method you see above.
vBox.StartVM("WIN2K");
}
这是服务安装程序类:
ServiceInstaller installer = new ServiceInstaller();
installer.ServiceName = "Steven-VBoxService"; //This has to be the exact Name of the Service that has ServiceBase Class
installer.DisplayName = "Steven-VBoxService";
installer.StartType = ServiceStartMode.Manual;
base.Installers.Add(installer);
//Creates an Executable that convokes the Service previously installed.
//Note: In theory, I can create 10 Services, and run them in a single Service Process
ServiceProcessInstaller installer2 = new ServiceProcessInstaller();
installer2.Account = ServiceAccount.LocalSystem; //Windows service.
//installer2.Password = "sh9852"; //Why would I used these options?
//installer2.Username = @"FITZMALL\hernandezs";
installer2.Password = null;
installer2.Username = null;
base.Installers.Add(installer2);
我注意到当我想要启动服务时,它会停留在“正在启动”,然后就会停止。 但是创建了cmd.exe或VBoxManage.exe的进程,但实际上根本没有做任何事情。
答案 0 :(得分:2)
所以唯一的替代方法是欺骗操作系统。并从内核创建一个Process实例,但改变谁是创建者。让我详细说明一下。
自Windows Vista及更高版本......微软认为将Windows服务即服务与用户GUI交互是一个坏主意(我在某些方面同意),因为它可能是一种病毒,每次都会运行在启动时。因此,他们创建了一个名为Session 0的东西。您的所有服务都在此Session中,因此他们无法与您的用户(或Session 1 +)GUI进行交互。这意味着Windows服务无法访问cmd.exe,VBoxManage.exe,以及任何其他具有GUI交互的应用程序。
所以...问题的解决方案是欺骗操作系统,使用平台调用(Win 32 API)从内核创建进程,这对于C#中的日常开发人员来说并不常见。 从KernelDLL创建Process时,您有权更改User或Creator的用户。在这种情况下,我没有让Session 0创建Process,而是将其更改为当前的Session ID或当前的User。这使我的Windows服务工作成为可能,就像我想要的那样。
要想实现这个想法,你必须阅读很多关于KernelDll,advapi32.dll,主要是他们的方法和枚举声明,因为它不是你可以引用到你的项目中的东西。这两个需要是P / Invoke才能使用它们。
我创建的以下类使您可以创建一个进程作为当前用户而不是作为Session 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);
}
}
}
根据您的意愿修改课程。如果你不知道那些是如何工作的,请注意不要触及很多初始枚举声明或外部方法。
答案 1 :(得分:0)
原始代码的问题(如问题中所示)非常简单:您将/c
参数遗漏给cmd.exe
,告诉它运行您的命令。
换句话说,你试图这样做:
cmd c:\vms\vboxmanage startvm {0}
而你需要做的是:
cmd /c c:\vms\vboxmanage startvm {0}
或者这个:
c:\vms\vboxmanage startvm {0}
现在,那就是 一些不喜欢在服务上下文中运行的应用程序。请注意,这不是因为它们显示GUI,而是出于其他几个原因。 (例如,某些应用程序仅在Explorer在同一桌面上运行时才有效。)
vboxmanage
可能是这样的应用程序,但如果您没有忘记/c
,则原始代码更有可能完美运行。