Май 2016

Том 31 номер 5

Windows PowerShell - Написание Windows-служб в PowerShell

Жан-Франсуа Лавуа | Май 2016

Продукты и технологии:

Windows Services, Windows PowerShell, C#

В статье рассматриваются:

  • архитектура Windows-служб;
  • управление Windows-службами;
  • использование фрагментов кода на C# в скриптах Windows PowerShell;
  • написание самоуправляемой службы в Windows PowerShell.

Исходный код можно скачать по ссылке.

Windows-службы (Windows Services) — это обычно скомпилированные программы на C, C++, C# или других языках на основе Microsoft .NET Framework, и отладка таких служб бывает весьма трудной. Несколько месяцев назад, вдохновившись тем, что другие операционные системы допускают написание служб как простых скриптов оболочки, я начал интересоваться, есть ли и в Windows более простой способ их создания.

В этой статье представлен конечный результат этих усилий: новый и простой способ создания Windows-служб. Их можно писать на скриптовом языке в Windows PowerShell. Больше никакой компиляции, просто быстрые циклы «правка-тест», которые можно выполнять в любой системе, а не только на компьютере разработчика.

Я предлагаю обобщенный шаблон скрипта службы, PSService.ps1, который позволяет создавать и проверять новые Windows-службы в считанные минуты, используя лишь текстовый редактор наподобие Notepad (блокнота). Этот метод может сэкономить уйму времени и усилий любому, кто хочет поэкспериментировать с Windows-службами или даже предоставлять настоящие Windows-службы, если производительность не является критическим фактором. PSService.ps1 включен в сопутствующий этой статье пакет исходного кода.

Что такое Windows-служба?

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

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

Windows Service Control Manager

Службы управляются Windows Service Control Manager (SCM). SCM отвечает за конфигурирование служб, их запуск и остановку и т. д.

Панель управления SCM доступна через Control Panel | System and Security | Administrative Tools | Services. Как показано на рис. 1, она отображает список всех сконфигурированных служб с их названиями, описаниями, состоянием, типом запуска и именем пользователя.

Windows Service Control Manager GUI в Windows 10
Рис. 1. Windows Service Control Manager GUI в Windows 10

Для SCM также имеются интерфейсы командной строки.

  • Старая утилита net.exe с хорошо известными командами net start и net stop, корнями уходящая аж в MS-DOS! Несмотря на такое название, ее можно использовать для запуска и остановки любой службы, а не только сетевых служб. Введите net help, чтобы получить подробное описание.
  • Более мощная утилита, sc.exe, введенная в Windows NT, дает тонкий контроль над всеми аспектами управления службами. Введите sc /?, чтобы получить подробное описание.

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

Подвох Как net.exe, так и sc.exe используют «короткие» имена служб в одно слово, которые, к сожалению, не совпадают с более описательными названиями, отображаемыми в панели управления SCM. Чтобы получить соответствие между двумя именами, используйте команду get-service из Windows PowerShell.

Состояния службы

Службы могут находиться в разнообразных состояниях. Некоторые состояния обязательны, другие не обязательны. Два базовых состояния, которые должны поддерживаться всеми службами: остановлена (stopped) и запущена (started). Эти состояния соответственно отображаются как пустая ячейка или Running (Выполняется) в столбце Status на рис. 1.

Третье, не обязательное состояние — Paused (Приостановлена). И еще одно неявное состояние, поддерживаемое каждой службой, даже если оно нигде не упоминается, — Uninstalled (Удалена).

Служба может переходить между этими состояниями, как показано на рис. 2.

Состояния службы
Рис. 2. Состояния службы

Наконец, существует несколько переходных состояний, которые службы могут поддерживать (не обязательно): StartPending, StopPending, PausePending, ContinuePending. Они полезны, только если переходы между состояниями занимают значительное время.

Функции управления службами в Windows PowerShell

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

  • согласованные имена функций;
  • полностью объектно-ориентированная;
  • простое управление любым .NET-объектом.

Windows PowerShell предоставляет много функций управления службами, известных как командлеты (cmdlets). Некоторые примеры показаны в табл. 1.

Табл. 1. Функции управления службами в Windows PowerShell

Имя функции Описание
Start-Service Запускает одну или более остановленных служб
Stop-Service Останавливает одну или более выполняемых служб
New-Service Устанавливает новую службу
Get-Service Получает службы на локальном или удаленном компьютере вместе с их свойствами
Set-Service Запускает, останавливает и приостанавливает службу, а также изменяет ее свойства

Чтобы получить полный список всех команд со строкой «service» в их именах, введите:

Get-Command *service*

Чтобы получить список только функций управления службами, введите:

Get-Command -module Microsoft.PowerShell.Management *service*

Как это ни удивительно, но в Windows PowerShell нет функции для удаления службы. Это один из редких случаев, когда по-прежнему приходится использовать старую утилиту sc.exe:

sc.exe delete $serviceName

.NET-класс ServiceBase

Все службы должны создавать .NET-объект, производный от класса ServiceBase. В документации Microsoft описаны все свойства и методы этого класса. В табл. 2 перечислены те из них, которые представляют интерес в данном проекте.

Табл. 2. Некоторые свойства и методы класса ServiceBase

Член Описание
ServiceName Краткое имя, используемое для идентификации службы в системе
CanStop Сообщает, можно ли остановить службу после того, как она запущена
OnStart() Действия, предпринимаемые при запуске службы
OnStop() Действия, предпринимаемые при остановке службы
Run() Регистрирует исполняемый файл службы в SCM

Реализуя эти методы, служба будет управляемой SCM и сможет запускаться автоматически при загрузке системы или по требованию; кроме того, такую службу можно будет запускать или останавливать вручную через панель управления SCM, старыми командами net.exe/sc.exe или новыми функциями Windows PowerShell для управления службами.

Все службы должны создавать .NET-объект, производный от класса ServiceBase.

Создание исполняемого файла из исходного кода на C#, встроенного в скрипт Windows PowerShell

PowerShell упрощает использование .NET-объектов в скриптах. По умолчанию предлагается встроенная поддержка многих типов .NET-объектов, достаточная для большинства задач. Еще лучше то, что она является расширяемой и позволяет встраивать короткие фрагменты C#-кода в скрипт Windows PowerShell для добавления поддержки любой другой .NET-функциональности. Эта возможность обеспечивается командой Add-Type, которая, несмотря на свое название, может делать гораздо больше, чем просто добавлять поддержку новых типов .NET-объектов в Windows PowerShell. Она позволяет даже компилировать и связывать полное C#-приложение в новый исполняемый файл. Например, следующий скрипт Windows PowerShell, hello.ps1:

$source = @"
  using System;
  class Hello {
    static void Main() {
      Console.WriteLine("Hello World!");
    }
  }
"@
Add-Type -TypeDefinition $source -Language CSharp
  -OutputAssembly "hello.exe"
  -OutputType ConsoleApplication

создаст приложение hello.exe, которое выводит в консоль «Hello world!»:

PS C:\Temp> .\hello.ps1
PS C:\Temp> .\hello.exe
Hello World!
PS C:\Temp>

Собираем все воедино

Возможности PSService.ps1 На основе всего того, что мы обсудили на данный момент, я могу создать ту службу Windows PowerShell, о которой я мечтал, — скрипт PSService.ps1, который:

  • может сам себя устанавливать и удалять (используя функции Windows PowerShell для управления службами);
  • может сам себя запускать и останавливать (используя тот же набор функций);
  • включает короткий фрагмент C#-кода, создающий PSService.exe для SCM (с помощью команды Add-Type);
  • делает обратный вызов из заглушки PSService.exe в скрипт PSService.ps1 для выполнения реальной операции службы (в ответ на события OnStart, OnStop и др.);
  • управляется из панели SCM и всех утилит командной строки (благодаря заглушке PSService.exe);
  • является отказоустойчивым и успешно обрабатывает любую команду, находясь в любом состоянии. Например, он может автоматически остановить службу перед ее удалением или ничего не делать, если ему поступит запрос запустить уже выполняемую службу;
  • поддерживает Windows 7 и все более поздние версии Windows (используя функциональность только Windows PowerShell v2).

Заметьте, что в этой статье я затронул лишь критически важные части проекта и реализации PSService.ps1. Скрипт-пример также содержит отладочный код и в какой-то мере поддерживает необязательную функциональность служб, но их описание здесь усложнило бы пояснения безо всякой необходимости.

Архитектура PSService.ps1 Скрипт организуется в серию разделов:

  • комментарий-заголовок, описывающий файл;
  • справочный блок в виде комментариев;
  • блок Param, определяющий ключи командной строки;
  • глобальные переменные;
  • вспомогательные процедуры: Now и Log;
  • блок исходного кода на C# для создания заглушки PSService.exe;
  • основную процедуру, обрабатывающую все ключи командной строки.

Глобальные переменные

Непосредственно за блоком Param скрипт PSService.ps1 содержит глобальные переменные, определяющие глобальные настройки, которые при необходимости можно изменять. Их значения по умолчанию приведены в табл. 3.

Табл. 3. Значения по умолчанию глобальных переменных

Переменная Описание Значение по умолчанию
$serviceName Однословное имя, используемое командами net start и другими Базовое имя скрипта
$serviceDisplayName Более описательное название службы Sample PowerShell Service
$installDir Куда устанавливаются файлы службы ${ENV:windir}\System32
$logFile Имя файла, в который записываются сообщения службы ${ENV:windir}\Logs\­$serviceName.log
$logName Имя журнала событий, в который записываются события службы Application

 

Используя базовое имя файла как имя службы (например, PSService для PSService.ps1), вы можете создавать несколько служб из одного и того же скрипта простым копированием этого скрипта, переименованием копии и последующей установкой копии.

Аргументы командной строки

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

Табл. 4. Аргументы командной строки для переходов состояний

Ключ Описание
-Start Запуск службы
-Stop Остановка службы
-Setup Установка себя как службы
-Remove Удаление службы

 

(Поддержка состояния paused [приостановлена] не реализована, но ее легко добавить, используя соответствующий ключ для перехода в это состояние.)

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

Табл. 5. Поддерживаемые аргументы для управления

Ключ Описание
-Restart Остановка службы и ее повторный запуск
-Status Отображение текущего состояния службы
-Service Запуск экземпляра службы (только для применения заглушкой service.exe)
-Version Отображение версии службы
Общие параметры –? , –Verbose , –Debug и т. д.

 

Каждый ключ перехода состояния имеет два режима работы.

  • При вызове конечным пользователем С помощью функций управления службами в Windows PowerShell для инициации перехода состояния.
  • При вызове из SCM (косвенно, через заглушку service.exe) Соответствующее управление экземпляром службы service.ps1.

Эти два случая можно различить в период выполнения, проверив имя пользователя: в первом случае это обычный пользователь (системный администратор), а во втором — реальный системный пользователь Windows. Системного пользователя можно идентифицировать примерно так:

$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$userName = $identity.Name   # Ex: "NT AUTHORITY\SYSTEM"
                             # or \"Domain\Administrator\"
$isSystem = ($userName -eq "NT AUTHORITY\SYSTEM")

Установка

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

Вот последовательность операций, выполняемых при обработке ключа –Setup.

  1. Удаление любого предыдущего экземпляра, если таковой есть.
  2. Создание каталога установки, если требуется. (Этого не нужно при использовании каталога по умолчанию: C:\Windows\System32.)
  3. Копирование скрипта службы в каталог установки.
  4. Создание заглушки service.exe в том же каталоге установки на основе фрагмента C#-кода в скрипте.
  5. Регистрация службы.

Заметьте, что, начав с единственного исходного скрипта Windows PowerShell (PSService.ps1), я получил в итоге три файла, установленных в C:\Windows\System32: PSService.ps1, PSService.pdb и PSService.exe. Эти три файла понадобится удалить при удалении службы. Для реализации установки включите две части кода в скрипт.

  • Определение ключа –Setup в блоке Param в начале скрипта:
[Parameter(ParameterSetName='Setup', Mandatory=$true)]
[Switch]$Setup,    # Install the service
  • Блок if (рис. 3) для обработки ключа –Setup в основной процедуре в конце скрипта.

Рис. 3. Обработчик кода установки

if ($Setup) {   # Установка службы
  # Проверяем, нужно ли это (служба не установлена или
  # этот скрипт новее установленной копии).
  [...] # Если нужно и уже установлена, удаляем старую копию.
  # Копируем скрипт службы в каталог установки.
  if ($ScriptFullName -ne $scriptCopy) {
    Copy-Item $ScriptFullName $scriptCopy
  }
  # Генерируем EXE-файл службы из исходного C#-кода,
  # встроенного в этот скрипт
  try {
    Add-Type -TypeDefinition $source -Language CSharp
      -OutputAssembly $exeFullName
      -OutputType ConsoleApplication
      -ReferencedAssemblies "System.ServiceProcess"
  } catch {
    $msg = $_.Exception.Message
    Write-error "Failed to create the $exeFullName
      service stub. $msg"
    exit 1
  }
  # Регистрируем службу
  $pss = New-Service $serviceName $exeFullName
    -DisplayName $serviceDisplayName -StartupType Automatic
  return
}

Запуск

За управление службами отвечает SCM. Каждая операция запуска должна проходить через SCM, чтобы он мог отслеживать состояния служб. Поэтому, даже если пользователь хочет вручную инициировать запуск через скрипт службы, этот запуск должен быть выполнен как запрос к SCM. В этом случае последовательность операция перечислена ниже.

  1. Пользователь (администратор) запускает первый экземпляр: PSService.ps1 –Start.
  2. Этот первый экземпляр сообщает SCM запустить службу: Start-Service $serviceName.
  3. SCM выполняет PSService.exe. Ее процедура Main создает объект службы, а затем вызывает его метод Run.
  4. SCM вызывает метод OnStart объекта службы.
  5. C#-метод OnStart запускает второй экземпляр скрипта: PSService.ps1 –Start.
  6. Этот второй экземпляр, теперь выполняемый в фоне как системный пользователь, запускает третий экземпляр, который останется в памяти как настоящая служба: PSService.ps1 –Service. Этот экземпляр и работает как служба.

В итоге будут выполняться две задачи: PSService.exe и экземпляр PowerShell.exe, выполняющий PSService.ps1 –Service.

За управление службами отвечает SCM.

Все это реализуется тремя частями кода в скрипте.

  • Определение ключа –Start в блоке Param в начале скрипта:
[Parameter(ParameterSetName='Start', Mandatory=$true)]
[Switch]$Start, # Запуск сервиса
  • В процедуре Main в конце скрипта блок if обрабатывает ключ –Start:
if ($Start) {# Запуск службы
  if ($isSystem) { # Если выполняется как SYSTEM
    Start-Process PowerShell.exe -ArgumentList (
      "-c & '$scriptFullName' -Service")
  } else { # Вызвана вручную администратором
    Start-Service $serviceName # Просим SCM запустить ее
  }
  return
}
  • Фрагмент исходного кода на C#, процедура Main и обработчик для OnStart, который выполняет команду PSService.ps1 –Start, как показано на рис. 4.

Рис. 4. Обработчик стартового кода

public static void Main() {
  System.ServiceProcess.ServiceBase.Run(new $serviceName());
}
protected override void OnStart(string [] args) {
  // Запускаем дочерний процесс с другой копией этого скрипта
  try {
    Process p = new Process();
    // Перенаправляем поток вывода дочернего процесса
    p.StartInfo.UseShellExecute = false;
    p.StartInfo.RedirectStandardOutput = true;
    p.StartInfo.FileName = "PowerShell.exe";
    p.StartInfo.Arguments = "-c & '$scriptCopyCname' -Start";
    p.Start();
    // Сначала считываем поток вывода, а затем ждем.
    // (Предлагается во избежание взаимоблокировок.)
    string output = p.StandardOutput.ReadToEnd();
    // Ждем завершения стартового кода скрипта,
    // который запускает экземпляр -Service
    p.WaitForExit();
  } catch (Exception e) {
    // Протоколируем сбой
  }
}

Получение состояния службы

Обработчик –Status просто запрашивает у SCM состояние службы и передает его в конвейер вывода:

try {
  # Не выполняется, если служба не установлена
  $pss = Get-Service $serviceName -ea stop
} catch {
  "Not Installed"
  return
}
$pss.Status

Но на этапе отладки вы можете столкнуться со сбоями скрипта, например из-за синтаксических ошибок в скрипте и т. п. В таких случаях состояние SCM может оказаться в конечном счете некорректным. Я попадал в такую ситуацию несколько раз, когда готовил эту статью. Чтобы помочь в диагностике такого рода проблем, целесообразно все перепроверять и провести поиск экземпляров –Service:

$spid = $null
$processes = @(gwmi Win32_Process –filter
  "Name = 'powershell.exe'" | where {
  $_.CommandLine -match ".*$scriptCopyCname.*-Service"
})
foreach ($process in $processes) { # Обычно процесс один
  $spid = $process.ProcessId
  Write-Verbose "$serviceName Process ID = $spid"
}
if (($pss.Status -eq "Running") -and (!$spid)) {
# Это произошло на этапе отладки
  Write-Error "The Service Control Manager thinks $serviceName
    is started, but $serviceName.ps1 -Service is not running."
  exit 1
}

Остановка и удаление

Операции Stop и Remove, по сути, отменяют то, что было сделано операциями Start и Setup соответственно:

  • –Stop (при запуске пользователем) сообщает SCM остановить службу;
  • при запуске системой экземпляр PSService.ps1 –Service просто уничтожается;
  • –Remove останавливает службу, отменяет ее регистрацию, используя sc.exe delete $serviceName, а затем удаляет файлы в каталоге установки.

Кроме того, их реализация очень похожа на таковую для Setup и Start.

  1. Определение каждого ключа в блоке Param в начале скрипта.
  2. Блок if обрабатывает ключ в процедуре Main в конце скрипта.
  3. Для операции Stop в C#-фрагменте имеется обработчик для OnStop, который запускает PSService.ps1 –Stop. Операция Stop работает по-разному в зависимости от типа пользователя — реального или системного.

Запись событий в журнал

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

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

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

  • Один из методов записывает в журнал Application объекты событий с именем службы в качестве имени источника, как показано на рис. 5. Эти объекты событий видны в Event Viewer, и их можно фильтровать и искать, используя все средства этой утилиты. Вы также можете получить эти записи с помощью командлета Get-Eventlog:
Get-Eventlog -LogName Application -Source PSService |
  select -First 10
  • Другой метод записывает строки сообщений в текстовый файл в каталоге Windows Logs, ${ENV:windir}\Logs\$serviceName.log (рис. 6). Этот файл журнала можно читать в блокноте, и вести по нему поиск с помощью findstr.exe или Win32-портов grep, tail и т. д.

Просмотр событий PSService
Рис. 5. Просмотр событий PSService

Рис. 6. Пример файла журнала

{Для верстки: Этот листинг придется дать с наездом на соседнюю колонку}

PS C:\Temp> type C:\Windows\Logs\PSService.log
2016-01-02 15:29:47 JFLZB\Larvoire C:\SRC\PowerShell\SRC\PSService.ps1 -Status
2016-01-02 15:30:38 JFLZB\Larvoire C:\SRC\PowerShell\SRC\PSService.ps1 -Setup
2016-01-02 15:30:42 JFLZB\Larvoire PSService.ps1 -Status
2016-01-02 15:31:13 JFLZB\Larvoire PSService.ps1 -Start
2016-01-02 15:31:15 NT AUTHORITY\SYSTEM & 'C:\WINDOWS\System32\PSService.ps1' -Start
2016-01-02 15:31:15 NT AUTHORITY\SYSTEM PSService.ps1 -Start: Starting script
  'C:\WINDOWS\System32\PSService.ps1' -Service
2016-01-02 15:31:15 NT AUTHORITY\SYSTEM & 'C:\WINDOWS\System32\PSService.ps1' -Service
2016-01-02 15:31:15 NT AUTHORITY\SYSTEM PSService.ps1 -Service # Beginning background job
2016-01-02 15:31:25 NT AUTHORITY\SYSTEM PSService -Service # Awaken after 10s
2016-01-02 15:31:36 NT AUTHORITY\SYSTEM PSService -Service # Awaken after 10s
2016-01-02 15:31:46 NT AUTHORITY\SYSTEM PSService -Service # Awaken after 10s
2016-01-02 15:31:54 JFLZB\Larvoire PSService.ps1 -Stop
2016-01-02 15:31:55 NT AUTHORITY\SYSTEM & 'C:\WINDOWS\System32\PSService.ps1' -Stop
2016-01-02 15:31:55 NT AUTHORITY\SYSTEM PSService.ps1 -Stop: Stopping script
  PSService.ps1 -Service
2016-01-02 15:31:55 NT AUTHORITY\SYSTEM Stopping PID 34164
2016-01-02 15:32:01 JFLZB\Larvoire PSService.ps1 -Remove
PS C:\Temp>

Функция Log упрощает запись таких сообщений, автоматически предваряя их временными метками по стандарту ISO 8601 и именем текущего пользователя:

Function Log ([String]$string) {
  if (!(Test-Path $logDir)) {
    mkdir $logDir
  }
  "$(Now) $userName $string" |
    out-file -Encoding ASCII -append "$logDir\$serviceName.log"
}

Пример тестового сеанса

Вот как были сгенерированы предыдущие журналы:

PS C:\Temp> C:\SRC\PowerShell\SRC\PSService.ps1 -Status
Not Installed
PS C:\Temp> PSService.ps1 -Status
PSService.ps1 : The term 'PSService.ps1' is not recognized
  as the name of a cmdlet, function, script file,
  or operable program.
[...]
PS C:\Temp> C:\SRC\PowerShell\SRC\PSService.ps1 -Setup
PS C:\Temp> PSService.ps1 -Status
Stopped
PS C:\Temp> PSService.ps1 -Start
PS C:\Temp>

Это показывает, как в принципе использовать службу. Учтите, что эти команды должны выполняться пользователем с правами локального администратора в сеансе Windows PowerShell, работающем под учетной записью Administrator. Заметьте, что скрипт PSService.ps1 сначала отсутствовал в пути, но появился после выполнения операции –Setup. (Первый вызов –Status без пути заканчивается неудачей;, а второй вызов –Status завершается успешно.)

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

Вызов PSService.ps1 –Status на этом этапе даст вывод: Running. А через 30 секунд ожидания вы получите:
PS C:\Temp> PSService.ps1 -Stop
PS C:\Temp> PSService.ps1 -Remove
PS C:\Temp>

Адаптация службы

Чтобы создать собственную службу, просто сделайте следующее.

  • Скопируйте службу-пример в новый файл с новым базовым именем, например C:\Temp\MyService.ps1.
  • Измените длинное название службы в разделе глобальных переменных.
  • Замените блок TO DO в обработчике –Service в конце скрипта. На данный момент блок while ($true) просто содержит макетный код, который пробуждается через каждые 10 секунд и записывает в файл журнала одно сообщение:
####### TO DO: Implement your own service code here. ########
#### Example that wakes up and logs a line every 10 sec: ####
Start-Sleep 10
Log "$script -Service # Awaken after 10s"
  • Установите и приступайте к тестированию:
C:\Temp\MyService.ps1 –Setup
MyService.ps1 –Start
type C:\Windows\Logs\MyService.log

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

Ограничения и проблемы

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

Пример скрипта работает в Windows версий от XP до 10 и в соответствующих серверных версиях. В Windows XP вы должны установить Windows PowerShell v2, который по умолчанию отсутствует. Скачайте и установите Windows Management Framework v2 for XP (bit.ly/­1MpOdpV), включающую Windows PowerShell v2. Заметьте, что я очень мало тестировал в этой ОС, поскольку она больше не поддерживается.

Во многих системах выполнение скриптов Windows PowerShell запрещено по умолчанию. Если вы получаете ошибку наподобие «выполнение скриптов отключено в этой системе» при попытке запустить PSService.ps1, то используйте:

Set-ExecutionPolicy RemoteSigned

Более подробные сведения см. во врезке «Ссылки».

Очевидно, такой скрипт службы, как этот, не может сравниться по производительности с компилированной программой. Скрипт службы, написанный в Windows PowerShell, очень удобен для проверки концепции и для задач с низкими издержками в отношении производительности вроде мониторинга системы, кластеризации служб и т. д. Но для любой высокопроизводительной задачи рекомендуется переписать службу на C++ или C#.

Кроме того, объем занимаемой памяти превышает таковой у скомпилированной программы, так как требует загрузки полнофункционального интерпретатора Windows PowerShell в сеансе System. Но в современных компьютерах со многими гигабайтами памяти это не имеет особого значения.

Этот скрипт не имеет абсолютно никакого отношения к Ps­Service.exe от Марка Руссиновича (Mark Russinovich). Я выбрал имя PSService.ps1 до того, как узнал о совпадении имен. В конечном счете я сохранил имя скрипта-примера таким, поскольку оно делает понятным предназначение скрипта. Конечно, если вы планируете поэкспериментировать со своей службой на основе Windows PowerShell, то должны переименовать его, чтобы получить уникальное имя службы из уникального базового имени скрипта!

Ссылки


Жан-Франсуа Лавуа (Jean-François Larvoire) работает на Hewlett-Packard Enterprise в Гренобле (Франция). В течение более 30 лет занимается разработкой программного обеспечения для BIOS персональных компьютеров, драйверов для Windows и для системного управления Windows и Linux. С ним можно связаться по адресу jf.larvoire@hpe.com.

Выражаю благодарность за рецензирование статьи эксперту JDH IT Solutions Джеффри Хиксу (Jeffery Hicks).


Discuss this article in the MSDN Magazine forum