Delphi 从LocalSystem服务运行非可视GUI应用程序 背景
我们需要从Windows服务运行GUI应用程序,设置为以本地系统登录(并且不启用与桌面交互) GUI应用程序接受一个命令行参数,执行特定任务,然后自行终止。它是一个GUI应用程序,因为它的一些组件需要父级Delphi 从LocalSystem服务运行非可视GUI应用程序 背景,delphi,user-interface,windows-services,windows-server-2008-r2,external-process,Delphi,User Interface,Windows Services,Windows Server 2008 R2,External Process,我们需要从Windows服务运行GUI应用程序,设置为以本地系统登录(并且不启用与桌面交互) GUI应用程序接受一个命令行参数,执行特定任务,然后自行终止。它是一个GUI应用程序,因为它的一些组件需要父级TForm,所以控制台应用程序无法工作。没有对话框或用户将看到的任何UI。事实上,它将自己创建为一个隐藏表单,没有任务栏图标: Application.Initialize; Application.MainFormOnTaskbar := False; // <- No task
TForm
,所以控制台应用程序无法工作。没有对话框或用户将看到的任何UI。事实上,它将自己创建为一个隐藏表单,没有任务栏图标:
Application.Initialize;
Application.MainFormOnTaskbar := False; // <- No taskbar icon
Application.ShowMainForm := False; // <- Main form is hidden
Application.CreateForm(TForm1, Form1);
Application.Run;
sCmd
设置为“c:\path\myapp.exe”参数。当sCmd
未正确设置时,CreateProcessAsUser()
将失败,错误为2-系统无法找到指定的文件。一旦我解决了这个问题,CreateProcessAsUser()
返回True
,但它从未真正启动GUI应用程序
问题:
我不确定我错过了什么。如果这是正确的方法,我将非常感谢您提供任何帮助,让服务在登录的用户名/密码配置文件下启动GUI应用程序。或者,如果有更好的方法,我将非常感谢您的指导和见解。感谢David、Remy和Andy的评论。它帮助我退一步,从新的角度看待问题。解决方案最终非常简单 问题1-GUI应用程序和“会话0”服务进程 服务不能具有UI元素,也不能生成具有UI元素的程序。我认为这意味着我不能使用任何GUI类型的控件,比如
TForm
或TWinControl
组件。因此,我试图找出如何从服务启动GUI程序(例如,到交互式桌面,或通过登录用户并将其启动到他们的桌面)
事实证明,只要您没有用户需要与之交互或响应的对话框或某些可视控件,就可以在服务或服务生成的应用程序中包含GUI组件
问题2-“控件没有父窗口”
我在代码中找到了一个实例,我在运行时创建了一个控件,但没有设置它的父控件。很难找到,但已修复
问题3-仅在“我的”用户配置文件下启动外部应用程序
我通过三种方式将服务设置为登录
,1)作为本地系统,2)作为我的用户名/密码,3)作为另一个管理员用户的用户名/密码(具有与我相同的权限)
在这三种情况下,服务内部用于启动外部应用程序的返回代码表明它成功启动了应用程序。但是,只有当服务设置为使用我的用户名/密码登录时,应用程序才会真正运行
我在系统事件日志中发现了一条信息消息,该消息表示在其他两个实例中都找不到BPL。这是因为我有一个个人用户路径环境变量,其中包含BPL目录的条目。其他管理员用户和本地系统帐户没有此功能。因此,应用程序当然无法加载所需的BPL,因此无法运行
问题4-本地系统对文件系统的访问
当我们将新代码和模块推送到生产服务器时,外部应用程序未能正确启动并执行其任务(但这次Windows事件日志中没有消息)
外部应用程序需要几个参数(太多,无法在命令行上传递),因此服务将所有参数放入唯一命名的INI文件中,并将INI文件的名称传递给外部应用程序。外部应用程序完成任务后,将删除INI文件
事实证明,外部应用程序是使用本地系统帐户启动的,该帐户无法访问文件系统,因此无法打开/使用INI文件。一旦我们授予本地系统帐户适当的权限,它也开始在我们的生产环境中正常工作
现在一切都很好。不要尝试从服务运行GUI应用程序。除了David所说的,我还质疑组件是否需要TForm所有者。如果它们不是GUI,那么你应该能够将Nil传递给构造函数,如果它们是GUI,那么它们一开始就不应该出现在应用程序中。你真的看错了。如果您编写的代码需要非可视代码的
TForm
所有者,那么您只需要修复它。去掉那个不必要的限制。@DavidHeffernan-我同意这不是一个好主意。但是这个过程需要在服务器端运行。我们尝试将其构建为控制台应用程序,但其中一个第三方组件返回错误控件没有父窗口。
我希望将其作为控制台应用程序运行,但由于没有父窗口问题,它需要是GUI应用程序。有些事情是不可能的。您是否考虑过您可能无法在会话0中创建GUI的事实?无论如何,为什么要将GUI放入会话0中?为什么要禁用“允许服务与桌面交互”,而这正是您要做的?您希望在服务中放置的可视组件是什么?
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;
SeTcbPrivilege
SeSecurityPrivilege
SeTakeOwnershipPrivilege
SeLoadDriverPrivilege
SeBackupPrivilege
SeRestorePrivilege
SeDebugPrivilege
SeSystemEnvironmentPrivilege
SeImpersonatePrivilege