用 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 运行器为前提。调用 Invoke-ScriptAnalyzer 的部分在其他 CI 上也通用,解析本身在非 Windows 运行器上也能执行
所需权限 由于导入方式是 Install-Module -Scope CurrentUser不需要管理员权限
示例的验证环境 文末分发的示例代码是在 PowerShell 7.6 上运行并验证的

1. 先说结论

  • PSScriptAnalyzer 是 PowerShell 官方的静态分析模块。Invoke-ScriptAnalyzer 解析脚本或模块,并报告违反的规则。1
  • 每条指摘都带有重大度(Severity)。分为 Error / Warning / Information 三个等级,现实的起点是先把 Error 降为零。1
  • 配置集中在 PSScriptAnalyzerSettings.psd1 中。SeverityIncludeRulesExcludeRulesRules 放进代码仓库,让所有人都能按同一基准解析。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 把每一条指摘作为一个对象返回,具有 SeverityRuleNameScriptNameLineMessage 等属性。第一条命令会按重大度顺序,把「哪个文件的第几行,触犯了哪条规则,原因是什么」逐行排列出来。第二条命令只返回 NameError / 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 被赋值但从未被使用过的变量 能检测出变量名的拼写错误,属于实质性的 bug 检测
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 这样的拼写错误,会被作为「已赋值却从未被使用的变量」检测出来,因此能够实际捕获只有静态分析才能发现的 bug(另外,由于 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 却失败了」的情况,好不容易得到的即时反馈也会因此失去可信度。

PSUseCompatibleSyntax 在 5.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(Architecture Decision Record)入门 —— 在小规模开发中,用最简方法留住「为什么这样设计」》相通。

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 运行器上只需几行代码即可实现。要点是让 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 实践 ── 用 GitHub Actions 实现从构建到签名、发布的自动化》。

即便没有 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,能够捕获变量名拼写错误这种实际存在的 bug。
  • 把配置集中放进 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 导入,以及对因人而异、过度依赖个人经验的运维脚本进行可维护性改善。

参考链接

  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,Variables reference ─ Default environment variables。关于在 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 的失败条件中明确写出规则名加入判断。接下来,把只对今后要修改的文件进行解析的规则纳入 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 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。

返回博客列表