Windows 服務 (Windows Service) 是在背景長期執行、無使用者介面、可開機自動啟動的應用程式。Visual Studio 提供 **Windows Service 專案範本**,讓 C# 開發者輕鬆建立。本文從零開始,涵蓋專案建立、核心程式碼、安裝程式、命令列安裝/解除安裝、除錯技巧與常見坑點。
MyWindowsService.NET Framework 專案產生ServiceBase繼承類別;.NET Core Worker Service 使用BackgroundService,本文以 **傳統 .NET Framework** 為主,最後補充 Worker Service 差異。
using System;
using System.IO;
using System.ServiceProcess;
namespace MyWindowsService
{
public partial class Service1 : ServiceBase
{
private readonly string _logPath = @"D:\Logs\MyService.log";
private Timer _timer;
public Service1()
{
InitializeComponent();
// 服務屬性設定 (也可在設計師屬性視窗設定)
this.ServiceName = "MyCustomService";
this.CanStop = true;
this.CanPauseAndContinue = true;
this.AutoLog = true; // 自動寫入系統事件日誌
}
protected override void OnStart(string[] args)
{
WriteLog("服務啟動");
// 範例:每 30 秒執行一次工作
_timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(30));
}
protected override void OnStop()
{
WriteLog("服務停止");
_timer?.Dispose();
}
protected override void OnPause()
{
WriteLog("服務暫停");
_timer?.Change(Timeout.Infinite, Timeout.Infinite);
}
protected override void OnContinue()
{
WriteLog("服務繼續");
_timer?.Change(TimeSpan.Zero, TimeSpan.FromSeconds(30));
}
protected override void OnShutdown()
{
WriteLog("系統關機,服務關閉");
_timer?.Dispose();
}
private void DoWork(object state)
{
try
{
WriteLog($"背景工作執行: {DateTime.Now}");
// 實際業務邏輯:資料庫同步、檔案處理、API 呼叫等
}
catch (Exception ex)
{
WriteLog($"工作錯誤: {ex}");
// 可選:EventLog.WriteEntry("MyCustomService", ex.ToString(), EventLogEntryType.Error);
}
}
private void WriteLog(string message)
{
try
{
Directory.CreateDirectory(Path.GetDirectoryName(_logPath));
File.AppendAllText(_logPath, $"[{DateTime:yyyy-MM-dd HH:mm:ss}] {message}\r\n");
}
catch { /* 忽略日誌錯誤,避免影響主流程 */ }
}
}
}
Service1.cs 設計檢視 (雙擊開啟設計師)。ProjectInstaller.cs,包含兩個元件:
serviceProcessInstaller1 (ServiceProcessInstaller)serviceInstaller1 (ServiceInstaller)| 元件 | 屬性 | 建議值 |
|---|---|---|
| serviceProcessInstaller1 | Account | LocalSystem (最高權限)、NetworkService、User (需帳密) |
| serviceInstaller1 | ServiceName | MyCustomService (需與 ServiceBase.ServiceName 一致) |
| serviceInstaller1 | DisplayName | 我的自訂服務 (服務管理員顯示名稱) |
| serviceInstaller1 | Description | 定期同步資料的背景服務 |
| serviceInstaller1 | StartType | Automatic (開機自動)、Manual、Disabled |
bin\Debug\MyWindowsService.exe
# 開啟「Visual Studio 開發人員命令提示字元」 (系統管理員)
cd D:\Projects\MyWindowsService\bin\Debug
# 安裝
installutil MyWindowsService.exe
# 解除安裝
installutil /u MyWindowsService.exe
InstallUtil 版本對應:
.NET 4.x:C:\Windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe
.NET 2.0:C:\Windows\Microsoft.NET\Framework64\v2.0.50727\InstallUtil.exe
# 建立服務 (binPath 需完整路徑,含引號)
sc create MyCustomService binPath= "D:\Projects\MyWindowsService\bin\Debug\MyWindowsService.exe" start= auto DisplayName= "我的自訂服務"
# 啟動
sc start MyCustomService
# 停止
sc stop MyCustomService
# 刪除
sc delete MyCustomService
New-Service -Name "MyCustomService" -BinaryPathName "D:\...\MyWindowsService.exe" -DisplayName "我的自訂服務" -StartupType Automatic
Start-Service "MyCustomService"
Remove-Service "MyCustomService" # 需先停止
MyWindowsService.exe,點擊附加。
static void Main()
{
if (Environment.UserInteractive) // 偵錯模式:直接在 Console 執行
{
var service = new Service1();
service.OnStart(null); // 手動呼叫
Console.WriteLine("服務執行中... 按 Enter 停止");
Console.ReadLine();
service.OnStop();
}
else
{
ServiceBase.Run(new ServiceBase[] { new Service1() });
}
}
EventLog.WriteEntry("MyCustomService", "啟動完成", EventLogEntryType.Information);
EventLog.WriteEntry("MyCustomService", ex.ToString(), EventLogEntryType.Error);
// Program.cs
public class Program
{
public static void Main(string[] args) => CreateHostBuilder(args).Build().Run();
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseWindowsService() // 關鍵:支援 Windows Service 生命週期
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService();
});
}
// Worker.cs
public class Worker : BackgroundService
{
private readonly ILogger _logger;
public Worker(ILogger logger) => _logger = logger;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker 執行: {time}", DateTimeOffset.Now);
await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
}
}
}
dotnet publish -c Release -r win-x64 --self-contained truesc create 或 InstallUtil (需 .NET Framework 相容),或使用 dotnet-service 工具。| 問題 | 原因 | 解決 |
|---|---|---|
| 服務啟動後立即停止 | OnStart 拋出未處理例外 | try-catch 包裝 OnStart、寫入事件日誌、Attach to Process 除錯 |
| 無法寫入檔案/登錄表 | LocalSystem 權限不足或路徑錯誤 | 確認路徑存在、使用完整路徑、檢查資料夾權限 |
| 安裝失敗 "Access denied" | 非管理員執行命令列 | 以系統管理員身分開啟 CMD/PowerShell |
| 服務名稱衝突 | 已有同名服務 | sc delete 移除舊服務、更換 ServiceName |
| 定時器不執行 | Timer 被 GC 回收 | 將 Timer 宣告為類別欄位,防止被回收 |
建立 Windows Service 關鍵在於:**繼承 ServiceBase 實作 OnStart/OnStop、加入安裝程式設定 StartType/Account、以管理員身分用 InstallUtil/sc.exe 安裝、用 Attach to Process 或 Console 模擬除錯**。掌握此流程,即可將任何背景作業 (排程同步、訊息佇列消費者、檔案監視、API 輪詢) 封裝為穩定的系統服務。