Запуск процесса из службы
От: lesovick Россия  
Дата: 17.09.09 12:41
Оценка:
Много я видел топиков по этой теме здесь. Видел даже вопрос по конкретно моей проблеме, но ответа на него так и не нашёл.

Есть служба, работающая от SYSTEM, которой разрешено взаимодействовать с рабочим столом. Она запускает "notepad.exe" от имени текущего пользователя. "notepad.exe" появляется на рабочем столе, запускается, но криво: окно приложения не прорисовывается полностью, только верхняя полоска окна, без панели инструментов, без поля ввода. Вот код на С++:

STARTUPINFO startUpInfo = { sizeof(STARTUPINFO),NULL,"winsta0\\default",NULL,0,0,0,0,0,0,0,STARTF_USESHOWWINDOW,0,0,NULL,0,0,0};
startUpInfo.wShowWindow = SW_SHOW;
LogonUser(pUserName,(::strlen(pDomain)==0)?".":pDomain,pPassword,LOGON32_LOGON_SERVICE,LOGON32_PROVIDER_DEFAULT,&hToken);
CreateProcessAsUser(hToken,NULL,pCommandLine,NULL,NULL,TRUE,NORMAL_PRIORITY_CLASS,NULL,(strlen(pWorkingDir)==0)?NULL:pWorkingDir,&startUpInfo,&pProcInfo[nIndex]);

Если же запускать "notepad.exe" от SYSTEM, всё работает нормально. Как запустить "notepad.exe" от текущего пользователя, чтобы окно приложения не глючило? Вот ссылка на похожую тему. Там звучит мой вопрос, но ответа не дано: http://rsdn.ru/forum/winapi/478913.aspx
Автор: DMichael
Дата: 16.12.03
Re: Запуск процесса из службы
От: -prus-  
Дата: 17.09.09 14:00
Оценка:
Здравствуйте, lesovick, Вы писали:

...

Может окно notepad'a было уменьшено пользователем до минимального размера по вертикали перед его закрытием (как раз полоска остается)? Notepad сохранил размеры окна и при следующем запуске показывает окошко с предыдущеми настройками. Попробуйте запустить из-под пользователя и растянуть окно notepad'a за нижний правый уголок окна.
С уважением,
Евгений
Re: Запуск процесса из службы
От: Alex Fedotov США  
Дата: 17.09.09 14:47
Оценка: 1 (1) -1
Здравствуйте, lesovick, Вы писали:

L>Много я видел топиков по этой теме здесь. Видел даже вопрос по конкретно моей проблеме, но ответа на него так и не нашёл.


Правильный ответ был, и много раз: не запускать пользовательские приложения непосредственно из службы.
-- Alex Fedotov
Re: Запуск процесса из службы
От: x64 Россия  
Дата: 17.09.09 15:25
Оценка: 6 (2) +1
Ну вот такой приблизительно код работает на ура у меня:

bool
ExecuteInSession (...)
{
    bool bResult = false;
    HANDLE hUserToken = NULL;
    STARTUPINFO StartupInfo = {0};
    PVOID pEnvironmentBlock = NULL;
    PROCESS_INFORMATION ProcessInfo = {0};

    DWORD uSessionId = ...;
    LPWSTR lpwCommandLine = ...;
    LPWSTR lpwWorkingDirectory = ...;
    LPWSTR lpwExecutableModulePath = ...;

    if (! WTSQueryUserToken (
        uSessionId,
        &hUserToken))
    {
        goto CLEANUP;
    }

    if (! CreateEnvironmentBlock (
        &pEnvironmentBlock,
        hUserToken,
        FALSE))
    {
        goto CLEANUP;
    }

    StartupInfo.cb = sizeof (STARTUPINFO);

    if (! CreateProcessAsUser (
        hUserToken,
        lpwExecutableModulePath,
        lpwCommandLine,
        NULL,
        NULL,
        FALSE,
        CREATE_UNICODE_ENVIRONMENT,
        pEnvironmentBlock,
        lpwWorkingDirectory,
        &StartupInfo,
        &ProcessInfo))
    {
        goto CLEANUP;
    }

    bResult = true;

CLEANUP:

    if (hUserToken) CloseHandle (hUserToken);
    if (pEnvironmentBlock) DestroyEnvironmentBlock (pEnvironmentBlock);

    if (ProcessInfo.hThread) CloseHandle (ProcessInfo.hThread);
    if (ProcessInfo.hProcess) CloseHandle (ProcessInfo.hProcess);

    return bResult;
}


В качестве uSessionId можно указать значение, полученное от вызова WTSGetActiveConsoleSessionId().
createprocessasuser
Re: Запуск процесса из службы
От: Мизантроп  
Дата: 17.09.09 16:04
Оценка:
Здравствуйте, lesovick, Вы писали:

L>STARTUPINFO startUpInfo = { sizeof(STARTUPINFO),NULL,"winsta0\\default",NULL,0,0,0,0,0,0,0,STARTF_USESHOWWINDOW,0,0,NULL,0,0,0};

L>startUpInfo.wShowWindow = SW_SHOW;
L>LogonUser(pUserName,(::strlen(pDomain)==0)?".":pDomain,pPassword,LOGON32_LOGON_SERVICE,LOGON32_PROVIDER_DEFAULT,&hToken);
L>CreateProcessAsUser(hToken,NULL,pCommandLine,NULL,NULL,TRUE,NORMAL_PRIORITY_CLASS,NULL,(strlen(pWorkingDir)==0)?NULL:pWorkingDir,&startUpInfo,&pProcInfo[nIndex]);

У Вас довольно странный выбор флага в LogonUser — LOGON32_LOGON_SERVICE. Попробуйте заменить его на LOGON32_LOGON_INTERACTIVE. Не уверен, что поможет — мне просто никогда не приходило в голову это проверять , но попробуйте.

Если говорить о самом подходе, то тоже не всё ладно. Во-первых пароль. Хранить его открытом виде — плохая идея. Можно конечно воспользоваться LsaStorePrivateData или более современными функциями, но тогда потребуется хранить пароли для всех пользователей, которые могут войти на данную машину. Лучшим варинтом будет получить готовый маркер от какого-нибудь процесса, запущенного от имени интерактивного пользователя, например, можно воспользоваться процессом оболочки, имя которого выяснить в реестре. Чаще всего это Explorer.exe, но может быть и какой-нибудь другой.

Второй момент — интерактивный сервис. Это само по себе — плохая идея, а висте они вообще запрещены. Лучше не делать сервис.

Ну и в случае наличия FUS всё это не годится, надо либо делать, как покахал x64, либо задействовать классовый моникёр, если запускаемая программа является COM-сервером.
"Нормальные герои всегда идут в обход!"
Re[2]: Запуск процесса из службы
От: Мизантроп  
Дата: 17.09.09 16:08
Оценка:
Здравствуйте, Мизантроп, Вы писали:

М> Лучше не делать сервис.


Имелось в виду — лучше не делать сервис интерактивным
"Нормальные герои всегда идут в обход!"
Re[2]: Запуск процесса из службы
От: lesovick Россия  
Дата: 18.09.09 07:57
Оценка:
Здравствуйте, Alex Fedotov, Вы писали:

AF>Правильный ответ был, и много раз: не запускать пользовательские приложения непосредственно из службы.


А как запускать?
Re[3]: Запуск процесса из службы
От: Alex Fedotov США  
Дата: 18.09.09 16:12
Оценка: 2 (2)
Здравствуйте, 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
Re[4]: Запуск процесса из службы
От: Мизантроп  
Дата: 19.09.09 06:51
Оценка:
Здравствуйте, Alex Fedotov, Вы писали:

AF>В общем, опыт подсказывает, что иметь постоянно работающее приложение-агент в пользовательской сессии проще и удобнее.


Всё так, но и этот способ не даёт полной гарантии. Мы точно так-же можем попасть на момент неполной загрузки либо частичной выгрузки сессии. К тому-же, он менее защищён от эффекта буратино, когда пользователь просто возьмёт, да и закроет неизвестно зачем висящий в памяти теневой процесс. Да и сам процесс, который может вообще не понадобиться ни разу в текущей сессии — непроизводительный расход ресурсов.

С другой стороны, ткакую штуку в любом случае необходимо использовать вкупе с тем или иным механизмом нотификации, будь то SetConsoleCtrlHandler, Winlogon notification или WTSRegisterSessionNotification. А LoadUserProfile решит проблему профиля. Моменты же попадания на границу существования пользовательской сессии так и так придётся обрабатывать.

Ну а с третьей стороны сама необходимомсть реализации такой схемы вызывает определённые сомнения. Для простой нотификации пользователя обычно достаточно MessageBox, а для взаимодействия я бы рекомендовал апплет панели управления. Или то-же приложение, которой пользователь сам и запустит. Да и делать сервис слишком зависимым от действий пользователя — не очень-то удачная идея.
"Нормальные герои всегда идут в обход!"
Re[2]: Запуск процесса из службы
От: lesovick Россия  
Дата: 21.09.09 10:37
Оценка:
Здравствуйте, x64, Вы писали:

x64>В качестве uSessionId можно указать значение, полученное от вызова WTSGetActiveConsoleSessionId().


Спасибо! А если надо не от текущего, а от ещё какого-нибудь пользователя запускаться (логин+пароль+домен известны)?
Re[3]: Запуск процесса из службы
От: Мизантроп  
Дата: 21.09.09 14:46
Оценка:
Здравствуйте, lesovick, Вы писали:

L>А если надо не от текущего, а от ещё какого-нибудь пользователя запускаться (логин+пароль+домен известны)?


SetTokenInformation с классом TokenSessionId.
"Нормальные герои всегда идут в обход!"
Re[3]: Запуск процесса из службы
От: x64 Россия  
Дата: 21.09.09 16:57
Оценка:
L>Спасибо! А если надо не от текущего, а от ещё какого-нибудь пользователя запускаться (логин+пароль+домен известны)?

Полагаю, в первую очередь стоит посмотреть в сторону функции CreateProcessWithLogonW().
Re[2]: Запуск процесса из службы
От: lesovick Россия  
Дата: 01.10.09 10:28
Оценка:
Запускаю процесс так, как мне советовал x64:
    HANDLE hUserToken = NULL;
    STARTUPINFOW StartupInfo = {0};
    PVOID pEnvironmentBlock = NULL;
    PROCESS_INFORMATION ProcessInfo = {0};
    
    DWORD uSessionId = WTSGetActiveConsoleSessionId();
    LPWSTR lpwCommandLine = pCommandLineW;
    LPWSTR lpwWorkingDirectory = pWorkingDirW;
    LPWSTR lpwExecutableModulePath = NULL;

    if (! WTSQueryUserToken (
        uSessionId,
        &hUserToken))
    {
        goto CLEANUP;
    }

    if (! CreateEnvironmentBlock (
        &pEnvironmentBlock,
        hUserToken,
        FALSE))
    {
        goto CLEANUP;
    }

    StartupInfo.cb = sizeof (STARTUPINFOW);
    if (!CreateProcessAsUserW (
        hUserToken,
        lpwExecutableModulePath,
        lpwCommandLine,
        NULL,
        NULL,
        FALSE,
        CREATE_UNICODE_ENVIRONMENT,
        pEnvironmentBlock,
        lpwWorkingDirectory,
        &StartupInfo,
        &ProcessInfo))
    {
    goto CLEANUP;
    }

    CLEANUP:

    if (hUserToken) CloseHandle (hUserToken);
    if (pEnvironmentBlock) DestroyEnvironmentBlock (pEnvironmentBlock);

    if (ProcessInfo.hThread) CloseHandle (ProcessInfo.hThread);
    if (ProcessInfo.hProcess) CloseHandle (ProcessInfo.hProcess);


Всё прекрасно запускается от имени текущего пользователя. Но мне этого мало. Надо мочь запускать процесс от любого пользователя. CreateProcessWithLogonW работает также, как пара функций LogonUser+CreateProcessAsUser, то есть неправильно.

Я так мыслю, что можно было бы применить вышенаписаный код, взяв hUserToken функцией WTSQueryUserToken от сессии интересующего нас пользователя. Но для этого сначала надо эту сессию открыть. Меня интересует, можно ли это сделать программно, какими функциями.

По умолчанию изначально запущена только моя ТЕКУЩАЯ СЕССИЯ. Таким образом, скажем, с помощью функции WTSEnumerateSessions можно получить ТОЛЬКО ЕЁ ID.
Re[3]: Запуск процесса из службы
От: Мизантроп  
Дата: 01.10.09 11:22
Оценка:
Здравствуйте, lesovick, Вы писали:

L>Всё прекрасно запускается от имени текущего пользователя. Но мне этого мало. Надо мочь запускать процесс от любого пользователя. CreateProcessWithLogonW работает также, как пара функций LogonUser+CreateProcessAsUser, то есть неправильно.


L>Я так мыслю, что можно было бы применить вышенаписаный код, взяв hUserToken функцией WTSQueryUserToken от сессии интересующего нас пользователя. Но для этого сначала надо эту сессию открыть. Меня интересует, можно ли это сделать программно, какими функциями.


L>По умолчанию изначально запущена только моя ТЕКУЩАЯ СЕССИЯ. Таким образом, скажем, с помощью функции WTSEnumerateSessions можно получить ТОЛЬКО ЕЁ ID.


Ну я же Вам уже говорил — SetTokenInformation. В этом коде, после получения маркера интересующего Вас пользователя и ID интересующей Вас сессии, но перед вызовом CreateProcessAsUser вызовите SetTokenInformation, передав в неё маркер, ID сессии и указав в качестве класса информации TokenSessionID.
Для этого потребуется право TOKEN_ADJUST_SESSIONID, не забудьте его запросить при открытии маркера.

Но если Вы собираетесь так запускать процессы в чужой сессии от имени любого пользователя, то у Вас скорее всего возникнут проблемы с правами доступа к объектом этой сессии. Вам потребуется сначала запустить в этой сессии процесс от имени системы, который подправит дескрипторы защиты оконной станции и десктопа, а уже потом запускать процесс от пользователя.

Можно ещё попробовать открыть станцию и стол, использовав префикс Session, но сам я не пробовал, и что-то мне подсказывает, что этот фокус не пройдёт
"Нормальные герои всегда идут в обход!"
Re[4]: Запуск процесса из службы
От: x64 Россия  
Дата: 01.10.09 12:05
Оценка:
М> Но если Вы собираетесь так запускать процессы в чужой сессии от имени любого пользователя, то у Вас скорее всего возникнут проблемы с правами доступа к объектом этой сессии. Вам потребуется сначала запустить в этой сессии процесс от имени системы, который подправит дескрипторы защиты оконной станции и десктопа, а уже потом запускать процесс от пользователя.

Да, всё именно так, без дополнительных телодвижений здесь не обойтись. Кстати, тут же на RSDN это как-то уже обсуждалось, правда, в контексте другой проблемы, — ссылку сами найдёте. Ну и пара ссылочек по теме ещё от первоисточников:

CreateProcessAsUser erratically on Vista
CreateProcessAsUser() windowstations and desktops
Re[4]: Запуск процесса из службы
От: lesovick Россия  
Дата: 14.10.09 12:09
Оценка:
Давайте ещё раз по порядку:

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" (ПРОГРАММНО, а не "ручками"). Вот я и спрашиваю, как это сделать?
Re[5]: Запуск процесса из службы
От: lesovick Россия  
Дата: 22.10.09 19:52
Оценка:
Прошел по первой ссылке, где человек утверждает, что у него работает все на XP, но не работает в Висте. Написал в коде весь набор функций, которые он описывает. У меня проблема в функции AddTheAceWindowStation (ее текст взят из второй ссылки). Функция GetUserObjectSecurity выдает ошибку ERROR_ACCESS_DENIED, в мануале написано, что процесс не имеет права READ_CONTROL.
Мой процесс — это сервис. ВОПРОС: в каком месте нужно прописывать данный флаг?
Re[3]: Запуск процесса из службы
От: ononim  
Дата: 23.10.09 12:41
Оценка:
LoadUserProfile еще надо.
Как много веселых ребят, и все делают велосипед...
Re[6]: Запуск процесса из службы
От: x64 Россия  
Дата: 23.10.09 15:41
Оценка:
L>Мой процесс — это сервис. ВОПРОС: в каком месте нужно прописывать данный флаг?

Честно говоря, лень сейчас с этим разбираться, давно дело было, вот ещё ссылочку могу подкинуть, мало ли. Пробуй, экспериментируй, под лежачий камень не течёт вода.
Re[5]: Запуск процесса из службы
От: Мизантроп  
Дата: 23.10.09 18:43
Оценка: +1
Здравствуйте, lesovick, Вы писали:

L>Давайте ещё раз по порядку:


Ну что Вы в самом деле, ей-богу, ведь всёуже сказано Ладно, давайте ещё раз.

Итак, имеем систему с Terminal Service, в том числе XP c Fast user switching или Vista. Имеем сервис, работающий от имени LocalSystem и интерактивную сессию пользователя А. Задача: запустить в сессии пользователя процесс от имени LocalSystem.
  1. Получаете SessionID интерактивной сессии — GetActiveConsoleSessionId.
  2. Получаете маркер LocalSystem. Для этого используем пару функций: GetCurrentProcess + OpenProcessToken, запросив права
    TOKEN_QUERY | TOKEN_WRITE | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_ADJUST_SESSIONID
    Это по памяти, может чего лишнее или забыл, сами проверите.
  3. Связываете маркер с интерактивной сессией
    SetTokenInformation(LocalSystemToken, TokenSessionId, &SessionID, sizeof(SessionID))

  4. Заполняете STARTUP_IMFO, указав в параметре lpDesktop указатель на строку "WinSta0\Default"
  5. Вызываете CreateProcessAsUser

Если Вы хотите запускать процесс не от имени LocalSystem, а от пользователя В, то на шаге 2 вместо указанных двух функций вызывайте LogonUser. Но при этом предварительно необходимо будет дать пользователю В права доступа к объектам Window Station and Desktop в сессии пользователя А. Необходимый набор прав можно, вероятно, скопировать из ACE пользователя А дескрипторов соответствующих объектов, я точно не помню — давно уже не занимался подобной ерундой.
"Нормальные герои всегда идут в обход!"
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.