CreateProcessAsUser не работает, когда «смена пользователя» - PullRequest
14 голосов
/ 10 ноября 2011

Во-первых, я хочу поблагодарить всех людей, которые работают на этом сайте, очень полезно для разработчика. Это первый раз, когда я заблокирован в моей разработке за 3 дня. Я искал решения в Интернете, но не нашел ничего, что решило бы эту проблему.

Итак, я разрабатываю сервис, который должен выполнять внешнюю программу на vista / seven / xp, когда пользователь входит в систему. Некоторые характеристики этой услуги:

  • автоматический
  • без интерактива.
  • определить идентификатор сеанса пользователя, вошедшего в систему

Чтобы запустить внешнее приложение с графическим интерфейсом в качестве интерактивного пользователя:

  1. Чтобы убедиться, что пользовательский сеанс открыт, я перечисляю ВСЕ процесс «explorer.exe», извлекаю их Pid и SessionID с помощью функции msdn ProcessIdToSessionId
  2. если SessionID зарегистрированного пользователя совпадает с идентификатором сеанса этого процесса explorer.exe, я уверен, что «хороший» рабочий стол запущен, поэтому теперь я могу выполнить внешнюю программу. (Я говорю «хороший» рабочий стол, потому что, как вы знаете, в системе может быть открыто более одного сеанса пользователя)
  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;
    

Этот код в большинстве случаев приемлем, кроме одного: когда я меняю Пользователя. Позвольте мне объяснить это с двумя простыми пользователями (домен \ пользователь1 и домен \ пользователь2):

  1. Чтобы быть чистым, я устанавливаю сервис и перезагружаю систему
  2. Я открываю сессию с user1: внешняя программа выполняется, и я вижу ее форму
  3. Я закрываю сессию и открываю сеанс с пользователем2: внешняя программа выполняется, и я вижу ее форму.

Если я делаю это X раз, результат всегда один и тот же, очень хороший ... но если я делаю это:

  1. Я переустанавливаю сервис и перезагружаю систему
  2. Я открываю сессию с user1: внешняя программа выполняется, и я вижу ее форму
  3. на этот раз я не закрываю сессию , но изменяю пользователя на user2: внешняя программа выполняется, но я не вижу форму и возникает ошибка: Код системной ошибки 5: Доступ запрещен.

Что-то не так, но я не могу найти решение. Спасибо за ваши ответы ...

Ответы [ 2 ]

15 голосов
/ 10 ноября 2011

Вам не нужно перечислять запущенные процессы 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;
1 голос
/ 10 ноября 2011

Вероятно, ваш метод получения идентификатора сеанса путем нахождения "хорошего" explorer.exe не работает для быстрого переключения пользователей.

Попробуйте зарегистрировать приложение для уведомлений об изменениях сеанса с помощью WTSRegisterSessionNotification .После этого вы получите уведомления о переключении сеанса с указанием текущего идентификатора сеанса.

Обратите внимание на следующее:

Чтобы получать уведомления об изменении сеанса от службы, используйте Функция HandlerEx .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...