— Разработайте программную реализацию для системы Windows, включающую пользовательские переменные и Изучите службу Windows — упрощенное руководство для нетехнических пользователей

Время на прочтение

— Разработайте программную реализацию для системы Windows, включающую пользовательские переменные и Изучите службу Windows — упрощенное руководство для нетехнических пользователей

Однажды вы задумаетесь, как превратить скрипт или приложение в Windows-службу. Скорее всего, задача окажется не такой уж тривиальной – приложению как минимум потребуется специальный интерфейс для получения команд от системы. А раз есть требования и ограничения, то есть и скрипты, и милые сердцу костылики для преодоления.

Статья будет полезна тем, кто, как и я — «программист не настоящий».

Зачем нужна служба, если есть назначенные задания

В отличие от назначенных заданий служба работает постоянно, запускается при старте ПК и может управляться средствами Windows. А еще регулярно запускаемому скрипту могут понадобиться данные с предыдущего запуска, и может быть полезно получение данных из внешних источников — например, в случае TCP или Web сервера.

Лично мне за последние пять лет приходилось создавать службу три с половиной раза:

  • Потребовалось создать сервис на fail2ban для Windows 2003., который работал с логами FileZilla и Apache, а при подозрении на брутфорс блокировал IP штатными средствами Windows — ipsec.
  • Аналог телнет-сервера для домашних версий Windows. Понадобилось выполнять команды на удаленных рабочих станциях, которые были под управлением Windows 7 Home. По сути, вторая попытка поиграть в службы.
  • Музыкальный проигрыватель для торгового зала под Windows. Задачу по ТЗ можно было решить при помощи mpd и пачки скриптов, но я решил — если уж делать скрипты, то почему бы и не «сваять» проигрыватель самому. За основу взял библиотеку BASS.dll.
  • Когда выбирали веб-сервер с поддержкой загрузки файлов под Windows, одним из вариантов был HFS. Сам по себе работать он не может, поэтому пришлось «запихивать» его в службу. В результате решение не понравилось, и просто установили «тему» Apaxy на web-сервере Apache.

Для создания службы можно использовать взрослые языки программирования вроде C. Но если вы не хотите связываться с Visual Studio, то возьмите готовые утилиты. Существуют платные решения вроде FireDaemon Pro или AlwaysUp, но мы традиционно сосредоточимся на бесплатных.

Способ первый. От Microsoft

Этот уже немолодой механизм состоит из двух компонентов: утилиты instsrv.exe для установки сервиса и srvany.exe — процесса для запуска любых исполняемых файлов. Предположим, что мы создали веб-сервер на PowerShell при помощи модуля Polaris. Скрипт будет предельно прост:

New-PolarisGetRoute -Path '/helloworld' -Scriptblock { $Response.Send('Hello World!')
}
Start-Polaris -Port 8080
while($true) { Start-Sleep -Milliseconds 10
}

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

Теперь попробуем превратить скрипт в службу. Для этого скачаем Windows Resource Kit Tools, где будут наши утилиты. Начнем с того, что установим пустой сервис командой:

instsrv WebServ C:\temp\rktools\srvany.exe

Где WebServ — имя нашего нового сервиса. При необходимости через оснастку services.msc можно задать пользователя, под которым будет запускаться служба, и разрешить взаимодействие с рабочим столом.

Теперь пропишем путь к нашему скрипту при помощи магии реестра. Параметры службы есть в разделе реестра HKLM\SYSTEM\CurrentControlSet\Services\WebServ. В нем нам нужно добавить новый раздел Parameters и создать там строковый параметр Application, указав в нем путь к исполняемому файлу. В случае скрипта PowerShell он будет выглядеть так:

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -NoProfile -File C:\temp\Polaris\server.ps1

Можно запустить и радоваться.

Однако у этого способа есть недостатки:

  • Утилиты старые, разработаны до изобретения PowerShell, UAC и прочих вещей.
  • Srvany не контролирует работу приложения. Даже если оно выпадет в ошибку, служба продолжит свое дело как ни в чем не бывало.
  • Придется донастраивать и копаться в реестре. Вы же помните, что копаться в реестре небезопасно?

Поэтому перейдем к методу, частично лишенному этих проблем.

Способ второй, почти взрослый

Существует утилита под названием NSSMNon-Sucking Service Manager, что можно перевести как не-плохой менеджер служб. В отличие от предыдущей, она поддерживается разработчиком, и исходный код опубликован на сайте. Помимо обычного способа, доступна и установка через пакетный менеджер Chocolately.

Создать сервис можно из обычной командной строки, вооружившись документацией на сайте разработчика. Но мы воспользуемся PowerShell. Потому что можем, разумеется.

$nssm = (Get-Command ./nssm).Source
$serviceName = 'WebServ'
$powershell = (Get-Command powershell).Source
$scriptPath = 'C:\temp\Polaris\server.ps1'
$arguments = '-ExecutionPolicy Bypass -NoProfile -File "{0}"' -f $scriptPath
& $nssm install $serviceName $powershell $arguments
& $nssm status $serviceName
Start-Service $serviceName
Get-Service $serviceName

— Разработайте программную реализацию для системы Windows, включающую пользовательские переменные и Изучите службу Windows — упрощенное руководство для нетехнических пользователей
Установка через PowerShell.

Для разнообразия проверим работу службы не браузером, а тоже через PowerShell командой Invoke-RestMethod.

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

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

GUI запускается командой:

nssm.exe install ServiceName

— Разработайте программную реализацию для системы Windows, включающую пользовательские переменные и Изучите службу Windows — упрощенное руководство для нетехнических пользователей
Настроить можно даже приоритет и использование ядер процессора.

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

Налицо нехватка «жести». Поэтому я перейду к самому хардкорному методу из всех опробованных.

Способ третий. AutoIT

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

Итак, попробуем «завернуть» в нее наш веб-сервис:

#NoTrayIcon
#RequireAdmin
#Region
#AutoIt3Wrapper_Version=Beta
#AutoIt3Wrapper_UseUpx=n
#AutoIt3Wrapper_Compile_Both=y
#AutoIt3Wrapper_UseX64=y
#EndRegion
Dim $MainLog = @ScriptDir & "\test_service.log"
#include <services.au3>
#include <WindowsConstants.au3>
$sServiceName="WebServ"
If $cmdline[0] > 0 Then Switch $cmdline[1] Case "install", "-i", "/i" InstallService() Case "remove", "-u", "/u", "uninstall" RemoveService() Case Else ConsoleWrite(" - - - Help - - - " & @CRLF) ConsoleWrite("params : " & @CRLF) ConsoleWrite(" -i : install service" & @CRLF) ConsoleWrite(" -u : remove service" & @CRLF) ConsoleWrite(" - - - - - - - - " & @CRLF) Exit EndSwitch
Else _Service_init($sServiceName) Exit
EndIf
Func _main($iArg, $sArgs)
If Not _Service_ReportStatus($SERVICE_RUNNING, $NO_ERROR, 0) Then _Service_ReportStatus($SERVICE_STOPPED, _WinAPI_GetLastError(), 0) Exit
EndIf
$bServiceRunning = True
$PID=Run("C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -NoProfile -File C:\temp\Polaris\server.ps1")
While $bServiceRunning
_sleep(1000)
WEnd
ProcessClose($PID)
_Service_ReportStatus($SERVICE_STOP_PENDING, $NO_ERROR, 1000)
DllCallbackFree($tServiceMain)
DllCallbackFree($tServiceCtrl)
_Service_ReportStatus($SERVICE_STOPPED, $NO_ERROR, 0)
DllClose($hAdvapi32_DLL)
DllClose($hKernel32_DLL)
EndFunc
Func _Sleep($delay)
Local $result = DllCall($hKernel32_DLL, "none", "Sleep", "dword", $delay)
EndFunc
Func InstallService() #RequireAdmin Local $bDebug = True If $cmdline[0] > 1 Then $sServiceName = $cmdline[2] EndIf If $bDebug Then ConsoleWrite("InstallService("&$sServiceName &"): Installing service, please wait") _Service_Create($sServiceName, $sServiceName, $SERVICE_WIN32_OWN_PROCESS, $SERVICE_AUTO_START, $SERVICE_ERROR_SEVERE, '"' & @ScriptFullPath & '"');,"",False,"","NT AUTHORITY\NetworkService") If @error Then Msgbox("","","InstallService(): Problem installing service, Error number is " & @error & @CRLF & " message : " & _WinAPI_GetLastErrorMessage()) Else If $bDebug Then ConsoleWrite("InstallService(): Installation of service successful") EndIf Exit
EndFunc
Func RemoveService() _Service_Stop($sServiceName) _Service_Delete($sServiceName) If Not @error Then EndIf Exit
EndFunc
Func _exit() _Service_ReportStatus($SERVICE_STOPPED, $NO_ERROR, 0);
EndFunc
Func StopTimer() _Service_ReportStatus($SERVICE_STOP_PENDING, $NO_ERROR, $iServiceCounter) $iServiceCounter += -100
EndFunc
Func _Stopping() _Service_ReportStatus($SERVICE_STOP_PENDING, $NO_ERROR, 3000) EndFunc

Разберу подробнее момент запуска приложения. Он начинается после операции $bServiceRunning = True и превращается в, казалось бы, бесконечный цикл. На самом деле этот процесс прервется, как только служба получит сигнал о завершении — будь то выход из системы или остановка вручную.

Поскольку программа для скрипта является внешней (powershell.exe), то после выхода из цикла нам нужно закончить ее работу с помощью ProcessClose.

Для этого скрипт необходимо скомпилировать в .exe, а затем установить службу, запустив exe с ключом -i.

Разумеется, этот способ не самый удобный, и все дополнительные возможности придется реализовывать самостоятельно, будь то повторный запуск приложения при сбое или ротация логов. Но зато он дает полный контроль над происходящим. Да и сделать в итоге можно куда больше — от уведомления в Telegram о сбое службы до IPC-взаимодействия с другими программами. И вдобавок — на скриптовом языке, без установки и изучения Visual Studio.

Расскажите, а вам приходилось превращать скрипты и приложения в службы?

— Разработайте программную реализацию для системы Windows, включающую пользовательские переменные и Изучите службу Windows — упрощенное руководство для нетехнических пользователей

Однажды вы задумаетесь, как превратить скрипт или приложение в Windows-службу. Скорее всего, задача окажется не такой уж тривиальной – приложению как минимум потребуется специальный интерфейс для получения команд от системы. А раз есть требования и ограничения, то есть и скрипты, и милые сердцу костылики для преодоления.

Статья будет полезна тем, кто, как и я — «программист не настоящий».

Зачем нужна служба, если есть назначенные задания

В отличие от назначенных заданий служба работает постоянно, запускается при старте ПК и может управляться средствами Windows. А еще регулярно запускаемому скрипту могут понадобиться данные с предыдущего запуска, и может быть полезно получение данных из внешних источников — например, в случае TCP или Web сервера.

Лично мне за последние пять лет приходилось создавать службу три с половиной раза:

  • Потребовалось создать сервис на fail2ban для Windows 2003., который работал с логами FileZilla и Apache, а при подозрении на брутфорс блокировал IP штатными средствами Windows — ipsec.
  • Аналог телнет-сервера для домашних версий Windows. Понадобилось выполнять команды на удаленных рабочих станциях, которые были под управлением Windows 7 Home. По сути, вторая попытка поиграть в службы.
  • Музыкальный проигрыватель для торгового зала под Windows. Задачу по ТЗ можно было решить при помощи mpd и пачки скриптов, но я решил — если уж делать скрипты, то почему бы и не «сваять» проигрыватель самому. За основу взял библиотеку BASS.dll.
  • Когда выбирали веб-сервер с поддержкой загрузки файлов под Windows, одним из вариантов был HFS. Сам по себе работать он не может, поэтому пришлось «запихивать» его в службу. В результате решение не понравилось, и просто установили «тему» Apaxy на web-сервере Apache.

Для создания службы можно использовать взрослые языки программирования вроде C. Но если вы не хотите связываться с Visual Studio, то возьмите готовые утилиты. Существуют платные решения вроде FireDaemon Pro или AlwaysUp, но мы традиционно сосредоточимся на бесплатных.

Способ первый. От Microsoft

Этот уже немолодой механизм состоит из двух компонентов: утилиты instsrv.exe для установки сервиса и srvany.exe — процесса для запуска любых исполняемых файлов. Предположим, что мы создали веб-сервер на PowerShell при помощи модуля Polaris. Скрипт будет предельно прост:

New-PolarisGetRoute -Path '/helloworld' -Scriptblock { $Response.Send('Hello World!')
}
Start-Polaris -Port 8080
while($true) { Start-Sleep -Milliseconds 10
}

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

Теперь попробуем превратить скрипт в службу. Для этого скачаем Windows Resource Kit Tools, где будут наши утилиты. Начнем с того, что установим пустой сервис командой:

instsrv WebServ C:\temp\rktools\srvany.exe

Где WebServ — имя нашего нового сервиса. При необходимости через оснастку services.msc можно задать пользователя, под которым будет запускаться служба, и разрешить взаимодействие с рабочим столом.

Теперь пропишем путь к нашему скрипту при помощи магии реестра. Параметры службы есть в разделе реестра HKLM\SYSTEM\CurrentControlSet\Services\WebServ. В нем нам нужно добавить новый раздел Parameters и создать там строковый параметр Application, указав в нем путь к исполняемому файлу. В случае скрипта PowerShell он будет выглядеть так:

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -NoProfile -File C:\temp\Polaris\server.ps1

Можно запустить и радоваться.

Однако у этого способа есть недостатки:

  • Утилиты старые, разработаны до изобретения PowerShell, UAC и прочих вещей.
  • Srvany не контролирует работу приложения. Даже если оно выпадет в ошибку, служба продолжит свое дело как ни в чем не бывало.
  • Придется донастраивать и копаться в реестре. Вы же помните, что копаться в реестре небезопасно?

Поэтому перейдем к методу, частично лишенному этих проблем.

Способ второй, почти взрослый

Существует утилита под названием NSSMNon-Sucking Service Manager, что можно перевести как не-плохой менеджер служб. В отличие от предыдущей, она поддерживается разработчиком, и исходный код опубликован на сайте. Помимо обычного способа, доступна и установка через пакетный менеджер Chocolately.

:/>  Как настроить FTP на Ubuntu 20.04 LTS

Создать сервис можно из обычной командной строки, вооружившись документацией на сайте разработчика. Но мы воспользуемся PowerShell. Потому что можем, разумеется.

$nssm = (Get-Command ./nssm).Source
$serviceName = 'WebServ'
$powershell = (Get-Command powershell).Source
$scriptPath = 'C:\temp\Polaris\server.ps1'
$arguments = '-ExecutionPolicy Bypass -NoProfile -File "{0}"' -f $scriptPath
& $nssm install $serviceName $powershell $arguments
& $nssm status $serviceName
Start-Service $serviceName
Get-Service $serviceName

— Разработайте программную реализацию для системы Windows, включающую пользовательские переменные и Изучите службу Windows — упрощенное руководство для нетехнических пользователей
Установка через PowerShell.

Для разнообразия проверим работу службы не браузером, а тоже через PowerShell командой Invoke-RestMethod.

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

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

GUI запускается командой:

nssm.exe install ServiceName

— Разработайте программную реализацию для системы Windows, включающую пользовательские переменные и Изучите службу Windows — упрощенное руководство для нетехнических пользователей
Настроить можно даже приоритет и использование ядер процессора.

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

Налицо нехватка «жести». Поэтому я перейду к самому хардкорному методу из всех опробованных.

Способ третий. AutoIT

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

Итак, попробуем «завернуть» в нее наш веб-сервис:

#NoTrayIcon
#RequireAdmin
#Region
#AutoIt3Wrapper_Version=Beta
#AutoIt3Wrapper_UseUpx=n
#AutoIt3Wrapper_Compile_Both=y
#AutoIt3Wrapper_UseX64=y
#EndRegion
Dim $MainLog = @ScriptDir & "\test_service.log"
#include <services.au3>
#include <WindowsConstants.au3>
$sServiceName="WebServ"
If $cmdline[0] > 0 Then Switch $cmdline[1] Case "install", "-i", "/i" InstallService() Case "remove", "-u", "/u", "uninstall" RemoveService() Case Else ConsoleWrite(" - - - Help - - - " & @CRLF) ConsoleWrite("params : " & @CRLF) ConsoleWrite(" -i : install service" & @CRLF) ConsoleWrite(" -u : remove service" & @CRLF) ConsoleWrite(" - - - - - - - - " & @CRLF) Exit EndSwitch
Else _Service_init($sServiceName) Exit
EndIf
Func _main($iArg, $sArgs)
If Not _Service_ReportStatus($SERVICE_RUNNING, $NO_ERROR, 0) Then _Service_ReportStatus($SERVICE_STOPPED, _WinAPI_GetLastError(), 0) Exit
EndIf
$bServiceRunning = True
$PID=Run("C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -NoProfile -File C:\temp\Polaris\server.ps1")
While $bServiceRunning
_sleep(1000)
WEnd
ProcessClose($PID)
_Service_ReportStatus($SERVICE_STOP_PENDING, $NO_ERROR, 1000)
DllCallbackFree($tServiceMain)
DllCallbackFree($tServiceCtrl)
_Service_ReportStatus($SERVICE_STOPPED, $NO_ERROR, 0)
DllClose($hAdvapi32_DLL)
DllClose($hKernel32_DLL)
EndFunc
Func _Sleep($delay)
Local $result = DllCall($hKernel32_DLL, "none", "Sleep", "dword", $delay)
EndFunc
Func InstallService() #RequireAdmin Local $bDebug = True If $cmdline[0] > 1 Then $sServiceName = $cmdline[2] EndIf If $bDebug Then ConsoleWrite("InstallService("&$sServiceName &"): Installing service, please wait") _Service_Create($sServiceName, $sServiceName, $SERVICE_WIN32_OWN_PROCESS, $SERVICE_AUTO_START, $SERVICE_ERROR_SEVERE, '"' & @ScriptFullPath & '"');,"",False,"","NT AUTHORITY\NetworkService") If @error Then Msgbox("","","InstallService(): Problem installing service, Error number is " & @error & @CRLF & " message : " & _WinAPI_GetLastErrorMessage()) Else If $bDebug Then ConsoleWrite("InstallService(): Installation of service successful") EndIf Exit
EndFunc
Func RemoveService() _Service_Stop($sServiceName) _Service_Delete($sServiceName) If Not @error Then EndIf Exit
EndFunc
Func _exit() _Service_ReportStatus($SERVICE_STOPPED, $NO_ERROR, 0);
EndFunc
Func StopTimer() _Service_ReportStatus($SERVICE_STOP_PENDING, $NO_ERROR, $iServiceCounter) $iServiceCounter += -100
EndFunc
Func _Stopping() _Service_ReportStatus($SERVICE_STOP_PENDING, $NO_ERROR, 3000) EndFunc

Разберу подробнее момент запуска приложения. Он начинается после операции $bServiceRunning = True и превращается в, казалось бы, бесконечный цикл. На самом деле этот процесс прервется, как только служба получит сигнал о завершении — будь то выход из системы или остановка вручную.

Поскольку программа для скрипта является внешней (powershell.exe), то после выхода из цикла нам нужно закончить ее работу с помощью ProcessClose.

Для этого скрипт необходимо скомпилировать в .exe, а затем установить службу, запустив exe с ключом -i.

Разумеется, этот способ не самый удобный, и все дополнительные возможности придется реализовывать самостоятельно, будь то повторный запуск приложения при сбое или ротация логов. Но зато он дает полный контроль над происходящим. Да и сделать в итоге можно куда больше — от уведомления в Telegram о сбое службы до IPC-взаимодействия с другими программами. И вдобавок — на скриптовом языке, без установки и изучения Visual Studio.

Расскажите, а вам приходилось превращать скрипты и приложения в службы?

Время на прочтение

Один из способов доморощенной классификации служб основывается на времени их жизни: некоторые из них запускаются сразу же при старте ОС, оставаясь активными постоянно (сюда, скажем, можно отнести веб-серверы и СУБД), другие же запускаются лишь при необходимости, делают свои архиважные дела и сразу завершаются; при этом, ни один из вариантов сам по себе не делает реализацию службы сложнее, однако второй требует от разработчика как минимум ещё и умения программно стартовать, а при необходимости и досрочно останавливать её работу. Именно указанный аспект управления службой, плюс добавление некоторых отсутствующих в штатной поставке Delphi возможностей, и сподвиг автора на данный опус.

Чтобы статья воспринималась максимально полезной и практичной, в ней предлагается заготовка (почти готовый к употреблению шаблон) службы, обрабатывающей очередь неких задач (или заданий – кому как больше нравится); после того, как все из них обработаны, служба тут же завершается. Если представить графически, то читатель познакомится со следующей конструкцией:

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

Техническое задание

Предложенное решение будет обладать перечисленными возможностями, а также предполагать следующее:

  • Очередь рассматривается как некая абстрактная структура, то есть кем она реализована, где хранится (в файле, БД или где-то ещё) и как конкретно с ней взаимодействовать (в виде программного кода) – всё это непринципиально и слабо пересекается с темой материала, однако предполагается, что задачи в ней обладают как минимум двумя свойствами:
    • Приоритетом, задающим порядок обработки.
    • Статусом, допускающим три значения:
      1. Ожидает обработки.
      2. Успешно обработана.
      3. Ошибка (не удалось обработать).
  • Служба:
    • Сразу после старта принимается за тяжкие труды и начинает, с учётом приоритета, извлекать из очереди задачи с первым статусом (который «ожидающий»), после чего, в зависимости от результата обработки, обновляет статус у каждой из них; работа прекращается после того, как в очереди не осталось необработанных элементов.
    • Если поступает команда на остановку, то обработка текущей задачи прерывается и служба завершается.
    • Во время работы может принять особую (нестандартную) команду от управляющего приложения (УП), суть которой описана чуть ниже.
    • Дабы не наделять службу чрезмерным набором прав, из-за которых может пострадать безопасность всей ОС, вместо обычно применяемого аккаунта LocalSystem станет использоваться специальный пользователь, создаваемый на лету.
    • При установке происходит автоматическое назначение минимально необходимых прав как пользователю самой службы (от имени которого она должна запускаться – о нём шла речь в предыдущем пункте), так и пользователю управляющего приложения.
  • Управляющее приложение:
    • Подаёт команды на запуск и остановку службы, т. е. примерно то, что вручную делается через Диспетчер служб:

      Кнопки управления службой в Диспетчере

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

Служба

В данном случае, веских причин изобретать велосипед для реализации службы не имеется, поэтому основа дальнейшего кода – это стандартный для IDE подход к созданию, основанный на классе TService. Также необходимо отметить, что автор использует не самую новую версию Delphi (10.1 Berlin), в связи с чем в иных выпусках могут иметься свои особенности (в более свежих, к примеру, часть предложенного функционала может быть уже реализована, однако подобное маловероятно, учитывая стойкое нежелание разработчиков Delphi развивать TService).

Описание кода службы логично вести в соответствии с циклом её жизни в системе – то есть начать с момента установки (регистрации).

Установка

Собственно самостоятельно реализовывать регистрацию и не требуется, т. к. запуск исполняемого файла службы с ключом /install сделает всё необходимое – программист от данной рутины избавлен. Намного интересней выглядит момент сразу после установки (чему соответствует событие AfterInstall), где и удобно приступить к воплощению части означенного в ТЗ; однако, хотелось бы начать с малого и показать на простом примере как происходит изменение параметра установленной службы – будет сделано то, чего уже так давно не добавляют в Delphi – реализована возможность указать описание, отображаемое, например, в Диспетчере:

Описание службы в Диспетчере

Основа обработчика указанного события, постепенно расширяемая далее, выглядит так:

interface
uses System.SysUtils, Vcl.SvcMgr;
...
implementation
uses Winapi.WinSvc;
resourcestring ServiceDescription = 'Шаблон (заготовка) службы, обрабатывающей очередь неких задач.';
procedure TQueueService.ServiceAfterInstall(Sender: TService);
var ManagerHandle, ServiceHandle: SC_HANDLE; Description: SERVICE_DESCRIPTION;
begin ManagerHandle := OpenSCManager(nil, nil, 0); if ManagerHandle = 0 then RaiseLastOSError; try ServiceHandle := OpenService( ManagerHandle, PChar(Name), SERVICE_CHANGE_CONFIG ); if ServiceHandle = 0 then RaiseLastOSError; try Description.lpDescription := PChar(ServiceDescription); Win32Check( ChangeServiceConfig2(ServiceHandle, SERVICE_CONFIG_DESCRIPTION, @Description) ); finally CloseServiceHandle(ServiceHandle); end; finally CloseServiceHandle(ManagerHandle); end;
end;

Здесь, прежде всего, выполняется получение дескриптора Менеджера служб (Service Control Manager), после чего у него запрашивается дескриптор уже нашей (только что установленной) службы по её имени; доступ к обоим объектам выбран минимально необходимый – SC_MANAGER_CONNECT и SERVICE_CHANGE_CONFIG, причём SC_MANAGER_CONNECT не требуется указывать, т. к. он подразумевается неявно (именно поэтому последний параметр функции OpenSCManager равен нулю).

Пользователь

Создание виртуального пользователя через свойства службы в Диспетчере

Казалось бы, чего проще – действуем аналогично в Инспекторе объектов Delphi и получаем тот же результат:

Создание виртуального пользователя через Инспектор объектов в Delphi

Но не тут-то было! В случае виртуального пользователя, WinAPI-функция CreateService, применяемая в модуле Vcl.SvcMgr для установки службы, в последнем параметре, содержащем пароль, должна получить значение nil, а не пустую строку,

как имеет место быть сейчас.

Svc := CreateService(SvcMgr, PChar(Name), PChar(DisplayName), SERVICE_ALL_ACCESS, GetNTServiceType, GetNTStartType, GetNTErrorSeverity, PChar(Path), PChar(LoadGroup), PTag, PChar(GetNTDependencies), PSSN, PChar(Password));

Собственно подобное даже нельзя назвать ошибкой – скорее всего, разработчики Delphi просто-напросто не стали улучшать TService и добавлять распознавание префикса NT Service\ в имени, ведь до Windows 7 такой особенности элементарно не существовало. Поэтому, дабы не править стандартный модуль, ограничимся заданием пользователя уже после установки службы (т. е. предполагается, что свойства ServiceStartName и Password оставлены пустыми), для чего достаточно вызова лишь одной функции (часть ранее приводимого кода, ответственного за получение дескрипторов, опущена):

procedure TQueueService.ServiceAfterInstall(Sender: TService);
const VirtualAccountPrefix = 'NT Service\';
var ManagerHandle, ServiceHandle: SC_HANDLE; Description: SERVICE_DESCRIPTION; VirtualAccount: string;
begin ... Description.lpDescription := PChar(ServiceDescription); Win32Check( ChangeServiceConfig2(ServiceHandle, SERVICE_CONFIG_DESCRIPTION, @Description) ); VirtualAccount := VirtualAccountPrefix + Name; Win32Check ( ChangeServiceConfig ( ServiceHandle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, nil, nil, nil, nil, PChar(VirtualAccount), nil, nil ) ); ...
end;

Надо сказать, что имя виртуального пользователя, указываемое после префикса, совсем не обязательно должно совпадать с именем службы – главное обеспечить его уникальность.

Права

На следующем этапе необходимо позаботиться о правах двух пользователей:

  • Первым из них идёт вышеупомянутый виртуальный, проблема с которым такова: если попробовать запустить службу в текущем виде, то система сообщит об отказе в доступе, ибо только что созданный аккаунт не имеет прав на запуск исполняемого файла службы (их у него вообще кот наплакал – за это и выбран). Другими словами, требуется добавить вот такую запись:

    Права на исполняемый файл службы

  • Вторым пользователем является тот, от имени которого запускается управляющее приложение, – дело в том, что любая команда (запуск, приостановка и т. п.) проверяется на наличие соответствующих прав у её инициатора, пока их, увы, не имеющего. Хотя в общем случае про УП служба может ничего не знать (оно, скажем, создаётся другим программистом на ином ЯП), но ситуация в статье иная и позволяет возложить на службу и данное бремя, а чтобы она знала какому пользователю выдать такие права, добавим новый ключ запуска /ControlUser, где после двоеточия необходимо указать имя; если привести конкретный пример, то теперь установку службы следует производить с такими ключами – /install /ControlUser:SomeUser1.

Доработки события под описанное выглядят следующим образом:

interface
uses System.SysUtils, Winapi.Windows, Vcl.SvcMgr;
...
implementation
uses Winapi.WinSvc, Winapi.AccCtrl, Winapi.AclAPI;
procedure TQueueService.ServiceAfterInstall(Sender: TService); procedure GrantAccess(const UserName, ObjectName: string; const ObjectType: SE_OBJECT_TYPE; const Rights: ACCESS_MASK); begin // Реализация процедуры приведена чуть ниже в статье. ... end;
const VirtualAccountPrefix = 'NT Service\'; ControlUserSwitch = 'ControlUser';
var ManagerHandle, ServiceHandle: SC_HANDLE; Description: SERVICE_DESCRIPTION; VirtualAccount, ControlUserName: string;
begin ... Description.lpDescription := PChar(ServiceDescription); Win32Check( ChangeServiceConfig2(ServiceHandle, SERVICE_CONFIG_DESCRIPTION, @Description) ); VirtualAccount := VirtualAccountPrefix + Name; Win32Check ( ChangeServiceConfig ( ServiceHandle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, nil, nil, nil, nil, PChar(VirtualAccount), nil, nil ) ); GrantAccess( VirtualAccount, ParamStr(0), SE_FILE_OBJECT, GENERIC_READ or GENERIC_EXECUTE ); if FindCmdLineSwitch(ControlUserSwitch, ControlUserName) then GrantAccess(ControlUserName, Name, SE_SERVICE, SERVICE_START or SERVICE_STOP or SERVICE_USER_DEFINED_CONTROL); ...
end;
procedure GrantAccess(const UserName, ObjectName: string; const ObjectType: SE_OBJECT_TYPE; const Rights: ACCESS_MASK);
var SecurityDescriptor: PSECURITY_DESCRIPTOR; OldDACL, NewDACL: PACL; UserAccess: EXPLICIT_ACCESS;
begin CheckOSError ( GetNamedSecurityInfo ( PChar(ObjectName), ObjectType, DACL_SECURITY_INFORMATION, nil, nil, @OldDACL, nil, SecurityDescriptor ) ); try BuildExplicitAccessWithName( @UserAccess, PChar(UserName), Rights, SET_ACCESS, NO_INHERITANCE ); CheckOSError( SetEntriesInAcl(1, @UserAccess, OldDACL, NewDACL) ); try CheckOSError ( SetNamedSecurityInfo ( PChar(ObjectName), ObjectType, DACL_SECURITY_INFORMATION, nil, nil, NewDACL, nil ) ); finally LocalFree( HLOCAL(NewDACL) ); end; finally LocalFree( HLOCAL(SecurityDescriptor) ); end;
end;

Завершая изыскания с AfterInstall, необходимо отметить, что любое исключение в этом событии приведёт к удалению только что установленной службы (с записью текста исключения в журнал Windows), а в приведённом коде его может сгенерировать, к примеру, функция Win32Check.

В заключение подраздела также хочется остановиться на моменте, связанном с правами, назначенными выше пользователю УП: если, предположим в целях отладки, их необходимо поменять, то совершенно не обязательно для этого удалять и заново устанавливать службу – достаточно воспользоваться всем известной утилитой Process Explorer: когда служба запущена, следует открыть её свойства и перейти на вкладку Services, после чего пройтись по показанным шагам:

:/>  «Каково определение клавиши Ctrl на клавиатуре и какова цель ее функциональной клавиши Ctrl, в дополнение к каким доступным комбинациям?»

Права на службу

Обработка очереди

Как известно, Delphi предлагает два подхода к реализации службы (подробнее о них можно узнать в материале на другом ресурсе в разделе «3. События службы»):

  1. На основе событий OnStart и OnStop, что подразумевает самостоятельное создание потоков, содержащих нужный функционал.
  2. На основе события OnExecute, обработчик которого выполняется в заранее заботливо созданном TService потоке, причём служба сразу же остановится после выхода из события; именно данный вариант хорошо подходит под поставленную в статье цель – как только в очереди обработаны все задачи, делать больше нечего и необходимо завершиться.

Основа события

В первом приближении код OnExecute прост и незатейлив – идёт извлечение задач до тех пор, пока они имеются в очереди:

procedure TQueueService.ServiceExecute(Sender: TService);
type TTask = ...; // Конкретный тип зависит от деталей Вашей реализации. TTaskList = array of TTask; // Массив использован лишь для иллюстрации, допустимы любые другие структуры данных (TList<TTask>, например). function ExtractTaskPortion(out Tasks: TTaskList): Boolean; begin // Функция вернёт True в случае, если в очереди ещё есть задачи для обработки (при этом // содержаться они будут в параметре Tasks). ... Result := Length(Tasks) > 0; end; procedure ProcessTask(const Task: TTask); begin // После обработки задачи, процедура должна обновить её статус (на 2-й или 3-й). ... end;
var Task: TTask; Tasks: TTaskList;
begin while ExtractTaskPortion(Tasks) do for Task in Tasks do ProcessTask(Task);
end;

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

Прерывание обработки

Несложно заметить, что в текущем виде не предусмотрено никакого механизма по прекращению цикла извлечения задач, а ведь причин такого прерывания, согласно ТЗ, может быть две:

  1. Команда на остановку службы, после которой никакого ожидания обработки текущей задачи быть не должно – она прерывается как можно быстрее, после чего все оставшиеся в порции задачи тоже отбрасываются и служба завершается.
  2. Команда на повторную обработку задач с третьим статусом, для чего необходимо прервать работу по текущей (как и в случае команды на остановку), обновить статус всех означенных задач на первый, запросить новую порцию и далее действовать как обычно; надобность прерывать обработку текущей порции связана с тем, что среди задач с только что установленным первым статусом могут иметься обладающие бо́льшим приоритетом.

В качестве решения данной проблемы предлагается воспользоваться исключениями – они в этом случае выступят в полном соответствии со своим названием, то есть будут сигнализировать не об ошибке, а именно об исключительной, прерывающей нормальное течение алгоритма ситуации (в нашем случае таковой являются команды от Менеджера служб и УП). Для этого сначала объявим новый класс исключения, содержащий поле с причиной прерывания:

...
implementation
...
type EInterruption = class(Exception) public type TReason = (irStop, irErrorsReset); public Reason: TReason; constructor Create(const Reason: TReason); end;
constructor EInterruption.Create(const Reason: TReason);
begin inherited Create(string.Empty); Self.Reason := Reason;
end;
...

Это исключение станет генерироваться в новой локальной процедуре CheckInterruption (как – об этом чуть позже), а реакция на него имеет следующий вид:

procedure TQueueService.ServiceExecute(Sender: TService);
type TTask = ...; TTaskList = array of TTask; function ExtractTaskPortion(out Tasks: TTaskList): Boolean; begin ... end; procedure CheckInterruption; begin // Отвечает за возбуждение исключения EInterruption. ... end; procedure ProcessTask(const Task: TTask); begin ... end; procedure ResetQueueErrors; begin // Меняет 3-й статус на первый у всех задач в очереди. ... end;
var Task: TTask; Tasks: TTaskList;
begin while ExtractTaskPortion(Tasks) do try for Task in Tasks do ProcessTask(Task); except on E: EInterruption do case E.Reason of irStop: Break; irErrorsReset: ResetQueueErrors; else raise; end; end;
end;

От разработчика требуется лишь вставлять вызов CheckInterruption периодически, через небольшие этапы обработки задачи в ProcessTask, навроде такого:

procedure ProcessTask(const Task: TTask);
begin // Некие действия (например инициализация обработки). CheckInterruption; ... // Ещё какой-то этап. CheckInterruption; ... // Некий этап-цикл. for ... to ... do begin CheckInterruption; ... end; // Обновление статуса задачи. CheckInterruption; ...
end;

Взаимодействие с Менеджером служб

В рассматриваемом событии осталось реализовать ещё три вещи, две из которых удобно объединить в одной CheckInterruption – во-первых, требуется наконец уже реальная генерация исключения, а во-вторых, служба обязана периодически извещать Менеджер о своём статусе, а также получать пришедшие от него же сообщения и реагировать на них. Если сообщение об остановке службы TService в основном обрабатывает сам, то вот специальная команда от УП требует дополнительного кодирования, заключающегося, прежде всего, в переопределении виртуального метода DoCustomControl – в нашем случае там достаточно всего лишь сохранять переданный службе целочисленный код команды в заведённом для этой цели поле FCustomCode:

interface
...
type TQueueService = class(TService) procedure ServiceAfterInstall(Sender: TService); procedure ServiceExecute(Sender: TService); private FCustomCode: DWORD; protected function DoCustomControl(CtrlCode: DWord): Boolean; override; ... end;
...
implementation
...
function TQueueService.DoCustomControl(CtrlCode: DWord): Boolean;
begin Result := inherited; FCustomCode := CtrlCode;
end;

Теперь можно полностью реализовать процедуру:

procedure CheckInterruption;
begin ReportStatus; FCustomCode := 0; ServiceThread.ProcessRequests(False); // Внутри вызывается DoCustomControl. if Terminated then raise EInterruption.Create(irStop); case FCustomCode of RESET_QUEUE_ERRORS_CONTROL_CODE: raise EInterruption.Create(irErrorsReset); end;
end;

Здесь методы ReportStatus и ProcessRequests отвечают за взаимодействие с Менеджером, а константа RESET_QUEUE_ERRORS_CONTROL_CODE (её допустимые значения см. в описании параметра dwControl) объявлена в новом модуле Services.Queue.Constants:

unit Services.Queue.Constants;
interface
const RESET_QUEUE_ERRORS_CONTROL_CODE = 128;
implementation
end.

Полезность добавления модуля проистекает из того факта, что управляющее приложение в нашем случае тоже написано на Delphi и при отправке специальной команды эта константа в нём тоже потребуется:

Зависимости от модуля Services.Queue.Constants

Кстати, если читатель задаётся вопросом о целесообразности добавления поля FCustomCode, когда, казалось бы, можно сгенерировать исключение прямо в методе DoCustomControl,

function TQueueService.DoCustomControl(CtrlCode: DWord): Boolean;
begin Result := inherited; case CtrlCode of RESET_QUEUE_ERRORS_CONTROL_CODE: raise EInterruption.Create(irErrorsReset); end;
end;

Окончательный вариант

В качестве последнего штриха к реализации службы, необходимо разобраться хоть и с небольшой (в плане устранения), но всё же загвоздкой, а именно: в текущем виде, если в очереди все задачи обработаны, но некоторые из них имеют третий статус (завершились ошибкой), то заново такие взять в работу не получится – служба после старта станет сразу завершаться, а, соответственно, и не сможет никогда принять команду от УП на повторную обработку ошибок. К счастью, при запуске службы можно передать ей произвольное количество текстовых параметров, хотя в данном случае достаточно одного параметра-флага – факт его наличия будет говорить о том, что ещё перед циклом по очереди требуется вызвать уже применявшуюся процедуру ResetQueueErrors:

procedure TQueueService.ServiceExecute(Sender: TService); ... procedure ResetQueueErrors; begin // Меняет 3-й статус на первый у всех задач в очереди. ... end;
var I: Integer; Task: TTask; Tasks: TTaskList;
begin for I := 0 to ParamCount - 1 do if Param[I] = ResetQueueErrorsParam then begin ResetQueueErrors; Break; end; while ExtractTaskPortion(Tasks) do try for Task in Tasks do ProcessTask(Task); except on E: EInterruption do case E.Reason of irStop: Break; irErrorsReset: ResetQueueErrors; else raise; end; end;
end;

Важно понимать, что эти параметры не имеют ничего общего с ключами, использующимися при установке и удалении, – те применяются при самостоятельном запуске исполняемого файла службы, а свойство Param содержит то, что было передано специальной WinAPI-функции, предназначенной для старта служб (она будет упомянута в следующем разделе). Что касается константы ResetQueueErrorsParam, то она объявлена в модуле Services.Queue.Constants:

unit Services.Queue.Constants;
interface
const RESET_QUEUE_ERRORS_CONTROL_CODE = 128; ResetQueueErrorsParam = 'ResetErrors';
implementation
end.

Управляющее приложение

В целях сосредоточения на главном, и дабы не отвлекаться на второстепенные нюансы, УП представляет собой обычный VCL-проект из одной простейшей формы, состоящей из 4-х кнопок; вместе с тем, весь приводимый код использует только WinAPI, поэтому применять его можно где угодно – хоть в другой службе, хоть вообще поместить в DLL.

Окно управляющего приложения

Кнопки отвечают за уже знакомые действия:

  1. Запуск без изысков (как будто через Диспетчер служб).
  2. Аналогично первой кнопке, но с параметром, отвечающим за предварительный сброс у задач третьего статуса.
  3. Передача службе специальной команды (см. константу RESET_QUEUE_ERRORS_CONTROL_CODE).
  4. Остановка службы (как будто через Диспетчер служб).

Предварительные действия

В дальнейшем довольно часто будет требоваться дескриптор Менеджера служб, поэтому, чтобы не получать его каждый раз заново, сделаем это при создании формы; также сотворим полезный метод OpenService, избавляющий далее от дублирования кода и возвращающий дескриптор службы:

interface
uses Winapi.Windows, System.SysUtils, ..., Winapi.WinSvc;
type TForm1 = class(TForm) ... procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); private FSCMHandle: SC_HANDLE; function OpenService(const Access: DWORD): SC_HANDLE; end;
...
implementation
procedure TForm1.FormCreate(Sender: TObject);
begin FSCMHandle := OpenSCManager(nil, nil, 0); if FSCMHandle = 0 then RaiseLastOSError;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin CloseServiceHandle(FSCMHandle);
end;
function TForm1.OpenService(const Access: DWORD): SC_HANDLE;
begin Result := Winapi.WinSvc.OpenService( FSCMHandle, PChar('QueueService'), Access ); if Result = 0 then RaiseLastOSError;
end;

Основной код

Запуск службы – без параметров и с ними – отличается незначительно (и там и там применяется одна и та же WinAPI-функция), поэтому видится разумным создать у формы метод, который затем и вызывать при нажатии на первые две кнопки:

interface
...
type TForm1 = class(TForm) ... private ... procedure RunService(const Parameters: array of string); end;
...
implementation
...
procedure TForm1.RunService(const Parameters: array of string);
var ServiceHandle: SC_HANDLE; Arguments: array of PChar; I: Integer;
begin ServiceHandle := OpenService(SERVICE_START); try if Length(Parameters) = 0 then Win32Check( StartService(ServiceHandle, 0, PPChar(nil)^) ) else begin SetLength( Arguments, Length(Parameters) ); for I := Low(Parameters) to High(Parameters) do Arguments[I] := PChar(Parameters[I]); Win32Check( StartService(ServiceHandle, Length(Arguments), Arguments[0]) ); end; finally CloseServiceHandle(ServiceHandle); end;
end;

Параметр-массив Parameters позволяет указать как раз тот набор параметров запуска службы, о которых шла речь выше. Итак, имея новый метод, очень легко закодировать обработчики у первой половины кнопок:

...
implementation
uses Services.Queue.Constants;
...
procedure TForm1.bStartClick(Sender: TObject);
begin RunService([]);
end;
procedure TForm1.bStartAndResetErrorsClick(Sender: TObject);
begin RunService([ResetQueueErrorsParam]);
end;

Две последние кнопки тоже позволяют обойтись вызовом одного и того же дополнительного метода, с совсем уж простой реализацией:

interface
...
type TForm1 = class(TForm) ... private ... procedure SendCommandToService(const Access, ControlCode: DWORD); end;
...
implementation
...
procedure TForm1.SendCommandToService(const Access, ControlCode: DWORD);
var ServiceHandle: SC_HANDLE; ServiceStatus: TServiceStatus;
begin ServiceHandle := OpenService(Access); try Win32Check( ControlService(ServiceHandle, ControlCode, ServiceStatus) ); finally CloseServiceHandle(ServiceHandle); end;
end;

Здесь в переменной ServiceStatus возвращается последнее, самое свежее состояние службы, однако оно в данном контексте неинтересно, поэтому полученное значение просто игнорируется. Таким образом, 3-я и 4-я кнопки на нажатие реагируют так:

...
implementation
...
procedure TForm1.bResetErrorsClick(Sender: TObject);
begin SendCommandToService(SERVICE_USER_DEFINED_CONTROL, RESET_QUEUE_ERRORS_CONTROL_CODE);
end;
procedure TForm1.bStopClick(Sender: TObject);
begin SendCommandToService(SERVICE_STOP, SERVICE_CONTROL_STOP);
end;

Последнее, о чём хочется сказать, касается нестандартных команд (рассмотренная служба реагирует только на одну – RESET_QUEUE_ERRORS_CONTROL_CODE): если они в Вашем случае являются более сложными, требующими для выполнения дополнительную информацию, а не просто факт получения службой одного числового кода, то для передачи таких сведений придётся задействовать механизмы межпроцессного обменаразделяемую память, неименованные каналы и т. п.

:/>  Где находятся шрифты в Windows 10: в какой папке хранятся и куда устанавливаются

Описание команды SC CREATE

Команда SC CREATE создает запись службы в реестре и в базе данных диспетчера служб. А для запуска только что созданной службы из командной строки служит команда SC START.

Синтаксис и параметры команды SC CREATE

  • имя_сервера – Задает имя удаленного сервера, на котором находится служба. В имени следует использовать формат UNC (“\\myserver”). Чтобы запустить SC локально, этот параметр следует пропустить.
  • имя_службы – Указывает имя службы, возвращенное операцией getkeyname.
  • type= {own|share|kernel|filesys|rec|adapt|interact type= {own|share}} – Указывает тип службы. Тип по умолчанию type= own.
  • start= {boot|system|auto|demand|disabled} – Указывает тип запуска для службы. Тип запуска по умолчанию start= demand.
  • error= {normal|severe|critical|ignore} – Указывает серьезность ошибки, если служба не запускается при загрузке. Значение параметра по умолчанию error= normal.
  • binpath= имя_двоичного_пути – Указывает путь в двоичном файле службы. Значение по умолчанию для параметра binpath= не задано. Эту строку необходимо указать.
  • group= группа_порядка_загрузки – Указывает имя группы, членом которой является эта служба. Список групп сохраняется в реестре в подразделе HKLM\System\CurrentControlSet\Control\ServiceGroupOrder. Значение по умолчанию является пустым.
  • tag= {yes|no} – Указывает, следует ли получить код TagID из вызова CreateService. Теги используются только драйверами, запускающимися при загрузке или запуске системы.
  • depend= зависимости – Указывает имена служб и групп, которые должны быть запущены раньше данной службы. Имена разделяются косой чертой (/).
  • obj= {имя_учетной_записи|имя_объекта} – Указывает имя учетной записи, для которой будет выполняться служба, или имя объекта драйвера Windows, в котором будет запущен драйвер
  • displayname= отображаемое_имя – Определяет понятное, точное имя для службы, которое используется в программах пользовательского интерфейса.
  • password= пароль – Задает пароль. Данный параметр требуется при использовании учетной записи, отличной от учетной записи «Локальная система».
  • /? – Отображает справку в командной строке.

Командная строка команда SC CREATE

Примеры команды SC CREATE

  • sc \\myserver create NewService binpath= c:\windows\system32\NewServ.exe
  • sc create NewService binpath= c:\windows\system32\NewServ.exe type= share start= auto depend= “+TDI Netbios”

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

Создание службы с помощью программы Sc.exe

Первый способ позволяет создавать службы, используя утилиты, работающие через командную строку. В данном случае, используется инструмент sc.exe. Он позволяет взаимодействовать с функциями API и выполнять операции со службами Windows 10. Несмотря на то, что данная программа даже не обладает графическим интерфейсом, она является мощным инструментом и может контролировать состояние служб, создавать, редактировать и управлять ими.
Если создавать службу, с помощью sc.exe, то не возникает необходимости в изменении параметров реестра и списка служб в диспетчере. Кроме того, утилита способна работать со службами на локальном компьютере, и выполнять те же действия на удаленных машинах.
Чтобы создать новый сервис, откройте командную строку от имени администратора и запустите команду «Sc create». Она запишет новую службу к базе диспетчера служб. Синтаксис команды представлен следующим образом:

  • ServiceName — данным параметром определяется имя раздела службы в реестре;
    Данное имя не совпадает с тем именем, которое будет отображаться диспетчере (например, в Services).
  • BinPath — в этом параметре записываем путь, где хранится исполняемый файл.

Чтобы было понятнее, в качестве примера, мы создадим службу «MySevice». При этом, отображаемое имя будет «My New Service». Указываем тип службы и включаем автозапуск:

Sc create MyService binPath=C:\MyService\MyService.exe DisplayName=″My New Service″ type=own start=auto

Теперь откройте оснастку «Services» и взгляните на то, что получилось:

оснастка «Services»

Параметры службы, которая уже была создана и запущена, можно изменить при помощи команды Sc config. К примеру, мы заменим имя службы, которое отображается как:

Sc config MyService DisplayName=″My Service″

А еще можно избавиться от службы полным ее удалением. Для этого используйте такую команду:

Sc delete MyService

удалить службу

С помощью утилиты PowerShell

Ну а с помощью такого мощного инструмента, как PowerShell можно хоть горы двигать. Он обладает большими возможностями и способен работать с различными службами. Здесь, для добавления новой службы, существует специальная команда «New -Service». Давайте попробуем новую службу, как и в прошлом примере, будем использовать те же имена и значения, только добавим дополнительный параметр, в виде описания. И так, команда, для создания нового сервиса, выглядит следующим образом:

New-Service -Name MyService -BinaryPathName C:\MyService\MyService.exe` -DisplayName ″My New Service″ -Description ″Very Important Service !!!″

Для изменения параметров служб, существует команда «Set -Service»:

Set-Service -Name MyService -Description ″Not Very Important Service″ -StartupType Manual

параметры службы windows 10

Если не считать дополнительную возможность добавления описаний к службам, PowerShell обладает таким же функционалом, как и утилита Sc.exe. Но есть один маленький минус — здесь нет простой команды для удаления службы. Поэтому приходиться использовать такой вот, немного мудреный, код:

(Get-WmiObject win32_service -Filter ″name=′MyService′″).delete()

Так что утилита sc.exe, в этом плане будет получше. И по этой причине я предпочитаю именно ее.

Автор статьи: Сергей

Это может пригодиться:

Как создать службу в Windows 10

Способ 1: Консольная утилита sc.exe

Назначить любой процесс системной службой в Windows 10 можно с помощью маленькой консольной утилиты sc.exe, входящей в состав операционной системы. Для обращения к ней можно использовать как классическую «Командную строку», так и консоль «PowerShell».

  1. Запустите от имени администратора «Командную строку» или «PowerShell». Первую можно открыть из поиска Windows, вторую – из контекстного меню кнопки «Пуск».
  2. Как создать службу в Windows 10-1

  3. Сформируйте команду следующего вида: sc create MyService binPath="C:\MyService.exe" DisplayName= "MyNewService" type=own start=auto. В качестве параметра MyService binPath укажите свой путь к исполняемому файлу создаваемой службы, а в качестве параметра DisplayName — имя службы, которое станет отображаться в оснастке управления службами.
  4. Вставьте сформированную команду в консоль и нажмите клавишу ввода.
  5. Как создать службу в Windows 10-2

Готово, осталось только проверить корректность создания службы в системной оснастке «Службы», запустить которую можно командой services.msc в диалоговом окошке, вызванном клавишами Win + R.
Как создать службу в Windows 10-3

Способ 2: Консоль «PowerShell»

Необходимыми средствами создания служб в Windows 10 располагает другой штатный инструмент – консоль «PowerShell».

  1. Запустите «PowerShell» от имени администратора.
  2. Как создать службу в Windows 10-4

  3. Сформируйте команду вида New-Service -Name MyService -BinaryPathName C:\MyService.exe -DisplayName "Отображаемое имя службы" -Description "Описание службы". Имя и описание службы могут быть произвольными.
  4. Вставьте команду в консоль и нажмите клавишу ввода.
  5. Как создать службу в Windows 10-5

Проверьте корректность работы в оснастке управления службами, там же ее можно настроить.

Способ 3: Сторонние программы

Также для создания собственных служб в Windows 10 можно использовать специализированные сторонние утилиты, например Non-Sucking Service Manager. Утилита работает через «Командную строку», но у нее также имеется и графический интерфейс.

  1. Скачайте архив с утилитой с сайта разработчика и распакуйте в удобное расположение — к примеру, поместите исполняемый файл nssm.exe в корень системного диска.
  2. Запустите «Командную строку» от имени администратора.
  3. Выполните команду C:\nssm.exe install MyService, где MyService – название создаваемой службы.
  4. Как создать службу в Windows 10-6

  5. В открывшемся окошке инсталлятора в поле «Patch» укажите полный путь к исполняемому файлу службы и нажмите кнопку «Install service».
  6. Как создать службу в Windows 10-7

  7. Служба будет установлена, подтверждением чему станет появление окошка с уведомлением «Service *Name* installed successfully!». Нажмите в нем «OK» и закройте «Командную строку».
  8. Как создать службу в Windows 10-8

Остальные поля в окошке инсталлятора службы заполнять не обязательно, кроме тех случаев, когда в качестве службы устанавливается командный файл, например CMD, BAT или PS1. В этом случае в поле «Patch» указывается путь к приложению-обработчику, а в поле «Arguments» – путь к файлу скрипта.

Еще статьи по данной теме:

Помогла ли Вам статья?

Службами в Windows называются процессы, стартующие вместе с системой, не имеющие графического интерфейса и работающие в фоновом режиме, причем большинство служб запускается и работают независимо от того, вошел ли пользователь в свою учетную запись или нет. Эту особенность операционной системы можно использовать в практических целях, например, создать собственную службу, которая станет отслеживать изменения в реестре или отправку пакетов .

В статье от 07 декабря 2020 года мы уже рассматривали процедуру создания служб в Windows 10, предлагая использовать для этой цели стороннюю утилиту Non-Sucking Service Manager, укомплектованную простейшим графическим интерфейсом.

Сегодня мы предлагаем познакомиться со способами создания служб исключительно средствами операционной системы. Используйте их, если в качестве службы вам нужно установить процесс исполняемого файла , так как для назначения службами процессов скриптов всё же удобнее использовать Non-Sucking Service Manager.

Командная строка

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

Запустите командную строку от имени администратора и выполните в ней команду следующего вида:

sc create MyService binPath=”C:\service.exe” DisplayName= “ServiceName” type=own start=auto

Sc create

Как нетрудно догадаться, C:\service.exe – это путь к исполняемому файлу службы, ServiceName – отображаемое имя , а MyService – фактическое имя службы, к которому нужно будет обращаться в процессе настройки службы. Параметр type задает статус, в параметр start – тип запуска.

Службы

В данном примере служба имеет статус «Отключена», а тип запуска у нее выставлен «Автоматически», в чем можно убедиться, открыв свойства добавленной службы в оснастке управления службами.

Свойства службы

Если служба станет больше не нужна, вы всегда сможете ее удалить командой:

sc delete MyService

Sc delete