Статья размещена автором Бетке Сергей Сергеевич

Службы (windows services) на powershell – возможно!

Добрый день, господа. В процессе решения проблемы с linkd и pickup папкой SMTP сервера пришло решение, реализация которого напрашивалась в виде службы Windows, но при этом хочется обойтись исключительно powershell.  Сделаем!

Итак, создадим модуль, экспортирующий пока только одну функцию регистрации службы на базе скрипта PowerShell:

set-variable `
    -name itgPSServiceHost `
    -value ([System.IO.Path]::GetFullPath("$env:ProgramFiles\Windows Resource Kits\Tools\srvany.exe")) `
    -option constant

set-variable `
    -name itgPSServiceCmdLine `
    -value '"powershell.exe -NoExit -File `"$($File)`" -NoLogo -NonInteractive -WindowStyle Hidden"' `
    -option constant

set-variable `
    -name itgPSServiceStartupOptions `
    -value @{ `
        [System.ServiceProcess.ServiceStartMode]::Manual = "demand"; `
        [System.ServiceProcess.ServiceStartMode]::Automatic = "auto"; `
        [System.ServiceProcess.ServiceStartMode]::Disabled = "disabled" `
    } `
    -option constant

function New-PSService {
	<#
		.Synopsis
		    Регистрация в реестре новой службы, представляющей из себя сценарий power shell.
		.Description
		    Регистрация в реестре новой службы, представляющей из себя сценарий power shell. В качестве хост процесса будет
            использован srvany.exe из состава RK.
		.Parameter File
		    Путь к файлу скрипта, который и станет службой
		.Parameter Name
			Идентификатор (короткий) службы
		.Parameter DisplayName
			Отображаемый идентификатор службы
		.Parameter Description
            Описание службы
        .Parameter Group
            Группа служб, к которой должна быть отнесена данная служба
        .Parameter DependsOnServices
            Массив идентификаторов служб, от которых зависит данная служба
        .Parameter DependsOnGroups
            Массив идентификаторов групп сервисов, от которых зависит данная служба
        .Parameter StartupType
            Вариант запуска службы
        .Parameter RestoreParams
            Параметры восстановления службы в случае сбоя
		.Example
			Регистрация новой службы:

			New-PSService `
                -File "c:\windows\service.ps1" `
                -Name "NewService1" `
                -DisplayName "Новый сервис" `
                -DependsOnServices "SMTPSVC", "NetLogon" `
                -StartupType "Automatic" `
                -RestoreParams @{ `
                    reset=77; `
                    reboot="";
                    command="";
                    failureActions = `
                        @{action="restart";period=60}, `
                        @{action="restart";period=60}, `
                        @{action="restart";period=60} `
                }
	#>
    param (
		[Parameter(
			Mandatory=$true,
			Position=0,
			ValueFromPipeline=$false,
			HelpMessage="Путь к файлу скрипта, который и станет службой."
		)]
        [string]$File,
		[Parameter(
			Mandatory=$true,
			Position=1,
			ValueFromPipeline=$false,
			HelpMessage="Идентификатор (короткий) службы."
		)]
        [string]$Name,
		[Parameter(
			Mandatory=$false,
			Position=2,
			ValueFromPipeline=$false,
			HelpMessage="Отображаемый идентификатор службы."
		)]
  		[string]$DisplayName = $Name,
		[Parameter(
			Mandatory=$false,
			Position=3,
			ValueFromPipeline=$false,
			HelpMessage="Описание службы."
		)]
  		[string]$Description = "Служба на базе сценария Power Shell $([System.IO.Path]::GetFileNameWithoutExtension($File))",
		[Parameter(
			Mandatory=$false,
			Position=4,
			ValueFromPipeline=$false,
			HelpMessage="Группа служб, к которой должна быть отнесена данная служба."
		)]
  		[string]$Group,
		[Parameter(
			Mandatory=$false,
			Position=5,
			ValueFromPipeline=$false,
			HelpMessage="Массив идентификаторов служб, от которых зависит данная служба."
		)]
  		[string[]]$DependsOnServices,
		[Parameter(
			Mandatory=$false,
			Position=6,
			ValueFromPipeline=$false,
			HelpMessage="Массив идентификаторов групп служб, от которых зависит данная служба."
		)]
  		[string[]]$DependsOnGroups,
		[Parameter(
			Mandatory=$false,
			Position=7,
			ValueFromPipeline=$false,
			HelpMessage="Вариант запуска службы."
		)]
  		[System.ServiceProcess.ServiceStartMode]$StartupType = [System.ServiceProcess.ServiceStartMode]::Manual,
		[Parameter(
			Mandatory=$false,
			Position=8,
			ValueFromPipeline=$false,
			HelpMessage="Параметры восстановления службы в случае сбоя."
		)]
  		$RestoreParams
	)

        # http://msdn.microsoft.com/en-us/library/bb490995.aspx
        [string[]]$depends = $null
        if ($DependsOnServices) {
            $depends += $DependsOnServices
        }
        if ($DependsOnGroups) {
            $depends += ($DependsOnGroups | %{ "+$_" })
        }
        ( `
            "sc.exe create " + `
                "$Name " + `
                "type= own " + `
                "start= $($itgPSServiceStartupOptions[$StartupType]) " + `
                "error= normal " + `
                "binPath= `"$($itgPSServiceHost)`" " + `
                "$(if ($Group) {'group= "' + $($Group) + '" '})" + `
                "$(if ($depends) {'depend= "' + ($depends -join ' ') + '" '})" + `
                "displayName= `"$($DisplayName)`" " `
        ) invoke-expression | write-debug

        ( `
            "sc.exe config " + `
                "$Name " + `
                "type= own " + `
                "start= $($itgPSServiceStartupOptions[$StartupType]) " + `
                "error= normal " + `
                "binPath= `"$($itgPSServiceHost)`" " + `
                "$(if ($Group) {'group= "' + $($Group) + '" '})" + `
                "$(if ($depends) {'depend= "' + ($depends -join ' ') + '" '})" + `
                "displayName= `"$($DisplayName)`" " `
        ) | invoke-expression | write-debug

        ( `
            "sc.exe description " + `
                "$Name " + `
                "`"$($Description)`"" `
        ) | invoke-expression | write-debug
        New-Item `
            -path "HKLM:\SYSTEM\CurrentControlSet\services\$Name\Parameters" `
            -ErrorAction SilentContinue
        Set-ItemProperty `
            -path "HKLM:\SYSTEM\CurrentControlSet\services\$Name\Parameters" `
            -name "Application" `
            -value (invoke-expression $itgPSServiceCmdLine) `
            -force

        if ($RestoreParams) {
            if ($RestoreParams.reset -isnot [System.TimeSpan]) {
                $RestoreParams.reset = New-TimeSpan -days $RestoreParams.reset
            }
            $RestoreParams.failureActions | %{
                if ($_.period -isnot [System.TimeSpan]) {
                    $_.period = New-TimeSpan -minutes $_.period
                }
            }

            # http://msdn.microsoft.com/en-us/library/cc742019.aspx
            ( `
                "sc.exe failure " + `
                    "$Name " + `
                    "reset= $($RestoreParams.reset.TotalSeconds) " + `
                    "$(if ($RestoreParams.reboot) {'reboot= "' + $($RestoreParams.reboot) + '" '})" + `
                    "$(if ($RestoreParams.command) {'command= "' + $($RestoreParams.command) + '" '})" + `
                    "actions= $(($RestoreParams.failureActions | %{ "$($_.action)/$($_.period.TotalMilliseconds)" }) -join '/')" `
            ) | invoke-expression | write-debug
        }

        Get-Service -Name $Name  | write-output
}

Export-ModuleMember "New-PSService"

В теле функции воспользовались утилитой sc из состава resource kit. В основном – из-за “прозрачной” конфигурации параметров восстановления после сбоя. Имеющиеся в powershell командлеты на текущий момент крайне ограничены и не дают возможности настроить все необходимые параметры службы. В дальнейшем также следует изучить возможности WMI в этой части…

Пример регистрации службы:

ImportSystemModules
Import-Module "itgPSServices.psm1"

$service = New-PSService `
    -File 'c:\temp\testWithEvent.ps1' `
    -Name 'itgSenderWhitelist' `
    -DisplayName '[ГАРО-ITG] Sender whitelist service' `
    -Description 'Сценарий автоматического резервирования и передачи на дальнейшую обработку файлов сообщений, отфильтрованных по белому списку отправителей.' `
    -DependsOnServices "SMTPSVC" `
    -StartupType "Automatic" `
    -RestoreParams @{ `
        reset=new-timeSpan -days 1; `
        failureActions = `
            @{action="restart";period=new-timeSpan -seconds 10}, `
            @{action="restart";period=new-timeSpan -seconds 60}, `
            @{action="restart";period=new-timeSpan -seconds 120} `
    }

$service.start()

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

Отзывы » (5)

  1. Для доступа к типам из пространства имён [system.serviceprocess] Вам потребуется включить в сценарий следующую инструкцию:

    
    
    [void][System.Reflection.Assembly]::LoadWithPartialName('system.serviceprocess')
    
  2. braginforsc:

    Хорошая идея, как долго ожидать публикацию свежего материала и вообщем стоит ожидать ?

    • Да, стоит. Ситуация следующая. Реаализация самой «службы» мне потребовалась для реализации «белого списка отправителей» для Exchange 2003. Сам сценарий службы приведён в указанной статье. Сейчас эту службу у себя отключил, выявился ряд подводных камней с подобной реализацией «белого списка». В частности — если письма содержат дополнительные адреса получателей, exchange после получения такого письма через pickup пытается доставить письмо не только локальному адресату, но и всем остальным. Но, в конце концов, указанная задача далеко не единственная, которая требует реализации в качестве службы.

  3. japanzone:

    Просто офигительная статья. Спасибки огромные вам за ваш труд кропотливый. Респектище))

    • Рад стараться :-). Следующее из интересного, что я попытаюсь реализоваться — SMTP sink средствами powershell. Примеров на VBS масса, на powershell — ни одного (по крайней мере — я не нашёл ни одного). Задавал вопрос на эту тему в зоне экспертов на Microsoft Platforma 2011 — гоняли меня от эксперта к эксперту, всё равно остался без ответа. Будем пытаться собстввенными силами!

Опубликовать комментарий

XHTML: Вы можете использовать следующие HTML теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Tags Связь с комментариями статьи:
RSS комментарии
Обратная ссылка