# 介紹好用工具：優異的 nssm 服務管理員 (Non-Sucking Service Manager)

我現在安裝 Windows 服務都使用**功能完整**的 [nssm](https://nssm.cc/) 來管理，他除了有 CLI 命令列工具介面外，也同時有支援 GUI 圖形化介面，所以非常容易上手。今天這篇文章我打算來分享幾個常見的用法。

> 以下命令皆使用 **Command Prompt** (命令提示字元) (`cmd.exe`) 來執行。

### 關於 `nssm` 服務管理員

Windows 內建的各種註冊 Windows 服務的工具都非常難用([sc.exe](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/sc-create?WT.mc_id=DT-MVP-4015686))，不然就是功能太過陽春([New-Service](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/new-service?view=powershell-7.1&WT.mc_id=DT-MVP-4015686))，而且這些工具沒辦法幫你處理一些程式出錯的狀況，所有例外你都要在自己的程式處理，簡單來說，就是**爛** (`Sucks`)！ 😅

所以 `nssm` 出現了，這個名字非常諷刺，這四個字就是 **Non-Sucking Service Manager** 的意思！ 😄

`nssm` 會額外幫你監控服務執行的狀況，如果發生「異常例外」導致程式掛掉，程式自動重啟。即便程式寫成異常發生但「正常結束」，最後 Windows 服務管理員還是會幫你自動重啟。

### 使用 `nssm` 安裝並啟動 Windows 服務

1.  安裝 `nssm` 工具
    
    常看我文章的朋友應該都知道，我都是用 [Chocolatey](https://chocolatey.org/install) 來安裝軟體：
    
    ```powershell
    choco install nssm -y
    ```
    
    事實上 `nssm` 是免安裝的綠色軟體，而且只有 `nssm.exe` 單一執行就可以運行。只要從 [官網下載](https://nssm.cc/download) 壓縮檔回來，將 `nssm.exe` 解壓縮到 `C:\Windows` 底下就直接可以用了！
    
    > 注意: 使用 `nssm` 註冊服務時，它會自動在你的 **事件記錄器** 中註冊一個 名為 `NSSM` 的 **事件來源** (Event Source)。
    
2.  使用 GUI 介面安裝 Windows 服務
    
    請以系統管理員身份執行 Command Prompt (`cmd.exe`)，並使用以下命令執行安裝：
    
    ```bash
    nssm install MyService
    ```
    
    這個命令事實上會自動開啟 `nssm` 的 GUI 圖形化介面，非常容易上手好操作：
    
    ![nssm 的 GUI 圖形化介面](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/c823bf077b1bc4d5ea61-133371419-cc48e2cc-3a72-407e-b063-de7ee27d568b.png)
    
    設定好之後，按下 **Install service** 就會自動安裝成功！👍
    
    如果要移除 Windows 服務，也可以這樣執行，也一樣會跳出 GUI 介面：
    
    ```bash
    nssm remove MyService
    ```
    
3.  使用 CLI 介面安裝 Windows 服務
    
    以下就是一個最簡單的「免互動」安裝 Windows 服務命令：
    
    ```bash
    nssm install MyService "C:\EC.Services\訂單處理服務.exe"
    ```
    
    > 他會自動幫你設定應用程式路徑(`Application`)、工作目錄(`AppDirectory`)等參數，如果你的程式可以這樣執行，不需要其他**應用程式殼層**(AppShell)，例如 `dotnet.exe` 或 `java.exe` 等等。那麼，這確實是最簡單的建立服務方法。
    
    以下是「免互動」重新啟動 Windows 服務命令：
    
    ```bash
    nssm stop MyService
    nssm start MyService
    nssm restart MyService
    ```
    
    以下是「免互動」移除安裝 Windows 服務命令：
    
    ```bash
    nssm remove MyService confirm
    ```
    
4.  使用 CLI 介面安裝需要有 **應用程式殼層** (AppShell) 才能啟動的 Windows 服務
    
    這邊我說的是像 `dotnet.exe` 或 `java.exe` 這種類型的應用程式，.NET Core 就需要 `dotnet.exe MyService.dll` 來啟動，而 Java 的 `*.jar` 就需要執行 `java.exe -jar MyService.jar` 才能啟動這種。
    
    這種類型的應用程式，第一次透過 `nssm` 註冊服務的人，會很容易忘記設定 `AppDirectory` (工作目錄) 參數，導致應用程式很容易會啟動失敗！
    
    以下就是一個完整的範例，從安裝服務、設定參數到自動啟動，一口氣設定到好：
    
    ```batch
    nssm install MyService "C:\Program Files\Zulu\zulu-8\bin\java.exe"
    nssm set MyService AppDirectory "C:\Projects\MyService\target"
    nssm set MyService AppParameters "-jar MyService-0.0.1-SNAPSHOT.jar --server.port=8080"
    nssm set MyService AppStdout "C:\Projects\MyService\target\MyService-0.0.1-SNAPSHOT.log"
    nssm start MyService
    ```
    

### 認識 `nssm` 的服務執行參數

`nssm` 提供非常大量的參數可供設定，我也趁著這篇文章全部閱讀過一遍，這才發現 `nssm` 真的非常強大，果然是我心目中的王者！

以下我列出所有的服務執行參數，這些參數都可以對應到 `nssm` 的 GUI 圖形化介面上的頁籤，我會額外加上註解說明用途與預設值，詳細說明請見 [NSSM Usage](https://nssm.cc/usage) 文件！

> 你可以透過以下命令查詢 `nssm` 實際註冊到 Windows 服務之後的 `nssm` 服務執行參數為何。

-   Application
    
    ```batch
    REM 應用程式路徑
    REM 預設: 應用程式完整路徑 (C:\EC.Services\訂單處理服務.exe)
    nssm get MyService Application
    
    REM 工作目錄
    REM 預設: 應用程式所在目錄路徑 (C:\EC.Services)
    nssm get MyService AppDirectory
    
    REM 應用程式參數
    REM 預設: 無內容
    nssm get MyService AppParameters
    ```
    
-   Details
    
    ```batch
    REM 服務的顯示名稱 (顯示名稱可以跟服務名稱不同)
    REM 預設: 服務名稱 (MyService)
    nssm get MyService DisplayName
    
    REM 服務詳細描述
    REM 預設: 無內容
    nssm get MyService Description
    
    REM 服務啟動方式
    REM 預設: SERVICE_AUTO_START (自動啟動)
    nssm get MyService Start
    ```
    
-   Log on
    
    ```batch
    REM 服務啟動方式
    REM 預設: LocalSystem (本機系統)
    nssm get MyService ObjectName
    
    REM 服務啟動方式
    REM 預設: SERVICE_WIN32_OWN_PROCESS
    nssm get MyService Type
    ```
    
-   Dependencies
    
    ```batch
    REM 相依服務
    REM 預設: 無內容
    nssm get MyService DependOnService
    ```
    
-   Process
    
    ```batch
    REM 執行優先權
    REM 預設: 無內容
    nssm get MyService AppPriority
    
    REM 是否不要啟動一個隱藏的 Console 視窗來執行服務 (有些程式沒有 STDIN 輸入會掛掉)
    REM 預設: 0 (要啟動一個隱藏的 Console 視窗來接受 STDIN 輸入)
    nssm get MyService AppNoConsole
    
    REM 要用多少 CPU 核心執行 (CPU Affinity)
    REM 預設: All (所有 CPU 核心)
    nssm get MyService AppAffinity
    ```
    
-   Shutdown
    
    ```batch
    REM 判斷要用什麼方式傳遞訊號給應用程式
    REM 預設: 0 (代表什麼訊號都傳給應用程式)
    nssm get MyService AppStopMethodSkip
    
    REM 當送出 Control-C 的時候要等待幾豪秒進行優雅結束 (Gracefully Shutdown)
    REM 預設: 1500 豪秒
    nssm get MyService AppStopMethodConsole
    
    REM 當送出 WM_CLOSE 訊號的時候要等待幾豪秒進行優雅結束 (Gracefully Shutdown)
    REM 預設: 1500 豪秒
    nssm get MyService AppStopMethodWindow
    
    REM 當送出 WM_QUIT 訊號的時候要等待幾豪秒進行優雅結束 (Gracefully Shutdown)
    REM 預設: 1500 豪秒
    nssm get MyService AppStopMethodThreads
    ```
    
-   Exit actions
    
    ```batch
    REM 如果程式在幾豪秒之內就死掉就延遲啟動
    REM 預設: 1500 豪秒
    nssm get MyService AppThrottle
    
    REM 應用程式結束時的預設行為
    REM 預設: Restart (重啟服務)
    nssm get MyService AppExit Default
    
    REM 服務重啟的延遲啟動豪秒數
    REM 預設: 0 豪秒
    nssm get MyService AppRestartDelay
    ```
    
-   I/O
    
    ```batch
    REM 應用程式的 STDOUT 訊息要輸出到哪個檔案
    REM 預設: 無內容
    nssm get MyService AppStdout
    
    REM 應用程式的 STDERR 訊息要輸出到哪個檔案
    REM 預設: 無內容
    nssm get MyService AppStderr
    ```
    
-   File rotation
    
    ```batch
    REM 設定是否要對記錄檔進行自動 Rotate 處理
    REM 預設: 1
    nssm get MyService AppRotateFiles
    
    REM STDOUT 訊息紀錄檔的建立方式
    REM 預設: 4 (開啟現有的紀錄檔進行寫入)
    nssm get MyService AppStdoutCreationDisposition
    
    REM STDERR 訊息紀錄檔的建立方式
    REM 預設: 4 (開啟現有的紀錄檔進行寫入)
    nssm get MyService AppStderrCreationDisposition
    
    REM 服務在執行的時候也可以 Rotate 記錄檔
    REM 預設: 0 (預設要停用服務才會 Rotate 記錄檔)
    nssm get MyService AppRotateOnline
    
    REM 當記錄檔已經超過幾豪秒就自動 Rotate
    REM 預設: 86400
    nssm get MyService AppRotateSeconds
    
    REM 當記錄檔已經超過多少 Bytes 就自動 Rotate
    REM 預設: 1048576
    nssm get MyService AppRotateBytes
    ```
    
-   Environment
    
    ```batch
    REM 服務啟動前先設定好環境變數
    REM 預設: 無內容
    nssm get MyService AppEnvironmentExtra
    ```
    

### 注意「雙引號」傳遞應用程式參數可能造成的問題

在 Command Prompt 命令提示字元中，一直以來最被詬病的問題，就是「雙引號」的跳脫處理會讓整個命令變的可讀性極差，所以當你需要傳遞參數到服務應用程式時，需要傳入執行的參數包含「雙引號」，那就有地方要特別注意了！

假設你的應用程式參數必須包含**空白字元**，所以需要使用雙引號框起來：

1.  手動執行的寫法
    
    ```batch
    "C:\EC.Services\訂單處理服務.exe" "This is one argument"
    ```
    
2.  使用 `nssm` 的安裝命令
    
    ```batch
    nssm install "訂單處理服務" "C:\EC.Services\訂單處理服務.exe" """This is one argument"""
    ```
    

假設你要執行 Java 應用程式，參數可能很複雜，例如：

1.  手動執行的寫法
    
    ```batch
    REM 設定 JavaExe 變數不需要加上雙引號
    JavaExe=C:\Program Files\Zulu\zulu-8\bin\java.exe
    
    "%JavaExe%" -Dsolr.solr.home="%CD%\solr" -Djetty.home="%CD%" -Djetty.logs="%CD%\logs" -cp "%CD%\lib\*.jar";"%CD%\start.jar" -jar "%CD%\start.jar"
    ```
    
    > 使用 `%CD%` 可以取得當前目錄的路徑！
    
2.  使用 `nssm` 的安裝命令
    
    ```batch
    REM 設定 JavaExe 變數不需要加上雙引號
    JavaExe=C:\Program Files\Zulu\zulu-8\bin\java.exe
    
    nssm install solr "%JavaExe%" -Dsolr.solr.home="\"%CD%\solr"\" -Djetty.home="\"%CD%"\" -Djetty.logs="\"%CD%\logs"\" -cp "\"%CD%\lib\*.jar"\";"\"%CD%\start.jar"\" -jar "\"%CD%\start.jar"\"
    ```
    
    > 你看看，這段批次檔多醜啊！ 😅
    

### 注意設定環境變數的寫法

在服務啟動之前設定好環境變數，這應該是我最愛 `nssm` 的功能之一了！

架設我希望在執行 `MyService` 服務之前，先給定三個環境變數，分別是 `A=1`, `B=2`, `C=3` 等等，那麼你應該在建立好服務且尚未啟動服務之前，先這樣設定好環境變數：

```batch
nssm set MyService AppEnvironmentExtra "A=1" "B=2" "C=3"
```

### 相關連結

-   [NSSM - the Non-Sucking Service Manager](https://nssm.cc/)
-   [Chocolatey Software | NSSM - the Non-Sucking Service Manager](https://community.chocolatey.org/packages/nssm)
-   [New-Service (Microsoft.PowerShell.Management) - PowerShell | Microsoft Docs](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/new-service?view=powershell-7.1&WT.mc_id=DT-MVP-4015686)
-   [sc.exe create | Microsoft Docs](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/sc-create?WT.mc_id=DT-MVP-4015686)
