You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 02:33:29