CreateProcessAsUser не работает, когда "сменить пользователя"
Во-первых, я хочу поблагодарить всех, кто работает на этом сайте, очень полезный для разработчика. Это первый я заблокирован в своем развитии с 3 дней. Я искал решения в Интернете, но ничего не нашел, что решает эту проблему.
Итак, я разрабатываю службу, которая должна выполнять внешнюю программу на vista/seven/xp, когда пользователь регистрируется. Некоторые характеристики этой услуги:
- автоматический
- нет интерактивных.
- определить идентификатор сеанса зарегистрированного пользователя
Чтобы запустить внешнее GUI-приложение в качестве интерактивного пользователя:
- Чтобы убедиться, что сеанс пользователя открыт, я перечисляю ВСЕ процесс "explorer.exe", извлекаю их Pid и SessionID с помощью функции msdn ProcessIdToSessionId
- если SessionID зарегистрированного пользователя равен идентификатору сеанса этого процесса "explorer.exe", я уверен, что рабочий стол "хороший" работает, поэтому теперь я могу выполнить внешнюю программу. (Я говорю "хороший" рабочий стол, потому что, как вы знаете, в системе может быть открыто несколько сеансов пользователя)
-
после этого я запускаю приложение с помощью этой функции:
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: выполняется внешняя программа, и я могу видеть ее форму
- я закрыть сеанс и открыть с помощью user2: выполняется внешняя программа, и я вижу ее форму.
Если я делаю это X раз, результат всегда один и тот же, очень хороший... но если я это сделаю:
- Я переустанавливаю службу и перезагружаю систему.
- Я открываю сеанс с user1: выполняется внешняя программа, и я могу видеть ее форму
- на этот раз я не закрываю сеанс, но меняет пользователя с user2: выполняется внешняя программа, но я не вижу формы и возникает ошибка: System код ошибки 5: Доступ запрещен.
Что-то не так, но я не нашел решения. Спасибо за ваши ответы...
Ответы
Ответ 1
Вам не нужно перечислять запущенные процессы explorer.exe, вместо этого вы можете использовать WTSGetActiveConsoleSessionId()
, а затем передать этот SessionId в WTSQueryUserToken()
. Обратите внимание, что WTSQueryUserToken()
возвращает токен олицетворения, но CreateProcessAsUser()
нужен первичный токен, поэтому используйте DuplicateTokenEx()
для этого преобразования.
Вы также должны использовать CreateEnvironmentBlock()
, чтобы порожденный процесс имел подходящую среду, которая подходит для используемой учетной записи пользователя.
Наконец, установите для поля STARTUPINFO.lpDesktop
значение 'WinSta0\Default'
вместо nil
, чтобы порожденный пользовательский интерфейс мог быть правильно отображен.
Я использую этот подход уже несколько лет и не испытываю никаких проблем с ним. Например:
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;
Ответ 2
Вероятно, ваш метод получения идентификатора сеанса путем поиска "хорошего" explorer.exe не работает для быстрого переключения пользователей.
Попробуйте зарегистрироваться в регистрации для уведомлений об изменении сеанса с помощью WTSRegisterSessionNotification. Затем вы получите уведомления при переключении сеанса в комплекте с текущим идентификатором сеанса.
Обратите внимание на следующее:
Чтобы получать уведомления об изменении сеанса из службы, используйте функция HandlerEx.