Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set "interact with desktop" in windows service installer

Tags:

c#

service

I have a windows service which runs under system account and executes some programs from time to time (yeah,yeah, I know that's a bad practice, but that's not my decision). I need to set the "interact with desktop" check, to see the gui of that executed programs, after the service is installed. I've tried several ways, putting the code below in AfterInstall or OnCommited event handlers of my service installer:

ConnectionOptions coOptions = new ConnectionOptions();
coOptions.Impersonation = ImpersonationLevel.Impersonate;

ManagementScope mgmtScope = new System.Management.ManagementScope(@"root\CIMV2", coOptions);
mgmtScope.Connect();

ManagementObject wmiService = new ManagementObject("Win32_Service.Name='" + ServiceMonitorInstaller.ServiceName + "'");

ManagementBaseObject InParam = wmiService.GetMethodParameters("Change");
InParam["DesktopInteract"] = true;
ManagementBaseObject OutParam = wmiService.InvokeMethod("Change", InParam, null); 

or

 RegistryKey ckey = Registry.LocalMachine.OpenSubKey(
    @"SYSTEM\CurrentControlSet\Services\WindowsService1", true);

  if(ckey != null)
  {
    if(ckey.GetValue("Type") != null)
    {
      ckey.SetValue("Type", ((int)ckey.GetValue("Type") | 256));
    }
  }

both of these methods "work". They set the check, but after I start the service it launches the exe - and gui isn't shown! So, if I stop the service, recheck and start it again - bingo! everything starts and is shown. The second way to achieve the result is to reboot - after it the gui is also shown.

So the question is: Is there a correct way to set "interact with desktop" check, so it'll start working without rechecks and reboots?

OS: Windows XP (haven't tried Vista and 7 yet...)

like image 332
Daniel Vygolov Avatar asked Jul 28 '10 09:07

Daniel Vygolov


3 Answers

private static void SetInterActWithDeskTop()
        {
            var service = new System.Management.ManagementObject(
                    String.Format("WIN32_Service.Name='{0}'", "YourServiceName"));
            try
            {
                var paramList = new object[11];
                paramList[5] = true;
                service.InvokeMethod("Change", paramList);
            }
            finally
            {
                service.Dispose();
            }


        }
like image 65
Heisa Avatar answered Oct 26 '22 05:10

Heisa


And finally after searching the internet for a week - I've found a great working solution: http://asprosys.blogspot.com/2009/03/allow-service-to-interact-with-desktop.html

Find the desktop to launch into. This may seem facetious but it isn't as simple as it seems. With Terminal Services and Fast User Switching there can be multiple interactive users logged on to the computer at the same time. If you want the user that is currently sitting at the physical console then you're in luck, the Terminal Services API call WTSGetActiveConsoleSessionId will get you the session ID you need. If your needs are more complex (i.e. you need to interact with a specific user on a TS server or you need the name of the window station in a non-interactive session) you'll need to enumerate the Terminal Server sessions with WTSEnumerateSessions and check the session for the information you need with WTSGetSessionInformation.

Now you know what session you need to interact with and you have its ID. This is the key to the whole process, using WTSQueryUserToken and the session ID you can now retrieve the token of the user logged on to the target session. This completely mitigates the security problem of the 'interact with the desktop' setting, the launched process will not be running with the LOCAL SYSTEM credentials but with the same credentials as the user that is already logged on to that session! No privilege elevation.

Using CreateProcessAsUser and the token we have retrieved we can launch the process in the normal way and it will run in the target session with the target user's credentials. There are a couple of caveats, both lpCurrentDirectory and lpEnvironment must point to valid values - the normal default resolution methods for these parameters don't work for cross-session launching. You can use CreateEnvironmentBlock to create a default environment block for the target user.

There is source code of the working project attached.

like image 43
Daniel Vygolov Avatar answered Oct 26 '22 07:10

Daniel Vygolov


Same as Heisa but with WMI. (code is Powershell, but can be easily ported to C#)

if ($svc = gwmi win32_service|?{$_.name -eq $svcname})
{
    try {
        $null = $svc.change($svc.displayname,$svc.pathname,16,1,`
        "Manual",$false,$svc.startname,$null,$null,$null,$null)
        write-host "Change made"
    catch { throw "Error: $_" }
} else
{ throw "Service $svcname not installed" }

See MSDN: Service Change() method for param description.

like image 2
Yevgeniy Avatar answered Oct 26 '22 05:10

Yevgeniy