Windowsサービスの作り方講座 その1

早速作り始めてみる。まずはVisual Studioを立ち上げて、新規プロジェクトを選ぶ。まあ、ここまではスムーズにできる人が対象ということで、あんまり細かすぎる事は説明しない!

ここでWindowsサービスを選んで、名前には、まあ、とりあえず「MyService」とかつけます。これは適宜適切な名前をつければOK。

で、この窓が出てくる。この時点でService1.vbの中身はこんな。

Public Class Service1

    Protected Overrides Sub OnStart(ByVal args() As String)
        ' サービスを開始するコードをここに追加します。このメソッドによって、
        ' サービスが正しく実行されるようになります
    End Sub

    Protected Overrides Sub OnStop()
        ' サービスを停止するのに必要な終了処理を実行するコードをここに追加します。
    End Sub

End Class

Mainサブルーチンはないです。なんとなくWindows Formアプリケーションのイベントハンドラに似ているけど、まさにこれはイベントハンドラ。具体的にはサービスコントロールパネルのボタンに対応してる。

これの「開始」がOnStartに、「停止」がOnStopに対応。argsは開始パラメータに対応。このプロジェクトをこれから説明する手順に従ってビルド、インストール、開始ボタンを押せば、OnStartに書いたコードが実行されます。コマンドラインのnet start、net stopでも同じです。

で、最初につまづくポイントはここから。この状態でVisual Stuidoのビルドは通るんだけど、F5では動かない。

(´・ω・`)
で、インストールすりゃいいんだろ! ということでインストールを試みるとこんな事になる。

D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MyService\bin\Deb
ug>installutil MyService.exe
Microsoft(R) .NET Framework Installation utility Version 2.0.50727.4927
Copyright(C) Microsoft Corporation.  All rights reserved.


トランザクションのインストールを実行中です。

インストール段階を開始しています。
D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MyService\bin\Deb
ug\MyService.exe アセンブリの進行状態については、ログ ファイルの内容を参照してく
ださい。
ファイルは D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MyServ
ice\bin\Debug\MyService.InstallLog にあります。
アセンブリ 'D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MySer
vice\bin\Debug\MyService.exe' をインストールしています。
該当するパラメータ:
   logtoconsole =
   assemblypath = D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService
\MyService\bin\Debug\MyService.exe
   logfile = D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MySe
rvice\bin\Debug\MyService.InstallLog
RunInstallerAttribute.Yes を含むパブリック インストーラが D:\Users\rinta\Documen
ts\Visual Studio 2008\Projects\MyService\MyService\bin\Debug\MyService.exe アセ
ンブリで見つかりませんでした。

インストール段階が正常に完了しました。コミット段階を開始しています。
D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MyService\bin\Deb
ug\MyService.exe アセンブリの進行状態については、ログ ファイルの内容を参照してく
ださい。
ファイルは D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MyServ
ice\bin\Debug\MyService.InstallLog にあります。
アセンブリ 'D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MySer
vice\bin\Debug\MyService.exe' をコミットしています。
該当するパラメータ:
   logtoconsole =
   assemblypath = D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService
\MyService\bin\Debug\MyService.exe
   logfile = D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MySe
rvice\bin\Debug\MyService.InstallLog
RunInstallerAttribute.Yes を含むパブリック インストーラが D:\Users\rinta\Documen
ts\Visual Studio 2008\Projects\MyService\MyService\bin\Debug\MyService.exe アセ
ンブリで見つかりませんでした。
インストーラが存在しないため、InstallState ファイルを削除します。

コミット段階が正常に終了しました。

トランザクション インストールが完了しました。

D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MyService\bin\Deb
ug>

何を言っているのかほとんど理解不能なんだけど、一言で言うと「だめだよ!」。出てくる文字列が長すぎて大事なことがどこに書いてあるのかとてもとてもとてもわかりにくいのが本当に困るんだけど、実はちゃんと書いてある。前半の方のここに注目しないといけない。

RunInstallerAttribute.Yes を含むパブリック インストーラが D:\Users\rinta\Documen
ts\Visual Studio 2008\Projects\MyService\MyService\bin\Debug\MyService.exe アセ
ンブリで見つかりませんでした。

Windowsサービスをサービスとして動かすにはInstallUtil.exeを使ってインストールをするんだけど、それだけだと足りないわけです。まだVisual Studioで作りこまないといけない。そんな不完全なものをプロジェクトテンプレートで生成するということが、なんとも不親切だなーと思うんだけど、多分インストーラを作るところを決め打ちにすると困る事情がMSにはあるんでしょう。じゃなかったら、ちょっと顧客サービスがなってねーんじゃねーの?という気がしますw

ということで、Visual Studioでまだ作業をやります。ソリューションエクスプローラでService1.vbを選んで、「デザイナを表示」を選ぶ。最初にプロジェクトテンプレートで表示されたのと同じ画面が出てくる。この窓の背景がグレーで「クラスにコンポーネントを…」とか書いてある部分で右クリックすると、「インストーラの追加」なんてのがあるのでそれを選ぶ。

で、こうなる。

ホントね、この作業必須なんだから、プロジェクトテンプレート選んだ時に生成しろよと言いたい。Visual Studio 2010はまだ試していないけど、改善されたのかな… ま、改善されたことを祈ろう。
で、これでビルド通るので、改めてInstallUtil.exeを実行! すると。

こんな窓が! 明らかにさっきとは違う。なんとなくうまく行きそうな予感! で、この窓は何かというと、サービスをどんなユーザアカウントで動かすのかと訊いてきている。とりあえず、暫定的に、あくまでも暫定的に今ログオンしているユーザ名パスを入れてここを通す。ここのところは設計の時点でセキュリティ設計どうするか考えて、適切なアカウントを割り当てるのが筋です。が、今回のエントリはセキュリティ設計がテーマではないので、そこんところはぶっ飛ばして次へ。

D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MyService\bin\Deb
ug>installutil.exe MyService.exe
Microsoft(R) .NET Framework Installation utility Version 2.0.50727.4927
Copyright(C) Microsoft Corporation.  All rights reserved.


トランザクションのインストールを実行中です。

インストール段階を開始しています。
D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MyService\bin\Deb
ug\MyService.exe アセンブリの進行状態については、ログ ファイルの内容を参照してく
ださい。
ファイルは D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MyServ
ice\bin\Debug\MyService.InstallLog にあります。
アセンブリ 'D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MySer
vice\bin\Debug\MyService.exe' をインストールしています。
該当するパラメータ:
   logtoconsole =
   assemblypath = D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService
\MyService\bin\Debug\MyService.exe
   logfile = D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MySe
rvice\bin\Debug\MyService.InstallLog

インストール段階で例外が発生しました。
System.ComponentModel.Win32Exception: アクセスが拒否されました。

インストールのロールバックを開始しています。
D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MyService\bin\Deb
ug\MyService.exe アセンブリの進行状態については、ログ ファイルの内容を参照してく
ださい。
ファイルは D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MyServ
ice\bin\Debug\MyService.InstallLog にあります。
アセンブリ 'D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MySer
vice\bin\Debug\MyService.exe' をロール バックしています。
該当するパラメータ:
   logtoconsole =
   assemblypath = D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService
\MyService\bin\Debug\MyService.exe
   logfile = D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MySe
rvice\bin\Debug\MyService.InstallLog
System.ServiceProcess.ServiceProcessInstaller インストーラロールバック段階で例
外が発生しました。
System.NullReferenceException: オブジェクト参照がオブジェクト インスタンスに設定
されていません。
インストールのロールバック段階で例外が発生しました。この例外は無視され、ロールバ
ックは続行します。ただし、ロールバック完了後、コンピュータは完全に元の状態に戻ら
ない可能性があります。

ロールバックの段階が正常に完了しました。

トランザクション インストールが完了しました。
インストールが失敗し、ロールバックが実行されました。

えーと、一口に言うと「だめだよ!」
orz
またしても長々とログを吐いてくれましたが、大事なのはここ。

インストール段階で例外が発生しました。
System.ComponentModel.Win32Exception: アクセスが拒否されました。

よーするに権限がたらんと。こんな時は「管理者として実行」だ!

D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MyService\bin\Deb
ug>installutil.exe MyService.exe
Microsoft(R) .NET Framework Installation utility Version 2.0.50727.4927
Copyright(C) Microsoft Corporation.  All rights reserved.


トランザクションのインストールを実行中です。

インストール段階を開始しています。
D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MyService\bin\Deb
ug\MyService.exe アセンブリの進行状態については、ログ ファイルの内容を参照してく
ださい。
ファイルは D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MyServ
ice\bin\Debug\MyService.InstallLog にあります。
アセンブリ 'D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MySer
vice\bin\Debug\MyService.exe' をインストールしています。
該当するパラメータ:
   logtoconsole =
   assemblypath = D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService
\MyService\bin\Debug\MyService.exe
   logfile = D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MySe
rvice\bin\Debug\MyService.InstallLog
サービス 'Service1' をインストールしています...
EventLog ソース Service1 をログ Application に作成しています...

インストール段階で例外が発生しました。
System.ComponentModel.Win32Exception: アカウント名が無効であるか、または存在しま
せん。あるいは、指定したアカウント名のパスワードが無効です。

インストールのロールバックを開始しています。
D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MyService\bin\Deb
ug\MyService.exe アセンブリの進行状態については、ログ ファイルの内容を参照してく
ださい。
ファイルは D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MyServ
ice\bin\Debug\MyService.InstallLog にあります。
アセンブリ 'D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MySer
vice\bin\Debug\MyService.exe' をロール バックしています。
該当するパラメータ:
   logtoconsole =
   assemblypath = D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService
\MyService\bin\Debug\MyService.exe
   logfile = D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MySe
rvice\bin\Debug\MyService.InstallLog
ソース Service1 の前の状態にイベント ログを復元しています。

ロールバックの段階が正常に完了しました。

トランザクション インストールが完了しました。
インストールが失敗し、ロールバックが実行されました。

orz
でもまあ、権限不足はなんとかなった。大事なところはここ。

インストール段階で例外が発生しました。
System.ComponentModel.Win32Exception: アカウント名が無効であるか、または存在しま
せん。あるいは、指定したアカウント名のパスワードが無効です。

これはちょっと一瞬まじで罠にはまったと思ったんだけど、ちっちゃいダイアログでアカウントを尋ねられた時に、コンピュータ名¥アカウント名というコンピュータ名で修飾した型にしてやらないとダメなようです*1。で、正解したときのInstallUtil.exeの出力はこんな。

D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MyService\bin\Deb
ug>installutil.exe MyService.exe
Microsoft(R) .NET Framework Installation utility Version 2.0.50727.4927
Copyright(C) Microsoft Corporation.  All rights reserved.


トランザクションのインストールを実行中です。

インストール段階を開始しています。
D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MyService\bin\Deb
ug\MyService.exe アセンブリの進行状態については、ログ ファイルの内容を参照してく
ださい。
ファイルは D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MyServ
ice\bin\Debug\MyService.InstallLog にあります。
アセンブリ 'D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MySer
vice\bin\Debug\MyService.exe' をインストールしています。
該当するパラメータ:
   logtoconsole =
   assemblypath = D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService
\MyService\bin\Debug\MyService.exe
   logfile = D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MySe
rvice\bin\Debug\MyService.InstallLog
サービス 'Service1' をインストールしています...
サービス 'Service1' は正常にインストールされました。
EventLog ソース Service1 をログ Application に作成しています...

インストール段階が正常に完了しました。コミット段階を開始しています。
D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MyService\bin\Deb
ug\MyService.exe アセンブリの進行状態については、ログ ファイルの内容を参照してく
ださい。
ファイルは D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MyServ
ice\bin\Debug\MyService.InstallLog にあります。
アセンブリ 'D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MySer
vice\bin\Debug\MyService.exe' をコミットしています。
該当するパラメータ:
   logtoconsole =
   assemblypath = D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService
\MyService\bin\Debug\MyService.exe
   logfile = D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MySe
rvice\bin\Debug\MyService.InstallLog

コミット段階が正常に終了しました。

トランザクション インストールが完了しました。

よし! ゲット! 俺様大勝利! で、きちんと導入できたかサービスコントロールパネルから確認。

サービス名がService1になってたよ(´・ω・`) でもまあ、入った事は入ったわけだ。ということで実行! もちろん「開始」ボタンを押す! GO!

ふーん… と、これだけだとなんだか本当にわからないのでタスクマネージャで確認。

よし、一応動いているようだ。というか、Visual Studioが生成したコードだけなんだから、これで止まられると困る。ま、これで「ひとまず動く」までは達成。わーい! でも後始末をきちんとしないと後で困るのでここから後始末をする。

まず、サービスを止める。サービスコントロールパネルから「停止」ボタンをクリック。

止まった、らしい。一応タスクマネージャでも確認。

PIDが割り当てられていないので、間違いなく停止している。よし。次はサービスをアンインストールする。

で、ここでなんでアンインストールがいるのかなんだけども、これからこのプロジェクトを修正してサービス名を変えるわけです。そうなると、インストーラも変わります。サービスのアンインストールもInstallUtil.exeを使うんだけども、インストールの時と同様に、サービスのロードモジュールの中に出来上がっているインストーラも使うわけです。この時サービス名が変わってると、アンインストールができなくなる。実際にやってみると…

D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MyService\bin\Deb
ug>installutil.exe /u MyService.exe
Microsoft(R) .NET Framework Installation utility Version 2.0.50727.4927
Copyright(C) Microsoft Corporation.  All rights reserved.



アンインストールを開始します。
D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MyService\bin\Deb
ug\MyService.exe アセンブリの進行状態については、ログ ファイルの内容を参照してく
ださい。
ファイルは D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MyServ
ice\bin\Debug\MyService.InstallLog にあります。
アセンブリ 'D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MySer
vice\bin\Debug\MyService.exe' をアンインストールしています。
該当するパラメータ:
   logtoconsole =
   assemblypath = D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService
\MyService\bin\Debug\MyService.exe
   logfile = D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MySe
rvice\bin\Debug\MyService.InstallLog
EventLog ソース MyService を削除しています。
警告: ソース MyService はローカル コンピュータで登録されていません。
サービス MyService をシステムから削除しています...
System.ServiceProcess.ServiceInstaller インストーラのアンインストール中に例外が
発生しました。
System.ComponentModel.Win32Exception: 指定されたサービスはインストールされたサー
ビスとして存在しません。
アンインストール中に例外が発生しました。この例外は無視され、アンインストール続行
します。ただし、アンインストール完了後、アプリケーションは完全にアンインストール
されない可能性があります。

アンインストールか完了しました。
アンインストール中に例外が発生しました。この例外は無視され、アンインストール続行
します。ただし、アンインストール完了後、アプリケーションは完全にアンインストール
されない可能性があります。

こうなってしまうと、同じサービス名のロードモジュールがないと、もうアンインストールできない。名前だけ同じで、中身が違ってもOKなんだろうけど、やはり気持ちが悪い。サービス名やサービスの属性になるような所を変えるときは、先に今の段階のモノをアンインストールしないとダメだ。で、正解はこちら。

D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MyService\bin\Deb
ug>installutil.exe /u MyService.exe
Microsoft(R) .NET Framework Installation utility Version 2.0.50727.4927
Copyright(C) Microsoft Corporation.  All rights reserved.



アンインストールを開始します。
D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MyService\bin\Deb
ug\MyService.exe アセンブリの進行状態については、ログ ファイルの内容を参照してく
ださい。
ファイルは D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MyServ
ice\bin\Debug\MyService.InstallLog にあります。
アセンブリ 'D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MySer
vice\bin\Debug\MyService.exe' をアンインストールしています。
該当するパラメータ:
   logtoconsole =
   assemblypath = D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService
\MyService\bin\Debug\MyService.exe
   logfile = D:\Users\rinta\Documents\Visual Studio 2008\Projects\MyService\MySe
rvice\bin\Debug\MyService.InstallLog
EventLog ソース Service1 を削除しています。
サービス Service1 をシステムから削除しています...
サービス 'Service1' は正常にシステムから削除されました。

アンインストールか完了しました。

よーしOK! コントロールパネルからも消えている事を確認。

うむ。きれいに消えた。これでよし。

ということで、ひとまずサービスとして動かすというところまで終了。残りは明日にしようかな。気分が乗れば夜中にUPするかも。
あと、ここまでの作業でできたVisual StudioのソリューションをUPしておく。というか、有料オプションのこの機能を試したかっただけなんだw さて、うまくUPできるかな? とりあえず、送信。
MyService.zip 直

*1:コンピュータ名¥の代わりに.\でもいいようです。