简体   繁体   English

C#Windows Service创建进程但不执行

[英]C# Windows Service Creates Process but doesn't executes it

So I have checked many many sites, researched for days now. 因此,我已经检查了许多网站,现在已经研究了几天。 And I have not found or come up with one of my own solution for this problem. 而且我还没有找到或提出自己的解决方案之一。

I know, apparently since Windows Vista, a Windows Service since its created in Session 0 its not able to interact with whats considered GUI executables like console apps and other softwares that are part of other Sessions that are not Session 0. 我知道,自Windows Vista以来,显然是Windows Service,因为它是在会话0中创建的,因此它无法与被视为GUI可执行文件(例如控制台应用程序和其他不是会话0的其他会话的一部分的其他软件)进行交互。

According to Microsoft, a Service that does this would be a potential 'Virus'. 根据微软的说法,执行此操作的服务将是潜在的“病毒”。 Which I understand the reasoning for their thinking. 我了解他们思考的理由。 But this is the only solution for our problems. 但这是解决我们问题的唯一方法。

//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);

}

So this is what happens: 这就是发生的情况:

I create a Windows Service, this service on startup will start a Process. 我创建一个Windows服务,该服务在启动时将启动一个进程。 In this case "cmd.exe". 在这种情况下为“ cmd.exe”。 I have checked many times and I am certain that the process is actually created. 我已经检查了很多次,并且我确定该过程是实际创建的。 But the arguments, the actual commands, that I want that cmd.exe to execute...they are being ignored. 但是我希望该cmd.exe执行的参数(实际命令)被忽略。 They just never happen. 他们只是永远不会发生。 I tested the code elsewhere, as a library, as a windows form application it is working like clockwork. 我在其他地方(作为库),Windows窗体应用程序(就像发条一样)对代码进行了测试。 But yet, as a Service it won't work. 但是,作为一项服务,它是行不通的。

I have tried solutions like enabling to interact with Desktop. 我已经尝试过启用与桌面交互的解决方案。 Even from Registry Key. 即使从注册表项。 I have tried even calling different executables, and it happens the same thing: it creates the process, but it doesn't execute the commands or arguments. 我什至尝试调用了不同的可执行文件,并且发生了相同的事情:它创建了进程,但是不执行命令或参数。

I have read many have had this problem... however no solution have been found by all these sites that I have seen this problem for. 我读过很多人都遇到过这个问题...但是,所有我看到此问题的站点都没有找到解决方案。 Even users from StackOverflow. 甚至来自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");

    }

This is the Service Installer Class: 这是服务安装程序类:

        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);

I have noticed that when I want to start the service, it gets stuck at "Starting", then it just stops. 我注意到,当我想启动该服务时,它停留在“正在启动”状态,然后就停止了。 But the Process the cmd.exe or the VBoxManage.exe get created but never actually do anything at all. 但是创建了cmd.exe或VBoxManage.exe进程,但实际上根本没有做任何事情。

So the only alternative to this is to trick the OS. 因此,唯一的替代方法是欺骗操作系统。 And make an instance of the Process from the Kernel but changing who was the creator. 并从内核中创建流程的实例,但更改谁是创建者。 Let me elaborate. 让我详细说明。

Since Windows Vista and greater...Microsoft thought that having the Windows Service as a Service that can interactive with User GUI was a bad idea(and I agree at some point) because it may be potentially a virus that will run everytime at startup. 从Windows Vista及更高版本开始... Microsoft认为拥有可与用户GUI交互的Windows服务即服务是一个坏主意(我在某些时候同意),因为它可能是一种病毒,它将在启动时每次运行。 So they created something called a Session 0. All your services are in this Session so that they are not able to interact with your user(or Session 1 +) GUI. 因此,他们创建了一个称为Session 0的东西。您的所有服务都在此Session中,因此它们无法与您的用户(或Session 1 +)GUI进行交互。 Meaning the Windows Service has no access to cmd.exe, VBoxManage.exe, any other app that has GUI interaction. 这意味着Windows服务无法访问cmd.exe,VBoxManage.exe和任何其他具有GUI交互功能的应用程序。

So... the solution to the problem is tricking the OS, creating the Process from the Kernel with Platform Invokes(Win 32 API) which is not that common for a day to day developer in C#. 因此...解决该问题的方法是欺骗OS,从具有平台调用(Win 32 API)的内核创建进程,这对于C#的日常开发人员而言并不常见。 When creating the Process from the KernelDLL you have access to change who the User or the Creator is. 从KernelDLL创建进程时,您可以更改用户或创建者的身份。 In this case instead of having the Session 0 creating the Process, I changed it to the current Session ID, or current User. 在这种情况下,我没有将Session 0创建进程,而是将其更改为当前Session ID或当前User。 This made it possible for my Windows Service Work like I wanted. 这使我的Windows服务工作如我所愿。

For this idea to work you have to read a lot about KernelDll, advapi32.dll, mostly their methods and enum declarations since its not something you can just reference into your project. 为了使这个想法起作用,您必须阅读很多有关KernelDll,advapi32.dll的信息,主要是它们的方法和枚举声明,因为它不是您可以直接引用到项目中的东西。 Those two need to be P/Invoke in order to use them. 这两个必须是P / Invoke才能使用。

The Following Class that I created makes it possible for you to create a process as the current user and not as Session 0. Hence solving my original problem. 我创建的以下类使您可以以当前用户而不是会话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);
        }
    }
}

Modify the class at your will. 随意修改课程。 Just be careful not to touch a lot of the initial enum declarations or the external methods if you have no clue how those work yet. 如果您不知道它们的工作方式,请小心不要接触很多初始的枚举声明或外部方法。

The problem with your original code (as shown in the question) is very simple: you left out the /c argument to cmd.exe to tell it to run your command. 原始代码的问题(如问题所示)非常简单:您将cmd.exe/c参数忽略了,以告诉它运行命令。

In other words, you were trying to do this: 换句话说,您正在尝试执行以下操作:

cmd c:\vms\vboxmanage startvm {0}

whereas what you needed to do was this: 而您需要做的是:

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

or this: 或这个:

c:\vms\vboxmanage startvm {0}

Now, that said, there are some applications that don't like running in a service context. 现在,这么说,也一些不喜欢在服务上下文中运行的应用。 Note that this isn't because they display a GUI but for any one of several other reasons. 请注意,这不是因为它们显示GUI,而是由于其他几种原因中的任何一种。 (For example, some applications only work if Explorer is running on the same desktop.) (例如,某些应用程序仅在资源管理器在同一桌面上运行时才能工作。)

It's possible that vboxmanage is such an application, but it's more likely that your original code would have worked perfectly if you hadn't forgotten the /c . vboxmanage可能是这样的应用程序,但是如果您没有忘记/c ,您的原始代码很有可能会正常工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM