Question

Background

We need to run a GUI application from a Windows Service, set to Log On as Local System (and without enabling interact with desktop).

The GUI application takes one command-line parameter, performs a specific task and then self-terminates. It is a GUI app because some of its components require a parent TForm, so a console app doesn't work. There are no dialogs or any UI a user would see. In fact, it creates itself as a hidden form with no taskbar icon:

  Application.Initialize;
  Application.MainFormOnTaskbar := False; // <- No taskbar icon
  Application.ShowMainForm := False;      // <- Main form is hidden
  Application.CreateForm(TForm1, Form1);
  Application.Run;

It is possible that the GUI app may be launched multiple times simultaneously, each with its own command-line parameter. Since a GUI app can't be spawned directly in the Session 0 process of the service, I created an Administrator user account so the service can log on the admin user and run the GUI app as the admin user. Once I get it to work once, I will leave this user logged in so the service can quickly launch the GUI app without the login/logout overhead each time it spawns the GUI app.

What I've Done

I'm using the following code, formed from dozens of discussions on this topic, even though most of them wanted the GUI app to be seen by a logged on user.

function CreateEnvironmentBlock(var lpEnvironment: Pointer; hToken: THandle; bInherit: BOOL): BOOL; stdcall; external 'userenv.dll';
function DestroyEnvironmentBlock(lpEnvironment: Pointer): BOOL; stdcall; external 'userenv.dll';

var
  _usertoken: THandle;
  _si: _STARTUPINFOW;
  _pi: _PROCESS_INFORMATION;
  _env: Pointer;
  _sid: Cardinal;
begin
  if LogonUser(PChar(Username), PChar('localhost'), PChar(Password), LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, _usertoken) then
    try
      ZeroMemory(@_si, SizeOf(_si));
      _si.cb := SizeOf(_si);
//    _si.lpDesktop := 'WinSta0\Default'; // <- behaves the same with or without this

      if CreateEnvironmentBlock(_env, _usertoken, False) then
        try
          if CreateProcessAsUser(_usertoken, nil, PChar(sCMD), nil, nil, False, CREATE_UNICODE_ENVIRONMENT, _env, nil, _si, _pi) then
          begin
            WaitForSingleObject(_pi.hProcess, 30000);
            CloseHandle(_pi.hThread);
            CloseHandle(_pi.hProcess);
          end
          else
            _handle_error('CreateProcessAsUser() failed.');
        finally
          DestroyEnvironmentBlock(_env);
        end
      else
        _handle_error('CreateEnvironmentBlock() failed.');
    finally
      CloseHandle(_usertoken);
    end
  else
    _handle_error('LogonUser() failed.');
end;

The Windows Event Viewer [Security Log] shows an entry when LogonUser() is called. The following privileges appear in the log entry:

  SeTcbPrivilege
  SeSecurityPrivilege
  SeTakeOwnershipPrivilege
  SeLoadDriverPrivilege
  SeBackupPrivilege
  SeRestorePrivilege
  SeDebugPrivilege
  SeSystemEnvironmentPrivilege
  SeImpersonatePrivilege

sCmd is set to "c:\path\myapp.exe" "parameter". When sCmd was not properly set, CreateProcessAsUser() would fail with an error of 2 - The system cannot find the file specified. Once I fixed that, CreateProcessAsUser() returns True, but it never actually launches the GUI application.

Question

I'm not sure what I'm missing. I would appreciate any help with getting the service to launch the GUI app under the logged on Username/Password profile, if that's the right way to do this. Or, if there is a better way to do it, I would appreciate any direction and insight.

Was it helpful?

Solution

Thanks to David, Remy and Andy for comments. It helped me to step back and look at the issues from a fresh point-of-view. The solutions ended up being very simple.

Problem 1 - GUI Apps and "Session 0" Services Processes

A service cannot have UI elements or spawn a program that has UI elements. I thought this meant I couldn't use any GUI type controls, like TForm or TWinControl components. So I was trying to figure out how to launch a GUI program from a service (e.g., to the interactive desktop or by logging a user on and launching it to their desktop).

Turns out that as long as you don't have a dialog or some visual control that a user needs to interact with or respond to, it works perfectly fine to include GUI components in a service or an app the service spawns.

Problem 2 - "Control has no parent window"

I found one instance in my code where I created a control at runtime and didn't set its parent. Hard to track down, but fixed.

Problem 3 - External App only launched under "my" user profile

I set the service to Log On three ways, 1) as Local System, 2) as my username/password, and 3) as another admin user's username/password (with identical rights as me).

In all three instances, the return code from within the service to launch the external app indicated it successfully launched the app. However, only when the service was set to Log On using my username/password would the app actually run.

I found a information message in the system event log that said a BPL wasn't found for both of the other two instances. This was because I have a personal user path environment variable that includes an entry for the BPL directory. The other admin user and the Local System accounts do not have this. So of course the app failed to load the needed BPLs and therefore could not run.

Problem 4 - LocalSystem Access to File System

When we pushed the new code and modules to our production servers, the external app failed to properly launch and perform its task (but this time with no messages in the Windows event logs).

There are several parameters that the external app needs (too many to pass on the command line) so the service places all of the parameters into a uniquely named INI file and pass the name of the INI file to the external app. Once the external app finishes its task, it deletes the INI file.

As it turns out, the external app was launched using the Local System account, which didn't have access to the file system and therefore could not open/use the INI file. Once we granted the appropriate rights to the Local System account, it started working correctly in our production environment as well.

 

Everything works great now.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top