Запуск программы из сервиса под юзером

Приходилось такое делать много раз, в разных позах вариациях.
Насколько мне известно, "родных" средств в .NET для такой задачи нет, придется
делать через interop с нативным кодом.

Нужно в службе ловить событие OnSessionChange, в него придет SessionChangeDescription.
А там будет ID сессии, в которой произошло событие и тип самого события. Для данной
задачи в первую очередь интересен SessionLogon - вход пользователя в систему. Имея ID сессии,
получаем маркер безопасности залогиневшегося в ней пользователя, для этого есть нейтив-
функция WTSQueryUserToken. Дальнейшие действия тоже через interop: создать блок переменных
окружения, используя полученный маркер - CreateEnvironmentBlock, а потом запустить новый
процесс в контексте безопасности юзера - CreateProcessAsUser. В структуру STARTUPINFO,
которая передается аргументом для CreateProcessAsUser, нужно обязательно вписать
lpDesktop = WinSta0\Default - это имя оконной станции (WinSta0) и десктопа (Default), где
будет создан новый процесс.

Пара моментов:
1) Службу нужно добавить в одну из групп загрузки, чтобы она стартовала до того,
как первый пользователь успеет залогиниться.
2) CreateProcessAsUser может не сработать с первого раза, т.к. сессия на тот момент
может быть еще не совсем готова для создания в ней нового процесса.
Можно вызывать функцию в цикле, с небольшой задержкой, пока не сработает.
3) WTSGetActiveConsoleSessionId для определения ID активной сессии пользователя
лучше не использовать, т.к. если юзер будет в этот момент находиться на десктопе
входа в систему (winlogon), функция может вернуть ID совсем не той сессии, в которой
этот юзер залогинен.
4) Для успешного вызова WTSQueryUserToken у процесса должна быть привилегия
SE_TCB_NAME ("Trusted Computer Base"), она только у служб LocalSystem.
5) Если нужно, чтобы новый процесс при старте запрашивал повышение прав до уровня
администратора (UAC Elevation Prompt), это лучше всего сделать через exe-"переходник", напрямую
не выйдет. Т.е. сначала создаем в сессии пользователя обычный процесс, а он уже запускает
тот exe, который нужно, но не через CreateProcessAsUser, а с помощью ShellExecute(Ex).

2