はじめに
.NET Windowsサービスを開発したものの、配布方法に悩んだことはありませんか?
「sc.exe で手動登録するのは面倒...」「エンドユーザーに簡単にインストールしてもらいたい...」
そんな時に便利なのが Visual Studio Installer Projects を使ったMSIインストーラーの作成です。
本記事では、.NET Windowsサービス用のMSIインストーラーを ゼロから作成する手順 を、実際のコードと画面設定を交えて解説します。
この記事で分かること
- Visual Studio Installer Projectsの導入方法
- Self-contained配布(.NETランタイム同梱)のMSI作成手順
- カスタムアクションによるWindowsサービスの自動登録/解除
- トラブルシューティングとベストプラクティス
前提条件
開発環境
| 項目 | 要件 |
|---|---|
| OS | Windows 10/11(開発用マシン) |
| Visual Studio | 2022以降(Community / Professional / Enterprise いずれも可) |
| VSワークロード | 「ASP.NET と Web 開発」または「.NET デスクトップ開発」がインストール済み |
| .NET SDK | 6.0以降がインストール済み |
インストール先環境
| 項目 | 要件 |
|---|---|
| OS | Windows 10/11、Windows Server 2019以降 |
| 権限 | 管理者権限(インストール/アンインストール時に必要) |
| .NETランタイム | 不要(Self-contained配布のため) |
その他
- 配布対象の .NET Windowsサービスプロジェクト(Worker Service)が作成済み であること
- 本記事ではサービスの開発方法自体は扱いません
全体の流れ
手順1: Visual Studio Installer Projectsのインストール
拡張機能の導入
- Visual Studioを起動
- メニュー →
拡張機能→拡張機能の管理 - 検索ボックスに 「Microsoft Visual Studio Installer Projects」 を入力
- インストール後、Visual Studioを再起動
インストールが完了すると、新規プロジェクト追加時に 「Setup Project」 テンプレートが選択可能になります。
手順2: サービスプロジェクトのPublish設定
csprojの設定
サービスプロジェクトの .csproj に以下を設定します。
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<PublishReadyToRun>true</PublishReadyToRun>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
</PropertyGroup>
</Project>
各プロパティの役割は以下の通りです。
| プロパティ | 説明 |
|---|---|
SelfContained |
.NETランタイムを同梱(インストール先に.NET不要) |
PublishSingleFile |
単一EXEに統合 |
PublishReadyToRun |
ネイティブコード事前コンパイルで起動高速化 |
IncludeNativeLibrariesForSelfExtract |
ネイティブDLLも単一ファイルに含める |
Publish実行
- ソリューションエクスプローラーでサービスプロジェクトを右クリック
-
発行(Publish)を選択 - 発行プロファイルが未作成の場合:
-
フォルダーを選択 →次へ - フォルダーの場所はデフォルトのままでOK →
完了
-
- 発行プロファイル画面で
すべての設定を表示をクリックし、以下を確認:-
構成:
Release -
ターゲットランタイム:
win-x64 -
配置モード:
自己完結
-
構成:
-
発行ボタンをクリック
出力フォルダに .exe と必要な依存ファイルが生成されていることを確認してください。
手順3: Setup Projectの作成
- ソリューションエクスプローラーで右クリック →
追加→新しいプロジェクト - テンプレート検索で 「Setup Project」 を選択
- プロジェクト名を
<サービス名>Installerに設定(例:MyServiceInstaller) -
作成をクリック
サービスプロジェクトがソリューションに含まれていない場合は、先にソリューションに追加しておきましょう。インストーラーから「プロジェクト出力」として参照するために必要です。
手順4: インストーラープロジェクトの構成
4.1 プロパティの設定
プロパティの開き方に注意!
Setup Projectを 左クリックで選択 → F4キー でプロパティウィンドウを開きます。
右クリック→プロパティ ではありません。
以下のプロパティを設定します。
| プロパティ | 設定値例 | 説明 |
|---|---|---|
| Author | 会社名 | 作成者 |
| Description | サービスの説明 | インストーラーの説明文 |
| Manufacturer | 会社名 | 製造元(インストールパスに使用) |
| ProductName | サービスの製品名 | 製品名(表示名に使用) |
| Version | 1.0.0 | バージョン番号 |
| TargetPlatform | x64 | 64ビット版Windows用 |
ポイント:
ManufacturerとProductNameがデフォルトのインストールパスに使われます。
→C:\Program Files\[Manufacturer]\[ProductName]\
4.2 ファイルシステムエディタの設定
Application Folderにファイルを追加
- Setup Projectを右クリック →
表示→ファイル システム -
Application Folderを右クリック →追加→プロジェクト出力 - ダイアログで以下を選択:
- プロジェクト: サービスプロジェクト
- 出力内容: 「項目の公開」 を選択
「プライマリ出力」ではなく 「項目の公開」 を選択してください!
「項目の公開」を選ぶことで、Self-containedのpublish成果物(EXE + すべての依存DLL)が自動的にインストーラーに含まれます。
インストールパスの変更(64bit対応)
TargetPlatform を x64 に設定した場合、インストール先パスも変更が必要です。
-
Application Folderを選択 → F4キー -
DefaultLocationを変更:
# 変更前(32bit用)
[ProgramFilesFolder][Manufacturer]\[ProductName]
# 変更後(64bit用)
[ProgramFiles64Folder][Manufacturer]\[ProductName]
設定ファイルの追加
Application Folder を右クリック → 追加 → ファイル で、appsettings.json などの設定ファイルを追加できます。
手順5: カスタムアクションの設定
カスタムアクションを使って、インストール/アンインストール時にWindowsサービスの登録/削除を自動実行します。
カスタムアクションエディタを開く
Setup Projectを右クリック → 表示 → カスタム アクション
Installアクションの設定
-
インストールノードを右クリック →カスタム アクションの追加 -
Application Folder→ サービスの.exeを選択 →OK - 追加されたアクションをF4キーで設定:
| プロパティ | 設定値 |
|---|---|
Name |
InstallService |
Arguments |
/InstallService |
InstallerClass |
False |
Uninstallアクションの設定
-
アンインストールノードを右クリック →カスタム アクションの追加 - 同様にサービスの
.exeを選択 - プロパティを設定:
| プロパティ | 設定値 |
|---|---|
Name |
UninstallService |
Arguments |
/UninstallService |
InstallerClass |
False |
InstallerClass は必ず False に設定してください。Trueのままだと、.NETのInstallerクラスを探そうとしてエラーになります。
手順6: サービスコードの修正
カスタムアクションから渡される引数を処理するコードを、Program.cs に追加します。
コマンドライン引数処理
using System.Diagnostics;
// コマンドライン引数チェック(MSIカスタムアクション用)
if (args.Length > 0)
{
switch (args[0])
{
case "/InstallService":
InstallWindowsService();
return;
case "/UninstallService":
UninstallWindowsService();
return;
}
}
// --- 通常のサービス起動処理 ---
var builder = Host.CreateApplicationBuilder(args);
// ... (既存のコード)
サービスインストール処理
static void InstallWindowsService()
{
try
{
var exePath = Process.GetCurrentProcess().MainModule?.FileName
?? throw new InvalidOperationException("実行ファイルパスを取得できませんでした");
// サービス作成
RunScCommand($"create \"MyService\" " +
$"binPath=\"{exePath}\" " +
$"start=delayed-auto " +
$"DisplayName=\"My Application Service\" " +
$"obj=LocalSystem");
// サービス説明設定
RunScCommand($"description \"MyService\" " +
$"\"データ処理を行うバックグラウンドサービス\"");
// 障害時リスタート設定
RunScCommand($"failure \"MyService\" reset=86400 " +
$"actions=restart/60000/restart/60000/restart/60000");
Console.WriteLine("サービスのインストールが完了しました。");
}
catch (Exception ex)
{
Console.WriteLine($"エラー: {ex.Message}");
Environment.Exit(1);
}
}
サービスアンインストール処理
static void UninstallWindowsService()
{
try
{
// サービス停止
RunScCommand("stop \"MyService\"");
Thread.Sleep(2000); // 停止待機
// サービス削除
RunScCommand("delete \"MyService\"");
Console.WriteLine("サービスのアンインストールが完了しました。");
}
catch (Exception ex)
{
Console.WriteLine($"エラー: {ex.Message}");
Environment.Exit(1);
}
}
sc.exe実行ヘルパー
static void RunScCommand(string arguments)
{
var startInfo = new ProcessStartInfo
{
FileName = "sc.exe",
Arguments = arguments,
UseShellExecute = false,
CreateNoWindow = true
};
using var process = Process.Start(startInfo);
process?.WaitForExit();
}
sc.exeの主要オプション
| オプション | 説明 |
|---|---|
start=delayed-auto |
遅延自動起動(推奨) |
start=auto |
自動起動 |
start=demand |
手動起動 |
obj=LocalSystem |
LocalSystemアカウントで実行 |
obj=NetworkService |
NetworkServiceアカウントで実行 |
手順7: ビルドとテスト
ビルド手順
すべてVisual Studioから実行します。
1. サービスプロジェクトのPublish
- ソリューションエクスプローラーでサービスプロジェクトを右クリック
-
発行(Publish)を選択 - 発行プロファイルが未作成の場合は新規作成:
- ターゲット:
フォルダー - 構成:
Release - ターゲットランタイム:
win-x64 - 配置モード:
自己完結
- ターゲット:
-
発行ボタンをクリック
2. Setup Projectのビルド
- ソリューションエクスプローラーでSetup Projectを右クリック
-
ビルドを選択 - ビルド完了を待つ
必ず サービスプロジェクトのPublish → Setup Projectのビルド の順番で実行してください。
Publish前にSetup Projectをビルドすると「プロジェクト出力が見つからない」エラーになります。
ビルド成功後、Setup Projectの Release フォルダに以下が生成されます。
-
<インストーラー名>.msi← これが配布ファイル -
setup.exe(オプション)
テストチェックリスト
インストールテスト
-
.msiをダブルクリックしてインストール - インストール先フォルダにEXEと設定ファイルが配置されている
-
services.mscでサービスが登録されている - サービスの表示名・説明が正しい
- サービスを起動して正常動作する
アンインストールテスト
- コントロールパネル → プログラムと機能からアンインストール
-
services.mscでサービスが削除されている - インストールフォルダが削除されている
トラブルシューティング
サービスが登録されない
原因: カスタムアクションの設定ミス
確認ポイント:
-
InstallerClassが False になっているか -
Argumentsに/InstallServiceが設定されているか -
Program.csに引数処理のコードが追加されているか
ビルドエラー「プロジェクト出力が見つからない」
原因: Publish実行前にSetup Projectをビルドした
解決: サービスプロジェクトを先に dotnet publish → その後Setup Projectをビルド
インストール先が「Program Files (x86)」になる
原因: DefaultLocationに [ProgramFilesFolder] を使用している
解決:
-
TargetPlatformをx64に設定 -
DefaultLocationを[ProgramFiles64Folder]に変更
appsettings.jsonがアップグレード時に上書きされる
対策: バージョンアップ時は事前にバックアップを取る運用を推奨。READMEに手順を記載しましょう。
サービスが起動しない
確認ポイント:
-
appsettings.jsonの構文エラーがないか - ログフォルダ等のパスが存在するか
- サービスアカウントに必要な権限があるか
ベストプラクティス
バージョン管理
- リリース時は必ずバージョン番号を更新する(メジャー.マイナー.ビルド)
- バージョン変更時に
ProductCodeの自動更新を確認
設定ファイル
-
appsettings.jsonはテンプレートとして配布し、インストール後に手動編集する方式を推奨 - READMEに設定手順を明記
ログ管理
- ログ出力先は設定ファイルで変更可能にする
- ログローテーションを実装する
- サービスアカウントの書き込み権限を確認
テスト
- クリーンな環境でのインストールテストを実施
- アンインストールの完全性を確認
- バージョンアップグレードのテストも忘れずに
Visual Studio Installer Projectsの制限事項
できること
- ファイルのコピー・配置
- カスタムアクション(EXE実行)
- レジストリ登録
- ショートカット作成
- 基本的なUIカスタマイズ
できないこと(またはコード追加が必要)
- 複雑な設定ダイアログ(入力フォーム)
- 設定ファイルの自動生成
- フォルダ権限の詳細設定
- 自動ファイル収集(WiXのHeat.exe的な機能)
より高度なインストーラーが必要な場合は、WiX Toolset の検討をおすすめします。
まとめ
| 観点 | 評価 |
|---|---|
| 導入の容易さ | Visual Studio拡張機能を入れるだけで即利用可能 |
| 操作性 | GUIベースで直感的に設定できる |
| 配布の利便性 | .NETランタイム不要のSelf-contained配布が可能 |
| CI/CD統合 |
.vdproj がバイナリ形式のためやや困難 |
| カスタマイズ性 | 基本的な用途には十分、複雑な要件にはWiXを検討 |
Visual Studio Installer Projectsは、手軽にMSIインストーラーを作りたい という場面で最適な選択肢です。特にWindowsサービスの配布では、カスタムアクションと sc.exe を組み合わせることで、インストールからサービス登録までをワンクリックで完了できます。
この記事が、Windowsサービスの配布に悩んでいる方の参考になれば幸いです。