2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

本シリーズは、.NET 8 で Windows Service を作る手順を 段階的に 紹介します。

この記事では「Windows Service として登録できる最小限のコード」を作ります。

開発環境

項目 バージョン
.NET 8.0 (LTS)
IDE Visual Studio 2022 / VS Code
OS Windows 10/11

Windows Service とは

Windows が管理するバックグラウンドプロセスです。ユーザーがログインしていなくても起動し続けられる点が通常アプリとの大きな違いです。

比較 通常のアプリ Windows Service
起動 手動 OS 起動時に自動起動可能
ログイン不要 不可 可能
UI あり なし(バックグラウンド)
管理ツール タスクマネージャー services.msc / sc.exe

プロジェクト構成

Project1_MinimalService/
├── Project1_MinimalService.csproj
├── Program.cs                  ← エントリポイント
├── Worker.cs                   ← サービスのメインロジック
├── Services/
│   └── FileLogService.cs       ← ファイルへのログ書き込み
└── scripts/
    ├── install-service.bat     ← サービス登録スクリプト
    └── uninstall-service.bat   ← サービス削除スクリプト

実装

1. プロジェクト作成

CLI またはVisual Studio の「ワーカー サービス」テンプレートで作成します。

dotnet new worker -n Project1_MinimalService --framework net8.0

2. csproj の設定

<Project Sdk="Microsoft.NET.Sdk.Worker">

  <PropertyGroup>
    <TargetFramework>net8.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <RootNamespace>Project1_MinimalService</RootNamespace>
    <AssemblyName>WindowsServiceDemo_1</AssemblyName>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
    <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.1" />
  </ItemGroup>

</Project>

AssemblyName を指定することで、生成される exe のファイル名をプロジェクト名と分けられます。

3. ログ書き込みサービス(FileLogService.cs)

using System.Text;

namespace Project1_MinimalService.Services;

public class FileLogService
{
    private readonly string _logFilePath;
    private readonly object _lock = new();

    public FileLogService()
    {
        var logDir = Path.Combine(AppContext.BaseDirectory, "logs");
        if (!Directory.Exists(logDir))
            Directory.CreateDirectory(logDir);

        // 日付単位でファイルを作成(同日は追記)
        _logFilePath = Path.Combine(logDir, $"service_{DateTime.Now:yyyyMMdd}.log");
    }

    public void WriteLog(string eventType, string message)
    {
        var line = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{eventType}] {message}";
        lock (_lock)
        {
            File.AppendAllText(_logFilePath, line + Environment.NewLine, Encoding.UTF8);
        }
    }

    public string LogFilePath => _logFilePath;
}

マルチスレッドからの同時書き込みに備えて lock を使っています。

4. メインワーカー(Worker.cs)

using Project1_MinimalService.Services;

namespace Project1_MinimalService;

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

    public Worker(ILogger<Worker> logger, FileLogService fileLogService)
    {
        _logger = logger;
        _fileLogService = fileLogService;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // 起動時ログ
        LogEvent("STARTUP", "Windows Service が起動しました。");

        try
        {
            // サービス停止まで待機(最小構成なので何もしない)
            await Task.Delay(Timeout.Infinite, stoppingToken);
        }
        catch (OperationCanceledException)
        {
            // 停止要求は正常終了
        }
        finally
        {
            // 停止時ログ
            LogEvent("SHUTDOWN", "Windows Service が停止しました。");
        }
    }

    private void LogEvent(string eventType, string message)
    {
        _fileLogService.WriteLog(eventType, message);
        _logger.LogInformation("[{EventType}] {Message}", eventType, message);
    }
}

Task.Delay(Timeout.Infinite, stoppingToken) はサービス停止シグナルが来るまで待ち続けるシンプルな実装です。

5. エントリポイント(Program.cs)

using Project1_MinimalService;
using Project1_MinimalService.Services;

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddWindowsService(options =>
{
    options.ServiceName = "WindowsServiceDemo_1";
});

builder.Services.AddSingleton<FileLogService>();
builder.Services.AddHostedService<Worker>();

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

AddWindowsService() を呼ぶだけで、コンソール実行(開発中)と Windows Service(本番)の両方に対応できます。

サービスの登録・起動

ビルド(発行)

cd C:\Program Files\Demo\Project1_MinimalService

dotnet publish -c Release -r win-x64 --self-contained -o publish --ignore-failed-sources

--self-contained を指定すると、実行先に .NET ランタイムがなくても動きます。

サービス登録スクリプト(install-service.bat)

@echo off
setlocal

set SERVICE_NAME=WindowsServiceDemo_1
set SERVICE_DISPLAY_NAME=Windows Service Demo 1 - Minimal
set EXE_PATH=%~dp0..\publish\WindowsServiceDemo_1.exe

for %%i in ("%EXE_PATH%") do set EXE_PATH=%%~fi

if not exist "%EXE_PATH%" (
    echo [ERROR] Executable not found: %EXE_PATH%
    pause & exit /b 1
)

sc query %SERVICE_NAME% >nul 2>&1
if %errorlevel% equ 0 (
    sc stop %SERVICE_NAME% >nul 2>&1
    timeout /t 3 /nobreak >nul
    sc delete %SERVICE_NAME%
    timeout /t 2 /nobreak >nul
)

sc create %SERVICE_NAME% ^
    binPath= "%EXE_PATH%" ^
    DisplayName= "%SERVICE_DISPLAY_NAME%" ^
    start= auto

sc start %SERVICE_NAME%

pause
endlocal

binPath== の後ろにスペースが必要です(sc.exe の仕様)。

管理者権限のコマンドプロンプトで実行します。

image.png

SERVICE_DISPLAY_NAMEがしっかり表示されて実行中になっていますね!

確認・削除

sc query WindowsServiceDemo_1   REM 状態確認
sc stop   WindowsServiceDemo_1   REM 停止
sc delete WindowsServiceDemo_1   REM 削除

動作確認

確認内容 手順 期待ログ
起動ログ サービス起動直後にログ確認 [STARTUP]
停止ログ sc stop 後にログ確認 [SHUTDOWN]

ログ出力例:

[2026-06-03 09:00:00] [STARTUP] Windows Service が起動しました。
[2026-06-03 09:30:00] [SHUTDOWN] Windows Service が停止しました。

ログファイルは publish\logs\service_yyyyMMdd.log に出力されます。

ポイントまとめ

  • AddWindowsService() + BackgroundService で最小構成のサービスが作れる
  • AssemblyName で exe のファイル名を自由に設定できる
  • --self-contained でランタイム不要の自己完結型 exe をビルドできる
  • 停止シグナルは CancellationToken で受け取る

次の記事(#2)では、ロック・スリープ復帰を検知する実装を追加します。

参考

本記事に掲載している内容は、私個人の見解であり、所属する組織の立場や戦略、意見を代表するものではありません。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?