Jaa


[Blog翻訳] トリガー起動サービス (TSS) のサンプル コード

みなさん、こんにちは。Windows 開発統括部の古内です。

さて、今日は 2011 年 3 月 24 日に Developing for Windows Blog に投稿された 「Trigger-Start Services Recipe」 の翻訳をお届けします。サービスを必要なときにだけ実行するようにし、Windows を “軽く” するノウハウについて説明されています。ぜひご自身のアプリケーション開発のヒントになさってください。


トリガー起動サービスのサンプル コード

Windows サービスを利用すれば、ユーザーがログオンしていなくても、バックグラウンドでコードを実行できます。 このバックグラウンド サービスでは、ユーザーのデスクトップ環境への干渉が最小限に抑えられるだけでなく、役に立つ機能が数多く提供されています。

Windows サービスは長年利用され続けている、実績のある有益なツールです。 現在では、アプリケーション開発者、PC メーカー、IT 企業といった多くの方々に利用されています。 一般的なシステムにおいては、多数のサービスが同時に実行されることがあります。たとえば今この瞬間も、私のコンピューターでは約 110 個のサービスが実行されています。これほど数が多いのは、さまざまなベータ版製品、異なるユーザー、複数のデスクトップが存在し、すべてのサービスが一斉に実行されているからです。

サービスをこれだけ多く実行すると、Windows の起動時間が長くなったり、コンピューターへの攻撃が増えるほか、メモリや CPU への負荷や消費電力が増える可能性があります。開発者であれば、こうした大きな負荷にも対応できるコンピューターをお持ちだと思います。実際、私のコンピューターも 8 GB の RAM に 4 コアの CPU と、十分なスペックなので問題ありません。しかし、個人ユーザーにとっては大きな問題となり得ます。

皆さんが Windows サービスを開発する際には、処理ができるだけ効率的になるように心がけてください。サービスを実行するのは、その必要があるときだけにするようにします。たとえば、必要に応じてアプリケーションから開始/停止できるオンデマンド サービスを開発したり、サービスを遅延自動開始サービスとして構成することが考えられます。これらを実現する機能については、Kenny Kerr 氏が 「Windows サービスの機能強化」 の中で詳しく説明しています。

ところが Windows 7 と Windows Server 2008 R2 については、必要なときにだけサービスを実行できるようにする新しいメカニズムが備えられています。それが、トリガー起動サービス (Trigger-Started Service, TSS) です。基本的に、TSS は一定の条件下でオペレーティング システムにサービスの開始を通知します。たとえば、特定のデバイスまたはデバイスの種類がコンピューターに接続されたとき、コンピューターがドメインに追加されたとき、あるいはドメインから削除されたとき、などの条件です。その他の条件の詳細については、「Windows 7 におけるサービスの新機能 (英語)」を参照してください。

問題点

上記のようなサービスを開発するにあたっては、参考となるドキュメント (英語) が既にあるほか、C++ なら簡単に作成できます。しかしマネージ コードで開発するとなると、いくつかサンプルやコード スニペットはありますが、一般的な利用には適さず、再利用しやすいソリューションを提供するものでもありません。ほとんどの場合、大量の相互運用コードを記述したり、修正したりする必要が出てきます。

Windows 7 トリガー起動サービスのサンプル コード (英語)」では、マネージ コード開発者向けに、TSS のコードの簡単な記述方法を紹介しています。

このサンプル コードには Win7TriggerStartRecipe のアセンブリが含まれています。このアセンブリは単純な .NET インターフェイスを提供し、それを介して Windows サービス コントロール マネージャー (SCM) とやりとりをし、サービスを TSS として設定します。 SCM は大きなツールであり、Win32 API やコマンド ライン インターフェイスを提供し、Windows コントロール パネル内のいくつかの機能を実装しています。その SCM の API を使って、サービスを設定し TSS を利用します。

通常の設定では、サービスはインストールされるとすぐに TSS として機能します。.NET で作成したサービスをインストールするには、.NET フレームワークにバンドルされている installutil.exe を使うことが推奨されています。したがって、そのサービスには Installer クラスを実装する必要があります。また、トリガー起動アセンブリを使用するのは、ServiceInstaller クラスの AfterInstall イベント ハンドラー内が最適でしょう。ただし、どのアプリケーションで使用するにしても、サービスをインストールするには、管理者権限が必要です。

このサンプル コードを使うには、まず初めにプロジェクトに Win7TriggerStartRecipe アセンブリへの参照を追加し、次に以下の Using ステートメントを追加してください。

using WindowsRecipes.TriggerStartServices;

メインの処理を記述する前に、お使いのオペレーティング システムがトリガー起動サービスをサポートしているか再確認してください。それには、TriggerStart クラスの IsSupported 静的メソッドを呼び出します。現在この機能は、Windows 7 とWindows Server 2008 R2 でのみサポートされています。

次に、TriggerStart クラスのインスタンスを作成します。3 つのコンストラクターのうちの 1 つを使用して、特定のサービスの実装への参照を渡します。これにより、そのサービスは TriggerStart マネージャーにリンク (バインド) されます。

その後、必要に応じてトリガー コレクションに対してトリガーの追加や削除を行い、CommitChangesToService を呼び出して、変更を SCM に登録します。以下のコードで登録するサービスは、汎用 USB デバイスがコンピューターに接続されたときに実行されます。

  1: try
  2: {
  3:     if (!TriggerStart.IsSupported)
  4:     {
  5:         Console.WriteLine("The current system does not support trigger-start services.");
  6:         return;
  7:     }
  8:     //サービスの登録には以下のコードがすべて必要
  9:     //エラーのチェックやログの出力は省略
  10:     TriggerStart ts = new TriggerStart(this.USBServiceInstaller.ServiceName);
  11:  
  12:     //USB デバイスが接続されたとき SCM にサービスの開始を通知するトリガーを追加
  13:     Guid Guid_USBDevice = new Guid("53f56307-b6bf-11d0-94f2-00a0c91efb8b");
  14:     const string DeviceName = @"USBSTOR\GenDisk";
  15:  
  16:     ts.Triggers.Add(
  17:                 new DeviceInterfaceArrivalTrigger(
  18:                         ServiceTriggerAction.ServiceStart,
  19:                         Guid_USBDevice,DeviceName));
  20:     ts.Triggers.Add(
  21:                 new DeviceInterfaceArrivalTrigger(
  22:                         ServiceTriggerAction.ServiceStop, 
  23:                         Guid_USBDevice, 
  24:                         DeviceName));
  25:                                 
  26:     //変更を確定 (ここで SCM にトリガーが登録される)
  27:     ts.CommitChangesToService();
  28:  
  29:     Console.WriteLine("Trigger registration successful. 
  30:                        To manually confirm this, type 'sc qtriggerinfo " 
  31:                        + this.USBServiceInstaller.ServiceName + "'.");
  32: }
  33: catch (Exception ex)
  34: {
  35:     Console.WriteLine("Registering the service for trigger starting failed:");
  36:     Console.WriteLine(ex.Message);
  37: }

上記のコードからわかるように、TSS がサポートされているかどうかを確認した後、TriggerStart クラスの新しいインスタンスを作成し、this.USBServiceInstaller.ServiceName というサービスの実装を渡します。

次に、USB デバイスに関連付けられた GUID と DeviceName を定義します。そして、特定のトリガーを追加し、引数としてトリガー アクションを渡します。たとえば、コンピューターの USB ポート に汎用 USB ストレージ デバイスが接続されると、DeviceInterfaceArrivalTrigger トリガーが追加され、 ServiceTriggerAction.ServiceStart トリガー アクションが渡されサービスが起動します。特定のデバイスやデバイスの種類がシステムに接続されたとき、あるいは既にデバイスが接続されている状態でシステムが起動すると、DeviceInterfaceArrivalTrigger が起動します。ここでは、DeviceInterface クラスの GUID を指定し、目的のデバイス クラスを特定する必要があります。さらに、監視したいデバイスを特定するために、少なくとも 1 つの Hardware ID あるいは Compatible (互換性) ID を指定します。

次に定義するトリガーは、USB キーが抜かれたときにサービスを停止するというものです。USB が接続されていないときにサービスを実行し続ける必要はありません。再び汎用 USB ストレージ デバイスが接続されれば、サービスはまた開始されればよいのです。

最後に、CommitChangesToService メソッドを呼び出して、サービスの設定に対する変更を確定します。

記述するのはこれだけです。一度サービスに関連付けられたトリガーは、トリガー コレクションに格納されます。必要に応じてこれを編集し、変更を確定してください。

DeviceInterfaceArrivalTrigger は、数多くあるトリガーの一例にすぎません。このサンプル コードに実装されているトリガーは以下のとおりです。

  • DomainEventTrigger: コンピューターがドメインに追加されたとき、またはドメインから除外されたときに起動します。操作は、コンストラクターか、イベント タイプ メンバーからトリガー アクションを指定するだけです。
  • FirewallEventTrigger: ファイアウォール上で指定されたポートのうちの 1 つが開いたとき、または閉じたときに起動します。まず、Ports オブジェクトのメンバーを使用して、監視したいポート (ファイアウォール ポート) をまとめて指定します。次に、トリガーを起動するのはそのポートが開いたときか閉じたときかをイベント タイプ メンバーで指定します。FirewallPort 構造体には省略可能な ServiceName メンバー と ServicePath メンバーがあり、イベント発生時にリッスンしたい場合は、そのサービス名を指定できます。
  • GroupPolicyEventTrigger: これは単純なトリガーで、グループ ポリシーが変更されたときに起動します。ユーザー ポリシーまたはコンピューター ポリシーを監視するかしないかをイベント タイプ メンバーで指定するだけです。
  • IPAddressAvailabilityTrigger: コンピューターが初めてネットワークに接続されたとき、または非接続になったときに起動します。トリガーを起動するタイミングをイベント タイプ メンバーで指定します。
  • CustomEventTrigger: このトリガーは、主にこのタイプの既存のトリガーを組み込んだサービスを変更するアセンブリのために使用します。このトリガーを使ってユーザー独自のカスタム トリガーを追加することもできますが、予期されるデータは、特定の Events for Windows (EFW) プロバイダーが提供する不明のアンマネージ データです。したがって、ユーザー自身でそのデータをマーシャルする必要があります。ただしマーシャルしやすいデータなので、カスタム トリガーの追加に関する役立つ情報が見つかるはずです。

このサンプル コードの動作の詳細については、コードとドキュメント (英語) をダウンロードして確認してください。