May 2016

Volume 31 Number 5

Windows PowerShell - PowerShell での Windows サービスの作成

Jean-François Larvoire | May 2016 | サンプル コードのダウンロード

Windows サービスは、通常、C、C++、C# などの Microsoft .NET Framework ベースの言語で記述されたコンパイル済みのプログラムなので、このようなサービスのデバッグはかなり難しくなる可能性があります。数か月前、他の OS ではサービスを簡単なシェル スクリプトとして作成できることにヒントを得て、Windows でも同じように簡単にサービスを作成できる方法はないかと考えました。

今回は、その努力の成果を紹介します。Windows PowerShell のスクリプト言語を使えば、Windows サービスの作成方法が新しく、容易になります。コンパイルは必要なくなります。編集からテストまでのサイクルが短くなり、開発者のシステムだけでなく、任意のシステムで実行できます。

ここでは、PSService.ps1 という汎用サービス スクリプト テンプレートを用意し、メモ帳のようなテキスト エディターを使って、数分で新しい Windows サービスを作成、テストできるようにしています。この手法は、Windows サービスのテストを目的としている開発者にとっては、時間や開発作業の大幅な節約になります。パフォーマンスを重視しなければ、Windows にとっても本物のサービスと変わりありません。PSService.ps1 は、bit.ly/1Y0XRQB からダウンロードできます。

Windows サービスとは

Windows サービスとは、バックグランウンドで実行され、ユーザー操作を伴わないプログラムです。ネットワーク経由で着信する Web ページの HTTP 要求に自動的に応答する Web サーバーや、パフォーマンス測定結果のログ記録やハードウェア センサー イベントの記録を自動的に行う監視アプリケーションなどが Windows サービスの例です。

サービスは、システムの起動時に自動的に開始されます。また、サービスを利用しているアプリケーションからの要求に応じて開始されることもあります。サービスは、UI セッションとは別の独自の Windows セッションで実行されます。サービスは多くのシステム プロセスで実行されるため、適切な権限を注意深く選択し、セキュリティ上のリスクを制限します。

Windows サービス コントロール マネージャー

サービスは、Windows サービス コントロール マネージャー (SCM) によって管理されます。SCM はサービスの構成、開始、停止などを行います。

SCM はコントロール パネルにあり、[コントロール パネル]、[システムとセキュリティ]、[管理ツール]、[サービス] の順にクリックしてアクセスします。SCM では、構成済みのサービスがすべて表示され、その名前、説明、状態、スタートアップの種類、ログオンしたアカウントが示されます (図 1 参照)。

Windows 10 での Windows サービス コントロール マネージャーの GUI
図 1 Windows 10 での Windows サービス コントロール マネージャーの GUI

SCM にはコマンドライン インターフェイスもあります。

  • "net start" コマンドや "net stop" コマンドなど、よく知られた旧形式の net.exe ツールは、MS-DOS の頃から使われています。これらのコマンドは名前から想像されるネットワーク サービスだけでなく、あらゆるサービスの開始と停止が可能です。コマンドの詳細を表示する場合は、「net help」と入力します。
  • sc.exe という強力なツールがあります。これは Windows NT で導入され、サービス管理のすべての側面をきめ細かく制御できます。詳細を表示する場合は、「sc /?」と入力します。

こうしたコマンドライン ツールは Windows 10 でも引き続き利用できますが、これらは非推奨とされ、Windows PowerShell のサービス管理関数が推奨されています。これについては後ほど説明します。

ポイント : net.exe と sc.exe はどちらも短い 1 語のサービス名を使います。これは、コントロール パネルの SCM に表示される説明的な名前とは異なります。この 2 つのサービス名を一致させるには、Windows PowerShell の get-service コマンドを使用します。

サービスの状態

サービスは、いくつか異なる状態に変化します。必須の状態もあれば、オプションの状態もあります。すべてのサービスは 2 つの基本状態として「停止」と「開始」の 2 つをサポートしなければなりません。図 1 の [状態] 列は、「停止」状態の場合は空白になり、「開始」状態の場合は [実行中] と表示されます。

オプションの状態には「一時停止」があります。すべてのサービスがサポートしているにもかかわらず、説明されていない状態が「アンインストール済み」です。

サービスはこれらの状態を遷移します (図 2 参照)。

サービスの状態
図 2 サービスの状態

最後に、サポートするかどうかをサービスが選択できる一時的な状態をいくつか示しておきます。「開始待ち」、「停止待ち」、「一時停止待ち」、「続行待ち」を選択でき、状態遷移に多くの時間を要するときに使用します。

Windows PowerShell のサービス管理関数

Windows PowerShell は Windows Vista での導入以来、推奨のシステム管理シェルになっています。このシェルは、強力なスクリプト言語を備え、OS のすべての側面を管理するための大きな関数ライブラリを有しています。Windows PowerShell は、以下のような点が優れています。

  • 関数名に一貫性がある
  • 完全なオブジェクト指向になっている
  • すべての .NET オブジェクトを容易に管理できる

Windows PowerShell はサービス管理に関する多くの関数を提供しています。こうした関数をコマンドレットと呼びます。図 3 に例をいくつか示します。

図 3 Windows PowerShell のサービス管理関数

関数名 説明
Start-Service 停止状態のサービスを 1 つまたは複数開始する
Stop-Service 実行中状態のサービスを 1 つまたは複数停止する
New-Service 新しいサービスをインストールする
Get-Service ローカル コンピューターまたはリモート コンピューター上のサービスとそのプロパティを取得する
Set-Service サービスを開始、停止、中断し、サービスのプロパティを変更する

 

文字列 "service" を含む名前のコマンドをすべて一覧するには、以下のコマンドレットを実行します。

Get-Command *service*

単にサービス管理関数を一覧するには、以下のコマンドレットを実行します。

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

意外にも、サービスを削除 (アンインストール) する Windows PowerShell 関数はありません。そのため、サービスの削除は旧形式の sc.exe ツールを使わなければならない珍しいケースの 1 つです。

sc.exe delete $serviceName

.NET ServiceBase クラス

すべてのサービスは、ServiceBase クラスから派生する .NET オブジェクトを作成する必要があります。ServiceBase クラスのすべてのプロパティとメソッドは、マイクロソフトのドキュメントに記載されています。図 4 に、今回のプロジェクトで具体的に扱うプロパティとメソッドを一覧します。

図 4 ServiceBase クラスのプロパティとメソッド (一部)

メンバー 説明
ServiceName システムがこのサービスを特定するために使う短い名前
CanStop サービスが開始された後に停止できるかどうか
Onstart() サービスの開始時に実行するアクション
OnStop() サービスの停止時に実行するアクション
Run() サービスの実行可能ファイルを SCM に登録

 

これらのメソッドを実装することで、サービス アプリケーションが SCM によって管理されるようになり、システムの起動時に自動的に開始されたり、要求に応じて開始されるようになります。つまり、コントロール パネル の SCM、従来の net.exe コマンドや sc.exe コマンド、または新しい Windows PowerShell サービス管理関数を使って、このサービスを手動で開始または停止できるようになります。

Windows PowerShell スクリプトに埋め込んだ C# ソースからの実行可能ファイルの作成

PowerShell により、スクリプトで .NET オブジェクトを簡単に扱えるようになります。PowerShell には既定で多くの型の .NET オブジェクトに対する組み込みサポートが含まれているため、ほとんどの目的に十分対応できます。さらに優れているのは拡張可能な点で、C# の短いコート スニペッドを Windows PowerShell スクリプトに埋め込んで、.NET のその他の機能へのサポートを追加できます。これを可能にするのが Add-Type コマンドです。その名前とは裏腹に、新たな型の .NET オブジェクトのサポートを Windows PowerShell に追加するだけではありません。このコマンドは、完全な C# アプリケーションをコンパイルおよびリンクして、新しい実行可能ファイルにすることもできます。たとえば、以下の hello.ps1 という Windows PowerShell スクリプトを考えてみます。

$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 サービス管理関数を使用)
  • サービス自体の開始と停止 (同じサービス管理関数を使用)
  • SCM に対応する PSService.exe を作成する短い C# スニペットの埋め込み (Add-Type コマンドを使用)
  • 実際のサービス操作のため PSService.exe スタブによる PSService.ps1 スクリプトへのコール バック (OnStart、OnStop などのイベントへの応答)
  • コントロール パネルの SCM やあらゆるコマンドライン ツールによる管理を可能に (PSService.exe スタブ)
  • どのような状態でも、すべてのコマンドを正常に処理する回復力 (アンインストール前のサービスの自動停止、起動済みサービスの起動要求の無視など)
  • Windows 7 以降の全バージョンの Windows のサポート (Windows PowerShell V2 の機能のみを使用)

ここでは、PSService.ps1 の設計と実装の中でも重要な部分のみを取り上げます。サンプル スクリプトは、デバッグ コードも含んでいます。いくつかオプション機能もサポートします。ですが、こうした説明をここに盛り込むと、説明が必要以上に複雑になると考えました。

PSService.ps1 のアーキテクチャ: スクリプトは一連のセクションから編成されます。

  • ファイルを説明するヘッダー コメント
  • コメントベースのヘルプ ブロック
  • コマンドライン スイッチを定義する Param ブロック
  • グローバル変数
  • ヘルパー ルーチン: Now と Log
  • PSService.exe スタブの C# ソース ブロック
  • すべてのコマンドライン スイッチを処理するメイン ルーチン

グローバル設定

PSService.ps1 では、Param ブロックの直下にグローバル設定を定義するグローバル変数を含めています。このグローバル設定は、必要に応じて変更できます。図 5 に示しているのは、グローバル設定の既定値です。

図 5 グローバル変数の既定値

変数 説明 既定値
$serviceName net start などのコマンドに使われる 1 語 の名前 スクリプトのベース名
$serviceDisplayName サービスの内容が分かる説明的な名前 A Sample PowerShell Service
$installDir サービス ファイルのインストール先 ${ENV:windir}\System32
$logFile サービスのメッセージをログ記録するファイルの名前 ${ENV:windir}\Logs\­$serviceName.log
$logName サービス イベントの記録先イベント ログの名前 アプリケーション

 

ファイルのベース名 (たとえば、PSService.ps1 の PSService 部分) をサービス名に使うと、スクリプトをコピーし、コピー先の名前を変更してインストールすれば、同じスクリプトから複数のサービスを作成できるようになります。

コマンドラインの引数

使いやすくするため、スクリプトは、すべての状態遷移に対応するコマンド ライン引数をサポートします (図 6 参照)。

図 6 状態遷移とコマンドラインの引数

スイッチ 説明
-Start サービスを開始する
-Stop サービスを停止する
-Setup サービス自体をサービスとしてインストールする
-Remove サービスをアンインストールする

 

(一時停止状態のサポートは実装していませんが、対応する状態遷移オプションを使って簡単に追加できます)。

図 7 に、スクリプトがサポートする管理用の引数をいくつか示します。

図 7 サポートする管理用の引数

スイッチ 説明
-Restart サービスを停止してから再開する
-Status サービスの現在状態を表示する
-Service サービス インスタンスを実行する (service.exe スタブのみが使用)
-Version サービスのバージョンを表示する
-?、-Verbose、-Debug など -? 一般的なパラメーター

 

各状態遷移スイッチには、以下の 2 つの操作モードがあります。

  • エンド ユーザーから呼び出されたとき: Windows PowerShell のサービス管理関数を使って、状態遷移をトリガーする。
  • SCM から呼び出されたとき (service.exe スタブから間接的に): 呼び出しに応じて、service.ps1 サービス インスタンスを管理する。

この 2 つのモードは、実行時にユーザー名をチェックすることで区別します。最初のモードは標準ユーザー (システム管理者)、2 つ目のモードは実際の 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. スクリプトの C# スニペットから、同じインストール ディレクトリに service.exe スタブを作成する。
  5. サービスを登録する。

最初は 1 つの Windows PowerShell ソース スクリプト (PSService.ps1) でしたが、最終的には PSService.ps1、PSService.pdb、PSService.exe の 3 つのファイルを C:\Windows\System32 にインストールすることになりました。この 3 つのファイルは、アンインストール時に削除する必要があります。インストールは、スクリプト内に以下の 2 つのコード部分を含めることにより実装します。

  • スクリプト冒頭にある Param ブロックの -Setup スイッチの定義
[Parameter(ParameterSetName='Setup', Mandatory=$true)]
[Switch]$Setup,    # Install the service
  • スクリプト末尾にあるメイン ルーチンの -Setup スイッチを処理する if ブロック (図 8 参照)

図 8 Setup コード ハンドラー

if ($Setup) {
  # Install the service
  # Check if it's necessary (if not installed,
  # or if this script is newer than the installed copy).
  [...] # If necessary and already installed, uninstall the old copy.
  # Copy the service script into the installation directory.
  if ($ScriptFullName -ne $scriptCopy) {
    Copy-Item $ScriptFullName $scriptCopy
  }
  # Generate the service .EXE from the C# source embedded in this script.
  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
  }
  # Register the service
  $pss = New-Service $serviceName $exeFullName -DisplayName $serviceDisplayName
    -StartupType Automatic
  return
}

開始

サービスを管理するのは SCM の役割です。SCM がサービスの状態を追跡できるように、すべての開始操作は SCM から行う必要があります。そのため、ユーザーがサービス スクリプトを使って手動で開始処理を起動する場合でも、サービスの開始は SCM に要求する必要があります。今回の場合、この操作シーケンスは以下のようになります。

  1. ユーザー (管理者) が最初のインスタンスを実行する (PSService.ps1 -Start)
  2. 最初のインスタンスからサービスの開始を SCM に指示する (Start-Service $serviceName)
  3. SCM が PSService.exe を実行し、そのメイン ルーチンがサービス オブジェクトを作成して Run メソッドを呼び出す
  4. SCM がサービス オブジェクトの OnStart メソッドを呼び出す
  5. C# の OnStart メソッドが 2 つ目のスクリプト インスタンスを実行する (PSService.ps1 -Start)
  6. この 2 つ目のインスタンスがシステムユーザーとして、バックグラウンドで実行され、3 つ目のインスタントを起動して、この 3 つ目のインスタンスが実際のサービスとしてメモリ内に残る (PSService.ps1 -Service)。サービスの実際のタスクを行うのは、この最後の -Service インスタンスで、このインスタンスを自由にカスタマイズします。

最後に、2 つのタスク、PSService.exe と、PSService.ps1 -Service を実行する PowerShell.exe インスタンスが実行されます。

スクリプトに以下の 3 つのコード部分を用意することで、これらすべてを実装します。

  • スクリプト冒頭にある Param ブロックの Start スイッチの定義
[Parameter(ParameterSetName='Start', Mandatory=$true)]
[Switch]$Start, # Start the service
  • スクリプト末尾にあるメイン ルーチンで -Start スイッチを処理する if ブロック
if ($Start) {# Start the service
  if ($isSystem) { # If running as SYSTEM, ie. invoked as a service
    Start-Process PowerShell.exe -ArgumentList (
      "-c & '$scriptFullName' -Service")
  } else { # Invoked manually by the administrator
  Start-Service $serviceName # Ask Service Control Manager to start it
  }
  return
}
  • C# ソースのスニペットのメイン ルーチンと、PSService.ps1 -Start を実行する OnStart メソッドのハンドラー (図 9 参照)

図 9 Start コード ハンドラー

public static void Main() {
  System.ServiceProcess.ServiceBase.Run(new $serviceName());
}
protected override void OnStart(string [] args) {
  // Start a child process with another copy of this script.
  try {
    Process p = new Process();
    // Redirect the output stream of the child process.
    p.StartInfo.UseShellExecute = false;
    p.StartInfo.RedirectStandardOutput = true;
    p.StartInfo.FileName = "PowerShell.exe";
    p.StartInfo.Arguments = "-c & '$scriptCopyCname' -Start";
    p.Start();
    // Read the output stream first and then wait. (Supposed to avoid deadlocks.)
    string output = p.StandardOutput.ReadToEnd();
    // Wait for the completion of the script startup code,     // which launches the -Service instance.
    p.WaitForExit();
  } catch (Exception e) {
    // Log the failure.
  }
}

サービス状態の取得

-Status ハンドラーは単純にサービスの状態を SCM に問い合わせ、結果を出力パイプに送ります。

try {
  $pss = Get-Service $serviceName -ea stop # Will error-out if not installed.
} 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) { # Normally there is only one.
  $spid = $process.ProcessId
  Write-Verbose "$serviceName Process ID = $spid"
}
if (($pss.Status -eq "Running") -and (!$spid)) {
# This happened during the debugging phase.
  Write-Error "The Service Control Manager thinks $serviceName is started,
    but $serviceName.ps1 -Service is not running."
  exit 1
}

停止とアンインストール

Stop と Remove の操作は、基本的にはそれぞれ Setup と Start の操作を取り消す動作です。

  • -Stop は、(ユーザーが呼び出した場合)、サービスを停止するよう SCM に指示します。
  • システムが呼び出した場合は、単純に PSService.ps1-Service インスタンスを強制終了します。
  • -Remove は、サービスを停止し、「sc.exe delete $serviceName」を使って登録を解除し、インストール ディレクトリからファイルを削除します。

実装も Setup と Start によく似ています。

  1. スクリプト冒頭にある Param ブロックでそれぞれのスイッチを定義します。
  2. スクリプト末尾にあるメイン ルーチンにスイッチを処理する if ブロックを用意します。
  3. 停止操作を行う場合、C# ソースのスニペットに「PSService.ps1 -Stop」を実行する OnStop メソッドのハンドラーを用意します。停止操作は、ユーザーが実際のユーザーかシステムかによって行う操作が変わります。

イベント ログ記録

サービスは、UI を伴わずバックグラウンドで実行されます。そのため、サービスのデバッグは困難です。UI に何も表示されないという設計上の制約があるのに、問題を診断する方法はあるのでしょうか。よく使われるのは、すべてのエラー メッセージをタイム スタンプ付きで記録する方法や、正しく行われた重要なイベント (状態遷移など) を記録する方法などです。

サンプル スクリプト PSService.ps1 では、以下の 2 つの異なるログ記録メソッドを実装します。この 2 つを適切な場所で使用します (基本操作を明確にするためここでは削除しましたが、前述のコード例には含まれています)。

  • サービス名をソース名として、イベント オブジェクトを アプリケーション ログに書き込みます (図 10 参照)。書き込まれたイベント オブジェクトは、イベント ビューアーに表示されます。イベント ビューアーではイベント オブジェクトのフィルター処理や検索も可能です。Get-Eventlog コマンドレットを使ってイベントのエントリを取得することもできます。

イベント ビューアーに表示された PSService のイベント
図 10 イベント ビューアーに表示された PSService のイベント

Get-Eventlog -LogName Application -Source PSService | select -First 10
  • Windows のログ ディレクトリのテキスト ファイル (${ENV:windir}\Logs\$serviceName.log) にメッセージ行を書き込みます (図 11 参照)。このログ ファイルはメモ帳で読むことができ、findstr.exe (Win32 の grep 機能) や tail などを使って検索することができます。

図 11 .サンプル ログ ファイル

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 セッションで、ローカル管理者権限を持つユーザーが実行する必要があります。これを見ると、PSService.ps1 スクリプトは最初うまく動作せず、-Setup 操作の後に動作していることがわかります (最初の -Status 呼び出しは失敗したことを示して終了しています。2 回目の -Status 呼び出しは成功しています)。

Calling PSService.ps1 -Status at this stage would produce this output: Running. And this, after waiting 30 seconds:
PS C:\Temp> PSService.ps1 -Stop
PS C:\Temp> PSService.ps1 -Remove
PS C:\Temp>

サービスのカスタマイズ

独自のサービスを作成するには、以下を行うだけです。

  • サンプル サービスに「C:\Temp\MyService.ps1」などの新しいベース名をつけて新しいファイルにコピーします。
  • グローバル変数セクションにある長いサービス名を変更します。
  • スクリプト末尾の -Service ハンドラーの TO DO ブロックを変更します。現在、while ($true) ブロックには、10 秒ごとに起動し、ログファイルにメッセージを 1 つ記録するダミーのコードが含まれています。
######### 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

Paused 状態のような新しい SCM 機能のサポートを追加する場合を除いて、スクリプトの他の部分を変更する必要はありません。

制限事項と問題点

今回のサービス スクリプトは管理者権限で実行されているシェル内で実行する必要があります。それ以外の場合は、さまざまなアクセス拒否エラーが発生します。

今回のサンプル スクリプトは、Windows XP から Windows 10 までのバージョンと、対応するサーバー バージョンで機能します。Windows XP では、Windows PowerShell V2 をインストールする必要があります。このバージョンは、既定では使用できません。XP 用の Windows Management Framework V2 (https://support.microsoft.com/ja-jp/kb/968929) をダウンロードして、インストールします。このバージョンに Windows PowerShell v2 が含まれています。XP のサポートは終了しているため、今回 XP ではテストを行っていません。

多くのシステムでは、Windows PowerShell スクリプトの実行が既定で無効になっています。PSService.ps1 を実行しようとして、「スクリプトの実行がシステムで無効になっているため、」のようなエラーが表示される場合は、以下を実行します。

Set-ExecutionPolicy RemoteSigned

詳細については、「参考資料」を参照してください。

当然、今回のようなサービス スクリプトは、コンパイル済みプログラムのようなパフォーマンスは望めません。Windows PowerShell で作成するサービス スクリプトは、概念のプロトタイピングや、システム監視、サービス クラスタリングなどのパフォーマンスのコストが低い事例に適しています。しかし、高いパフォーマンスが求められるタスクでは、C++ や C# で書き直すことをお勧めします。

フル装備の Windows PowerShell インタープリターを System セッションに読み込む必要があるため、メモリのフットプリントもコンパイル済みプログラムに比べて大きくなります。ただし、最近のシステムには数 GB の RAM が搭載されているため、それほど大きな問題にはなりません。

今回のスクリプトは、Mark Russinovich の Ps­Service.exe とはまったく無関係です。PSService.ps1 という名前を選んだ後で、名前がほぼ同じであることに気づきました。この名前が今回のサンプル スクリプトの目的をよく表していると考えた結果です。もちろん、独自の Windows PowerShell サービスをテストする場合は、名前を変えて、一意のスクリプト ベース名から一意のサービス名を取得してください。

参考資料


Jean-François Larvoire は、フランス、グルノーブルの Hewlett-Packard Enterprise に勤務しています。彼は 30 年にわたり、PC BIOS、Windows ドライバー、Windows と Linux のシステム管理用のソフトウェア開発を行ってきました。彼の連絡先は、jf.larvoire@hpe.comです。

この記事のレビューに協力してくれた技術スタッフの Jeffery Hicks (JDH IT Solutions) に心より感謝いたします。