Pull to refresh

Реализация сервисов в MSWin (часть вторая)

Reading time4 min
Views934
В дополнение к хабратопику про написание сервисов отмечу еще одну возможность — запуск приложений от имени текущего пользователя терминала.

Это удобно, если выбран вариант управления сервисом и его взаимодействия с пользователем по принципу — сервис не интерактивный и запущен от имени Local System, а приложение должно быть запущено от имени пользователя.

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

Лично мне удобнее, когда как сервис, так и пользовательское приложение располагаются в одном исполняемом файле, но это скорее личная прихоть, ни кто не ограничивает программиста в этом.

Начиная с Windows XP/Windows 2003 Server в обработчик событий сервиса может поступить следующее сообщение SERVICE_CONTROL_SESSIONCHANGE, сигнализирующее о смене сессии пользователем (отключение от сессии, создание новой и т.п.) К сожалению в Windows 2000 это не предусмотрено, поэтому (если эта версия значится в списке целевых) следует учитывать, что даже определение этого сообщения в windows.h обрамлено проверкой дефайна _WIN32_WINNT.

В итоге обработчик сообщений дополняется примерно следующим кодом:
Copy Source | Copy HTML
  1. #if (_WIN32_WINNT > 0x0500)
  2.     case SERVICE_CONTROL_SESSIONCHANGE:
  3.         if (dwEventType == WTS_SESSION_LOGON)
  4.         {
  5.             system_logger_t::instance()->trace_info(L"service accept SESSION LOGON signal");
  6.             if (!run_gui())
  7.                 system_logger_t::instance()->trace_info(L"[ERROR] creating GUI window for service failed");
  8.         }
  9.         break;
  10. #endif


Собственно запуск от имени текущего терминального пользователя исполняемого файла сервиса, но с ключем gui:
Copy Source | Copy HTML
  1. /*<br/> */
  2. bool service_t::run_gui()
  3. {
  4.     HANDLE l_hToken = NULL;
  5.     STARTUPINFOW l_startInfo;
  6.     PROCESS_INFORMATION l_processInfo;
  7.     ULONG l_SID = -1;
  8.     memset(&l_startInfo, 0x00, sizeof(STARTUPINFOW));
  9.     memset(&l_processInfo, 0x00, sizeof(PROCESS_INFORMATION));
  10.     l_startInfo.cb = sizeof(STARTUPINFOW);
  11.     l_SID = static_cast <ULONG> (WTSGetActiveConsoleSessionId());
  12.     if (l_SID != -1)
  13.     {
  14.         if (WTSQueryUserToken(l_SID, &l_hToken) != FALSE)
  15.         {
  16.             std::wstringstream l_wss;
  17.             l_wss << get_path() << get_name();
  18.             if (CreateProcessAsUserW(
  19.                 l_hToken,
  20.                 l_wss.str().c_str(),
  21.                 L" /gui",
  22.                 NULL,
  23.                 NULL,
  24.                 FALSE,
  25.                 CREATE_DEFAULT_ERROR_MODE,
  26.                 NULL,
  27.                 NULL,
  28.                 &l_startInfo,
  29.                 &l_processInfo) != FALSE )
  30.             {
  31.                 system_logger_t::instance()->trace_info(
  32.                     L"creating GUI window for service succeeded"
  33.                 );
  34.                 return true;
  35.             }
  36.         }
  37.     }
  38.     return false;
  39. }
  40. //--------------------------------------------------------------------------- 


Остается только напомнить, что у приложения должна быть возможность запуска только одной копии если сервис обрабатывает только WTS_SESSION_LOGON, а для этого достаточно создать именованный мьютекс «имя» которого начинается с L«Local\\». Это позволит создать мьютекс в пределах текущей сессии пользователя. Если же экземпляр приложения должен существовать в принципе в одном экземпляре, то создаем мьютекс «имя» которого начинается с L«Global\\» и не забываем, что в таком случае приложение даже в случае экстренного завершения должно успеть мьютекс удалить.
Tags:
Hubs:
Total votes 15: ↑12 and ↓3+9
Comments0

Articles