从LocalSystem服务运行非Visual GUI应用程序

时间:2014-03-25 09:33:18

标签: delphi user-interface windows-services windows-server-2008-r2 external-process

背景

我们需要从Windows服务运行GUI应用程序,设置为本地系统登录(并且不启用与桌面交互)。

GUI应用程序采用一个命令行参数,执行特定任务然后自行终止。它是一个GUI应用程序,因为它的一些组件需要父TForm,因此控制台应用程序不起作用。没有对话框或用户可以看到的任何UI。事实上,它将自己创建为一个没有任务栏图标的隐藏表单:

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

GUI应用程序可能会同时多次启动,每个应用程序都有自己的命令行参数。由于GUI应用程序无法直接在服务的会话0 过程中生成,因此我创建了一个 Administrator 用户帐户,以便该服务可以登录管理员用户并运行GUI应用程序作为管理员用户。一旦我让它工作一次,我将让这个用户登录,这样服务就可以在每次生成GUI应用程序时快速启动GUI应用程序,而无需登录/注销开销。

我做了什么

我正在使用以下代码,这些代码来自关于此主题的数十个讨论,尽管他们中的大多数都希望登录用户可以看到GUI应用程序。

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;

Windows事件查看器[安全日志]显示调用LogonUser()时的条目。以下权限出现在日志条目中:

  SeTcbPrivilege
  SeSecurityPrivilege
  SeTakeOwnershipPrivilege
  SeLoadDriverPrivilege
  SeBackupPrivilege
  SeRestorePrivilege
  SeDebugPrivilege
  SeSystemEnvironmentPrivilege
  SeImpersonatePrivilege

sCmd设置为“c:\ path \ myapp.exe”“参数”。正确设置sCmd 后,CreateProcessAsUser()将失败,错误为 2 - 系统无法找到指定的文件。一旦我修复了,CreateProcessAsUser()返回True,但它实际上从未启动GUI应用程序。

问题

我不确定我错过了什么。如果这是正确的方法,我将非常感谢您在登录的用户名/密码配置文件下启动GUI应用程序的任何帮助。或者,如果有更好的方法,我会欣赏任何方向和见解。

1 个答案:

答案 0 :(得分:2)

感谢David,Remy和Andy的评论。它帮助我退后一步,从一个全新的角度来看问题。 解决方案最终变得非常简单。

问题1 - GUI应用程序和“会话0”服务进程

服务不能包含UI元素,也不能生成具有UI元素的程序。我认为这意味着我无法使用任何GUI类型控件,例如TFormTWinControl组件。所以我试图找出如何从服务启动GUI程序(例如,到交互式桌面或通过登录用户并将其启动到桌面)。

事实证明,只要您没有用户需要与之交互或响应的对话框或某些可视控件,就可以很好地将GUI组件包含在服务或服务生成的应用程序中。 / p>

问题2 - “控制没有父窗口”

我在我的代码中找到了一个实例,我在运行时创建了一个控件,但没有设置它的父控件。很难追查,但已修复。

问题3 - 仅在“我的”用户个人资料

下启动外部应用程序

我将服务设置为Log On三种方式,1)作为本地系统,2)作为我的用户名/密码,以及3)作为另一个管理员用户的用户名/密码(具有相同的)作为我的权利。)

在所有三个实例中,服务中用于启动外部应用程序的返回代码表明它已成功启动应用程序。但是,只有当使用我的用户名/密码将服务设置为Log On时,应用程序才会实际运行。

我在 system 事件日志中发现了一条信息消息,表示未找到其他两个实例的BPL。这是因为我有一个个人用户路径环境变量,其中包含BPL目录的条目。其他管理员用户和本地系统帐户没有此帐户。所以当然应用程序无法加载所需的BPL,因此无法运行。

问题4 - LocalSystem访问文件系统

当我们将新代码和模块推送到我们的生产服务器时,外部应用程序无法正确启动并执行其任务(但这次没有Windows事件日志中的消息)。

外部应用程序需要多个参数(在命令行上传递太多),因此服务将所有参数放入一个唯一命名的INI文件中,并将INI文件的名称传递给外部应用程序。外部应用程序完成其任务后,将删除INI文件。

事实证明,外部应用程序是使用本地系统帐户启动的,该帐户无法访问文件系统,因此无法打开/使用INI文件。一旦我们授予本地系统帐户的相应权限,它就会在我们的生产环境中正常运行。

现在一切都很好。