Много я видел топиков по этой теме здесь. Видел даже вопрос по конкретно моей проблеме, но ответа на него так и не нашёл.
Есть служба, работающая от SYSTEM, которой разрешено взаимодействовать с рабочим столом. Она запускает "notepad.exe" от имени текущего пользователя. "notepad.exe" появляется на рабочем столе, запускается, но криво: окно приложения не прорисовывается полностью, только верхняя полоска окна, без панели инструментов, без поля ввода. Вот код на С++:
Если же запускать "notepad.exe" от SYSTEM, всё работает нормально. Как запустить "notepad.exe" от текущего пользователя, чтобы окно приложения не глючило? Вот ссылка на похожую тему. Там звучит мой вопрос, но ответа не дано: http://rsdn.ru/forum/winapi/478913.aspx
Может окно notepad'a было уменьшено пользователем до минимального размера по вертикали перед его закрытием (как раз полоска остается)? Notepad сохранил размеры окна и при следующем запуске показывает окошко с предыдущеми настройками. Попробуйте запустить из-под пользователя и растянуть окно notepad'a за нижний правый уголок окна.
Здравствуйте, lesovick, Вы писали:
L>Много я видел топиков по этой теме здесь. Видел даже вопрос по конкретно моей проблеме, но ответа на него так и не нашёл.
Правильный ответ был, и много раз: не запускать пользовательские приложения непосредственно из службы.
У Вас довольно странный выбор флага в LogonUser — LOGON32_LOGON_SERVICE. Попробуйте заменить его на LOGON32_LOGON_INTERACTIVE. Не уверен, что поможет — мне просто никогда не приходило в голову это проверять , но попробуйте.
Если говорить о самом подходе, то тоже не всё ладно. Во-первых пароль. Хранить его открытом виде — плохая идея. Можно конечно воспользоваться LsaStorePrivateData или более современными функциями, но тогда потребуется хранить пароли для всех пользователей, которые могут войти на данную машину. Лучшим варинтом будет получить готовый маркер от какого-нибудь процесса, запущенного от имени интерактивного пользователя, например, можно воспользоваться процессом оболочки, имя которого выяснить в реестре. Чаще всего это Explorer.exe, но может быть и какой-нибудь другой.
Второй момент — интерактивный сервис. Это само по себе — плохая идея, а висте они вообще запрещены. Лучше не делать сервис.
Ну и в случае наличия FUS всё это не годится, надо либо делать, как покахал x64, либо задействовать классовый моникёр, если запускаемая программа является COM-сервером.
Здравствуйте, lesovick, Вы писали:
AF>>Правильный ответ был, и много раз: не запускать пользовательские приложения непосредственно из службы.
L>А как запускать?
Обычно рекомендуется запустить свое (возможно, невидимое) приложение через ключ Run в реестре. Это приложение соединяется с сервисом при старте и после этого может взаимодействовать с сервисом как угодно. Например, оно может запускать другое приложение по команде сервиса, или же просто показывать какой-то UI, когда это необходимо.
KB308403 How To Design a Service to Interact with Multiple User Sessions
То, что советуют другие товарищи, почти всегда работает, но там могут быть неприятные racing conditions в момент входа пользователя в систему или выхода из нее. Например, терминальная сессия уже может существовать, а userinit.exe еще не отработал, roaming user profile по сети не загрузился. Или наоборот, терминальная сессия еще существует, а приложения уже закрываются, потому что пользователь только что Logoff нажал.
В общем, опыт подсказывает, что иметь постоянно работающее приложение-агент в пользовательской сессии проще и удобнее.
Здравствуйте, Alex Fedotov, Вы писали:
AF>В общем, опыт подсказывает, что иметь постоянно работающее приложение-агент в пользовательской сессии проще и удобнее.
Всё так, но и этот способ не даёт полной гарантии. Мы точно так-же можем попасть на момент неполной загрузки либо частичной выгрузки сессии. К тому-же, он менее защищён от эффекта буратино, когда пользователь просто возьмёт, да и закроет неизвестно зачем висящий в памяти теневой процесс. Да и сам процесс, который может вообще не понадобиться ни разу в текущей сессии — непроизводительный расход ресурсов.
С другой стороны, ткакую штуку в любом случае необходимо использовать вкупе с тем или иным механизмом нотификации, будь то SetConsoleCtrlHandler, Winlogon notification или WTSRegisterSessionNotification. А LoadUserProfile решит проблему профиля. Моменты же попадания на границу существования пользовательской сессии так и так придётся обрабатывать.
Ну а с третьей стороны сама необходимомсть реализации такой схемы вызывает определённые сомнения. Для простой нотификации пользователя обычно достаточно MessageBox, а для взаимодействия я бы рекомендовал апплет панели управления. Или то-же приложение, которой пользователь сам и запустит. Да и делать сервис слишком зависимым от действий пользователя — не очень-то удачная идея.
Всё прекрасно запускается от имени текущего пользователя. Но мне этого мало. Надо мочь запускать процесс от любого пользователя. CreateProcessWithLogonW работает также, как пара функций LogonUser+CreateProcessAsUser, то есть неправильно.
Я так мыслю, что можно было бы применить вышенаписаный код, взяв hUserToken функцией WTSQueryUserToken от сессии интересующего нас пользователя. Но для этого сначала надо эту сессию открыть. Меня интересует, можно ли это сделать программно, какими функциями.
По умолчанию изначально запущена только моя ТЕКУЩАЯ СЕССИЯ. Таким образом, скажем, с помощью функции WTSEnumerateSessions можно получить ТОЛЬКО ЕЁ ID.
Здравствуйте, lesovick, Вы писали:
L>Всё прекрасно запускается от имени текущего пользователя. Но мне этого мало. Надо мочь запускать процесс от любого пользователя. CreateProcessWithLogonW работает также, как пара функций LogonUser+CreateProcessAsUser, то есть неправильно.
L>Я так мыслю, что можно было бы применить вышенаписаный код, взяв hUserToken функцией WTSQueryUserToken от сессии интересующего нас пользователя. Но для этого сначала надо эту сессию открыть. Меня интересует, можно ли это сделать программно, какими функциями.
L>По умолчанию изначально запущена только моя ТЕКУЩАЯ СЕССИЯ. Таким образом, скажем, с помощью функции WTSEnumerateSessions можно получить ТОЛЬКО ЕЁ ID.
Ну я же Вам уже говорил — SetTokenInformation. В этом коде, после получения маркера интересующего Вас пользователя и ID интересующей Вас сессии, но перед вызовом CreateProcessAsUser вызовите SetTokenInformation, передав в неё маркер, ID сессии и указав в качестве класса информации TokenSessionID.
Для этого потребуется право TOKEN_ADJUST_SESSIONID, не забудьте его запросить при открытии маркера.
Но если Вы собираетесь так запускать процессы в чужой сессии от имени любого пользователя, то у Вас скорее всего возникнут проблемы с правами доступа к объектом этой сессии. Вам потребуется сначала запустить в этой сессии процесс от имени системы, который подправит дескрипторы защиты оконной станции и десктопа, а уже потом запускать процесс от пользователя.
Можно ещё попробовать открыть станцию и стол, использовав префикс Session, но сам я не пробовал, и что-то мне подсказывает, что этот фокус не пройдёт
М> Но если Вы собираетесь так запускать процессы в чужой сессии от имени любого пользователя, то у Вас скорее всего возникнут проблемы с правами доступа к объектом этой сессии. Вам потребуется сначала запустить в этой сессии процесс от имени системы, который подправит дескрипторы защиты оконной станции и десктопа, а уже потом запускать процесс от пользователя.
Да, всё именно так, без дополнительных телодвижений здесь не обойтись. Кстати, тут же на RSDN это как-то уже обсуждалось, правда, в контексте другой проблемы, — ссылку сами найдёте. Ну и пара ссылочек по теме ещё от первоисточников:
1) Я сижу в системе от имени "User1". Запускаю службу от имени SYSTEM. Служба должна запускать приложение от имени "User2".
2) "В этом коде, после получения маркера интересующего Вас пользователя и ID интересующей Вас сессии..." — КАК получить маркер интересующего меня пользователя "User2"?! Пока я понял о 2-х способах это сделать:
1.1) Функция "LogonUser". Воспользовавшись ей, как я описывал с самого начала, возникла проблема, которую мы здесь обсуждаем.
1.2) Функции WTSEnumerateSessions + WTSQueryUserToken.
1.2.1) Предварительно, "ручками" залогинившись и открыв сессию для пользователя "User2", меняю пользователя и захожу в систему под "User1" (сессия "User2" при этом всё ещё открыта).
1.2.2) Вызываю функцию WTSEnumerateSessions. Она выдаёт информацию о 2-х открытых сессиях: текущей, где мы работаем от "User1" и предворительно открытой нами, где мы залогинились от "User2".
1.2.3) Из этой информации мы берём ID сессии "User2" и вставляем его в функцию WTSQueryUserToken, получая таким образом интересующий нас маркер.
1.2.4) Но в моём случае сессия с залогининым "User2" НЕ ОТКРЫТА. Может быть её можно как-то открыть (ПРОГРАММНО, а не "ручками"), залогиниться от "User2" (ПРОГРАММНО, а не "ручками"). Вот я и спрашиваю, как это сделать?
Прошел по первой ссылке, где человек утверждает, что у него работает все на XP, но не работает в Висте. Написал в коде весь набор функций, которые он описывает. У меня проблема в функции AddTheAceWindowStation (ее текст взят из второй ссылки). Функция GetUserObjectSecurity выдает ошибку ERROR_ACCESS_DENIED, в мануале написано, что процесс не имеет права READ_CONTROL.
Мой процесс — это сервис. ВОПРОС: в каком месте нужно прописывать данный флаг?
L>Мой процесс — это сервис. ВОПРОС: в каком месте нужно прописывать данный флаг?
Честно говоря, лень сейчас с этим разбираться, давно дело было, вот ещё ссылочку могу подкинуть, мало ли. Пробуй, экспериментируй, под лежачий камень не течёт вода.
Здравствуйте, lesovick, Вы писали:
L>Давайте ещё раз по порядку:
Ну что Вы в самом деле, ей-богу, ведь всёуже сказано Ладно, давайте ещё раз.
Итак, имеем систему с Terminal Service, в том числе XP c Fast user switching или Vista. Имеем сервис, работающий от имени LocalSystem и интерактивную сессию пользователя А. Задача: запустить в сессии пользователя процесс от имени LocalSystem. Получаете SessionID интерактивной сессии — GetActiveConsoleSessionId.
Получаете маркер LocalSystem. Для этого используем пару функций: GetCurrentProcess + OpenProcessToken, запросив права
Заполняете STARTUP_IMFO, указав в параметре lpDesktop указатель на строку "WinSta0\Default"
Вызываете CreateProcessAsUser
Если Вы хотите запускать процесс не от имени LocalSystem, а от пользователя В, то на шаге 2 вместо указанных двух функций вызывайте LogonUser. Но при этом предварительно необходимо будет дать пользователю В права доступа к объектам Window Station and Desktop в сессии пользователя А. Необходимый набор прав можно, вероятно, скопировать из ACE пользователя А дескрипторов соответствующих объектов, я точно не помню — давно уже не занимался подобной ерундой.