Как запустить заставку из системного сервиса - PullRequest
1 голос
/ 20 апреля 2011

У меня есть несколько вариантов запуска заставки.Мне нравится

[DllImport("user32.dll", SetLastError = false)]
private static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

private void startScreensaver()
{
    UInt32 WM_SYSCOMMAND = 0x112;
    IntPtr SC_SCREENSAVE = new IntPtr(0xf140);
    IntPtr hWnd = GetDesktopWindow();
    SendMessage(hWnd, WM_SYSCOMMAND, SC_SCREENSAVE, new IntPtr(0));
}

Моя проблема в том, что я хочу запустить заставку из системной службы.Если я, например, хочу запустить заставку, как только сеанс заблокирован (только для подтверждения концепции), я могу попробовать

protected override void OnSessionChange(SessionChangeDescription changeDescription)
{
    base.OnSessionChange(changeDescription);
    if (changeDescription.Reason == SessionChangeReason.SessionLock)
        startScreensaver();
}

Это не работает, и я думаю, что причина в том, что службаустановлен с

ServiceProcessInstaller.Account = ServiceAccount.LocalSystem;

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

Есть предложения?Спасибо.

отредактировано: очевидно, проблема связана с вызовом GetDesktopWindow();, но я не знаю, как это исправить

Обновление:

Согласно предложению Erics, теперь я выполняю итерацию всех оконных станций (используя OpenWindowStation), затем для всех этих я выполняю итерации всех рабочих столов (используя EnumDesktops).Затем я открываю рабочие столы с помощью OpenDesktop и сохраняю дескриптор на рабочем столе.Моя стандартная установка Windows уступает следующему списку windowStation: Рабочий стол: dskHandle

  • WinSta0: По умолчанию: 732
  • WinSta0: Отключение: 760
  • WinSta0: Winlogon:784
  • msswindowstation: mssrestricteddesk: 0

Теперь я запускаю новый поток, в котором i

[DllImport("user32.dll", SetLastError = true)]
static extern bool SetThreadDesktop(IntPtr hDesktop);

, а затем вызываю метод startScreensaver (), описанный выше.IntPtr hWnd = GetDesktopWindow() возвращает приемлемые результаты, но заставка не запускается.В

[DllImport("user32.dll")]
static extern IntPtr OpenDesktop(string lpszDesktop, uint dwFlags, bool fInherit, uint dwDesiredAccess);

я использую GENERIC_ALL = 0x10000000 в качестве dwDesiredAccess.И, как заметил Фарзин, я проверил

Разрешить сервис взаимодействовать с рабочим столом

Я не win32 или pInvoke pro, поэтому я полностью потерян.Может ли sb объяснить, как все это работает вместе?У sb есть лучшее предложение?Все, что я хочу сделать, это вызвать скринсейвер из системной службы.

Ответы [ 3 ]

2 голосов
/ 21 апреля 2011

OK. Я отформатировал эту публикацию со ссылками в надежде помочь, а затем, когда нажал, сообщение получило следующее:

Oops! Ваш ответ не может быть отправлен, потому что: • мы сожалеем, но в качестве механизма предотвращения спама новые пользователи могут публиковать не более двух гиперссылок. Заработайте более 10 репутации, чтобы публиковать больше гиперссылок.

Итак, вы идете, совершенно нечитаемая публикация ....

Перейдите по этой ссылке с гиперссылками: http://social.msdn.microsoft.com/Forums/en-US/vcgeneral/thread/89485c95-61e5-46ea-84c7-5d8e03081c61

Эрик, Фарзин Закер и ОП не используют флаг SERVICE_INTERACTIVE_PROCESS и не полагаются на методы Interactive Services. Этот подход был прекращен со времен Windows Vista. [Подробнее здесь]:

От собственных слов Microsoft:

Важно Службы не могут напрямую взаимодействовать с пользователем начиная с Windows Vista. Поэтому методы, упомянутые в разделе «Использование интерактивного сервиса», не должны использоваться в новом коде.

Таким образом, даже если любой из упомянутых выше подходов сработает, они не будут ничем иным, как «взломом» и могут перестать работать в любой новой версии или даже в обновлении для Windows.

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

Поверьте мне, я потратил бесчисленные часы, пытаясь сделать то, что вы хотите ( неправильный путь ), и мне не удалось. Вот как Microsoft делает это самостоятельно в своем программном обеспечении и как вам нужно это сделать:

  1. В вашей системной службе создайте глобальное именованное событие автосброса, установите для него состояние non-sginaled. Обязательно настройте дескриптор безопасности для этого события, которое будет считываться и синхронизироваться «Все». Подробнее [здесь] и [здесь] и [здесь] о создании дескриптора безопасности. Этот шаг важен, если вы не хотите иметь дело с ERROR_ACCESS_DENIED позже.

  2. Создайте небольшую программу Win32 GUI, у которой есть скрытое окно. При запуске открывается глобальное событие, созданное сервисом выше. Если бы я написал его на C ++, он бы выглядел так: OpenEvent (READ_CONTROL | SYNCHRONIZE, FALSE, _T ("Global \\ Whwhat_name_you_use")); Затем создайте рабочий поток, который просто ожидает, пока это событие не станет сигнализировать, используя один из API-интерфейсов WaitFor * Object из [функции синхронизации]. Конечно, убедитесь, что рабочий поток обрабатывает ситуацию, когда эта небольшая программа с графическим интерфейсом закрывается.

  3. Из рабочего потока запустите следующий код, когда глобальное именованное событие автоматического сброса станет сигнальным. Отправьте уведомление WM_SYSCOMMAND в свое собственное окно в главном потоке графического интерфейса с wParam = SC_SCREENSAVE и lParam = 0 или сделайте это с помощью вызова API DefWindowProc () из основного потока графического интерфейса. Это должно запустить текущую настройку экранной заставки для пользователя, на котором запущена программа с графическим интерфейсом.

  4. Если вы хотите запустить конкретную заставку, вы можете просто запустить ее с помощью ShellExecute с параметром / s из вашей программы с графическим интерфейсом. (Конечно, делайте это из рабочего потока, когда сигнализируется глобальное именованное событие автосброса.) Все хранители экрана обычно помещаются в папку «% WINDIR% \ System32». Они имеют расширение .scr.

  5. ОК, теперь как активировать его из системной службы.

  6. Когда вам нужно запустить заставку, вы должны убедиться, что ваша небольшая программа с графическим интерфейсом работает в сеансе пользователя, который в данный момент активен.Часть active важна.Здесь есть две аплодисменты.Первый.Вы можете сделать это каждый раз, когда пользовательский сеанс становится активным (конечно, закрыв копию этой программы с графическим интерфейсом для сеанса, который перестает быть активным. Вы можете закрыть его, выполнив команду через глобальное именованное событие. И вы можете отслеживать пользователясеанс изменяется из вашей системной службы и ServiceHandlerEx (), перехватывая уведомления SERVICE_CONTROL_SESSIONCHANGE.) Вы также можете запустить эту программу с графическим интерфейсом пользователя, когда вам нужно активировать заставку, а затем сразу же закрыть ее.Я оставлю на ваше усмотрение, какой подход вы выберете.Суть в том, что вы должны каким-то образом запускать программу с графическим интерфейсом в активном сеансе пользователя и использовать глобальные именованные события для взаимодействия с ней.(Конечно, вы можете включить любые [другие средства IPC]. В моей книге глобальные события проще всего передать в виде логического типа или команды типа «да и нет».) Я должен сразу сказать вам, что запускпроцесс в другом сеансе пользователя является наиболее трудоемкой частью, плохо документирован и трудно отлаживаем.В двух словах, вам нужно использовать API CreateProcessAsUser () из вашей системной службы, но сложная часть - это подготовка к вызову этого API.К сожалению, нет четкого консенсуса по поводу того, как его назвать, и есть [несколько советов, доступных в Интернете], которые все несколько отличаются.Шаги, которые работали для меня, следующие:

    • Поместите вашу программу с графическим интерфейсом в общедоступное место (даже для пользователей с наименьшими привилегиями).Поскольку это часть системной службы, вы можете использовать «% WINDIR% \ System32», но убедитесь, что удалили его оттуда, когда он больше не нужен!

    • Получить текущий активный сеанс, вызвав WTSEnumerateSessions (), и посмотреть сеанс с состоянием WTSActive.

    • WTSQueryUserToken (), чтобы получить маркер активного сеанса пользователя

    • DuplicateTokenEx (, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary и &);

    • Создание блока строк среды с вызовом CreateEnvironmentBlock ()

    • Загрузить профиль пользователя, вызвав LoadUserProfile ().Ранее вы можете собрать всю необходимую информацию с помощью следующих API: NetUserGetInfo () для пути к профилю и WTSQuerySessionInformation (WTS_CURRENT_SERVER_HANDLE,, WTSUserName, &, &) для получения имени пользователя сеанса.

    • И выдать себя за этого пользователя с помощью вызова ImpersonateLoggedOnUser ()

    • На этом этапе вызовите CreateProcessAsUser () в расположении вашей программы с графическим интерфейсом, где вы ее разместили.Позвольте мне повторить, что вы должны запустить его из местоположения, доступного для пользователя, которого вы только что олицетворяли!Распространенной ошибкой здесь является запуск его из местоположения, похожего на это: « C: \ Users \ SomeUserName \ AppData \ Roaming ».Этот вызов может выглядеть следующим образом: CreateProcessAsUser (hToken2, NULL, pNonConstOrStaticBufferWithPathToGUIProgram, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT, pEnvironmentBlock, NULL, & pSTARTUPINFO, & pPROCESS_INFORMATION);

  7. WaitForInputIdle (), чтобы убедиться, что ваш процесс графического интерфейса запущен и достиг насоса сообщений.

  8. Очистите с помощью вызоваUnloadUserProfile (), DestroyEnvironmentBlock (), WTSFreeMemory (), CloseHandle () и т. Д.

  9. Теперь вы можете установить глобальное именованное событие автоматического сброса, вызвав SetEvent () для подачи сигнала в графический интерфейс пользователя.Процесс запуска заставки.И вы сделали!Вы также можете включить обратную обратную связь от программы с графическим интерфейсом, чтобы убедиться, что заставка действительно запущена, но я оставлю это на ваше усмотрение.Снова обратитесь к [средствам МПК], чтобы узнать, как это сделать.

В заключение позвольте мне сказать, что вышеупомянутый подход был собран посредством бесчисленных публикаций на форуме и путем сбора множества поисковых запросов в Интернете. И, да, я понимаю, насколько громоздким и громоздким является этот подход, но, эй, это и есть Windows, не так ли? Если вы хотите простоты, переходите на OS X или iOS. Это то, что я в конце концов сделал ...

Приветствие.

PS. Кто в мире придумал правила форматирования на этом форуме? Это самая трудная вещь, чтобы набрать и получить что-то читаемое ....

1 голос
/ 20 апреля 2011

Может работать, чтобы служба могла взаимодействовать с рабочим столом при установке (Передать SERVICE_INTERACTIVE_PROCESS в CreateService). В противном случае (могут быть проблемы с доступом - я не пробовал это), вам нужно начать с Window Station и функций рабочего стола .

Что вам нужно сделать, это найти станцию ​​окна пользователя, вошедшего в систему (EnumWindowStations, OpenWindowStation), рабочий стол (EnumDesktops, OpenDesktop), создать поток и SetThreadDesktop, а затем, наконец, использовать GetDesktopWindow.

1 голос
/ 20 апреля 2011

Перейдите к вашим услугам, щелкните правой кнопкой мыши по сервису и на вкладке LogOn установите значение ниже true:

Разрешить сервису взаимодействовать с рабочим столом

если вы хотите сделать это при установке:

public WindowsServiceInstaller()
{
  // This call is required by the Designer.

  InitializeComponent();
  ServiceInstaller si = new ServiceInstaller();
  si.ServiceName = "WindowsService1"; 
  si.DisplayName = "WindowsService1";
  si.StartType = ServiceStartMode.Manual;
  this.Installers.Add(si);
  ServiceProcessInstaller spi = new ServiceProcessInstaller();
  spi.Account = System.ServiceProcess.ServiceAccount.LocalSystem; 
  spi.Password = null;
  spi.Username = null;
  this.Installers.Add(spi);

  // Here is where we set the bit on the value in the registry.

  // Grab the subkey to our service

  RegistryKey ckey = Registry.LocalMachine.OpenSubKey(
    @"SYSTEM\CurrentControlSet\Services\WindowsService1", true);
  // Good to always do error checking!

  if(ckey != null)
  {
    // Ok now lets make sure the "Type" value is there, 

    //and then do our bitwise operation on it.

    if(ckey.GetValue("Type") != null)
    {
      ckey.SetValue("Type", ((int)ckey.GetValue("Type") | 256));
    }
  }
}

Ссылка: http://www.codeproject.com/KB/install/cswindowsservicedesktop.aspx

...