.NET 6でWindowsサービスを作成する

.NET6 でWindow サービスを作成する方法の紹介です。

確認環境

作成と動作確認環境は以下の通りです。

  • .NET6
  • VisualStudio 2022
  • Windows11

プロジェクトの作成と準備

まず「ワーカーサービス」プロジェクトを「MyService」という名前で作成します。今回はトップレベルステートメントを使用しないのでウィザードの Do not use top-level statements にチェックを入れてプロジェクトを作成します。

次に NuGet で Microsoft.Extensions.Hosting を追加します。2022年10月現在最新版の 6.0.1 を導入します。

// パッケージ マネージャー コンソールから追加
PM> NuGet\Install-Package Microsoft.Extensions.Hosting
PM> NuGet\Install-Package Microsoft.Extensions.Hosting.WindowsServices

「ワーカーサービス」自体は実行時間が長く、GUIの無いプロセス向けの機能です。Windows サービス向けの機能と統合されているので Windows サービス作成機能がワーカーサービスに乗ってるような感じとなっています。

コードの修正

MyService.cs

以下の通りコードを変更します。Microsoft.Extensions.Hosting 名前空間の BackgroundService といクラスを継承した Worker がテンプレートとして配置されているので MyService にリネームして自分のサービスの処理を書いていきます。

// Worker.cs → MyService.cs にリネーム

namespace Takap
{
    public class MyService : BackgroundService
    {
        private readonly ILogger<MyService> _logger;

        public MyService(ILogger<MyService> logger)
        {
            _logger = logger; // DIのコンストラクターインジェクション
        }

        // サービスが開始されたときの処理
        public override async Task StartAsync(CancellationToken ct)
        {
            _logger.LogInformation("Start Service");
            await base.StartAsync(ct);
        }

        // サービスが終了したときの処理
        public override async Task StopAsync(CancellationToken ct)
        {
            _logger.LogInformation("Stop Service");
            await base.StopAsync(ct);
        }

        // サービスが実行中の処理
        protected override async Task ExecuteAsync(CancellationToken ct)
        {
            try
            {
                _logger.LogInformation("Start ExecuteAsync");

                while (!ct.IsCancellationRequested)
                {
                    _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
                    await Task.Delay(1000 * 30, ct);
                }
            }
            finally
            {
                _logger.LogInformation("Finish ExecuteAsync");
            }
        }
    }
}

Program.cs

次にメインメソッドを以下のように修正します。Windows のサービスだということを表す実装を追記します。

追加しないでもデバッグ時は起動してデバッグできてしまいます。しかもサービス登録もできます。が、実際にサービス管理画面から開始すると起動できずタイムアウトしてエラーが発生するため、必ず追加します。

// Program.cs

namespace Takap
{
    public class Program
    {
        public static void Main(string[] args)
        {
            IHost host = Host.CreateDefaultBuilder(args)
                // ★★追記する --->
                .UseWindowsService(options =>
                {
                    options.ServiceName = nameof(MyService); // サービス名
                })
                // <---
                .ConfigureServices(services =>
                {
                    // ★★追記する --->
                    services.AddSingleton<MyService>();
                    // <---
                    services.AddHostedService<MyService>();
                })
                .ConfigureLogging((context, logging) =>
                {
                    logging.AddConfiguration(context.Configuration.GetSection("Logging"));
                })
                .Build();

            host.Run();
        }
    }
}

appsettings.json

任意の項目ですが、動作を確認するためにログをイベントログに出力するため以下のような設定を appsettings.json と appsetting.Development.json に追記します。

// appsettings.json
{
    "Logging": {
        "LogLevel": {
            "Default": "Information",
            "Microsoft.Hosting.Lifetime": "Information"
        },
        "EventLog": {
            "LogLevel": {
                "Default": "Information"
            }
        }
    }
}

さすがにサービスの稼働ログ全てをイベントログに出力するのは現実的ではないので動作確認程度という形になります。

本番環境では NLog や自分たちのログ出力システムを使用することになると思います。

サービスの登録・実行

サービスの発行

次にサービスを発行します。プロジェクトのコンテキストメニューから発行を選択します。

発行先を「フォルダー」で選択します。

発行先は「D:\MyService」とします。

完了すると「MyServic: 公開」というタブが表示されるのでビルドオプションを変更します。

Windowsサービスなのでマルチプラットフォームが関係なくなるのでターゲットランタイムを「win-x64」に変更します。単一ファイルの作成はチェックはお好みですが入れておくのがおすすめです。

設定を変更してhぞオンしたらもう一度「発行」を行います。

サービスの登録・開始

作成したプログラムを Windows のサービスとして登録・削除する方法です。

コマンドプロンプトを管理者権限で起動して以下コマンドを入力します。

// サービスの登録
sc create MyService start=auto binPath="D:\MyService\MyService.exe"

// startオプション
// auto, 自動
// delayed-auto, 自動(遅延)
// delayed-auto, 自動(遅延)
// demand, 手動 (既定値)
// disabled:無効
// boot:ブート
// system:システム

// サービスの開始
sc start MyService

これで、サービス管理画面上に MyService というサービスが登録され開始されていることが確認できます。

イベントビューアーにも以下のようにログが出力されています。

// 開始時
> Start Service
> Start ExecuteAsync
> Worker running at: YYYY/MM...

// 終了時
// Stop Service
// Finish ExecuteAsync

// 停止時は CancellationToken 経由でキャンセルされて OperationCanceledException が発生する

サービスの終了・削除

一度登録したサービスの停止や削除のコマンドは以下の通りです。

サービスの停止や登録したサービスの挙動の変更はサービス管理画面上から行えます。

// サービスの停止
sc stops MyService

// サービスの削除
sc delete MyService

// 補足:
// サービス管理画面を閉じるまで削除されません。
// 以下のメッセージが表示される場合ウインドウを閉じてから開きなおします。
// 
// > [SC] DeleteService FAILED 1072:
// > 指定されたサービスは削除の対象としてマークされています。

参考

本記事は以下を参考にしました。

learn.microsoft.com