从 LocalSystem 服务运行非可视 GUI 应用程序
Posted
技术标签:
【中文标题】从 LocalSystem 服务运行非可视 GUI 应用程序【英文标题】:Run Non-Visual GUI App from LocalSystem Service 【发布时间】:2014-03-25 09:33:18 【问题描述】:背景
我们需要从 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 应用程序可能会同时启动多次,每次都有自己的命令行参数。由于不能在服务的 Session 0 进程中直接生成 GUI 应用程序,因此我创建了一个 Administrator 用户帐户,以便服务可以登录 admin 用户并运行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;
当调用LogonUser()
时,Windows 事件查看器 [安全日志] 会显示一个条目。以下权限出现在日志条目中:
SeTcbPrivilege
SeSecurityPrivilege
SeTakeOwnershipPrivilege
SeLoadDriverPrivilege
SeBackupPrivilege
SeRestorePrivilege
SeDebugPrivilege
SeSystemEnvironmentPrivilege
SeImpersonatePrivilege
sCmd
设置为 "c:\path\myapp.exe" "参数"。当sCmd
未 正确设置时,CreateProcessAsUser()
将失败并出现错误 2 - 系统找不到指定的文件。一旦我解决了这个问题,CreateProcessAsUser()
返回 True
,但它从未真正启动 GUI 应用程序。
问题
我不确定我错过了什么。如果这是正确的方法,我将不胜感激让服务在登录的用户名/密码配置文件下启动 GUI 应用程序。或者,如果有更好的方法,我将不胜感激任何方向和见解。
【问题讨论】:
不要尝试从服务运行 GUI 应用程序。 除了大卫所说的之外,我还质疑组件需要 TForm 所有者。如果它们是非 GUI 的,您应该能够将 Nil 传递给构造函数,如果它们是 GUI,它们一开始就不应该出现在应用程序中。 你真的看错了。如果您编写的代码需要TForm
所有者的非可视代码,那么您只需要修复它。消除不必要的限制。
@DavidHeffernan - 我同意这不是一个好主意。但是该过程需要在服务器端运行。我们尝试将其构建为控制台应用程序,但第 3 方组件之一返回错误 Control has no parent window.
我很想将其作为控制台应用程序运行,但由于 没有父窗口,它需要是 GUI 应用程序 问题。
有些事情是不可能的。您是否考虑过您可能无法在会话 0 中创建 GUI 的事实?你为什么要尝试将 GUI 放入会话 0 中?当您尝试这样做时,为什么要禁用“允许服务与桌面交互”?您想在服务中添加什么可视化组件?
【参考方案1】:
感谢 David、Remy 和 Andy 提供 cmets。它帮助我退后一步,从新的角度看待问题。 解决方案最终变得非常简单。
问题 1 - GUI 应用程序和“会话 0”服务进程
服务不能具有 UI 元素或生成具有 UI 元素的程序。我认为这意味着我不能使用任何 GUI 类型的控件,例如 TForm
或 TWinControl
组件。所以我试图弄清楚如何从服务启动 GUI 程序(例如,到交互式桌面或通过登录用户并将其启动到他们的桌面)。
事实证明,只要您没有用户需要与之交互或响应的对话框或某些视觉控件,将 GUI 组件包含在服务或服务产生的应用程序中就可以完美地工作。
问题 2 - “控件没有父窗口”
我在我的代码中发现了一个实例,我在运行时创建了一个控件并且没有设置它的父级。难以追踪,但已修复。
问题 3 - 仅在“我的”用户配置文件下启动的外部应用程序
我将服务设置为Log On
三种方式,1) 作为 本地系统,2) 作为我的用户名/密码,以及 3) 作为另一个管理员用户的用户名/密码(具有与我)。
在所有三个实例中,服务内用于启动外部应用程序的返回代码表明它已成功启动应用程序。但是,只有使用我的用户名/密码将服务设置为 Log On
时,该应用才会真正运行。
我在 system 事件日志中发现一条信息消息,指出没有为其他两个实例找到 BPL。这是因为我有一个包含 BPL 目录条目的个人用户路径环境变量。其他管理员用户和 本地系统 帐户没有这个。因此,该应用当然无法加载所需的 BPL,因此无法运行。
问题 4 - LocalSystem 对文件系统的访问
当我们将新代码和模块推送到生产服务器时,外部应用程序无法正确启动和执行其任务(但这次 Windows 事件日志中没有消息)。
外部应用程序需要几个参数(在命令行上传递的参数太多),因此服务将所有参数放入一个唯一命名的 INI 文件中,并将 INI 文件的名称传递给外部应用程序。外部应用完成任务后,会删除 INI 文件。
事实证明,外部应用程序是使用 本地系统 帐户启动的,该帐户无权访问文件系统,因此无法打开/使用 INI 文件。一旦我们为 本地系统 帐户授予了适当的权限,它也开始在我们的生产环境中正常工作。
现在一切都很好。
【讨论】:
以上是关于从 LocalSystem 服务运行非可视 GUI 应用程序的主要内容,如果未能解决你的问题,请参考以下文章
非阻塞 PyQT 作为一个额外的 GUI 可视化主进程的结果?