.NET 8 で Windows のサービスを作る

ページ作成日 :

動作確認環境

Visual Studio
  • Visual Studio 2022
.NET
  • .NET 8
Windows
  • Windows 11

動作必須環境

Visual Studio
  • Visual Studio 2022
.NET
  • .NET 8
Windows
  • Windows 10
  • Windows 11
Windows Server
  • Windows 2012 以降

前提条件

  • Visual Studio がすでにインストール済みであること

はじめに

Visual Studio の .NET 関連で Windows のサービスを作成しようとすると .NET Framework のテンプレートしかありませんが、 .NET (Core) でも作れるようになっており、テンプレートの名称が「ワーカー サービス」となっています。

今回はこれを使用して Windows のサービスを作成・登録を行い動かすところまで説明します。 なお処理内容は最低限なので、Windows のサービスとして動かすところまで確認出来たらあとは機能の作りこみを行ってください。

プロジェクトの作成

Visual Studio を起動します。「新しいプロジェクトの作成」を選択します。

上の検索欄に サービス と入力し、一覧から「ワーカー サービス」を選択します。 「Windows サービス」は .NET Framework 版でありこちらは .NET 版はありません。

プロジェクト名や場所は任意に設定してください。登録するサービスには影響しません。

「.NET 8.0」が選択されていることを確認しデフォルトのまま作成します。

プロジェクトが作成されました。

ライブラリの追加

初期状態ではあくまでも「サービス」の機能しか持っておらず Windows 固有の機能がありません。 NuGet から Windows サービスで使用することができるライブラリを追加します。

「依存関係」を右クリックして「NuGet パッケージの管理」を選択します。

「参照」タブを選択し、検索欄に Microsoft.Extensions.Hosting.WindowsServices を入力します。 一覧に表示されるのでインストールします。

「適用」をクリックします。

パッケージとして追加されました。

プログラムの編集

今回はサンプルとして定期的にテキストファイルにテキストを追加するサービスを作成してみます。

Program.cs

以下のように Windows サービスの機能を追加します。

var builder = Host.CreateApplicationBuilder(args);
// ↓ここから追加
builder.Services.AddWindowsService();
// ↑ここまで追加
builder.Services.AddHostedService<Worker>();

var host = builder.Build();
host.Run();

Worker.cs

クラス名は任意に変更して問題ありませんが今回はデフォルトのままとします。

デフォルトではサービスが実行されたときに処理される ExecuteAsync メソッドのみがありますが以下のように変更してみます。

public class Worker : BackgroundService
{
  private readonly ILogger<Worker> _logger;

  /// <summary>ログの出力先フォルダパス。</summary>
  private const string OutputLogFolderPath = @"C:\Temporary\";

  /// <summary>ログの出力先ファイルパス。</summary>
  private const string OutputLogFilePath = @$"{OutputLogFolderPath}Test.log";


  public Worker(ILogger<Worker> logger)
  {
    _logger = logger;
  }

  /// <summary>
  /// サービスが開始されたときに呼ばれます。
  /// </summary>
  /// <param name="stoppingToken"></param>
  /// <returns></returns>
  public override async Task StartAsync(CancellationToken stoppingToken)
  {
    if (Directory.Exists(OutputLogFolderPath) == false)
    {
      Directory.CreateDirectory(OutputLogFolderPath);
    }
    File.AppendAllText(OutputLogFilePath, $"StartAsync サービスを開始しました。\r\n");

    await base.StartAsync(stoppingToken);
  }

  /// <summary>
  /// サービスが終了したときに呼ばれます。
  /// </summary>
  /// <param name="stoppingToken"></param>
  /// <returns></returns>
  public override async Task StopAsync(CancellationToken stoppingToken)
  {
    File.AppendAllText(OutputLogFilePath, $"StopAsync サービスを終了しました。\r\n");
    File.AppendAllText(OutputLogFilePath, $"------------------------------\r\n");

    await base.StopAsync(stoppingToken);
  }

  /// <summary>
  /// サービスが実行されたときに呼ばれます。
  /// </summary>
  /// <param name="stoppingToken">サービスの非同期キャンセルトークン。</param>
  /// <returns></returns>
  protected override async Task ExecuteAsync(CancellationToken stoppingToken)
  {
    while (!stoppingToken.IsCancellationRequested)
    {
      File.AppendAllText(OutputLogFilePath, $"{DateTime.Now}\r\n");

      if (_logger.IsEnabled(LogLevel.Information))
      {
        _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
      }
      await Task.Delay(1000 * 60, stoppingToken);
    }
  }
}

今回新たに StartAsync メソッドと StopAsync メソッドを追加しました。 名前から分かるとおりサービスが開始されたときと終了されたときに呼ばれるメソッドです。 処理している内容は単にフォルダを作成したりテキストを書き込んでいるだけなので説明は省きます。

ExecuteAsync メソッドでは stoppingToken にキャンセル(停止)のフラグが立つまでは while でループし続けます。 サービスが動いている間に処理させたい内容を while の中に追加します。 ただし動かしたい処理だけを書いてしまうとサービスがフル稼働してしまうので Task.Delay メソッドで一定時間待機させながら動かすのが基本となります。 デフォルトでは1秒(1000ms)に設定されているので任意の時間に書き換えてください。

デバッグ

Visual Studio からデバッグ実行できます。 実際にサービスに登録されることはありませんので安心してください。

実行するとコンソールが表示されます。

処理が正しければファイルが作成されていることを確認できます。

デバッグを終了する場合はコンソールを閉じてください。

ログを確認するとサービスの開始処理は通っていますが終了処理は通っていません。 終了処理を確認するには実際に Windows サービスに登録して確認する必要があります。

発行

Windows サービスに登録できるようにするためにはプログラムを発行する必要があります。 プロジェクトを右クリックして「発行」を選択します。

「フォルダー」を選択します。

フォルダーの場所はデフォルトでいいです。

発行の設定が作成されるので「すべての設定を表示」を選択します。

以下のように設定します。

パラメータ名 備考
構成 Release (デフォルト)
ターゲット フレームワーク net8.0 (デフォルト)
配置モード フレームワーク依存 サービス登録環境に別途 .NEt 8 ランタイムをインストールする場合はこの設定でOKです。
ターゲットランタイム win-x64 OS が 32bit 環境の場合は win-x86 を選択してください
ターゲットの場所 デフォルト
単一ファイルの作成 ON
ReadyToRun コンパイル 任意

設定したら「発行」ボタンをクリックします。

左下に「公開は成功しました」が表示されれば完了です。

出力されたファイルは「ターゲットの場所」をクリックすれば開けます。

プログラムの配置とサービスへの登録

管理者権限で Windows サービスを登録する環境にログインします。

発行したファイルを Windows サービスとして登録する環境にコピーします。 任意のフォルダに配置して構いませんが、Windows サービスは常にそのフォルダのプログラムを参照する点に注意してください。

また、発行したファイルに拡張子が .pdb のファイルが含まれていますが、開発情報などが含まれているため不特定の人に見られるような環境ではコピーしないでください。

ファイルを配置したら Windows サービスとして登録します。登録にはコマンドを使用します。 スタートメニューを右クリックして「ターミナル (管理者)」を選択してください。 サンプルでは Windows 11 の環境で実行していますがほかの環境では管理者権限のコマンドプロンプトを開けばOKです。

ターミナルの場合最初に PowerShell が開いている場合がありますが PowerShell では正しく設定できない場合がありますので「コマンド プロンプト」を開いてください。

以下のコマンドで Windows サービスに登録できます。

フォーマット

sc create "<サービス名>" start=auto binpath="<プログラム(.exe)のパス>"

入力例

sc create "WindowsServiceDotNet8" start=auto binpath="C:\Service\WindowsServiceDotNet8\WindowsServiceDotNet8.exe"

<サービス名> は Windows サービスの一覧に表示される名称です。別途表示名は設定できますが、表示名を指定しない場合はこの名前が表示されます。 レジストリにも影響するのでサービス名は英数字が好ましいです。

start=auto は Windows が起動したときにサービスも自動起動させるための設定です。 手動で起動する場合はこの記述は消してください。

binpath にはプログラムファイルのパスをフルパスで指定してください。

コマンドを実行して [SC] CreateService SUCCESS と表示されれば成功です。

登録したサービスが一覧に表示されるはずです。

サービスの起動と動作確認

サービスを動かしたい場合はサービス画面から起動するか、以下のコマンドで起動可能です。

sc start <サービス名>

停止コマンドは以下になります。

sc stop <サービス名>

サービスを停止すると停止処理が実行されていることを確認できます。

サービスに説明追加

追加したサービスの説明欄は空白ですが、以下のコマンドで追加可能です。

sc description <サービス名> "<説明文>"

サービスの削除

サービスを削除する場合は以下のコマンドで削除できます。コマンド プロンプトで管理者権限が必要です。

sc delete <サービス名>