# 如何對 PowerShell 腳本檔案進行數位簽章

我個人寫過的 PowerShell 腳本可能有數百到上千支，數不清了，由於大部分的腳本都是自己個人使用為主，所以大多都不會特別對這些腳本進行數位簽章。但是對於金融業這種高度管制的企業或組織來說，其實 PowerShell 腳本是被嚴格禁止的，此時對你的 PowerShell 腳本進行數位簽章就顯的十分重要。除此之外，若你要發佈腳本給其他人使用，對腳本進行數位簽章也是一個很好的選擇，不但可以增加可信度，也可以確保腳本的完整性，不會被惡意竄改後重新散佈有問題的版本。這篇文章我將介紹如何對 PowerShell 腳本檔案進行數位簽章。

![the idea of digitally signing PowerShell scripts](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/7ae123053c45a60d44bc-b5aa03e2-0cc2-409c-99a0-f81485a5d8a9.webp)

每次在 PowerShell 使用 [GitHub Copilot CLI](https://docs.github.com/en/copilot/using-github-copilot/using-github-copilot-in-the-command-line) 的時候，他都經常會回我 `bash` 底下才有的命令，所以我最近設計了一支 [ghcs.ps1](https://gist.github.com/doggy8088/18ca8d450062d484d8ec8ad08cd7ad3f) 腳本，讓我在尋求 AI 建議時可以盡可能的以 PowerShell 為主，比較不會亂回答，實用性很高！👍

今天我就拿這個腳本來做數位簽章的示範！

### 新電腦使用 PowerShell 的起手式

這裡我以 Windows Sandbox 為例，快速打造一個全新的 Windows 環境，並且安裝好 PowerShell 7.x 版本。

```powershell
# 安裝 Chocolatey
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))

# 安裝 PowerShell 7.x, Windows Terminal, Git, GitHub CLI
choco install -y powershell-core microsoft-windows-terminal git gh

# 安裝 GitHub Copilot CLI
gh extension install github/gh-copilot

# 如果已經安裝過也可以用以下命令升級
gh extension upgrade gh-copilot

# 登入 GitHub 帳戶
gh auth login --web -h github.com
```

接著請下載 [ghcs.ps1](https://gist.github.com/doggy8088/18ca8d450062d484d8ec8ad08cd7ad3f) 腳本，並且放到你的 `$HOME\Documents\PowerShell\Scripts` 目錄下，若是全新的電腦，該目錄可能不存在，需要事先建立：

```powershell
mkdir $HOME\Documents\PowerShell\Scripts
```

> 注意: 這裡的 `$HOME` 是 PowerShell 的環境變數，代表你的家目錄，通常是 `C:\Users\你的使用者名稱`。

除此之外，你還可能需要設定一次 `PATH` 環境變數，讓你可以直接在 PowerShell 中執行 `ghcs` 這個指令：

### 理解 PowerShell 的執行原則

接著我們就可以開始執行 `ghcs.ps1` 這個腳本了，例如：

```powershell
ghcs How to list all files?
```

不過，我們若在 Windows 10 或 Windows 11 第一次執行 PowerShell，通常都會遇到以下錯誤：

> 因為這個系統上已停用指令碼執行，所以無法載入 C:\\Users\\XXX\\Documents\\PowerShell\\Scripts\\ghcs.ps1 檔案。如需詳細資訊，請參閱 about\_Execution\_Policies，網址為 https://go.microsoft.com/fwlink/?LinkID=135170。

我在授課 Angular 前端開發課程時，也常常有許多學員遇到這個問題，這是因為 Windows Client 預設的執行原則是 `Restricted`，也就是不允許執行任何 PowerShell 腳本，如下圖示：

![ng new](https://stwillblogassets.blob.core.windows.net/files/images/external/github.com/92e075f80ba441febfd1-1ec5560b-8f32-4e65-abbb-54d5f5765c47.jpg)

可以透過以下命令查看不同 Scope 底下的執行原則為何：

```powershell
Get-ExecutionPolicy -List
```

事實上，在不同的 Windows 版本中，執行原則有不同的預設值設定：

-   Windows Client: `Restricted` (允許執行個別命令，但不允許執行腳本)
-   Windows Server: `RemoteSigned` (允許執行本機編寫的腳本，但從遠端下載的腳本必須經過數位簽章)
-   Windows Sandbox: `Undefined` (當所有 Scope 都是 `Undefined` 時，預設會是 `Restricted` 限制執行)

> 只要你是透過 `Teams` 或 `Outlook` 下載的 PowerShell 腳本，該檔案在儲存到 NTFS 檔案系統時，就會自動幫你加上一些隱形標記。若要移除這些標記，可以透過 Windows 內建的 [Unblock-File](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/unblock-file?WT.mc_id=DT-MVP-4015686) Cmdlet 來解除封鎖。

由於 `Restricted` 直接從本機電腦的層級禁止了所有腳本執行，但你依然可以在 `CurrentUser` 這個各明確的 Scope 層級，將執行原則改成 `RemoteSigned`，這樣就可以執行自己寫的腳本了：

```powershell
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
```

因此，你若是寫了一支 PowerShell 腳本，不但想要在自己的電腦上執行，也希望可以讓更多人使用的話，當使用者將執行原則設定為 `RemoteSigned` 時，你的 PowerShell 腳本要是沒簽章，同時使用者又是從網路上下載你的腳本，那麼他們即便下載也會無法執行的。

> 注意: 建議不要鼓勵使用者調低 PowerShell 的執行原則，設定為 `Bypass` 的安全風險極高，因為這樣會讓所有的腳本都可以執行，包括從網路上下載的惡意腳本。

### 對 PowerShell 腳本檔案進行數位簽章

由於我公司每年都會購買 GlobalSign 的 EV Code Signing 憑證，所以我這裡就以 GlobalSign 為例，示範如何對 PowerShell 腳本檔案進行數位簽章。

1.  首先，你需要先安裝 GlobalSign 的驅動程式
    
    因為 GlobalSign 的 EV Code Signing 憑證是透過 USB Token 來進行簽章的，所以你需要先安裝 GlobalSign 的 [Safenet Drivers 驅動程式](https://support.globalsign.com/ssl/ssl-certificates-installation/safenet-drivers#Windows)，讓你的電腦可以讀取 USB Token。
    
    > 注意: 第一次拿到 USB Token 時，你需要先將 USB 進行初始化的操作，每個 USB Token 只需要設定一遍！
    
2.  接著，取得 GlobalSign 的 EV Code Signing 憑證
    
    因為 GlobalSign 的 EV Code Signing 憑證是透過 USB Token 來進行簽章的，你不能將憑證的金鑰匯出，所以你每次要進行簽章時，都需要將 USB Token 插在電腦上。然而，你必須先安裝好 [Safenet Drivers](https://support.globalsign.com/ssl/ssl-certificates-installation/safenet-drivers#Windows) 驅動程式，然後 USB 要插在電腦上，讓你的電腦可以讀取 USB Token 的資訊，並執行以下 PowerShell 命令：
    
    ```powershell
    $cert = Get-ChildItem -Path Cert:\CurrentUser\My -CodeSigningCert |
    Where-Object { $_.Thumbprint -like "*28A7F86E813C879989426E401947473D7FDB911E*" } |
    Select-Object -First 1
    ```
    
3.  簽署指定 PowerShell 腳本檔案
    
    ```powershell
    Set-AuthenticodeSignature -FilePath "$HOME\Documents\PowerShell\Scripts\ghcs.ps1" -Certificate $cert -TimestampServer "http://timestamp.globalsign.com/tsa/r6advanced1"
    ```
    
    > 簽署完畢後，會在檔案最下方出現一大堆像亂碼的簽章內容，這表示你的 PowerShell 腳本已經被數位簽章了。
    
4.  驗證簽章是否有效
    
    ```powershell
    Get-AuthenticodeSignature "$HOME\Documents\PowerShell\Scripts\ghcs.ps1"
    ```
    

如此一來，我們的 PowerShell 腳本就已經被數位簽章了，這樣就可以讓其他人在下載執行時，就可以用來確認你這個腳本是由你所簽署的，並且沒有被竄改過。

### 相關連結

-   [about\_Signing - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_signing?view=powershell-7.2&WT.mc_id=DT-MVP-4015686)
-   [about\_Execution\_Policies - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_execution_policies?WT.mc_id=DT-MVP-4015686)
-   [Unblock-File (Microsoft.PowerShell.Utility) - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/unblock-file?WT.mc_id=DT-MVP-4015686)
-   [Learn and understand all about Code Signing :: Learn and understand all about Code Signing :: GlobalSign Support](https://support.globalsign.com/code-signing/code-signing-faq)
