Delphi Windows服务作为多用户任务调度器:获取登录用户执行任务问题
Windows服务获取登录用户并定向执行任务的Delphi实现方案
一、获取所有交互式登录用户
服务运行在SYSTEM上下文,GetUserName()只能拿到系统账户,要获取登录用户得用Windows终端服务API枚举会话和用户:
- 调用
WTSEnumerateSessions枚举服务器所有会话,筛选出WTSActive状态的交互式会话 - 对每个有效会话,用
WTSQueryUserToken获取用户令牌 - 通过
GetTokenInformation和LookupAccountSid从令牌中提取用户名、SID等信息
Delphi代码示例:
uses Windows, Wtsapi32, SysUtils; procedure EnumerateLoggedInUsers; var hServer: THandle; pSessionList: PWTS_SESSION_INFO; SessionCount, i: DWORD; hUserToken: THandle; pTokenUser: PTOKEN_USER; TokenBufSize: DWORD; pUserName: LPWSTR; NameBufSize: DWORD; begin // 连接本地服务器,远程服务器传对应主机名 hServer := WTSOpenServer(nil); if hServer = 0 then Exit; try if not WTSEnumerateSessions(hServer, 0, 1, pSessionList, SessionCount) then Exit; try for i := 0 to SessionCount - 1 do begin // 仅处理活跃的用户会话 if pSessionList[i].State = WTSActive then begin if WTSQueryUserToken(pSessionList[i].SessionId, hUserToken) then try // 获取用户SID信息 TokenBufSize := 0; GetTokenInformation(hUserToken, TokenUser, nil, 0, TokenBufSize); pTokenUser := AllocMem(TokenBufSize); try if GetTokenInformation(hUserToken, TokenUser, pTokenUser, TokenBufSize, TokenBufSize) then begin // 从SID解析用户名 NameBufSize := 0; LookupAccountSidW(nil, pTokenUser.User.Sid, nil, NameBufSize, nil, nil, nil); pUserName := AllocMem(NameBufSize * SizeOf(WCHAR)); try if LookupAccountSidW(nil, pTokenUser.User.Sid, pUserName, NameBufSize, nil, nil, nil) then Writeln('登录用户: ', WideCharToString(pUserName), ' | 会话ID: ', pSessionList[i].SessionId); finally FreeMem(pUserName); end; end; finally FreeMem(pTokenUser); end; finally CloseHandle(hUserToken); end; end; end; finally WTSFreeMemory(pSessionList); end; finally WTSCloseServer(hServer); end; end;
二、针对特定用户执行任务
1. 启动UI程序到用户桌面
服务默认在系统会话,要让UI程序显示在用户桌面,需用CreateProcessAsUser结合用户令牌,并且指定桌面路径:
function RunAppInUserSession(hUserToken: THandle; const AppPath: string; const CmdArgs: string = ''): Boolean; var StartupInfo: STARTUPINFO; ProcessInfo: PROCESS_INFORMATION; begin ZeroMemory(@StartupInfo, SizeOf(StartupInfo)); StartupInfo.cb := SizeOf(StartupInfo); // 指定用户桌面,必须是'winsta0\default'才能正常显示UI StartupInfo.lpDesktop := PChar('winsta0\default'); Result := CreateProcessAsUser( hUserToken, PChar(AppPath), PChar(CmdArgs), nil, nil, False, CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS, nil, nil, StartupInfo, ProcessInfo ); if Result then begin CloseHandle(ProcessInfo.hThread); CloseHandle(ProcessInfo.hProcess); end; end;
调用时,从会话枚举中拿到目标用户的令牌传入即可。
2. 检测特定用户的程序冻结
- 用
CreateToolhelp32Snapshot枚举系统所有进程 - 对每个进程调用
OpenProcessToken获取令牌,提取用户SID后和目标用户SID对比 - 检测程序冻结可使用
IsHungAppWindow(针对带窗口的程序),或通过检查进程响应超时判断
三、关键注意事项
- 服务必须以本地系统账户运行,且需配置
SE_ASSIGNPRIMARYTOKEN_NAME和SE_INCREASE_QUOTA_NAME权限,否则CreateProcessAsUser会失败 - 调用
WTSQueryUserToken需要服务具备足够权限,否则无法获取用户令牌 - 所有获取的令牌、句柄使用后必须及时关闭,避免资源泄漏
内容的提问来源于stack exchange,提问作者Giansi87
相关产品推荐
相关产品推荐

