CreateProcessAsUser не работает при «смене пользователя»

#windows #delphi #winapi #windows-7

#Windows #delphi #winapi #windows-7

Вопрос:

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

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

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

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

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

Этот код в порядке в большинстве случаев, за исключением одного: когда я меняю пользователя. Позвольте мне объяснить это с помощью двух простых пользователей (Domain user1 и Domain user2):

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

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

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

Что-то не так, но я не нахожу решения. Спасибо за ваши ответы…

Комментарии:

1. «Уволить всех пользователей», вероятно, не то, что вы хотите сказать — это означает игнорировать их или отсылать прочь! Может быть, «поаплодировать» или «поблагодарить»? В любом случае, мы знаем, что вы имеете в виду, просто подумали, что вам может быть интересно 🙂

2. Отлично! Я использую Google Translate только для этого слова («remercier» по-французски). Вы можете проверить это, Google возвращает: поблагодарить, отклонить и выразить благодарность. Я больше не доверяю этому

Ответ №1:

Вам не нужно перечислять запущенные explorer.exe процессы, которые вы можете использовать WTSGetActiveConsoleSessionId() вместо этого, а затем передать этот идентификатор сеанса WTSQueryUserToken() . Обратите внимание, что WTSQueryUserToken() возвращает токен олицетворения, но CreateProcessAsUser() требуется основной токен, поэтому используйте DuplicateTokenEx() для этого преобразования.

Вы также должны использовать CreateEnvironmentBlock() , чтобы созданный процесс имел надлежащую среду, подходящую для используемой учетной записи пользователя.

Наконец, установите для STARTUPINFO.lpDesktop поля значение 'WinSta0Default' вместо 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 := 'WinSta0Default';

  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. Спасибо, Реми, за твои отличные объяснения. Проблема не решена, но ваш ответ помогает мне. Я компилирую службу с вашим кодом: проблема та же при быстром переключении пользователей. Поэтому я решаю запустить другую внешнюю программу, например notepad.exe : нет проблем! После этого я попытался просто запустить внешнюю программу с командным файлом: «ДОСТУП ЗАПРЕЩЕН». ИТАК, я прихожу к выводу, что проблема не в исходном коде службы, а в исходном коде внешней программы, которая плохо себя ведет только при «быстром переключении пользователя». Я собираюсь увидеть это и вернуться к вам, если я нашел решение. Но, может быть, у вас есть идея…

2. Я забыл сказать, что после сообщения в диалоговом окне об ошибке «Код 5, доступ запрещен» появляется второе диалоговое окно об ошибке с этим сообщением: «Невозможно изменить видимое на OnShow или onHide». Другое дело: мои функции «write_log», включенные в метод FormCreate основной формы, похоже, не работают

3. Учетная запись пользователя, которую вы используете для запуска программы, скорее всего, не имеет разрешений на файл журнала, в который вы пытаетесь записать. Помните, что по умолчанию файл принадлежит учетной записи пользователя, которая его создала, если только файл не хранится в папке, к которой применяются меньшие ограничения. Чтобы предоставить общий доступ к файлу нескольким пользователям, либо сохраните файл во вложенной папке %ALLUSERSPROFILE% и / или используйте SetFileSecurity() для настройки SACL / DACL файла, чтобы разрешить доступ всем пользователям.

4. @remy: WtsQeuryUserToken a,ready возвращает первичный токен, поэтому нет необходимости дублировать его. Я также советую выдавать себя за другого, чтобы у вас был доступ к его / ее файлам, и вы также могли бы подумать о загрузке профиля пользователя.

5. Кроме того, CPAUW может изменять строку командной строки (параметр на самом деле является входом / выходом). Таким образом, он не должен указывать на буквальную строку (в этом примере). По соображениям безопасности имя файла должно быть заключено в кавычки, иначе пробелы в пути могут привести к некорректному поведению; особенно с параметрами. Если вы просто хотите запустить приложение, рассмотрите возможность использования параметра lpApplicationName вместо этого. Как указал Ремко, CPAU следует вызывать с использованием олицетворенного токена. В противном случае доступ к исполняемому файлу осуществляется с использованием токена текущего процесса (SYSTEM). Смотрите примечания в MSDN CPAU. Обязательное чтение.

Ответ №2:

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

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

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

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

Комментарии:

1. Идентификатор сеанса в порядке, я записываю это значение в файл и сравниваю его с идентификатором сеанса процесса в taskmanager. Это то же значение. Для уведомления я уже использую эту функцию и использую WTS_SESSION_LOGON для условия выполнения внешней программы.

2. Спасибо Маркусу за ваш ответ, но идентификатор сеанса хорош, я записываю это значение в файл и сравниваю его с идентификатором сеанса процесса в taskmanager. Это то же значение.