我们需要从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应用程序的任何帮助。或者,如果有更好的方法,我会欣赏任何方向和见解。
答案 0 :(得分:2)
感谢David,Remy和Andy的评论。它帮助我退后一步,从一个全新的角度来看问题。 解决方案最终变得非常简单。
服务不能包含UI元素,也不能生成具有UI元素的程序。我认为这意味着我无法使用任何GUI类型控件,例如TForm
或TWinControl
组件。所以我试图找出如何从服务启动GUI程序(例如,到交互式桌面或通过登录用户并将其启动到桌面)。
事实证明,只要您没有用户需要与之交互或响应的对话框或某些可视控件,就可以很好地将GUI组件包含在服务或服务生成的应用程序中。 / p>
我在我的代码中找到了一个实例,我在运行时创建了一个控件,但没有设置它的父控件。很难追查,但已修复。
我将服务设置为Log On
三种方式,1)作为本地系统,2)作为我的用户名/密码,以及3)作为另一个管理员用户的用户名/密码(具有相同的)作为我的权利。)
在所有三个实例中,服务中用于启动外部应用程序的返回代码表明它已成功启动应用程序。但是,只有当使用我的用户名/密码将服务设置为Log On
时,应用程序才会实际运行。
我在 system 事件日志中发现了一条信息消息,表示未找到其他两个实例的BPL。这是因为我有一个个人用户路径环境变量,其中包含BPL目录的条目。其他管理员用户和本地系统帐户没有此帐户。所以当然应用程序无法加载所需的BPL,因此无法运行。
当我们将新代码和模块推送到我们的生产服务器时,外部应用程序无法正确启动并执行其任务(但这次没有Windows事件日志中的消息)。
外部应用程序需要多个参数(在命令行上传递太多),因此服务将所有参数放入一个唯一命名的INI文件中,并将INI文件的名称传递给外部应用程序。外部应用程序完成其任务后,将删除INI文件。
事实证明,外部应用程序是使用本地系统帐户启动的,该帐户无法访问文件系统,因此无法打开/使用INI文件。一旦我们授予本地系统帐户的相应权限,它就会在我们的生产环境中正常运行。
现在一切都很好。