Re[3]: иконки интерактивных служб под Windows 7
От: okman Беларусь https://searchinform.ru/
Дата: 21.04.11 09:39
Оценка:
Здравствуйте, 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 бита.
запуск процесса из службы
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.