1

所以我检查了很多网站,研究了好几天。而且我还没有找到或想出我自己的解决方案来解决这个问题。

我知道,显然自 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 窗体应用程序,它像发条一样工作。但是,作为一项服务,它不会起作用。

我已经尝试过启用与桌面交互的解决方案。即使来自注册表项。我什至尝试调用不同的可执行文件,但它发生了同样的事情:它创建了进程,但它不执行命令或参数。

我读过很多人都遇到过这个问题......但是我看到这个问题的所有这些网站都没有找到解决方案。甚至来自 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 被创建但实际上根本没有做任何事情。

4

2 回答 2

3

所以唯一的选择就是欺骗操作系统。并从内核创建 Process 的实例,但改变谁是创建者。让我详细说明。

由于 Windows Vista 和更高版本...Microsoft 认为将 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);
        }
    }
}

随意修改课程。如果您还不知道它们是如何工作的,请注意不要触及很多初始枚举声明或外部方法。

于 2013-11-07T19:26:28.597 回答
0

您的原始代码(如问题所示)的问题非常简单:您省略了/c参数cmd.exeto 告诉它运行您的命令。

换句话说,你试图这样做:

cmd c:\vms\vboxmanage startvm {0}

而你需要做的是:

cmd /c c:\vms\vboxmanage startvm {0}

或这个:

c:\vms\vboxmanage startvm {0}

现在,就是说,有些应用程序不喜欢在服务上下文中运行。请注意,这不是因为它们显示 GUI,而是出于其他几个原因中的任何一个。(例如,某些应用程序仅在资源管理器在同一桌面上运行时才有效。)

有可能vboxmanage是这样的应用程序,但如果您没有忘记/c.

于 2013-11-10T22:23:18.150 回答