Loading GUI App from Windows Service

2020-03-27 06:03发布

问题:

I am writing a .NET Windows service whose role is to launch a GUI application(whose source is unavailable). The operation is a fire and forget, no communication besides initial command-line parameters.

The service ought to run as a given Windows account.

Problem: The app that the service launches is not displayed on the desktop. It needs to be, as it is an interactive app. What is the solution?

Note: this is the specification and the design of the system. The typical concerns raised with service/app communication and security do not apply in this specific case.

edit: The GUI elements display correctly when logged in as the Local System account, however, due to the GUI app needing to access networked drives(It can't understand UNC mapping), it needs to be run as a specified user account, and that does not have a "display interactive elements" setting.

edit2: The OS is Windows 2003 Server, and there are no plans to upgrade it.

回答1:

This is, unfortunately, more problematic since Vista... Some details as to why are posted on this blog post.

That post has some references to a couple of potential workarounds. Here is a thread on MSDN that details the entire process, and some of the potential hiccups you may face.

However, I'd highly recommend trying to see if you can switch to having a user-mode application run as a startup app (when the user logs in), and any communication to your service be handled by that application. It's much more reliable, especially under vista, terminal services, and other situations.



回答2:

This is a stab in the dark but hopefully will lead you down some sort of path to the solution.

Even though security isn't a concern, it may be the problem. The service is launching the app under a different set of credentials than the currently logged in user. It'd be like Remote Desktop to a users machine and launch an app that they would see.

As a test, perhaps change the credentials of the service to the currently logged in user just to see what happens.

Since a service runs even though a user isn't logged in, what happens if it launches the app? Your business rules or functionality might keep that from happening, but perhaps windows is doing something to keep it from working.



回答3:

You can use free Autologon utility http://technet.microsoft.com/en-us/sysinternals/bb963905.aspx from Sysinternals/Microsoft and put you application into Startup for autologon user profile. After that you can configure screen sever to start in several minutes and check "On resume, display logon screen" checkbox.



回答4:

Do you expect your application/service to work when the terminal server role is enabled? If so, you really need to do the "application which polls the service" model and not the "service which launches the application" model.

The reason for this is that you may have multiple users connected to the machine at any time and there's no way of knowing which is at the "console" - in fact there might be nobody at the console at all.



回答5:

This is the code I have used in the past to do this in a task-management service, which sometimes needed to run stuff in an interactive session. Replace wibble.exe with your app. It should run fine on Server 2003 (i.e. NT5). We didn't bother trying to get interactive mode running on NT6 (too many hassles), we left our apps running in the service session and wrote our own debug utility to talk to them via pipes.

STARTUPINFO  sui ;
PROCESS_INFORMATION pi;

ZeroMemory (&sui, sizeof(STARTUPINFO));
sui.cb = sizeof (STARTUPINFO);
sui.wShowWindow = pTask->GetWinStartState();
sui.dwFlags     = STARTF_USESHOWWINDOW;
ZeroMemory (&pi,sizeof(pi));

if (InteractiveMode)
{
   HANDLE  hToken = NULL;
   DWORD dwSessionId = GetCurrentUserSession();

   if (dwSessionId != (DWORD)-1)
   {
      if (WTSQueryUserToken (dwSessionId, &hToken))
      {
         sui.lpDesktop = TEXT("winsta0\\default");
         LPVOID  pEnv = NULL;
         dwCreateFlags |= CREATE_NEW_CONSOLE;
         HMODULE hModu = LoadLibrary(TEXT("Userenv.dll"));

         if (hModu)
         {
            if (CreateEnvironmentBlock (&pEnv, hToken, FALSE))
            {
               dwCreateFlags |= CREATE_UNICODE_ENVIRONMENT;    
            }
            else
            {
               pEnv = NULL;
            }
         }

         bCreatedOk = CreateProcessAsUser (hToken,
                                           NULL,
                                           TEXT("wibble.exe"),
                                           NULL,
                                           NULL,
                                           FALSE,
                                           dwCreateFlags,
                                           pEnv,
                                           NULL,
                                           &sui,
                                           &pi);
      }
      else
      {
         // error case
      }
   }
   else
   {
      // remote session? error case.
   }
}

Your "specified user account" would have to be the console session here, I'm thinking. If you needed it to run in a specified account without that account being already logged in, you're in a whole new world of hurt, loading registry hives etc.