Windows CreateProcessAsUser不';“我不工作时”;“更改用户”;

Windows CreateProcessAsUser不';“我不工作时”;“更改用户”;,windows,delphi,winapi,windows-7,Windows,Delphi,Winapi,Windows 7,首先,我要感谢所有为这个网站工作的人,这对开发者来说非常有用。这是我3天以来第一次在发展中受阻。我在互联网上搜索了解决方案,但没有找到解决这个问题的方法 因此,我开发了一个服务,当用户登录时,它必须在vista/seven/xp上执行一个外部程序。这项服务的一些特点: 自动的 没有互动 检测已登录用户的会话ID 要以交互用户身份运行外部GUI应用程序,请执行以下操作: 为了确保打开了一个用户会话,我列出了所有的“explorer.exe”进程,用msdn函数ProcessIdToSessio

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

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

  • 自动的
  • 没有互动
  • 检测已登录用户的会话ID
要以交互用户身份运行外部GUI应用程序,请执行以下操作:

  • 为了确保打开了一个用户会话,我列出了所有的“explorer.exe”进程,用msdn函数ProcessIdToSessionId提取它们的Pid和SessionID
  • 如果登录用户的会话ID与此“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;
    
  • 这个代码在大多数情况下都是可以的,除了一个:当我更改用户时。让我用两个简单的用户(Domain\user1和Domain\user2)解释一下:

  • 为了保持整洁,我安装了服务并重新启动了系统
  • 我打开与user1的会话:外部程序被执行,我可以看到它的形式
  • I关闭会话并使用user2打开会话:执行外部程序,我可以看到它的表单
  • 如果我这样做X次,结果总是一样的,非常好…但是如果我这样做:

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

  • 出了点问题,但我找不到解决办法。感谢您的回答…

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

    尝试让您的应用程序向注册会话更改通知。然后,当会话切换时,您将收到通知,并填写当前会话ID

    注意以下几点:

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


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

    “解雇所有人”可能不是你想说的意思——它的意思是忽略他们或把他们赶走!也许是“鼓掌”或“谢谢”?我们知道你的意思,反正,只是觉得你可能会感兴趣:)太好了!我只使用谷歌翻译这个词(“remercier”法语)。你可以检查它,谷歌返回:谢谢,辞退和致谢。我再也不相信它了sessionID很好,我将这个值记录在一个文件中,并将它与taskmanager中进程的会话Id进行比较。这是相同的值。对于通知,我已经使用了此函数,并使用WTS_SESSION_LOGON作为外部程序执行的条件。对于您的答案,我将此值记录在一个文件中,并将其与taskmanager中进程的会话Id进行比较。这是相同的值。感谢你的出色解释。问题没有解决,但你的回答帮助了我。我用你的代码编译服务:快速用户切换中的问题是相同的。所以我决定运行另一个外部程序,比如notepad.exe:没问题!之后,我尝试使用批处理文件“拒绝访问”简单地运行外部程序。因此,我得出结论,问题不在于服务的源代码,而在于外部程序的源代码,它只在“快速用户切换”中表现糟糕。如果我找到了解决方案,我会查看并返回给您。但是,也许您有一个想法……我忘了说,在错误对话框消息“代码5,访问被拒绝”之后,会出现第二个错误对话框,其中包含以下消息:“无法更改在显示或隐藏时可见”。其他事项:主窗体的FormCreate方法中包含的“write_log”函数似乎不起作用您用于生成程序的用户帐户可能对您尝试写入的日志文件没有权限。请记住,默认情况下,文件由创建它的用户帐户拥有,除非该文件存储在应用较少限制的文件夹中。要让多个用户共享对文件的访问,请将文件存储在%ALLUSERSPROFILE%的子文件夹中,和/或使用
    SetFileSecurity()
    调整文件的SACL/DACL以允许所有用户访问。@remy:WtsQeuryUserToken a,ready返回一个主令牌,因此无需复制它。我还建议冒充你可以访问他/她的文件,你也可以考虑加载用户配置文件。此外CPAUW也可以更改命令行字符串(参数实际上是一个输入/输出)。所以它不能指向文本字符串(在本例中)。对于se