Windows Service(舊稱 NT Service)是在背景長期執行、無使用者介面、支援開機自動啟動的應用程式。本文手把手教學建立一個檔案監控服務,涵蓋 FileSystemWatcher、PerformanceCounter、專案安裝類別、installutil / PowerShell 部署、偵錯技巧與現代化替代方案。
FileMonitorService。ServiceBase 衍生類別 FileMonitorService 與 Program.cs。
using System;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.ServiceProcess;
namespace FileMonitorService
{
public partial class FileMonitorService : ServiceBase
{
private FileSystemWatcher _watcher;
private PerformanceCounter _createCounter, _deleteCounter, _renameCounter, _changeCounter;
private bool _paused;
public FileMonitorService()
{
InitializeComponent();
ServiceName = "FileMonitorService";
CanPauseAndContinue = true;
CanStop = true;
AutoLog = true; // 寫入 Windows 事件日誌
}
protected override void OnStart(string[] args)
{
_paused = false;
InitializeCounters();
InitializeWatcher();
EventLog.WriteEntry("檔案監控服務已啟動", EventLogEntryType.Information);
}
protected override void OnStop()
{
_watcher?.Dispose();
ResetCounters();
EventLog.WriteEntry("檔案監控服務已停止", EventLogEntryType.Information);
}
protected override void OnPause()
{
_paused = true;
_watcher.EnableRaisingEvents = false;
}
protected override void OnContinue()
{
_paused = false;
_watcher.EnableRaisingEvents = true;
}
private void InitializeCounters()
{
const string category = "File Monitor Service";
if (!PerformanceCounterCategory.Exists(category))
{
var ccds = new CounterCreationDataCollection
{
new CounterCreationData("Files Created", "建立檔案數", PerformanceCounterType.NumberOfItems32),
new CounterCreationData("Files Deleted", "刪除檔案數", PerformanceCounterType.NumberOfItems32),
new CounterCreationData("Files Renamed", "重命名檔案數", PerformanceCounterType.NumberOfItems32),
new CounterCreationData("Files Changed", "修改檔案數", PerformanceCounterType.NumberOfItems32)
};
PerformanceCounterCategory.Create(category, "檔案監控計數器", PerformanceCounterCategoryType.SingleInstance, ccds);
}
_createCounter = new PerformanceCounter(category, "Files Created", false) { ReadOnly = false };
_deleteCounter = new PerformanceCounter(category, "Files Deleted", false) { ReadOnly = false };
_renameCounter = new PerformanceCounter(category, "Files Renamed", false) { ReadOnly = false };
_changeCounter = new PerformanceCounter(category, "Files Changed", false) { ReadOnly = false };
}
private void ResetCounters()
{
foreach (var c in new[] { _createCounter, _deleteCounter, _renameCounter, _changeCounter })
{
if (c?.RawValue > 0) c.IncrementBy(-c.RawValue);
}
}
private void InitializeWatcher()
{
string path = ConfigurationManager.AppSettings["MonitorPath"] ?? @"C:\Monitor";
if (!Directory.Exists(path)) Directory.CreateDirectory(path);
_watcher = new FileSystemWatcher(path)
{
IncludeSubdirectories = true,
NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirName | NotifyFilters.LastWrite | NotifyFilters.Size,
EnableRaisingEvents = true
};
_watcher.Created += (s, e) => { if (!_paused) _createCounter.Increment(); };
_watcher.Deleted += (s, e) => { if (!_paused) _deleteCounter.Increment(); };
_watcher.Renamed += (s, e) => { if (!_paused) _renameCounter.Increment(); };
_watcher.Changed += (s, e) => { if (!_paused) _changeCounter.Increment(); };
_watcher.Error += (s, e) => EventLog.WriteEntry($"Watcher 錯誤: {e.GetException().Message}", EventLogEntryType.Error);
}
}
}
using System.ComponentModel;
using System.Configuration.Install;
using System.ServiceProcess;
[RunInstaller(true)]
public class ProjectInstaller : Installer
{
private ServiceProcessInstaller _processInstaller;
private ServiceInstaller _serviceInstaller;
public ProjectInstaller()
{
_processInstaller = new ServiceProcessInstaller { Account = ServiceAccount.LocalSystem };
_serviceInstaller = new ServiceInstaller
{
ServiceName = "FileMonitorService",
DisplayName = "檔案監控服務",
Description = "監控指定目錄檔案變更並記錄效能計數器",
StartType = ServiceStartMode.Automatic,
DelayedAutoStart = true // 延遲啟動,加快開機速度
};
Installers.AddRange(new Installer[] { _processInstaller, _serviceInstaller });
}
}
# 開發人員命令提示字元 (管理員)
cd bin\Debug
installutil FileMonitorService.exe
# 移除
installutil /u FileMonitorService.exe
# 發布單一檔案
dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true
# 使用 sc.exe 建立服務
sc.exe create FileMonitorService binPath= "C:\Deploy\FileMonitorService.exe" start= auto DisplayName= "檔案監控服務"
sc.exe start FileMonitorService
sc.exe stop FileMonitorService
sc.exe delete FileMonitorService
# 安裝
New-Service -Name "FileMonitorService" -BinaryPathName "C:\Deploy\FileMonitorService.exe" -DisplayName "檔案監控服務" -StartupType Automatic -Description "監控檔案變更"
Start-Service FileMonitorService
# 移除
Stop-Service FileMonitorService -Force
Remove-Service FileMonitorService
Program.cs 加入條件編譯:
static void Main()
{
#if DEBUG
var svc = new FileMonitorService();
svc.OnStart(null);
Console.WriteLine("按 Enter 停止...");
Console.ReadLine();
svc.OnStop();
#else
ServiceBase.Run(new FileMonitorService());
#endif
}
FileMonitorService 查看啟停/錯誤日誌。| 問題 | 原因 | 解決 |
|---|---|---|
| 服務啟動後立即停止 | OnStart 拋出未處理例外 | try-catch 包裹 OnStart,寫入 EventLog |
| FileSystemWatcher 漏事件 | 內部緩衝區溢位 (預設 8KB) | InternalBufferSize = 65536,避免密集操作目錄 |
| 網路磁碟機監控失效 | SMB 通知機制不穩 | 改用輪詢 或 FileSystemWatcher + 定時掃描補漏 |
| 效能計數器需管理員 | 建立 Category 需提權 | 安裝程式以管理員執行,或預先用腳本建立 Category |
| 單一檔案發布無法載入設定 | App.config 不會內嵌 | 改用 appsettings.json + ConfigurationBuilder |
IHostedService + BackgroundService,支援 DI、設定、日誌、優雅關機,跨平台 (Linux systemd / Docker)。BackgroundService 覆寫 ExecuteAsync。--restart=always 替代傳統 Windows Service。掌握 ServiceBase 生命週期、FileSystemWatcher 事件模型、PerformanceCounter 監測、安裝類別與部署指令,即可建立穩健的 Windows Service。新專案強烈建議採用 Generic Host (Worker Service),享受現代化 DI、設定、日誌生態,並保留未來跨平台遷移彈性。