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

时间:2011-11-10 14:55:11

标签: windows delphi winapi windows-7

首先,我要感谢所有为本网站工作的人,对开发人员非常有用。这是我从3天开始就被阻止的第一次。我在互联网上搜索过解决方案,但我找不到任何可以解决这个问题的方法。

因此,我开发了一项服务,当用户登录时,必须在vista / seven / xp上执行外部程序。这项服务的一些特点:

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

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

  1. 为确保打开用户会话,我列出所有“explorer.exe”进程,使用msdn函数ProcessIdToSessionId提取其Pid和SessionID
  2. 如果记录用户的SessionID与此“explorer.exe”进程的会话ID相等,我确信“好”桌面正在运行,所以现在我可以执行外部程序。 (我说“好”的桌面,因为,如你所知,可以在系统上打开多个用户会话)
  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;
    
  4. 这个代码在大多数情况下都没问题,除了一个:当我更改用户时。让我用2个简单用户(Domain \ user1和Domain \ user2)解释它:

    1. 要清洁,我安装服务并重新启动系统
    2. 我用user1打开会话:执行外部程序,我可以看到它的形式
    3. 关闭会话并使用user2开启:外部程序已执行,我可以看到它的形式。
    4. 如果我这样做X次,结果总是一样的,非常好......但如果我这样做:

      1. 我重新安装服务并重新启动系统
      2. 我用user1打开会话:执行外部程序,我可以看到它的形式
      3. 这次,我没有关闭会话,但更改用户与user2:执行外部程序但我看不到表单并发生错误:系统错误代码5:拒绝访问。
      4. 出了点问题,但我找不到解决办法。谢谢你的回答...

2 个答案:

答案 0 :(得分:15)

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

答案 1 :(得分:1)

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

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

请注意以下事项:

  

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