用 PSScriptAnalyzer 守護 PowerShell 腳本的品質 ── 規則挑選與 CI 導入

· · PowerShell, 靜態分析, CI/CD, GitHub Actions, 品質管理, 維護性, 維運改善, 腳本

公司內部的 PowerShell 腳本一多起來,必然會出現「品質參差不齊」的問題。滿是別名而難以閱讀的腳本、寫著明文密碼的腳本、打錯字的變數名稱在無人察覺的情況下留存下來的腳本。當寫下這些程式碼的人已經不在時,問題就會以「讀的人很困擾」的形式浮現出來

這類問題有相當一部分可以用靜態分析工具機械式地檢測出來。PowerShell 有官方的靜態分析模組 PSScriptAnalyzer,只要一行指令就能分析整批腳本。完全不用寫一行測試程式碼,第一天就能看到效果,這是它最大的優點。

本文將依「應優先啟用的規則」「對既有資產的階段性導入」「在 CI 中自動檢查」的順序,整理把 PSScriptAnalyzer 導入公司內部腳本資產的實務步驟。關於用測試來確保品質,請一併參考「用 Pester 整備 PowerShell 測試」。

目標讀者・前提環境

項目 內容
目標讀者 想開始機械式地管理公司內部日益增加之 PowerShell 腳本品質的資訊系統・開發負責人
執行環境 PSScriptAnalyzer 在 Windows PowerShell 5.1 與 PowerShell 7 上都能執行。要分析的腳本是針對 5.1 還是 7,與執行分析那一端的版本無關,是用 PSUseCompatibleSyntax 另外指定的(第4章)
CI 的前提 第7章的 CI 範例是以 GitHub Actions 的 windows-latest runner 為前提。呼叫 Invoke-ScriptAnalyzer 的部分在其他 CI 上也一樣,分析本身在非 Windows 的 runner 上也能執行
所需權限 導入時使用 Install-Module -Scope CurrentUser,因此不需要管理員權限
範例的驗證環境 文末提供下載的範例程式碼,是以 PowerShell 7.6 執行驗證的

1. 先講結論

  • PSScriptAnalyzer 是 PowerShell 官方的靜態分析模組。Invoke-ScriptAnalyzer 分析腳本或模組,並回報違反規則的項目。1
  • 指摘帶有重大度(Severity)。分為 Error / Warning / Information 三個等級,先把 Error 歸零,是現實可行的出發點。1
  • 設定集中放在 PSScriptAnalyzerSettings.psd1 中。Severity IncludeRules ExcludeRules Rules 放在儲存庫裡,讓所有人都能用同一套基準來分析。2
  • 個別抑制用 SuppressMessageAttribute + 理由說明。在整條規則排除之前,先評估縮小範圍的抑制是否已經足夠。3
  • 有些指摘可以用 -Fix 自動修正。整形則由 Invoke-Formatter 負責。14
  • VS Code 的 PowerShell 擴充功能內建了 PSScriptAnalyzer。編輯時當場就會出現警告,比 CI 更早發揮作用。5
  • CI 的失敗條件設為「重大度 Error + 指名的重大規則」。重大度是依規則各自決定的,明文密碼的偵測(PSAvoidUsingPlainTextForPassword)是 Warning。如果只把 Error 當作條件,就會蒙混過關。6
  • 既有資產採階段性導入。依「先把 Error 歸零」→「僅對變更檔案嚴格檢查」→「逐步擴大範圍」的順序進行。

2. 導入 ── 先從一行指令開始

Install-Module -Name PSScriptAnalyzer -Scope CurrentUser

# 一次分析整個資料夾底下的內容
Invoke-ScriptAnalyzer -Path 'D:\Scripts' -Recurse |
    Sort-Object Severity, RuleName |
    Format-Table Severity, RuleName, ScriptName, Line, Message -AutoSize

# 掌握依重大度分類的件數(盤點的第一步)
Invoke-ScriptAnalyzer -Path 'D:\Scripts' -Recurse |
    Group-Object Severity | Select-Object Name, Count

先執行這兩行,用數字掌握自家資產目前處於什麼狀態。就算出現好幾百件也不用驚訝,大多數現場一開始都是這樣。

會回傳什麼。Invoke-ScriptAnalyzer 會把每一件指摘當作一個物件回傳,具有 Severity RuleName ScriptName Line Message 等屬性。第一個指令會依重大度順序,把「哪個檔案的第幾行、觸犯了哪條規則、為什麼會被抓到」逐行列出來。第二個指令只回傳 Name(Error / Warning / Information)與 Count 兩個欄位,請先把這幾行數字記下來。階段性導入(第6章)是否有進展,就是用這些數字的變化來衡量的。如果一件指摘都沒有,兩者都不會顯示任何內容(輸出為空 = 合格)。

可用規則的清單與說明,可以用 Get-ScriptAnalyzerRule 確認。1

Get-ScriptAnalyzerRule | Select-Object Severity, RuleName, CommonName | Sort-Object Severity
Get-ScriptAnalyzerRule -Name PSAvoidUsingWriteHost | Format-List *   # 個別規則的說明

3. 最先見效的指摘 ── 實務上的優先順序

在數十條規則之中,依優先順序列出與公司內部腳本品質直接相關的規則。

規則 重大度 檢測什麼 為什麼重要
PSAvoidUsingPlainTextForPassword Warning 用參數接收明文密碼 認證資訊以明文保存,在稽核中也會被指出6
PSAvoidUsingConvertToSecureStringWithPlainText Error 從明文建立 SecureString 與上一項同源,會讓加密失去意義
PSUseDeclaredVarsMoreThanAssignments Warning 已被賦值但從未使用過的變數 能檢測出變數名稱的打字錯誤,是實質意義上的錯誤檢測
PSAvoidUsingInvokeExpression Warning 使用 Invoke-Expression 會把字串當成程式碼執行,是注入攻擊的溫床
PSUseShouldProcessForStateChangingFunctions Warning 會改變狀態的函式卻沒有 -WhatIf 檢測出無法事前確認危險操作的設計
PSAvoidUsingCmdletAliases Warning ls % ? 等別名 在互動操作時方便,但在腳本中會損害可讀性
PSUseApprovedVerbs Warning 使用未經核准的動詞的函式名稱 不符合 Get-/Set- 等慣例的話很難被發現
PSAvoidGlobalVars Warning 使用全域變數 會讓副作用難以掌握,也無法撰寫測試
PSUseSingularNouns Warning 複數形名詞(如 Get-Users) PowerShell 的命名慣例,取讓別人能推測到的名稱

請看重大度那一欄。9 條規則中只有 1 條是 Error,其餘全部是 Warning7 連明文密碼的偵測(PSAvoidUsingPlainTextForPassword)都是 Warning,所以如果把 CI 的失敗條件設為「僅限重大度 Error」,這張表裡大部分的規則都會被蒙混過去。重大度是依規則各自決定的,因此不論重大度都想擋下的規則,要用規則名稱來指定(第6章・第7章)。想在本機確認的話,可以執行 Get-ScriptAnalyzerRule | Select-Object Severity, RuleName

尤其 PSUseDeclaredVarsMoreThanAssignments 是投資報酬率很高的指摘。像是原本打算賦值給 $fileName,後面卻誤打成 $fileNmae 這種打字錯誤,可以用「已賦值卻未被使用的變數」抓出來,確實能捕捉到只有靜態分析才找得到的錯誤(順帶一提,PowerShell 的變數名稱不區分大小寫,所以 $fileName$filename 是同一個變數。這種檢測能抓到的,是拼字本身就不同的情況)。

4. 用設定檔固定團隊基準

如果每個人各用各的基準來分析,就沒有意義了。把 PSScriptAnalyzerSettings.psd1 放進儲存庫,讓所有人和 CI 都使用相同的設定。2

# PSScriptAnalyzerSettings.psd1
@{
    # 使用預設的規則集
    IncludeDefaultRules = $true

    # 階段性導入的第1階段先限定在 Error 與 Warning
    Severity = @('Error', 'Warning')

    # 依公司方針目前先暫緩的規則(理由以註解留存)
    ExcludeRules = @(
        'PSAvoidUsingWriteHost'          # 多數是互動式工具,目前先容許
        'PSUseSingularNouns'             # 因為無法一次改完既有的函式名稱
    )

    # 各規則的詳細設定
    Rules = @{
        PSUseCompatibleSyntax = @{
            # 檢查需要同時在 5.1 與 7 上執行的腳本群組。
            # TargetVersions 只能指定規則本身有語法定義的版本
            # (可用 Get-ScriptAnalyzerRule 確認)。若寫入未支援的值,
            # 讀取設定時會發生錯誤,請注意
            Enable         = $true
            TargetVersions = @('5.1', '7.0')
        }
        PSPlaceOpenBrace = @{
            Enable             = $true
            OnSameLine         = $true
            NewLineAfter       = $true
            IgnoreOneLineBlock = $true
        }
        PSUseConsistentIndentation = @{
            Enable          = $true
            IndentationSize = 4
            Kind            = 'space'
        }
    }
}
Invoke-ScriptAnalyzer -Path . -Recurse -Settings .\PSScriptAnalyzerSettings.psd1

這個設定檔,VS Code 的擴充功能也會讀取。PowerShell 擴充功能的設定 powershell.scriptAnalysis.settingsPath 預設值就是 PSScriptAnalyzerSettings.psd1,所以只要用這個名稱放在儲存庫的根目錄下,編輯時出現的警告與 CI 的判定基準就會自動一致8 如果要用別的名稱或放在子資料夾裡,請在這個設定中明確指定路徑。這裡如果沒對齊,就會發生「本機什麼都沒出現,CI 卻失敗」的情況,好不容易做到的即時回饋也會失去信任。

PSUseCompatibleSyntax5.1 與 7 混用的環境中特別有用。它能在執行前就檢測出把 7 專用語法(三元運算子或管線鏈結運算子等)誤寫進 5.1 適用腳本裡的事故。遷移方針本身請參考「Windows PowerShell 5.1 與 PowerShell 7 的差異」。

5. 例外要「附上理由」保留下來

對於怎麼樣都無法遵從的指摘,不要停用整條規則,而是只在那個地方抑制3

function Show-KsBanner {
    # 目的是互動式工具的裝飾顯示,因此刻意使用 Write-Host
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSAvoidUsingWriteHost', '',
        Justification = '僅供互動執行使用的顯示函式。設計上不回傳值')]
    [CmdletBinding()]
    param([string] $Title)

    Write-Host ('=' * 60) -ForegroundColor Cyan
    Write-Host $Title -ForegroundColor Cyan
}

務必寫上 Justification,這是重點。沒有理由的抑制,對之後閱讀的人來說,會分不清這只是「單純把警告消掉了」而已。這就像是程式碼上的 ADR(決策紀錄)一樣,這種想法與「在小團隊中使用 ADR(架構決策紀錄)」相通。

6. 對既有資產的階段性導入

面對數百件警告,如果想著「全部修完才導入」,多半會半途而廢。要分階段進行。

第1階段:止血(1天) 只把 Severity = 'Error' 的項目放進 CI,把它歸零。這裡要注意的是,重大度是依規則各自決定的,不見得符合直覺。例如 PSAvoidUsingPlainTextForPassword 的重大度是 Warning,如果只把 Error 當作失敗條件,就不會被抓到。6 像認證資訊相關這種「不論重大度都想擋下」的規則,要像下面這樣用規則名稱明確加入失敗條件。

# 先取得解析結果
$issues = Invoke-ScriptAnalyzer -Path . -Recurse -Settings .\PSScriptAnalyzerSettings.psd1

# 把重大度 Error + 個別指定的重大規則,設為 CI 的失敗條件
$mustFix = @(
    'PSAvoidUsingPlainTextForPassword'
    'PSAvoidUsingConvertToSecureStringWithPlainText'
    'PSAvoidUsingUsernameAndPasswordParams'
)
$blocking = $issues | Where-Object { $_.Severity -eq 'Error' -or $_.RuleName -in $mustFix }

第2階段:守護新增與變更的部分(1週) 只把有變更的檔案當作分析對象。既有的負債即使先放著不管,也能阻止新問題的增加

如果要在 CI 中執行,必須先取得作為比較對象的提交actions/checkout 預設只會取得 1 個提交,所以請指定 fetch-depth: 0,或明確地 fetch 基準分支(不這麼做的話,會因為 unknown revision 而失敗)。

還有一點,請不要把比較對象固定為 maingit diff A...HEAD 的意思是「A 與 HEAD 的共同祖先之後的差異」,因此如果對指向 develop 或發佈分支的 PR 使用 origin/main...HEAD,就會把該 PR 沒有動過的變更也納入分析對象,導致無關檔案的既有指摘讓 CI 失敗。在 GitHub Actions 中,PR 的目標分支會放在 GITHUB_BASE_REF 裡,請使用這個變數。9

此外,請務必偵測 git 的失敗。PowerShell 預設情況下,即使外部指令回傳非零的結束代碼,也不會變成終止錯誤。10 因此,在尚未取得基準 ref 的狀態下,git diff 會失敗、只是輸出變成空白,後續的處理就會解讀成「沒有變更的檔案」,結果一個檔案都沒分析,CI 就變成綠燈。這是差異檢查中最危險的一種壞法。請在執行後立刻檢查 $LASTEXITCODE,明確地讓流程失敗(PowerShell 7.3 以後,也可以設定 $PSNativeCommandUseErrorActionPreference = $true 這種做法)。10

      - uses: actions/checkout@v4
        with:
          fetch-depth: 0        # 要取得差異就需要完整歷史
# 只解析有變更的 ps1/psm1(CI 中的差異檢查)
# 不要把比較對象固定為 main。若是指向 develop 或發佈分支的 PR,
# 與 main 的差異會混入無關的變更,導致沒有動過的檔案也讓 CI 失敗。
# PR 的目標分支可從 GITHUB_BASE_REF 取得(push 時為空)
$base = if ($env:GITHUB_BASE_REF) { "origin/$($env:GITHUB_BASE_REF)" } else { 'origin/main' }

# 若不加上 -c core.quotePath=false,含有日文等字元的路徑會
# 以類似 "scripts/\346..." 這種加上引號+八進位跳脫的形式回傳,導致無法判斷副檔名
$diff = git -c core.quotePath=false diff --name-only "$base...HEAD"

# 原生指令的失敗,在預設情況下不會變成終止錯誤。若尚未取得基準 ref,
# git 就會失敗、輸出變成空白,於是被當成「無變更 = 解析對象為零 = 合格」而蒙混過關。
# 緊接著檢查 $LASTEXITCODE,明確地讓流程失敗
if ($LASTEXITCODE -ne 0) {
    throw "git diff 失敗了 (exit $LASTEXITCODE)。可能尚未取得基準分支 $base"
}

$changed = $diff |
    Where-Object { $_ -match '\.ps(m|d)?1$' } |   # 以 .ps1 / .psm1 / .psd1 為對象
    Where-Object { Test-Path $_ }

# -Path 是只接受單一路徑的參數,如果直接傳入陣列,
# 會在參數繫結時失敗。因此逐一分析每個檔案,再彙整結果
$issues = foreach ($file in $changed) {
    Invoke-ScriptAnalyzer -Path $file -Settings .\PSScriptAnalyzerSettings.psd1
}

第3階段:擴大範圍(持續進行)ExcludeRules 一項一項移除,依處理進度逐步變嚴格。趁著重構的機會修正既有檔案,逐步減少負債。

能自動修正的指摘,可以用 -Fix 一次處理(套用前務必確認差異)。1 如果只是要整形,可以用 Invoke-Formatter4

Invoke-ScriptAnalyzer -Path .\Scripts -Recurse -Fix -Settings .\PSScriptAnalyzerSettings.psd1
git diff        # 務必用肉眼確認變更了什麼

7. 用 CI 自動化

如果是 GitHub Actions,在 Windows runner 上只要幾行就能完成。重點是讓 Error 失敗,Warning 只做顯示

name: powershell-lint

on:
  pull_request:
    paths: ['**/*.ps1', '**/*.psm1', '**/*.psd1']

jobs:
  analyze:
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0        # 若要切換成差異解析則需要此設定(第6章)

      - name: Install PSScriptAnalyzer
        shell: pwsh
        run: |
          Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
          Install-Module PSScriptAnalyzer -Scope CurrentUser -Force

      - name: Analyze
        shell: pwsh
        run: |
          $issues = Invoke-ScriptAnalyzer -Path . -Recurse `
                    -Settings ./PSScriptAnalyzerSettings.psd1

          # 把全部結果輸出到記錄(讓警告也能被看到)
          $issues | Sort-Object Severity, ScriptName, Line |
              Format-Table Severity, RuleName, ScriptName, Line, Message -AutoSize |
              Out-String -Width 200 | Write-Host

          # 失敗條件 = 重大度 Error + 不論重大度都不容許的規則
          $mustFix = @(
              'PSAvoidUsingPlainTextForPassword'
              'PSAvoidUsingConvertToSecureStringWithPlainText'
              'PSAvoidUsingUsernameAndPasswordParams'
          )
          $blocking = @($issues | Where-Object { $_.Severity -eq 'Error' -or $_.RuleName -in $mustFix })
          $warns    = @($issues | Where-Object Severity -eq 'Warning')
          Write-Host "Blocking: $($blocking.Count) / Warning: $($warns.Count)"

          # 隨著階段性導入的推進,之後也把 Warning 納入條件
          if ($blocking.Count -gt 0) {
              throw "有 $($blocking.Count) 件需要修正的指摘"
          }

如果把它和 Pester 的測試整合到同一個工作流程,就能建立出「lint 通過 → 測試通過 → 可以合併」的流程。Windows 應用程式整體的 CI/CD 建置方式,請參考「WinForms / WPF 應用程式的 CI/CD 實踐」。

即使沒有 CI 環境,只要每個月執行一次 Invoke-ScriptAnalyzer 並把結果留存為 CSV,資產狀態也能得到充分的可視化。

Invoke-ScriptAnalyzer -Path '\\fileserver\scripts' -Recurse |
    Select-Object Severity, RuleName, ScriptName, Line, Message |
    Export-Csv "D:\盤點\lint_$(Get-Date -f yyyyMM).csv" -Encoding utf8BOM -NoTypeInformation

8. 實務的定石(判斷表)

論點 選項 判斷依據
導入順序 從 Pester 開始 / 從 PSScriptAnalyzer 開始 靜態分析不用寫測試,第一天就能見效
最初的對象 全部規則 / Severity=Error + 明確指名認證資訊相關規則 把全部規則都當作條件的話,誰都過不了。要注意重大度是依規則各自決定的6
既有的大量警告 全部修完 / 僅對變更檔案嚴格檢查 先阻止增加,再找機會逐步減少
個別的例外 ExcludeRules / SuppressMessageAttribute + Justification 把影響範圍降到最小,務必留下理由3
設定的共享 各自的設定 / 儲存庫中的 .psd1 讓 CI 與開發者的基準一致2
5.1 與 7 混用 執行後確認 / PSUseCompatibleSyntax 在執行前就檢測出語法層級的不相容
自動修正 手動處理 / -Fix + 確認差異 套用後務必查看 git diff1
編輯時的回饋 只靠 CI / VS Code 擴充功能 當場就能修正,是成本最低的方式5

9. 總結

  • PSScriptAnalyzer 是官方的靜態分析模組,不用寫測試就能導入,第一天就能見效。
  • 先把 Error 歸零,接著只對變更檔案嚴格檢查,這種階段性導入是現實可行的做法。重大度是依規則各自決定的,因此像明文密碼(Warning)這類想擋下的規則,要用規則名稱加入失敗條件。
  • 有些規則,像是 PSUseDeclaredVarsMoreThanAssignments,能抓到變數名稱打字錯誤這種實際存在的錯誤。
  • 設定集中放在 PSScriptAnalyzerSettings.psd1 並放進儲存庫,讓開發者與 CI 的基準一致。
  • 例外要在 SuppressMessageAttribute 中寫下理由再保留。整條規則排除是最後的手段。
  • CI 中讓 Error 失敗,Warning 做可視化。整合進與 Pester 相同的工作流程,就能把品質的把關集中在一個地方。

範例程式碼下載

本文所提到的程式碼,已整理成可以直接執行的形式提供下載。內含設定檔、CI 合格判定腳本、GitHub Actions 的範例。

下載範例程式碼(zip)

本文的範例是以 PowerShell 7.6 實際執行驗證過的(Pester 14 件)。只要執行 zip 中所附的 Invoke-SampleTests.ps1,您也能在自己的環境中重現相同的驗證。

# 語法解析 + 靜態分析 + Pester 測試
./Invoke-SampleTests.ps1

設定值(路徑、伺服器名稱、租用戶 ID 等)僅為範例。請勿直接在正式環境中執行,請依自家環境調整後再使用。

相關文章

相關諮詢領域

合同會社小村軟體處理公司內部腳本資產的盤點與品質基準制定、靜態分析・測試的 CI 導入、以及依賴特定人員之維運腳本的維護性改善。

參考連結

</content>

  1. Microsoft Learn, PSScriptAnalyzer 模組概觀. 關於 PSScriptAnalyzer 是針對 PowerShell 腳本・模組的靜態分析工具、透過 Invoke-ScriptAnalyzer 進行分析以及 -Path / -Recurse / -Settings / -Fix / -ExcludeRule 等參數、用 Get-ScriptAnalyzerRule 取得規則清單、診斷結果具有重大度(Error / Warning / Information)。  2 3 4 5 6

  2. Microsoft Learn, PSScriptAnalyzer 的設定檔. 關於可以在設定檔(.psd1)中指定 Severity・IncludeRules・ExcludeRules・IncludeDefaultRules・Rules 等、可以用 -Settings 參數傳入設定檔、各規則的詳細設定(PSUseCompatibleSyntax 的 TargetVersions 或整形相關規則的選項)。  2 3

  3. Microsoft Learn, PSScriptAnalyzer 的規則抑制. 關於可以用 System.Diagnostics.CodeAnalysis.SuppressMessageAttribute 依規則單位・對象單位抑制診斷、RuleName・Target・Justification 各引數。  2 3

  4. Microsoft Learn, Invoke-Formatter. 關於依設定整形腳本文字、可以在設定檔中指定整形規則(縮排、開頭大括號的位置、空白的處理方式等)。  2

  5. Microsoft Learn, 在 Visual Studio Code 中使用 PowerShell. 關於 PowerShell 擴充功能會利用 PSScriptAnalyzer 在編輯中顯示警告、並提供格式設定功能。  2

  6. Microsoft Learn, AvoidUsingPlainTextForPassword. 關於不應該用明文字串型別的參數接收密碼或機密資訊,而應該使用 SecureString 或 PSCredential;以及這條規則的重大度(Severity Level)為 Warning 且一律啟用。相關規則請一併參考 AvoidUsingConvertToSecureStringWithPlainText(從明文產生 SecureString 無法保護機密資訊)。  2 3 4

  7. Microsoft Learn, PSScriptAnalyzer 規則清單. 關於內建規則的清單,以及各規則的重大度(Severity)・預設是否啟用・是否可設定,都整理成表格。本文表格中列出的規則之重大度(AvoidUsingConvertToSecureStringWithPlainText 為 Error,AvoidUsingPlainTextForPassword・UseDeclaredVarsMoreThanAssignments・AvoidUsingInvokeExpression・UseShouldProcessForStateChangingFunctions・AvoidUsingCmdletAliases・UseApprovedVerbs・AvoidGlobalVars・UseSingularNouns 為 Warning),以及第6章・第7章中指名的 AvoidUsingUsernameAndPasswordParams 為 Error 一事,均依此清單與各規則的個別頁面而來。 

  8. PowerShell/vscode-powershell, package.json(擴充功能的設定定義). 關於 powershell.scriptAnalysis.settingsPath 是指定 PSScriptAnalyzer 設定檔路徑的設定、其預設值為 PSScriptAnalyzerSettings.psd1、powershell.scriptAnalysis.enable 可以切換編輯中即時分析的啟用・停用。 

  9. GitHub Docs, 變數參考 ─ 預設環境變數. 關於在 pull_request 事件中,GITHUB_BASE_REF 會存有 PR 目標分支的名稱(其他事件則為空)。三點記法的意義(從明確指定的兩個 ref 之共同基準起算的差異)請參考 Git 官方的 git diff。 

  10. Microsoft Learn, about_Preference_Variables ─ $PSNativeCommandUseErrorActionPreference. 關於原生指令的非零結束代碼,在預設情況下不會變成終止錯誤;將 PowerShell 7.3 導入的這個設定值設為 $true,就會依 $ErrorActionPreference 變成終止錯誤;最近一次外部指令的結束代碼可以用 $LASTEXITCODE 取得。  2

共用相同標籤的最新文章。能以相近的主題延伸理解。

與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。

常見問題

整理諮詢這個主題時常見的問題。

對既有腳本執行 PSScriptAnalyzer 後,出現了數百件警告。應該從哪裡開始處理?
請不要一開始就想全部修完。實務上的做法是,先只鎖定重大度 Error 的項目,把它歸零。另外,重大度是依規則各自決定的,例如偵測明文密碼的 PSAvoidUsingPlainTextForPassword 就是 Warning。如果有像認證資訊相關這種、不論重大度都想擋下的規則,請在 CI 的失敗條件中明確加入規則名稱。接著,把只解析即將變更之檔案的規則放進 CI,阻止新增問題的產生。既有的警告則先決定「目前先容許」,在設定檔中排除,再趁著重構的機會逐一減少,這才是現實可行的做法。
我只想在特定的地方抑制警告,該怎麼做?
在該函式或腳本上加上 SuppressMessageAttribute。請在 System.Diagnostics.CodeAnalysis.SuppressMessageAttribute 中指定規則名稱,並在 Justification 中寫下理由。寫理由很重要,這樣之後閱讀的人才能判斷「為什麼這是例外」。如果想停用整個規則,可以寫在設定檔的 ExcludeRules 中,不過這樣影響範圍較大,請先評估個別抑制是否已經足夠。
使用 Write-Host 會出現警告。是不可以使用嗎?
PSAvoidUsingWriteHost 是一項設計上的指摘,意思是在應該回傳值的場合使用 Write-Host,會導致無法取出輸出結果。如果目的是在互動式工具中做裝飾顯示,那麼在 SuppressMessageAttribute 中寫明理由並抑制,是合理的做法。另一方面,如果是在無人值守執行的腳本中只使用了 Write-Host,那就值得依警告所指出的內容重新檢視。請不要機械式地遵從規則,而是理解指摘背後的用意再做判斷。
Pester 和 PSScriptAnalyzer,應該先導入哪一個?
先導入 PSScriptAnalyzer,相對於導入成本能得到的效果更大。完全不用寫測試程式碼,一行指令就能分析全部腳本,第一天就能看到效果。Pester 則因為需要撰寫測試,上手需要花時間,但唯有測試才能守護邏輯的正確性。建議的順序是:先把靜態分析放進 CI,擋下明顯的問題,接著再針對「壞掉會很麻煩」的處理逐步加上 Pester 測試。
沒有 CI 伺服器的小型團隊,導入也有意義嗎?
有意義。就算沒有 Git 或 CI,只要對共用資料夾裡的一整批腳本執行 Invoke-ScriptAnalyzer -Path . -Recurse,就能完成一次盤點。把結果輸出成 CSV,每個月看一次「重大度 Error 有幾件」,光是這樣就能讓資產狀態可視化。此外,VS Code 的 PowerShell 擴充功能內建了 PSScriptAnalyzer,編輯時當場就會顯示警告。光是這一點,就能確實改善寫法上的習慣。

作者檔案

本文作者的個人檔案頁面。

Go Komura

小村軟體有限公司 代表

以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。

回到部落格一覽