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

Posted

技术标签:

【中文标题】“更改用户”时 CreateProcessAsUser 不起作用【英文标题】:CreateProcessAsUser doesn't work when "change user" 【发布时间】:2011-12-26 06:08:16 【问题描述】:

首先,我要感谢所有为本网站工作的人,这对开发人员非常有用。这是我 3 天以来的第一次发展受阻。我在互联网上搜索了解决方案,但没有找到解决此问题的方法。

所以,我开发了一个服务,当用户登录时,它必须在 vista/seven/xp 上执行一个外部程序。该服务的一些特点:

自动 没有互动。 检测登录用户的会话 ID

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

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

    之后,我用这个函数运行应用程序:

    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)来解释一下:

    为了干净,我安装服务并重新启动系统 我打开与 user1 的会话:外部程序已执行,我可以看到它的形式 我关闭会话并打开与 user2 的会话:外部程序已执行,我可以看到它的表单。

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

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

出了点问题,但我没有找到解决方案。感谢您的回答...

【问题讨论】:

“解雇所有的人”可能不是你的意思——它的意思是忽略他们或把他们赶走!也许是“鼓掌”或“感谢”?无论如何,我们知道您的意思,只是认为您可能感兴趣:) 太棒了!我只对这个词使用谷歌翻译(法语中的“remercier”)。你可以检查一下,谷歌返回:谢谢,解雇和表示感谢。我再也不相信它了 【参考方案1】:

您不需要枚举正在运行的 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;

【讨论】:

感谢 U Remy 的出色解释。问题尚未解决,但您的回答对我有帮助。我用您的代码编译服务:快速用户切换中的问题是相同的。所以我决定运行另一个外部程序,比如 notepad.exe:没问题!之后,我尝试使用批处理文件简单地运行外部程序:“ACCESS DENIED”。所以我得出结论,问题不在于服务的源代码,而在于仅表现不佳的外部程序的源代码在“快速用户切换”中。如果我找到解决方案,我会看到并回复你。但也许你有一个想法...... 我忘了说,在错误对话框消息“代码 5,访问被拒绝”之后,会出现第二个错误对话框,其中包含以下消息:“无法在 OnShow 或 OnHide 上更改可见”。其他事情:我的“write_log”函数包含在主表单的 FormCreate 方法中似乎不起作用 您用来生成程序的用户帐户可能对您尝试写入的日志文件没有权限。请记住,默认情况下,文件归创建它的用户帐户所有,除非文件存储在应用较少限制的文件夹中。要让多个用户共享对文件的访问权限,请将文件存储在 %ALLUSERSPROFILE% 的子文件夹中,和/或使用SetFileSecurity() 调整文件的 SACL/DACL 以允许所有用户访问。 @remy: WtsQeuryUserToken a,ready 返回一个主令牌,因此无需复制它。我还建议模拟,以便您可以访问他/她的文件,并且您还可以考虑加载用户配置文件。 另外 CPAUW 可能会改变命令行字符串(参数实际上是一个输入/输出)。所以它不能指向一个文字字符串(在这个例子中)。出于安全原因,文件名应该用引号引起来,否则路径中的空格可能会导致错误的行为;尤其是参数。如果您只想运行应用程序,请考虑改用参数 lpApplicationName。正如 remko 所指出的,应该使用模拟令牌调用 CPAU。否则,可执行文件是使用当前进程令牌(SYSTEM)访问的。请参阅 CPAU 的 MSDN 中的备注。必读。【参考方案2】:

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

尝试使用WTSRegisterSessionNotification 让您的应用程序注册会话更改通知。然后,您将在会话切换时收到通知,并显示当前会话 ID。

注意以下几点:

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

【讨论】:

sessionID 很好,我将这个值记录在一个文件中,并将它与任务管理器中进程的会话 ID 进行比较。这是相同的值。为了通知,我已经使用了这个函数并使用 WTS_SESSION_LOGON 作为外部程序执行的条件。 坦克 Marcus 为您解答,但 sessionID 很好,我将这个值记录在一个文件中,并将它与任务管理器中进程的会话 ID 进行比较。这是相同的值。

以上是关于“更改用户”时 CreateProcessAsUser 不起作用的主要内容,如果未能解决你的问题,请参考以下文章

登录时更改spring security中的用户名

WPF在运行方法时更改用户控件中的按钮内容

MVC 5在用户ID文本框更改时自动填充电子邮件文本框

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

更改用户角色时如何避免数据库异常?

我可以更改用户注册时 FirebaseUI 要求的信息吗?