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.