Windows Service (Windows 服務) 是長期在後台運行、無使用者介面、支援開機自動啟動的系統級程式。.NET System.ServiceProcess 命名空間完整封裝了 SCM (Service Control Manager) 介面,讓 C# 開發者能以極低成本構建健壯的後台服務。本文從 **服務概念**、**開發基本流程**、**專案架構設計**、**命令列參數路由設計**、**資料庫結構**、**FileSystemWatcher 監視邏輯**、**單一 EXE 多模式啟動器** 完整鋪陳,為下集客戶端控制與部署奠基。
| 屬性 | 說明 |
|---|---|
| 服務名稱 | 唯一識別碼,net start/stop <名稱> 操作依據 |
| 啟動類型 | 自動 (開機啟動)、手動、已停用 |
| 登入身分 | LocalSystem (預設, 高權限, 無網路)、NetworkService、LocalService、指定使用者帳號 |
| 桌面互動 | 勾選「允許服務與桌面互動」可顯示 UI,**強烈不建議** (Session 0 隔離、安全風險) |
| 依賴關係 | 啟動前會自動啟動相依服務 (如 IIS 依賴 RPC) |
System.ServiceProcess.dll, System.Configuration.Install.dll。ServiceBase,設 ServiceName,覆寫 OnStart/OnStop/OnPause/OnContinue。**方法必須立即返回**,耗時工作另開執行緒。Installer,標記 [RunInstaller(true)],建構式加入 ServiceProcessInstaller (帳號) + ServiceInstaller (名稱/啟動類型/描述)。Main() 呼叫 ServiceBase.Run(new MyService())。InstallUtil.exe MyService.exe 註冊至 SCM;InstallUtil.exe /u MyService.exe 移除。FileSystemWatcher.mdb)。| 參數 | 模式 | 行為 |
|---|---|---|
| (無) | 服務 | ServiceBase.Run 進入 SCM 管控 |
| /install | 安裝 | 啟動 InstallUtil.exe 註冊自身 |
| /uninstall | 解除安裝 | 啟動 InstallUtil.exe /u 移除註冊 |
| /client | 客戶端 | 啟動 WinForm UI (Application.Run) |
| /debug | 除錯 | 直接實例化服務類別呼叫 StartFileSystemWatching,主執行緒 Sleep 方便 VS 斷點 |
RecordID Text(50) PK (GUID)
WatchTime Text(20) yyyy-MM-dd HH:mm:ss
ObjectName Text(250) 完整路徑
EventStyle Text(50) Created / Changed / Deleted / Renamed
ConfigName Text(50) PK
ConfigValue Text(250)
-- 範例資料
LogChanged True
LogCreated True
LogDeleted True
LogRenamed False
path0 C:\Data\*.txt
path1 D:\
SystemConfig → MyConfig.Instance。WatchedPaths,解析目錄與檔名過濾器:
C:\Data\*.txt → Path=C:\Data, Filter=*.txtD:\ → Path=D:\, Filter=*.*FileSystemWatcher,設 EnableRaisingEvents=true、IncludeSubdirectories=false (需遞迴改 true)。MyConfig 旗標動態綁定 Changed/Created/Deleted/Renamed 事件 → 統一呼叫 WriteLog 寫入資料庫。
private bool _serviceInstalled;
private void frmClient_Load(object sender, EventArgs e)
{
try
{
using (var sc = new ServiceController("MyFileSystemWatcher"))
{
var _ = sc.Status; // 嘗試讀取狀態,若服務未安裝會拋例外
_serviceInstalled = true;
}
}
catch
{
_serviceInstalled = false;
lblServiceStatus.Text = "服務尚未安裝";
btnStart.Enabled = btnStop.Enabled = false;
timer.Enabled = false;
MessageBox.Show(this, "服務尚未安裝,請先以 /install 安裝。");
}
btnRefresh_Click(null, null);
}
private void btnRefresh_Click(object sender, EventArgs e)
{
Cursor = Cursors.WaitCursor;
lvwLog.BeginUpdate();
try
{
lvwLog.Items.Clear();
using (var cmd = Util.DBConnection.CreateCommand())
{
cmd.CommandText = "SELECT RecordID, ObjectName, WatchTime, EventStyle FROM FileSystemLog ORDER BY WatchTime";
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
var item = new ListViewItem(reader.GetString(1)) // ObjectName
{
Tag = reader.GetString(0) // RecordID
};
item.SubItems.Add(reader.GetString(2)); // WatchTime
string style = reader.GetString(3); // EventStyle
item.SubItems.Add(style);
// 分組與圖示
style = style.ToLower();
if (style == "created") { item.Group = lvwLog.Groups[0]; item.ImageIndex = 0; }
else if (style == "changed") { item.Group = lvwLog.Groups[1]; item.ImageIndex = 1; }
else if (style == "deleted") { item.Group = lvwLog.Groups[2]; item.ImageIndex = 2; }
else if (style == "renamed") { item.Group = lvwLog.Groups[3]; item.ImageIndex = 3; }
item.StateImageIndex = item.ImageIndex;
lvwLog.Items.Add(item);
}
}
}
lblStatus.Text = $"共列出 {lvwLog.Items.Count} 筆記錄";
}
catch (Exception ex) { MessageBox.Show(ex.ToString(), "錯誤"); }
finally { Cursor = Cursors.Default; lvwLog.EndUpdate(); }
}
private void btnDelete_Click(object sender, EventArgs e)
{
if (lvwLog.SelectedItems.Count == 0) return;
using (var cmd = Util.DBConnection.CreateCommand())
{
for (int i = lvwLog.Items.Count - 1; i >= 0; i--)
{
var item = lvwLog.Items[i];
if (item.Selected)
{
cmd.CommandText = $"DELETE FROM FileSystemLog WHERE RecordID = '{item.Tag}'";
cmd.ExecuteNonQuery();
lvwLog.Items.Remove(item);
}
}
}
}
[STAThread]
static void Main(string[] args)
{
try
{
// 定位 InstallUtil.exe (在 .NET Framework 目錄下,與 mscorlib.dll 同層)
string runtimePath = Path.GetDirectoryName(typeof(string).Assembly.CodeBase.Replace("file:///", ""));
string installUtil = Path.Combine(runtimePath, "InstallUtil.exe");
foreach (string arg in args)
{
switch (arg.ToLower())
{
case "/install":
Process.Start(installUtil, $"\"{Application.ExecutablePath}\"");
return;
case "/uninstall":
Process.Start(installUtil, $"/u \"{Application.ExecutablePath}\"");
return;
case "/client":
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
using (var frm = new frmClient())
{
Application.Run(frm);
Util.CloseDBConnection();
}
return;
case "/debug":
var svc = new MyFileSystemWatcherService();
svc.StartFileSystemWatching();
Thread.Sleep(Timeout.Infinite); // 阻塞,保持服務邏輯在前景執行
return;
}
}
}
catch (Exception ex) { Console.WriteLine(ex); return; }
// 無參數 → 服務模式
ServiceBase.Run(new MyFileSystemWatcherService());
}
[RunInstaller(true)]
public partial class ProjectInstaller : Installer
{
public ProjectInstaller()
{
InitializeComponent();
// ServiceProcessInstaller: 帳號設定
var spi = new ServiceProcessInstaller { Account = ServiceAccount.LocalSystem };
// ServiceInstaller: 服務屬性
var si = new ServiceInstaller
{
ServiceName = "MyFileSystemWatcher",
DisplayName = "檔案系統監視服務",
Description = "監視指定目錄檔案變更並記錄至資料庫",
StartType = ServiceStartMode.Manual // 或 Automatic
};
Installers.AddRange(new Installer[] { spi, si });
}
}
上集完成了:
ServiceBase 衍生、Installer 註冊、SCM 概念釐清。InstallUtil 自動定位免硬編碼路徑。FileSystemLog, SystemConfig)。FileSystemWatcher 多實例、路徑解析、配置驅動事件綁定、匿名委託。下集將完善:系統設定對話框、MyConfig 單例持久化、服務生命週期完整實現、部署指令與常見問題排查。