Re[4]: иконки интерактивных служб под Windows 7
От: Maclaud  
Дата: 29.06.11 03:27
Оценка:
Здравствуйте, 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) с теми же правами но в консольной сессии.
За ранее благодарю.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.