“更改用户”时 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 不起作用的主要内容,如果未能解决你的问题,请参考以下文章