1

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.

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. In this case "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. They just never happen. I tested the code elsewhere, as a library, as a windows form application it is working like clockwork. 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.

    //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.

S.H.
  • 2,833
  • 3
  • 26
  • 28
  • 1
    This was never a good idea when you could do it. The way you should do it is write something that will run on login and request what it needs from the service, to run under that user's authority or perhaps as admin, given you ant to deal with that. – Tony Hopkinson Nov 04 '13 at 20:57
  • According to a whitepaper it is still possible to communicate from services in Session 0 to UI mechanisms in user sessions through the use of a client/server mechanism such as RPC or named pipes. I interpret this to mean that in your case you need to create a wrapper (user agent) for cmd which simply passes the command you need to run as a string via a named piped. See page 6: http://msdn.microsoft.com/en-us/windows/hardware/gg463353.aspx – P.Brian.Mackey Nov 04 '13 at 21:07
  • i read about the named pipe work around... but havent found a concrete solution. And I need it to be without users because I won't be able to know all admin users at all times. Therefore the Local System was perfect – S.H. Nov 04 '13 at 21:48
  • If you need to bypass UAC then I you may be able to accomplish such a feat by modifying the trust level of the assembly for the computers your domain with a new policy. Then you can install the app and let it run without elevation for non-admins. If this is a public application then I'd say you are out of luck because its not secure. Change trust level: http://support.microsoft.com/kb/815147 – P.Brian.Mackey Nov 04 '13 at 22:01
  • Alternatively, you could try having the users enable Interactive Service Detection Service (assuming its still available). As far as a working example I suspect any old client/server named pipe example available online would suffice. It should require only minor modification to fit into a windows service. – P.Brian.Mackey Nov 04 '13 at 22:04
  • Okay the changing trust level wouldn't work because I want the application to work to multiple users and it would be considered a Security hole. Because I want to make this open source once I finish it. It is something I think it would be helpful to the many that use Virtual Box. – S.H. Nov 05 '13 at 01:13
  • Can you please post your actual code launching CMD.EXE? Starting a process with arguments from a service should work with no special tricks necessary (it does in our Windows Services applications!) so something is off in your situation. The devil will be in the details. One more thing -- use Microsoft's excellent [Process Explorer](http://technet.microsoft.com/en-us/sysinternals/bb896653.aspx) to view the command line passed to the launched application. Perhaps arguments are passed but ignored, which is different problem entirely. – CoreTech Nov 05 '13 at 01:03
  • Yes I believe that is the issue as well... that they are passed but are ignored. The actual code would have to be update till tomorrow that I am back at the office. But I do know that it will show you the same thing its just that I tried calling the actual application right from the Process class as a different attempt. But I will post my code tomorrow. – S.H. Nov 05 '13 at 01:09
  • Not all software will work properly when launched in a non-interactive session, and the Local System environment may not be correctly configured to launch the applications you are trying to run (e.g. maybe the PATH variable isn't correct). A tool like Process Monitor should be able to show you exactly what is failing. – bmm6o Nov 05 '13 at 01:14
  • Ill check into that... but like I mentioned. My code for launching the cmd.exe and VBoxManage.exe were working in test console app, windows form app, and even windows library dll app. So the code itself to summon and work with a Process its working. Its the Windows Service that is not letting me work with the process hence why I think its actually ignoring it... But until tomorrow that I get my hands back on the code I will be able to check that Process Explorer. – S.H. Nov 05 '13 at 01:23
  • @tony-hopkinson if you can give me a solution for my problem tgat would be great. But stating the obvious is not gona help hehe. I know its not meant to be used that way the service if not microsoft would have not fix it since windows vista. However I need my software at startuo to start the vms desired to start an to hutdown gracefully at shutdown or reboot of the host. And it cant be task scheduler because there can be from 5 - 20 vms, and the server admins are not that tech savyy. – S.H. Nov 05 '13 at 13:40
  • According to the code, you are launching `VBoxManage.exe` not `cmd.exe`. How does command fit into this? – P.Brian.Mackey Nov 05 '13 at 14:28
  • I added and edited... and yeah I added Vboxmanage.exe because I am trying to use that process with cmd.exe or just directly to try to find the solution. – S.H. Nov 05 '13 at 14:30
  • Thanks for the help! I figured out a way to solve this... working on it right now. – S.H. Nov 05 '13 at 15:36
  • @StevenHernandez - Is it fixed? Do you want to post a solution? I am curious as this appears to be something for Oracle VirtualBox which I am a user and fan of the app. – P.Brian.Mackey Nov 07 '13 at 18:58
  • Yes it did work. Sure I'll post it give me a few mins... I am making this app Open Source later on. Just making sure its working like intended... Its that I found a VirtualBox Service but its Open License, but I can't modify the code, So i decided to create one myself that will be Open Source. Check VBoxVMService... thats the one I tried to use before but didn't work like I wanted it to. – S.H. Nov 07 '13 at 19:14
  • @P.Brian.Mackey - There... the answer is below :) Hope it helps you and any other person that has have this problem. Because I have seen it a lot, but no real answers to the problem. – S.H. Nov 07 '13 at 19:42

2 Answers2

3

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. 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. Meaning the Windows Service has no access to cmd.exe, VBoxManage.exe, any other app that has GUI interaction.

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#. When creating the Process from the KernelDLL you have access to change who the User or the Creator is. In this case instead of having the Session 0 creating the Process, I changed it to the current Session ID, or current User. This made it possible for my Windows Service Work like I wanted.

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. Those two need to be P/Invoke in order to use them.

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.

//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.

S.H.
  • 2,833
  • 3
  • 26
  • 28
0

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.

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. (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.

Harry Johnston
  • 35,639
  • 6
  • 68
  • 158
  • I did use /c and /k... But like i said it didnt work. I still didnt execute the process. – S.H. Nov 10 '13 at 22:50
  • I see you say the service didn't actually start properly; perhaps `startVM` was never actually getting called? Did you try running a simpler command such as `cmd /c dir` to see whether the problem was specific to `vboxmanage`? Could you post the complete code, or email it to me? – Harry Johnston Nov 11 '13 at 00:36
  • Another possibility is that the .NET `Process` class doesn't work in a service context for some reason. My code is all native Win32, so if there's a problem specific to .NET I might not have heard about it. – Harry Johnston Nov 11 '13 at 00:41
  • No no the service did start correctly. I even tried short commands. It created the process as session id 0 and just ignored the arguments. I did test my code as a windows application an it worked, so the comman was not the problem. However the solution i posted made it work and it works for anything now. In short it became a loader service(a more complex Windows Service) – S.H. Nov 11 '13 at 00:43
  • Ahh thats interesting how do you manage do be all WIN32 native what language you using? And yes .Net is not native with win32 api thats why my solution required platorm invokes pr WIN32 api – S.H. Nov 11 '13 at 00:45
  • Did you remember to set `ProcessStartInfo.UseShellExecute` to false? – Harry Johnston Nov 11 '13 at 00:49
  • Also, it might be worth trying using `CreateProcess` (via pinvoke) instead of the `Process` class. – Harry Johnston Nov 11 '13 at 00:52
  • Yes i did put shell to startinfo.useshellexecute = false. I read it could help but it didnt it just was the same. C# .Net is harder to make windows service interact with GuI user side because of session 0 – S.H. Nov 11 '13 at 00:53
  • And yes thats how i solve the problem, calling createaprocessasuser, just creating the process with pinvokr didnt work either because it still used session 0 to create the process. So createas user let me create the process as current user and it worked like a charm. – S.H. Nov 11 '13 at 00:55
  • Well, all I can do is repeat that I don't have any session 0 problems in my code, and reiterate my offer that if you'll email me your original code, I'll see if I can make it work. (The problem with your solution is that, presumably, it requires a user to be logged on in order to work.) – Harry Johnston Nov 11 '13 at 00:58
  • Yes u are right indeed. Can you give me your email to send to my original code before my solution? But it will be until tomorrow that i have access to the code cuz right now I am at my house and I have the computer at work shuttedown. But i wonder how come you dont have the session 0 issue? How are you coding it with which technology/language? – S.H. Nov 11 '13 at 01:00
  • harry.maurice.johnston@gmail.com and I'm using Microsoft Visual C. But any language that generates Win32 code would do. And I do think that while using .NET may add some complications, they shouldn't be insurmountable. – Harry Johnston Nov 11 '13 at 01:30
  • Well ill post you the link by msdn explaining how .net c# does have that issue and how c and c++ are more win32 api compliant. – S.H. Nov 11 '13 at 02:04