当“更改用户”时,CreateProcessAsUser不起作用

首先,我要感谢所有为这个网站工作的人,对开发人员非常有用。 这是我3天以来第一次受到阻挠。 我在互联网上search的解决scheme,但我找不到解决这个问题。

所以,我开发了一个服务,当用户login时,必须在vista / 7 / xp上执行一个外部程序。 这项服务的一些特点:

  • 自动
  • 没有互动性。
  • 检测login用户的会话标识

以交互式用户身份运行外部GUI应用程序:

  1. 为了确保打开用户会话,我列出了所有“explorer.exe”进程,使用msdn函数ProcessIdToSessionId提取它们的Pid和SessionID
  2. 如果login用户的SessionID与此“explorer.exe”进程的会话ID相同,我确信“good”桌面正在运行,所以现在我可以执行外部程序。 (我说“好”的桌面,因为如你所知,可以在系统上打开多个用户会话)
  3. 之后,我用这个函数运行应用程序:

    function RunInteractive(prog_filename: String; sessionID: Cardinal): boolean; var hToken: THandle; si: _STARTUPINFOA; pi: _PROCESS_INFORMATION; begin ZeroMemory(@si, SizeOf(si)); si.cb := SizeOf(si); SI.lpDesktop := nil; if WTSQueryUserToken(sessionID, hToken) then begin if CreateProcessAsUser(hToken, nil, PChar(prog_filename), nil, nil, False, 0, nil, PChar(ExtractFilePath(prog_filename)), si, pi) then result := true else result := false; end else Begin result := false; End; CloseHandle(hToken); end; 

这个代码在大多数情况下都可以,除了一个:当我改变用户。 让我用2个简单的用户(Domain \ user1和Domain \ user2)来解释它:

  1. 为了干净,我安装服务并重启系统
  2. 我打开与user1的会话:外部程序被执行,我可以看到它的forms
  3. closures会话,并与user2开放:外部程序被执行,我可以看到它的forms。

如果我这样做X次,结果总是一样的,非常好…但如果我这样做:

  1. 我重新安装服务并重新启动系统
  2. 我打开与user1的会话:外部程序被执行,我可以看到它的forms
  3. 这一次,我没有closures会话,但用user2 更改用户 :外部程序执行,但我看不到表单,并出现错误:系统错误代码5:访问被拒绝。

有什么不对,但我找不到解决办法。 感谢您的回答…

您不需要枚举运行的explorer.exe进程,您可以使用WTSGetActiveConsoleSessionId() ,然后将该SessionId传递给WTSQueryUserToken() 。 请注意, WTSQueryUserToken()返回模拟令牌,但CreateProcessAsUser()需要主令牌,因此使用DuplicateTokenEx()进行该转换。

您还应该使用CreateEnvironmentBlock()以便生成的进程具有适合正在使用的用户帐户的适当环境。

最后,将STARTUPINFO.lpDesktop字段设置为'WinSta0\Default'而不是nil这样生成的UI可以正确显示。

我已经使用了这种方法好几年了,并没有任何问题。 例如:

 function CreateEnvironmentBlock(var lpEnvironment: Pointer; hToken: THandle; bInherit: BOOL): BOOL; stdcall; external 'userenv.dll' function DestroyEnvironmentBlock(lpEnvironment: Pointer): BOOL; stdcall; external 'userenv.dll'; function RunInteractive(prog_filename: String): Boolean; var hUserToken, hToken: THandle; si: _STARTUPINFOA; pi: _PROCESS_INFORMATION; SessionId: DWORD; Env: Pointer; begin Result := False; ZeroMemory(@si, SizeOf(si)); si.cb := SizeOf(si); si.lpDesktop := 'WinSta0\Default'; SessionId := WTSGetActiveConsoleSessionId; if SessionId = $FFFFFFFF then Exit; if not WTSQueryUserToken(SessionID, hToken) then Exit; try if not DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, nil, SecurityIdentification, TokenPrimary, hUserToken) then Exit; finally CloseHandle(hToken); end; try if not CreateEnvironmentBlock(Env, hUserToken, False) then Exit; try Result := CreateProcessAsUser(hUserToken, nil, PChar(prog_filename), nil, nil, False, CREATE_UNICODE_ENVIRONMENT, Env, PChar(ExtractFilePath(prog_filename)), si, pi); if Result then begin CloseHandle(pi.hThread); CloseHandle(pi.hProcess); end; finally DestroyEnvironmentBlock(Env); end; finally CloseHandle(hUserToken); end; end; 

可能您通过查找“好”explorer.exe获取会话ID的方法不适用于快速用户切换。

尝试使用WTSRegisterSessionNotification为您的应用程序注册会话更改通知。 当会话切换时,您将收到通知,并附有当前的会话ID。

请注意以下几点:

要从服务接收会话更改通知,请使用HandlerEx函数。