#windows #delphi #winapi #windows-7
#Windows #delphi #winapi #windows-7
Вопрос:
Во-первых, я хочу поблагодарить всех людей, которые работают на этом сайте, очень полезном для разработчика. Это первый случай, когда я заблокирован в своей разработке за последние 3 дня. Я искал решения в Интернете, но я не нахожу ничего, что решало бы эту проблему.
Итак, я разрабатываю службу, которая должна запускать внешнюю программу в vista / seven / xp при входе пользователя в систему. Некоторые характеристики этой службы :
- автоматический
- нет интерактивного.
- определить идентификатор сеанса зарегистрированного пользователя
Для запуска внешнего приложения с графическим интерфейсом в качестве интерактивного пользователя:
- Чтобы убедиться, что сеанс пользователя открыт, я перечисляю ВСЕ «explorer.exe «обработайте, извлеките их Pid и SessionID с помощью функции msdn ProcessIdToSessionId
- если идентификатор сеанса зарегистрированного пользователя равен идентификатору сеанса этого «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):
- Чтобы быть чистым, я устанавливаю службу и перезагружаю систему
- Я открываю сеанс с пользователем 1: внешняя программа выполнена, и я могу видеть ее форму
- Я закрываю сеанс и открываю сессию с помощью user2: внешняя программа выполняется, и я могу видеть ее форму.
Если я делаю это X раз, результат всегда один и тот же, очень хороший … но если я делаю это:
- Я повторно устанавливаю службу и перезагружаю систему
- Я открываю сеанс с пользователем 1: внешняя программа выполнена, и я могу видеть ее форму
- на этот раз я не закрываю сеанс, а меняю пользователя с помощью 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. Это то же значение.