Добрый день
Существует стартующая с системной учетной записью интерактивная служба, успешно работающая под Win 2k, 2k3, XP
Под Windows 7 Pro 32x в трее (области уведомлений) не появляется иконка.
Отметка "Разрешить взаимодействие с рабочим столом" стоит.
Shell_NotifyIcon(NIM_ADD, ...)=FALSE даже если рабочий стол уже существует
GetLastError()=0
Вариант сделать клиента запускающегося не как служба не подходит
Здравствуйте, kovbas, Вы писали:
K>Существует стартующая с системной учетной записью интерактивная служба, успешно работающая под Win 2k, 2k3, XP K>Под Windows 7 Pro 32x в трее (области уведомлений) не появляется иконка.
Подозреваю, под Вистой у Вас такая же беда? Ибо начиная с Висты (если память не изменяет), MS назвала все интерактивные службы небезопасными.
K>Вариант сделать клиента запускающегося не как служба не подходит
Почему? Будет два (или более*) модуля, каждый из которых будет заниматься своим делом.
* Вы тестировали такой кейс, когда на серверной ОС одновременно залогинены два юзера (скажем, через remote desktop)?
Трассировка показывет, что все в порядке. Запускаю службу под пользователем с системной учетной записью.
Как <Обнаружитель интерактивных служб> реагировал на ShowWindow(), так и реагирует.
ВОПРОС: Как правильно подменить рабочий стол системных служб на рабочий стол залогиненного пользователя в Windows 7 / Vista / 2008
Здравствуйте, Mr.Delphist, Вы писали:
MD>* Вы тестировали такой кейс, когда на серверной ОС одновременно залогинены два юзера (скажем, через remote desktop)?
На самом деле проблемы с интерактивными службами начались еще с XP после появления fast user switching.
Здравствуйте, kovbas, Вы писали: K>Родил следующий код:
... K>Трассировка показывет, что все в порядке. Запускаю службу под пользователем с системной учетной записью. K>Как <Обнаружитель интерактивных служб> реагировал на ShowWindow(), так и реагирует. K>ВОПРОС: Как правильно подменить рабочий стол системных служб на рабочий стол залогиненного пользователя в Windows 7 / Vista / 2008
Сама постановка вопроса немного некорректна.
У служб вообще нету своего рабочего стола, а в Vista и выше они выполняются в
разных с пользовательскими процессами сессиях. И не забывайте, что сервис — почти как
консольный процесс, то есть никакие оконные сообщения до него не дойдут.
Могу посоветовать запустить из службы дочерний процесс от имени пользователя и весь
GUI-интерфейс организовать внутри этого процесса.
Но для того, чтобы писать код в этой области, нужно хотя бы в общих чертах
разбираться в таких понятиях, как пользовательская сессия (консольная или
терминальная), оконная станция, рабочий стол, контекст и маркер безопасности.
И вообще, понимание системы безопасности Windows тоже очень желательно, иначе
можно постоянно наступать на одни и те же грабли, не находя причин.
Очень рекомендую разобраться во всей этой "кухне".
Есть две разных схемы запуска дочерних интерактивных процессов из служб.
Первая из них довольно тривиальна (ее мы и рассмотрим подробно).
Если вкратце, то суть ее в том, чтобы получить маркер безопасности
пользователя и создать процесс с этим маркером. При этом не нужно проводить
никаких манипуляций с рабочими столами и оконными станциями, так как вся
нужная информация уже содержится внутри этого маркера.
Созданный процесс, за исключением очень незначительных деталей, ничем не
отличается от других, запущенных пользователем.
Единственная важная деталь — профиль пользователя должен быть загружен, иначе
созданному процессу будут недоступны кое-какие данные из Documents And
Settings и ветки реестра HKEY_CURRENT_USER.
Вторая схема более сложная и извращенная.
Ее в каком-то смысле можно назвать стыковкой — новый интерактивный процесс
создается в сессии пользователя, но при этом сохраняет все атрибуты безопасности
службы (LocalSystem), которая его запустила. Это более "веселенько" получается,
потому что порожденный процесс уже может взаимодействовать с рабочим столом
пользователя, отправлять и принимать оконные сообщения и так далее.
Для создания такого процесса требуется несколько не очень очевидных действий с
маркером безопасности (DuplicateToken, SetTokenInformation с ключом TokenSessionId и
некоторые другие). Разумеется, нет нужды говорить, что это является потенциальной
дырой в безопасности. Поэтому обычно рекомендуется не использовать для нового
процесса системный контекст, а создавать специальный ограниченный маркер
(см. CreateRestrictedToken). Если кого-то заинтересует, могу описать подробно.
Вот функция, которая запускает стандартный Windows-калькулятор на
рабочем столе определенного пользователя.
Скрытый текст
/************************************************************************
Функция StartCalculator.
Открывает стандартный калькулятор (calc.exe) на рабочем столе
определенного пользователя. Предназначена только для запуска из службы (LocalSystem).
Возвращает TRUE в случае успеха и FALSE в случае ошибки.
*************************************************************************/
BOOL
_stdcall
StartCalculator()
{
// Определяется Id активной пользовательской сессии.
// Значение 0xFFFFFFFF означает что консольной сессии не существует.
// Идентификатор сессии лучше всего получать в обработчике
// событий службы, см. RegisterServiceCtrlHandler(Ex).
// Там же можно определить консольная сессия или терминальная.
// См. SERVICE_CONTROL_SESSIONCHANGE, WM_WTSSESSION_CHANGE,
// WTSSESSION_NOTIFICATION, а также WTSQuerySessionInformation.
DWORD IdSession = WTSGetActiveConsoleSessionId();
if (IdSession == 0xFFFFFFFF)
{
return FALSE;
}
// Для того, чтобы действовать от имени пользователя, нужно
// получить так называемый маркер безопасности, представляющий
// этого пользователя в системе. Для этого существует функция
// WTSQueryUserToken, она требует высоких привилегий, которые
// обычно есть только у сервисов LocalSystem.
HANDLE hUserToken;
if (WTSQueryUserToken(IdSession, &hUserToken) == FALSE)
{
return FALSE;
}
// Перед созданием процесса в контексте пользователя необходимо
// выполнить некоторые дополнительные действия.
// Во-первых, нужно загрузить профиль пользователя - без этого
// новому процессу будут недоступны данные, лежащие в папках
// профиля и ключе реестра HKEY_CURRENT_USER.
// Для загрузки профиля нужно знать имя пользователя.
// Узнать его можно с помощью функции WTSQuerySessionInformation с
// ключом WTSUserName, но дело в том, что при эта функция зависит
// от службы терминалов, и если та еще не успела загрузиться (а такое
// бывает нередко при включении компьютера и быстром входе в систему),
// вызов WTSQuerySessionInformation вернет ошибку.
// Это имеет значение, когда запуск интерактивного процесса должен
// выполняться сразу же после входа пользователя.
// Мы поступим по-другому - временно переключимся в контекст
// безопасности пользователя и оттуда вызовем GetUserName.
// Замечу, что без подмены маркера безопасности вызов GetUserName
// будет возвращать имя SYSTEM (это распостраненная ошибка при попытке
// получить имя пользователя из службы).
// В MSDN сказано, что максимальная длина имени пользователя - 20 символов.
// Добавим еще один для нулевого завершающего символа.wchar_t pUserName[21];
DWORD cchUserNameBuffer = 21;
// Теперь данный поток временно будет действовать от имени пользователя.
// Нужно учитывать, что при этом заимствуется также его контекст безопасности.
// То есть, доступ к критическим системным объектам будет ограничен.if (ImpersonateLoggedOnUser(hUserToken) == FALSE)
{
CloseHandle(hUserToken);
return FALSE;
}
if (GetUserNameW(pUserName, &cchUserNameBuffer) == FALSE)
{
RevertToSelf();
CloseHandle(hUserToken);
return FALSE;
}
// Возвращаем системный контекст "на место".
RevertToSelf();
// Заполняем нужные структуры и загружаем профиль пользователя.
PROFILEINFOW prof;
ZeroMemory(&prof, sizeof (prof));
prof.dwSize = sizeof (prof);
prof.lpUserName = pUserName;
prof.dwFlags = PI_NOUI;
if (LoadUserProfileW(hUserToken, &prof) == FALSE)
{
CloseHandle(hUserToken);
return FALSE;
}
// Последнее приготовление - нужно подготовить переменные окружения.
// Подробнее можно почитать в MSDN-справке к функции CreateProcessAsUser.void *pEnvBlock;
if (CreateEnvironmentBlock(&pEnvBlock, hUserToken, FALSE) == FALSE)
{
UnloadUserProfile(hUserToken, prof.hProfile);
CloseHandle(hUserToken);
return FALSE;
}
// Создание процесса почти ничем не отличается от стандартного способа с
// CreateProcess, только нужно указать маркер безопасности, от имени
// которого этот процесс будет создан.
STARTUPINFOW si;
GetStartupInfoW(&si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOWNORMAL;
si.lpDesktop = L"WinSta0\\Default";
wchar_t pCmdLine[] = {L"calc.exe"};
PROCESS_INFORMATION pi;
if (CreateProcessAsUserW(hUserToken,
NULL,
pCmdLine,
NULL,
NULL,
FALSE,
NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT,
pEnvBlock,
NULL,
&si,
&pi) == FALSE)
{
DestroyEnvironmentBlock(pEnvBlock);
UnloadUserProfile(hUserToken, prof.hProfile);
CloseHandle(hUserToken);
return FALSE;
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
DestroyEnvironmentBlock(pEnvBlock);
// Эта функция должна вызываться при завершении созданного процесса.
// UnloadUserProfile(hUserToken, prof.hProfile);
CloseHandle(hUserToken);
// Собственно, все !return TRUE;
}
Испытано практически на XP/Vista/Seven/Server2008, 32-64 бита.
Здравствуйте, kovbas, Вы писали:
K>Добрый день K>Существует стартующая с системной учетной записью интерактивная служба, успешно работающая под Win 2k, 2k3, XP K>Под Windows 7 Pro 32x в трее (области уведомлений) не появляется иконка. K>Отметка "Разрешить взаимодействие с рабочим столом" стоит. K>Shell_NotifyIcon(NIM_ADD, ...)=FALSE даже если рабочий стол уже существует K>GetLastError()=0 K>Вариант сделать клиента запускающегося не как служба не подходит
Кстати, Shell_NotifyIcon иногда возвращает ошибку, если пользователь уже вошел в систему, а
его интерактивное окружение (рабочий стол) еще не до конца загрузилось.
Надо просто организовать цикл и в нем вызывать Shell_NotifyIcon до тех пор, пока она
не вернет код успеха. Скажем, раз в секунду. Проверено.
Здравствуйте, okman, Вы писали:
O>У служб вообще нету своего рабочего стола, а в Vista и выше они выполняются в O>разных с пользовательскими процессами сессиях. И не забывайте, что сервис — почти как O>консольный процесс, то есть никакие оконные сообщения до него не дойдут.
Что-то Вы тут в понятиях напутали Всякий процесс. в т.ч. сервисный, запускается в какой-то оконной станции(window station), и каждый поток связан с каким-то рабочим столом(Desktop). Оконные сообщения вполне могут доходить как до потоков консольки, так и сервиса. Другое дело, что есть ограничения на передачу сообщений между рабочими столами, и их нельзя передать в другую терминальную сессию.
Здравствуйте, okman, Вы писали:
O>Сама постановка вопроса немного некорректна. O>У служб вообще нету своего рабочего стола, а в Vista и выше они выполняются в O>разных с пользовательскими процессами сессиях.
Если у служб нет своего рабочего стола, что тогда нам выводит сервис <Обнаружение интерактивных служб>?
На чем в этом случае располагается окно приложения?
Кстати, если это не рабочий стол, может его можно как-то минимизировать или сделать прозрачным?
O>Но для того, чтобы писать код в этой области, нужно хотя бы в общих чертах O>разбираться в таких понятиях, как пользовательская сессия (консольная или O>терминальная), оконная станция, рабочий стол, контекст и маркер безопасности. O>И вообще, понимание системы безопасности Windows тоже очень желательно, иначе O>можно постоянно наступать на одни и те же грабли, не находя причин. O>Очень рекомендую разобраться во всей этой "кухне".
O>Есть две разных схемы запуска дочерних интерактивных процессов из служб.
O>Первая из них довольно тривиальна (ее мы и рассмотрим подробно). O>Если вкратце, то суть ее в том, чтобы получить маркер безопасности O>пользователя и создать процесс с этим маркером. O>Единственная важная деталь — профиль пользователя должен быть загружен, иначе O>созданному процессу будут недоступны кое-какие данные из Documents And O>Settings и ветки реестра HKEY_CURRENT_USER.
Насколько я понял, это будет отдельный process, работающий с GUI и посылать message'и ему будет нельзя: фактически тоже самое что написать клиентскую программу работающую из Автозагрузки?
O>Вторая схема более сложная и извращенная. O>Ее в каком-то смысле можно назвать стыковкой — новый интерактивный процесс O>создается в сессии пользователя, но при этом сохраняет все атрибуты безопасности O>службы (LocalSystem), которая его запустила. Это более "веселенько" получается, O>потому что порожденный процесс уже может взаимодействовать с рабочим столом O>пользователя, отправлять и принимать оконные сообщения и так далее. O>Для создания такого процесса требуется несколько не очень очевидных действий с O>маркером безопасности (DuplicateToken, SetTokenInformation с ключом TokenSessionId и O>некоторые другие). Разумеется, нет нужды говорить, что это является потенциальной O>дырой в безопасности. Поэтому обычно рекомендуется не использовать для нового O>процесса системный контекст, а создавать специальный ограниченный маркер O>(см. CreateRestrictedToken). Если кого-то заинтересует, могу описать подробно.
Что значит создается в сессии пользователя? В смысле служба не сможет стартовать с системной учетной записью?
И что именно создается: process или thread. Если process, то как он обменивается сообщениями?
O>Вот функция, которая запускает стандартный Windows-калькулятор на O>рабочем столе определенного пользователя.
Насколько я понял, функция реализует 1 вариант?
O>Кстати, Shell_NotifyIcon иногда возвращает ошибку, если пользователь уже вошел в систему, а O>его интерактивное окружение (рабочий стол) еще не до конца загрузилось. O>Надо просто организовать цикл и в нем вызывать Shell_NotifyIcon до тех пор, пока она O>не вернет код успеха. Скажем, раз в секунду. Проверено.
Я так и делаю. При запуске как службы Windows 7 эффекта нет никогда
Не подскажете также, почему если ставишь на Windows 7 программу в планировщик (при старте Windows) то интерфейс не появляется точно также как при старте службы?
Здравствуйте, kovbas, Вы писали:
K>ВОПРОС: Как правильно подменить рабочий стол системных служб на рабочий стол залогиненного пользователя в Windows 7 / Vista / 2008
ОТВЕТ: Никак В этих системах сервисы запускаются в отдельной терминальной сессии, а для каждого вошедшего юзера создаётся своя терминальная сессия. Перепрыгнуть из одной такой сессии в другую возможности нет.
Здравствуйте, kovbas, Вы писали:
K>Если у служб нет своего рабочего стола, что тогда нам выводит сервис <Обнаружение интерактивных служб>?
Выводится какой-то отдельный рабочий стол, который специально сделали для того, чтобы
перехватывать интерактивные (SERVICE_INTERACTIVE_PROCESS) службы.
K>На чем в этом случае располагается окно приложения? K>Кстати, если это не рабочий стол, может его можно как-то минимизировать или сделать прозрачным?
Насколько я знаю — нельзя.
K>Насколько я понял, это будет отдельный process, работающий с GUI и посылать message'и ему будет нельзя: фактически тоже самое что написать клиентскую программу работающую из Автозагрузки?
Да. Отдельный процесс. Но совершенно такой же, как и все остальные, и сообщения до него тоже доходят.
Кстати, помимо сообщений есть всякие механизмы межпроцессных коммуникаций — пайпы, например...
K>Что значит создается в сессии пользователя? В смысле служба не сможет стартовать с системной учетной записью? K>И что именно создается: process или thread. Если process, то как он обменивается сообщениями?
Служба создается и конфигурируется как обычно.
Потом ею создается дочерний процесс — он уже выполняется в сессии пользователя.
Обмен сообщениями выполняется через IPC, как говорилось выше. То есть, пайпы, файлы, события и тому подобное.
O>>Вот функция, которая запускает стандартный Windows-калькулятор на O>>рабочем столе определенного пользователя.
K>Насколько я понял, функция реализует 1 вариант?
Да.
O>>Кстати, Shell_NotifyIcon иногда возвращает ошибку, если пользователь уже вошел в систему, а O>>его интерактивное окружение (рабочий стол) еще не до конца загрузилось. O>>Надо просто организовать цикл и в нем вызывать Shell_NotifyIcon до тех пор, пока она O>>не вернет код успеха. Скажем, раз в секунду. Проверено.
K>Я так и делаю. При запуске как службы Windows 7 эффекта нет никогда
Видимо, что-то неправильно делаете.
K>Не подскажете также, почему если ставишь на Windows 7 программу в планировщик (при старте Windows) то интерфейс не появляется точно также как при старте службы?
Где-то я слышал, что планировщик Windows (в Vista и выше) запускает задания с более низкими, чем у
пользователя, правами. Может быть, из-за этого ? Я в том смысле, что программа, возможно, обращается к
каким-то объектам безопасности, а выполняясь под планировщиком, не имеет к ним доступа ?..
Здравствуйте, Jolly Roger, Вы писали:
JR>Здравствуйте, okman, Вы писали:
O>>У служб вообще нету своего рабочего стола, а в Vista и выше они выполняются в O>>разных с пользовательскими процессами сессиях. И не забывайте, что сервис — почти как O>>консольный процесс, то есть никакие оконные сообщения до него не дойдут.
JR>Что-то Вы тут в понятиях напутали
Всякий процесс. в т.ч. сервисный, запускается в какой-то оконной станции(window station), и каждый поток связан с каким-то рабочим столом(Desktop). Оконные сообщения вполне могут доходить как до потоков консольки, так и сервиса. Другое дело, что есть ограничения на передачу сообщений между рабочими столами, и их нельзя передать в другую терминальную сессию.
Да, это я хватил лишку. Про оконные станции в курсе.
А вот на счет оконных сообщений непонятно — у сервисов же нет оконной процедуры.
Или Вы имеете в виду обработчик, который устанавливается с помощью RegisterServiceCtrlHandler ?
Здравствуйте, okman, Вы писали:
O>А вот на счет оконных сообщений непонятно — у сервисов же нет оконной процедуры.
Процедур нет по как-бы умолчанию Но ведь нет никаких препятствий запустить цикл выборки-диспетчирезации, и отправлять сообщения с помощью PostThreadMessage. Или создать окно и диспетчировать из петли в него. Опять-же оконный таймер вполне можно использовать в сервисе, конечно при наличии в установившем его потоке петли сообщений. Ну и COM — COM вполне штатно работает в службе, но ему для маршалинга вызовов в STA необходимо наличие петли сообщений в потоке этого STA.
Надо только помнить, что сообщения окну можно передать только в пределах desktop'а, т.е. оба потока — получатель и отправитель — должны быть подключены к одному desktop'у. В случае использования PostThreadMessage этого не нужно, сообщения можно передавать между любыми потоками, но в пределах терминальной сессии.
Здравствуйте, Jolly Roger, Вы писали:
JR>Здравствуйте, okman, Вы писали:
O>>А вот на счет оконных сообщений непонятно — у сервисов же нет оконной процедуры.
JR>Процедур нет по как-бы умолчанию Но ведь нет никаких препятствий запустить цикл выборки-диспетчирезации, и отправлять сообщения с помощью PostThreadMessage. Или создать окно и диспетчировать из петли в него. Опять-же оконный таймер вполне можно использовать в сервисе, конечно при наличии в установившем его потоке петли сообщений. Ну и COM — COM вполне штатно работает в службе, но ему для маршалинга вызовов в STA необходимо наличие петли сообщений в потоке этого STA.
Интересно, что сделает система с таким сервисом после обработки им сообщения WM_QUERYENDSESSION ?
Надо будет как-нибудь поставить эксперимент...
Здравствуйте, okman, Вы писали: O>Здравствуйте, kovbas, Вы писали: K>>Родил следующий код: O>... K>>Трассировка показывет, что все в порядке. Запускаю службу под пользователем с системной учетной записью. K>>Как <Обнаружитель интерактивных служб> реагировал на ShowWindow(), так и реагирует. K>>ВОПРОС: Как правильно подменить рабочий стол системных служб на рабочий стол залогиненного пользователя в Windows 7 / Vista / 2008 O>Сама постановка вопроса немного некорректна. O>У служб вообще нету своего рабочего стола, а в Vista и выше они выполняются в O>разных с пользовательскими процессами сессиях. И не забывайте, что сервис — почти как O>консольный процесс, то есть никакие оконные сообщения до него не дойдут. O>Могу посоветовать запустить из службы дочерний процесс от имени пользователя и весь O>GUI-интерфейс организовать внутри этого процесса. O>Но для того, чтобы писать код в этой области, нужно хотя бы в общих чертах O>разбираться в таких понятиях, как пользовательская сессия (консольная или O>терминальная), оконная станция, рабочий стол, контекст и маркер безопасности. O>И вообще, понимание системы безопасности Windows тоже очень желательно, иначе O>можно постоянно наступать на одни и те же грабли, не находя причин. O>Очень рекомендую разобраться во всей этой "кухне". O>Есть две разных схемы запуска дочерних интерактивных процессов из служб. O>Первая из них довольно тривиальна (ее мы и рассмотрим подробно). O>Если вкратце, то суть ее в том, чтобы получить маркер безопасности O>пользователя и создать процесс с этим маркером. При этом не нужно проводить O>никаких манипуляций с рабочими столами и оконными станциями, так как вся O>нужная информация уже содержится внутри этого маркера. O>Созданный процесс, за исключением очень незначительных деталей, ничем не O>отличается от других, запущенных пользователем. O>Единственная важная деталь — профиль пользователя должен быть загружен, иначе O>созданному процессу будут недоступны кое-какие данные из Documents And O>Settings и ветки реестра HKEY_CURRENT_USER. O>Вторая схема более сложная и извращенная. O>Ее в каком-то смысле можно назвать стыковкой — новый интерактивный процесс O>создается в сессии пользователя, но при этом сохраняет все атрибуты безопасности O>службы (LocalSystem), которая его запустила. Это более "веселенько" получается, O>потому что порожденный процесс уже может взаимодействовать с рабочим столом O>пользователя, отправлять и принимать оконные сообщения и так далее. O>Для создания такого процесса требуется несколько не очень очевидных действий с O>маркером безопасности (DuplicateToken, SetTokenInformation с ключом TokenSessionId и O>некоторые другие). Разумеется, нет нужды говорить, что это является потенциальной O>дырой в безопасности. Поэтому обычно рекомендуется не использовать для нового O>процесса системный контекст, а создавать специальный ограниченный маркер O>(см. CreateRestrictedToken). Если кого-то заинтересует, могу описать подробно. O>Вот функция, которая запускает стандартный Windows-калькулятор на O>рабочем столе определенного пользователя. O>
Скрытый текст
O>/************************************************************************
O>Функция StartCalculator.
O>Открывает стандартный калькулятор (calc.exe) на рабочем столе
O>определенного пользователя. Предназначена только для запуска из службы (LocalSystem).
O>Возвращает TRUE в случае успеха и FALSE в случае ошибки.
O>*************************************************************************/
O>BOOL
O>_stdcall
O>StartCalculator()
O>{
O> // Определяется Id активной пользовательской сессии.
O> // Значение 0xFFFFFFFF означает что консольной сессии не существует.
O> // Идентификатор сессии лучше всего получать в обработчике
O> // событий службы, см. RegisterServiceCtrlHandler(Ex).
O> // Там же можно определить консольная сессия или терминальная.
O> // См. SERVICE_CONTROL_SESSIONCHANGE, WM_WTSSESSION_CHANGE,
O> // WTSSESSION_NOTIFICATION, а также WTSQuerySessionInformation.
O> DWORD IdSession = WTSGetActiveConsoleSessionId();
O> if (IdSession == 0xFFFFFFFF)
O> {
O> return FALSE;
O> }
O> // Для того, чтобы действовать от имени пользователя, нужно
O> // получить так называемый маркер безопасности, представляющий
O> // этого пользователя в системе. Для этого существует функция
O> // WTSQueryUserToken, она требует высоких привилегий, которые
O> // обычно есть только у сервисов LocalSystem.
O> HANDLE hUserToken;
O> if (WTSQueryUserToken(IdSession, &hUserToken) == FALSE)
O> {
O> return FALSE;
O> }
O> // Перед созданием процесса в контексте пользователя необходимо
O> // выполнить некоторые дополнительные действия.
O> // Во-первых, нужно загрузить профиль пользователя - без этого
O> // новому процессу будут недоступны данные, лежащие в папках
O> // профиля и ключе реестра HKEY_CURRENT_USER.
O> // Для загрузки профиля нужно знать имя пользователя.
O> // Узнать его можно с помощью функции WTSQuerySessionInformation с
O> // ключом WTSUserName, но дело в том, что при эта функция зависит
O> // от службы терминалов, и если та еще не успела загрузиться (а такое
O> // бывает нередко при включении компьютера и быстром входе в систему),
O> // вызов WTSQuerySessionInformation вернет ошибку.
O> // Это имеет значение, когда запуск интерактивного процесса должен
O> // выполняться сразу же после входа пользователя.
O> // Мы поступим по-другому - временно переключимся в контекст
O> // безопасности пользователя и оттуда вызовем GetUserName.
O> // Замечу, что без подмены маркера безопасности вызов GetUserName
O> // будет возвращать имя SYSTEM (это распостраненная ошибка при попытке
O> // получить имя пользователя из службы).
O> // В MSDN сказано, что максимальная длина имени пользователя - 20 символов.
O> // Добавим еще один для нулевого завершающего символа.
O> wchar_t pUserName[21];
O> DWORD cchUserNameBuffer = 21;
O> // Теперь данный поток временно будет действовать от имени пользователя.
O> // Нужно учитывать, что при этом заимствуется также его контекст безопасности.
O> // То есть, доступ к критическим системным объектам будет ограничен.
O> if (ImpersonateLoggedOnUser(hUserToken) == FALSE)
O> {
O> CloseHandle(hUserToken);
O> return FALSE;
O> }
O> if (GetUserNameW(pUserName, &cchUserNameBuffer) == FALSE)
O> {
O> RevertToSelf();
O> CloseHandle(hUserToken);
O> return FALSE;
O> }
O> // Возвращаем системный контекст "на место".
O> RevertToSelf();
O> // Заполняем нужные структуры и загружаем профиль пользователя.
O> PROFILEINFOW prof;
O> ZeroMemory(&prof, sizeof (prof));
O> prof.dwSize = sizeof (prof);
O> prof.lpUserName = pUserName;
O> prof.dwFlags = PI_NOUI;
O> if (LoadUserProfileW(hUserToken, &prof) == FALSE)
O> {
O> CloseHandle(hUserToken);
O> return FALSE;
O> }
O> // Последнее приготовление - нужно подготовить переменные окружения.
O> // Подробнее можно почитать в MSDN-справке к функции CreateProcessAsUser.
O> void *pEnvBlock;
O> if (CreateEnvironmentBlock(&pEnvBlock, hUserToken, FALSE) == FALSE)
O> {
O> UnloadUserProfile(hUserToken, prof.hProfile);
O> CloseHandle(hUserToken);
O> return FALSE;
O> }
O> // Создание процесса почти ничем не отличается от стандартного способа с
O> // CreateProcess, только нужно указать маркер безопасности, от имени
O> // которого этот процесс будет создан.
O> STARTUPINFOW si;
O> GetStartupInfoW(&si);
O> si.dwFlags = STARTF_USESHOWWINDOW;
O> si.wShowWindow = SW_SHOWNORMAL;
O> si.lpDesktop = L"WinSta0\\Default";
O> wchar_t pCmdLine[] = {L"calc.exe"};
O> PROCESS_INFORMATION pi;
O> if (CreateProcessAsUserW(hUserToken,
O> NULL,
O> pCmdLine,
O> NULL,
O> NULL,
O> FALSE,
O> NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT,
O> pEnvBlock,
O> NULL,
O> &si,
O> &pi) == FALSE)
O> {
O> DestroyEnvironmentBlock(pEnvBlock);
O> UnloadUserProfile(hUserToken, prof.hProfile);
O> CloseHandle(hUserToken);
O> return FALSE;
O> }
O> CloseHandle(pi.hProcess);
O> CloseHandle(pi.hThread);
O> DestroyEnvironmentBlock(pEnvBlock);
O> // Эта функция должна вызываться при завершении созданного процесса.
O> // UnloadUserProfile(hUserToken, prof.hProfile);
O> CloseHandle(hUserToken);
O> // Собственно, все !
O> return TRUE;
O>}
O>
O>Испытано практически на XP/Vista/Seven/Server2008, 32-64 бита.
Опиши пожалуйста второй способ.
У меня как раз задача, есть сервис и нужно чтобы он запускал себя же (в смысле свой exe) с теми же правами но в консольной сессии.
За ранее благодарю.
Здравствуйте, Maclaud, Вы писали:
M>Опиши пожалуйста второй способ. M>У меня как раз задача, есть сервис и нужно чтобы он запускал себя же (в смысле свой exe) с теми же правами но в консольной сессии. M>За ранее благодарю.
Несколько основных положений:
1. Сервис должен выполняться от имени LocalSystem, иначе некоторые функции будут недоступны.
2. При старте нужно подписаться на событие SERVICE_ACCEPT_SESSIONCHANGE.
Любые события, связанные со входом и выходом пользователя из системы, включая
терминальный доступ, будут проходить через обработчик (HandlerEx), а по параметрам
легко определить тип события (например, WTS_SESSION_LOGON, см. WTSSESSION_NOTIFICATION).
Я настоятельно рекомендую сделать демо-сервис с записью событий в журнал, а затем
поэкспериментировать с тем, какие события и когда вызываются, особенно в контексте
быстрого переключения пользователей и терминальных сеансов. Потому что кроме событий
logon/logoff есть еще connect/disconnect, которые вносят некоторую путаницу.
3. Id сессии, в которой произошло событие, нужно получать именно из обработчика, а
не путем всяких WTSGetActiveConsoleSessionId, потому что так можно будет вести учет того,
какие сеансы у нас запускаются, а какие отключаются.
4. Есть один подводный камень, связанный с запуском службы и перехватом первого логина.
Дело в том, что если используется простой вход в систему (минуя окно ввода имя
пользователя и пароля), служба в некоторых случаях может не успеть запуститься.
То есть, событие первого входа в систему не будет перехвачено.
Я не знаю, каков рекомендованный MSDN способ обхода этого ограничения и поступаю так —
всегда указываю принадлежность своей службы группе "Boot Bus Extender".
Радикально, но работает.
5. Id сессии, в которой произошел вход в систему, нужно запомнить.
Потому что это — "наше все". По нему можно определить имя пользователя, тип сеанса
(консольный или терминальный) и многие другие данные, см. WTSQuerySessionInformation.
Еще один ключевой показатель — маркер безопасности пользователя.
Получается с помощью WTSQueryUserToken.
Допустим, некто вошел в систему и мы знаем Id его сессии, а также получили маркер безопасности.
И нужно создать в его сессии интерактивный процесс, выполняющийся в контексте безопасности
нашей службы (LocalSystem).
Последовательность действий примерно такова:
1. Заполняем структуру STARTUPINFO, в lpDesktop пишем "WinSta0\Default".
2. Создаем новый маркер безопасности:
DuplicateTokenEx(hProcessToken, // получается с помощью OpenProcessToken.
MAXIMUM_ALLOWED,
NULL,
SecurityDelegation,
TokenPrimary,
&hNewToken);
3. Создаем процесс с помощью CreateProcessAsUser, первым
аргументом передается новый маркер безопасности.
Созданный процесс будет отображаться в диспетчере задач как SYSTEM и его
нельзя будет "прибить" из-под неадминистраторской учетки, но при этом он
приаттачен к пользовательской сессии и полностью интерактивен.
Со всеми, так сказать, вытекающими.
Процессы, создаваемые описанным выше способом, имеют несколько особенностей.
Во-первых, происходит путаница с пользовательскими профилями.
Во-вторых, такой процесс представляет собой потенциальную дыру в безопасности.
В-третьих, при выходе из системы пользователя процесс уничтожается как и все
оконные — то есть, не позже обработки WM_ENDSESSION.
Есть и другие особенности, но они специфичны и к теме отношения не имеют.
Здравствуйте, okman, Вы писали:
O>Здравствуйте, Maclaud, Вы писали:
M>>Опиши пожалуйста второй способ. M>>У меня как раз задача, есть сервис и нужно чтобы он запускал себя же (в смысле свой exe) с теми же правами но в консольной сессии. M>>За ранее благодарю.
O>Несколько основных положений:
O>1. Сервис должен выполняться от имени LocalSystem, иначе некоторые функции будут недоступны.
O>2. При старте нужно подписаться на событие SERVICE_ACCEPT_SESSIONCHANGE. O>Любые события, связанные со входом и выходом пользователя из системы, включая O>терминальный доступ, будут проходить через обработчик (HandlerEx), а по параметрам O>легко определить тип события (например, WTS_SESSION_LOGON, см. WTSSESSION_NOTIFICATION). O>Я настоятельно рекомендую сделать демо-сервис с записью событий в журнал, а затем O>поэкспериментировать с тем, какие события и когда вызываются, особенно в контексте O>быстрого переключения пользователей и терминальных сеансов. Потому что кроме событий O>logon/logoff есть еще connect/disconnect, которые вносят некоторую путаницу.
O>3. Id сессии, в которой произошло событие, нужно получать именно из обработчика, а O>не путем всяких WTSGetActiveConsoleSessionId, потому что так можно будет вести учет того, O>какие сеансы у нас запускаются, а какие отключаются.
O>4. Есть один подводный камень, связанный с запуском службы и перехватом первого логина. O>Дело в том, что если используется простой вход в систему (минуя окно ввода имя O>пользователя и пароля), служба в некоторых случаях может не успеть запуститься. O>То есть, событие первого входа в систему не будет перехвачено. O>Я не знаю, каков рекомендованный MSDN способ обхода этого ограничения и поступаю так - O>всегда указываю принадлежность своей службы группе "Boot Bus Extender". O>Радикально, но работает.
O>5. Id сессии, в которой произошел вход в систему, нужно запомнить. O>Потому что это — "наше все". По нему можно определить имя пользователя, тип сеанса O>(консольный или терминальный) и многие другие данные, см. WTSQuerySessionInformation. O>Еще один ключевой показатель — маркер безопасности пользователя. O>Получается с помощью WTSQueryUserToken.
O>Допустим, некто вошел в систему и мы знаем Id его сессии, а также получили маркер безопасности. O>И нужно создать в его сессии интерактивный процесс, выполняющийся в контексте безопасности O>нашей службы (LocalSystem).
O>Последовательность действий примерно такова:
O>1. Заполняем структуру STARTUPINFO, в lpDesktop пишем "WinSta0\Default".
O>2. Создаем новый маркер безопасности:
O>
O>DuplicateTokenEx(hProcessToken, // получается с помощью OpenProcessToken.
O> MAXIMUM_ALLOWED,
O> NULL,
O> SecurityDelegation,
O> TokenPrimary,
O> &hNewToken);
O>
O>3. Создаем процесс с помощью CreateProcessAsUser, первым O>аргументом передается новый маркер безопасности. O>Созданный процесс будет отображаться в диспетчере задач как SYSTEM и его O>нельзя будет "прибить" из-под неадминистраторской учетки, но при этом он O>приаттачен к пользовательской сессии и полностью интерактивен. O>Со всеми, так сказать, вытекающими.
O>Процессы, создаваемые описанным выше способом, имеют несколько особенностей. O>Во-первых, происходит путаница с пользовательскими профилями. O>Во-вторых, такой процесс представляет собой потенциальную дыру в безопасности. O>В-третьих, при выходе из системы пользователя процесс уничтожается как и все O>оконные — то есть, не позже обработки WM_ENDSESSION.
O>Есть и другие особенности, но они специфичны и к теме отношения не имеют.
O>Собственно, все.
Спасибо за ответ. Я так понял тут основной момент это установка ID сессии в новом маркере.
А OpenProcessToken как процесс сервиса я так понял делается?
Здравствуйте, Maclaud, Вы писали:
M>Я так понял тут основной момент это установка ID сессии в новом маркере.
В целом — да.
Но есть еще нюанс, связанный с параметрами безопасности таких объектов,
как оконная станция и рабочий стол.
Вот в этом примере от MSDN явно просматривается, что теоретически может
потребоваться определенная настройка.
Я пробовал оба подхода,с изменением DACL и без, — результат одинаковый, хотя
вероятность того, что какой-нибудь сервис запустится раньше нашего и установит
более строгие параметры безопасности для оконной станции и рабочего стола,
списывать со счетов не стоит.
M>А OpenProcessToken как процесс сервиса я так понял делается?