[C#] C#でサービスを作る

# サービス関連記事

https://tera1707.com/entry/2022/02/06/144447#WindowsService

# やりたいこと

仕事でWindowsのサービスを扱った際、ログイン/ログアウトしたときに〇〇をする、みたいなことを書いたのだが、なにか、再起動したときのログインの動きが直感的ではないというか、妙な動きをしているように見えた。(まだログイン画面でログイン押してないのにサービス的にはログインしたことになって動き始めてる、とか。)

その辺を検証したかったのだが、Windowsの**「サービス」**というものを作ったことがなかったので、自宅でちゃちゃっと実験できる用意がなかった。

上の実験をするために、ついでに＆急がば回れで、まずはC#でWindowsサービスをつくれるようになっておきたい。


# やったこと

MSの公式と下記のページをかなり参考にさせていただき、C#でサービスを作った。

■C#でWindows Serviceを作る 1～5
https://qiita.com/yossihard/items/46f72af754915b242bed

■Tutorial: Create a Windows service app
https://docs.microsoft.com/ja-jp/dotnet/framework/windows-services/walkthrough-creating-a-windows-service-application-in-the-component-designer


# 手順

### ■プロジェクトの作成

まずプロジェクトを作る。
「新しいプロジェクトの作成」画面で、**「Windowsサービス(.NET Framework)」**を選択する。
(今回は.NETで作成。)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/0cec4e9e-7b5f-ca8a-bbd1-858d9b18369b.png)

プロジェクトを開くと、下図のように、サービスの**[デザイン]**が開いた状態になっている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/32147632-a269-9793-c563-b8833fdaa791.png)

### ■インストーラーの追加

このデザインの画面で、何もないところを右クリック > [インストーラーの追加] を選択する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/71bdb5cf-1f20-feb0-4efc-41f239d42481.png)

ProjectInstaller.csが追加される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/99452744-7477-49d5-4076-f53943c3615c.png)

ProjectInstaller.csの**[デザイン]**が開いた状態になる。
SericeProcessInstallerとServiceInstallerがもとから入っている

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/7c3e4185-1231-beec-3a58-36ac57a49835.png)

ここに、サービスの基本設定をしていく。

### ■サービスの基本設定をする

**SericeProcessInstallerの設定**をする。
デザイナー上の「SericeProcessInstaller」を右クリ→プロパティ

Accountを設定する。
とりあえず、今回はサービスの作成をしたいだけで、管理者権限が必要になるようなことはしないので、**User**にしておく。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/61ba37aa-6874-12f0-af05-37a78ba1798c.png)


次に、**ServiceInstallerの設定**をする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/38da1402-e850-a61d-b96e-a366afeb3ac7.png)

- DelayedAutoStart：他の自動的に開始されるサービスが実行されるまで、サービスの開始を遅延するかどうか
- Description：サービスの説明
- DisplayName：サービス管理ツール等での表示用の名前
- ServiceName：サービス識別名
- StartType：サービスの開始タイミングを指定

※それぞれ、タスクマネージャとかサービス管理画面でみると、こういう関係。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/d816a992-76ae-595b-775f-71511b9ec8d6.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/50817234-7dbc-147e-ad24-2e7a189bf1be.png)



StartTypeは下記を参照。
https://docs.microsoft.com/ja-jp/dotnet/api/system.serviceprocess.servicestartmode?view=netframework-4.8
BootとSystemはドライバ用で、普通のサービスはAutomaticかManualを選択しておけばよいっぽい。

### ■ビルドする

インストールする際に使うので、ビルドしてexeができたフォルダパスを控えておく。
例えば、私の環境だと`C:\Users\masa\source\repos\WindowsService2\WindowsService2\bin\Debug`

### ■作ったサービスをインストールする

開発者コマンドプロンプトを管理者権限で起動する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/c3507f7e-7961-7ec0-fbc5-68a4c6d8596c.png)


開発者コマンドプロンプトで下記を実行する

- cd exeの出力先フォルダ
- installutil exe名.exe
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/8f2b8608-f51f-b8dc-a063-3708f8ed340e.png)


上記を実行すると、ユーザー名とパスワードを入力する画面が表示されるので、管理者権限のあるユーザー名とパスワードを入力する。※ユーザー名の先頭には、**「コンピューター名\」**をつけること。

たとえば、PC名が「masa-PC」でユーザー名が「masa」だったら、下記のような感じ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/a27442f6-41fe-940f-67e7-15f40c922831.png)

コンピュータ名はコントロールパネルの「システム」で見れる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/fdfa027d-5a6c-96dc-fd9a-005e3b637ca1.png)

成功したら、こんなのが出る。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/1123aa47-4ef0-7ce0-d6d3-df2682ecfade.png)

インストールしたサービスをservices.mscで見ると下記のような感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/7b0eba55-f5a6-35d5-95e6-2e9b1323a8d7.png)

インストールしたサービスをタスクマネージャーで見ると下記のような感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/7b10247b-d7e9-05fc-e8f1-d5cffaa6488b.png)

### ■その他サービスの各種設定

デザイナー画面にしてるときに表示されるプロパティで、ServiceBaseの設定をする。

まず、電源周りのイベントを受け取りたい、Session関連のイベントを受け取りたいなど、必要な情報、うけとりたい情報を設定する。（いろいろ実験してたので、画像中のサービス名がservice **1**だったり**2**だったりしてます。すみません）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/146a3b89-7a9a-dc09-bfce-8f45a10fc9f3.png)

また、Shutdownできるか、一時停止できるか、などもここで設定する。
上のプロパティで`CanPauseAndContinue`をtrueにしてると、下記のようにサービス管理画面で右クリしたときのメニューで「一時停止」と「再起動」が押せるようになる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/f701ba45-401d-a04d-fa0d-ec34f8c68fff.png)

### ■デバッグする

デバッグするときは、アタッチを行う。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/7eb823b6-6f80-3b4a-d1f6-6465aa9490a1.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/eef068fa-30ec-0592-075b-af332d9020c0.png)


※管理者権限でVSを起動していない場合、アタッチしようとすると「管理者権限で再起動しますか？」ということを聞かれるので、管理者権限でVSを再起動してからもう一度アタッチする。

### ■各種イベント発生時にやりたいことを書く

ソリューションエクスプローラーで
service1.csを右クリックして、コードを表示、を選択し、コードを出す。

クラス名のところ（今回はService1）の右クリ→**[上書きを追加]**を押す。
(オーバーライドを追加するということ)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/9d53311b-075b-524e-49c4-8704cf22647e.png)


下記のような画面がでるので、必要なものだけチェックしてOKを押す。
今回はとりあえず実験的に、サービス管理画面でサービスを**「一時停止」**して**「再開」**したときにオーバーライドしたメソッドを通ることをみたいので**「OnPause」**と**「OnContinue」**にチェックを入れてOKを押す。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/a1951da2-23e3-32a8-d1b1-e938d1fc0dd4.png)

そのメソッドを通ったときにデバッグ用メッセージを出すようにコードを書く。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/37fc7863-a92b-2511-c4d6-b37580649ce7.png)

これで、サービスを一時停止→再開と操作してやると、メッセージが出る。

**これで、いったんサービスを作って実際に動かす一通りの実験はできたことになる。**

### ■コードを修正したときにサービスに反映させる

ビルドしなおしてそれを反映させようと思ったら、一度サービスをアンインストールして、ビルドしなおした後、再度インストールする。

- サービス停止
- ビルド
- サービスをアンインストール
- サービスをインストール

という順番。手動でやっていると面倒なので、下記のようなバッチを作っておくと楽かもしれない。

```bat

rem まず開発者用コマンドプロンプトを起動してから
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\Tools\VsDevCmd.bat"

rem サービス名(サービス管理画面の名前欄に出てくる名前)
set SERVICENAME=Service1DisplayName
set SOLUTIONPATH="C:\Users\masa\source\repos\WindowsService1\WindowsService1.sln"
set EXEPAH="C:\Users\masa\source\repos\WindowsService1\WindowsService1\bin\Debug\WindowsService1.exe"

rem サービス停止
net stop "%SERVICENAME%"

rem ビルドする
MSBuild %SOLUTIONPATH% /t:clean;rebuild /p:Configuration=Debug;Platform="Any CPU" > nul
if %ERRORLEVEL% neq 0 (
    echo ErrorLevel:%ERRORLEVEL%
    echo ビルド失敗
    pause
    exit
)

rem アンインストール実施
installutil -u %EXEPAH%

rem インストール実施
installutil /username=masa-PC\masa /password=******** /unattended %EXEPAH%

rem サービス再開
net start "%SERVICENAME%"

pause
```

# 参考

■C#でWindows Serviceを作る 1～5
https://qiita.com/yossihard/items/46f72af754915b242bed

■Tutorial: Create a Windows service app
https://docs.microsoft.com/ja-jp/dotnet/framework/windows-services/walkthrough-creating-a-windows-service-application-in-the-component-designer

■権限
https://www.atmarkit.co.jp/ait/articles/0905/08/news095.html