| | 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>
|