PSScriptAnalyzerでPowerShellスクリプトの品質を守る ── ルール選定とCI導入

· 更新日: · · PowerShell, 静的解析, CI/CD, GitHub Actions, 品質管理, 保守性, 運用改善, スクリプト

更新履歴(4件・最終更新 2026年08月02日)

この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。

記事の冒頭に「この記事の知識マップ」節を追加しました。本文で扱っている概念とその関係を、要約・図・詳細ページへのリンクにまとめたものです。本文の主張は変えていません。
外部レビュー(1283件)への対応として本文を更新しました。個々の変更内容は、この下の履歴を参照してください。
優先して有効にするルールの表に重大度の列を追加し、Errorだけを失敗条件にすると素通りするルールがある点を明示しました。あわせて対象読者と前提環境の表、解析結果として何が返るか、VS Codeの設定ファイルの既定値を追加しています。
CIの失敗条件を作るコード例で、$issues がどこにも定義されないまま使われていたため、解析結果を取得する行を補いました。あわせて関連記事へのリンクの文言を、リンク先の現在のタイトルに揃えました。
初版公開
この記事を引用する(DOI(登録済みアーカイブ): 10.5281/zenodo.21547453)

以下のDOIは過去に登録されたアーカイブを指しており、現在の本文とは一致しない場合があります。現在の本文を参照するときは、このページのURLを使用してください。

小村 豪(2026)「PSScriptAnalyzerでPowerShellスクリプトの品質を守る ── ルール選定とCI導入」合同会社小村ソフト. https://comcomponent.com/blog/powershell-psscriptanalyzer-ci/

DOI(登録済みアーカイブ)
10.5281/zenodo.21547453
DOI(前回登録した版)
10.5281/zenodo.21733160

社内のPowerShellスクリプトが増えると、「動くけれど読みにくい」「資格情報の扱いが危ない」「変数名のタイプミスが残っている」といった品質のばらつきが問題になります。書いた本人だけでなく、後から保守する人が安心して変更できる状態を作る必要があります。

その出発点になるのが、PowerShell公式の静的解析モジュールPSScriptAnalyzerです。スクリプトをルールに照らして調べ、問題が疑われる箇所を報告します。テストコードを書かずに導入でき、まずはコマンド1つで既存資産の棚卸しを始められます。1

本記事は、棚卸し → ルールと設定の共有 → 例外の記録 → 段階導入 → CIでの判定という順に説明します。特に、「どの指摘を取得するか」と「どの指摘があればCIを失敗させるか」を分けて読み進めてください。ロジックの正しさを守るテストは別の役割なので、「PesterによるPowerShellのテスト整備」もあわせて参照してください。

困っていることから読む

確認したいこと 最初に押さえること 読む箇所
警告が大量に出て、着手点が分からない まず全体の件数を把握し、直す順番を決める 2章: 棚卸し、6章: 段階導入
平文パスワードがあるのにCIが通る 重大度と、チームとして許容するかは別 3章: 優先ルール、失敗条件
手元とCIで指摘が違う 同じ設定ファイルを読んでいるか 4章: 設定共有、VS Code
PowerShell 5.1と7の両方を扱いたい 解析を実行する環境と、対象の構文バージョンを分ける 互換構文の確認
意図したWrite-Hostまで警告される ルール全体の除外より、理由付きの個別抑止 5章: 例外
変更していないファイルでCIが落ちる/何も解析していない 比較元の履歴・PRの宛先・gitの終了コード 変更ファイルの解析
自動修正や整形を使いたい 適用する処理と、差分を確認するタイミング 自動修正
CIや配布サンプルを導入したい 全件解析と差分解析、失敗条件、テストとの分担 7章: GitHub Actions、サンプル

通して読む場合は次の前提を確認して1章から、CIの実装を確認したい場合は6章の失敗条件から進んでください。

対象読者・前提環境

項目 内容
対象読者 社内に増えた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 の3段階で、まずErrorだけをゼロにするのが現実的な出発点です。1
  • 設定は PSScriptAnalyzerSettings.psd1 に集約します。Severity IncludeRules ExcludeRules Rules をリポジトリに置き、全員が同じ基準で解析できるようにします。2

例外と修正の扱いを決める

  • 個別の抑止は SuppressMessageAttribute + 理由の記述です。ルールごと除外する前に、範囲を絞った抑止で足りないか検討します。3
  • -Fix で自動修正できる指摘もあります。整形は Invoke-Formatter が担当します。14
  • VS CodeのPowerShell拡張機能はPSScriptAnalyzerを内蔵しています。編集中にその場で警告が出るため、CIより先に効きます。5

全部を一度に直さず、CIで増加を止める

  • CIの失敗条件は「重大度Error + 名指しした重大ルール」にします。重大度はルールごとに決まっており、平文パスワードの検出(PSAvoidUsingPlainTextForPassword)はWarningです。Errorだけを条件にすると素通りします。6
  • 既存資産には段階導入。「Errorをゼロに」→「変更ファイルのみ厳格に」→「範囲を広げる」の順です。

図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全22件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle

2. 導入 ── まずは1コマンド

2.1. 全体の指摘と件数を取得する

最初は合否判定を厳しくするより、どのファイルに何が残っているかを確認します。次の例は、一覧表示と重大度別の集計をそれぞれ行います。

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

まずはこの2つを実行して、自社の資産がどういう状態にあるかを数字で把握します。何百件出ても驚く必要はありません。ほとんどの現場で最初はそうなります。

2.2. 指摘の一覧と、件数の推移を読み分ける

Invoke-ScriptAnalyzerは、指摘1件を1オブジェクトとして返します。Severity、RuleName、ScriptName、Line、Messageから、「どのファイルの何行目が、どのルールに、なぜ引っかかったか」を追えます。1

1つ目のコマンドは個々の指摘を並べ、2つ目はName(Error / Warning / Information)とCountの2列に集計します。最初の件数を控え、6章の段階導入でどう減ったかを比べると、進捗を数字で確認できます。

指摘が1件もなければ、どちらも何も表示しません。これは選んだ設定・対象で指摘が出なかったという結果であり、Pesterで確かめるロジックの正しさとは分けて扱います。

2.3. 個別ルールの説明を確認する

利用可能なルールの一覧と説明は 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の命名規約。他人が推測できる名前にする

3.1. 重大度と、修正の優先度は同じではない

表の9件のうち、Errorは1件だけで、残りはWarningです。7 CIをErrorだけで判定すると、平文パスワードの検出を含め、表の大半は失敗条件に入りません。

重大度はルールごとに決まっています。チームとして重大度に関係なく直すと決めたルールは、名前を明示して失敗条件へ加えます。実装は6章と7章、手元での確認はGet-ScriptAnalyzerRule | Select-Object Severity, RuleNameを使ってください。

3.2. 未使用変数から、綴り違いを見つける

とくに PSUseDeclaredVarsMoreThanAssignments は費用対効果が高い指摘です。$fileName に代入したつもりが後段で $fileNmae を参照している、といったタイプミスを「代入されたのに使われていない変数」として拾えるため、静的解析でしか見つからないバグを実際に捕まえます(なお、PowerShellの変数名は大文字小文字を区別しないため、$fileName と $filename は同一の変数です。この種の検出で拾えるのは綴り自体が違うケースです)。

4. 設定ファイルでチーム基準を固定する

各自が別の基準で解析していると、手元で直してもCIの結果が変わりません。PSScriptAnalyzerSettings.psd1をリポジトリに置き、全員とCIが同じ設定を使うようにします。2

4.1. 解析結果の選択と、CIの合否判定を分ける

決めること 書く場所 この後の例での扱い
どの指摘を取得するか 設定ファイルのSeverityやルール設定 ErrorとWarningを取得する
どの指摘を許容しないか CI側の$mustFixと$blocking Errorと、名前で指定した資格情報系ルールを選ぶ
いつCIを失敗させるか CI側の判定処理 $blockingが1件以上なら失敗させる

設定のSeverityをErrorだけに絞ると、後からルール名でWarningを選ぼうとしても、その指摘を取得できません。 以下の設定例はErrorとWarningを残し、合否判定を6〜7章で別に行います。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

4.2. VS CodeとCIで、同じファイルを参照する

この設定ファイルは、VS Codeの拡張機能も読みます。PowerShell拡張機能の設定 powershell.scriptAnalysis.settingsPath の既定値が PSScriptAnalyzerSettings.psd1 なので、この名前でリポジトリ直下に置いておけば、編集中に出る警告とCIの判定基準が自動的にそろいます。8 別の名前にする場合やサブフォルダーに置く場合は、この設定でパスを明示してください。ここがずれると「手元では何も出ないのにCIで落ちる」が起き、せっかくの即時フィードバックが信用されなくなります。

4.3. 解析対象の構文バージョンを指定する

PSUseCompatibleSyntax は、5.1と7が混在する環境で特に有用です。7専用の構文(三項演算子やパイプライン連鎖演算子など)を5.1向けスクリプトに書いてしまう事故を、実行前に検出できます。移行方針そのものは「Windows PowerShell 5.1とPowerShell 7の違い」を参照してください。

5. 例外は「理由付き」で残す

どうしても指摘に従えない箇所は、ルール全体を無効化するのではなく、その場所だけ抑止します。3 4章のExcludeRulesはチーム全体の方針、次の例は「この関数では意図した使い方である」という局所的な判断です。影響範囲を分けて考えてください。

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. 既存資産への段階導入

数百件の警告を前にして「全部直してから導入」と考えると、着手しにくくなります。先に増加を止め、直した範囲から基準を厳しくする順に進めます。以下の日数は段階導入の目安として読み、対象と失敗条件を一緒に決めてください。

6.1. 第1段階: 出血を止める(1日)

まずはErrorをゼロにすることを合否判定の出発点にします。ただし、ルールの重大度は直感と一致するとは限りません。平文パスワードを検出するPSAvoidUsingPlainTextForPasswordはWarningです。6 4章のようにErrorとWarningを取得したうえで、資格情報まわりなど、重大度に関係なく許容しないルールを名前で失敗条件へ加えます。

# まず解析結果を取得する
$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 }

このコードは$blockingに要修正の指摘を集めるところまでです。変数を作っただけではCIは失敗しません。 7章の例で件数を確認し、1件以上ならthrowする処理と組み合わせます。

6.2. 第2段階: 新規・変更分を守る(1週間)

変更されたファイルだけを解析対象にします。既存の負債はそのままでも、新しい問題が増えるのを止められます。

比較元の履歴を取得する

CIで実行する場合、比較対象のコミットが取得済みである必要があります。actions/checkout は既定で1コミットしか取得しないため、fetch-depth: 0 を指定するか、ベースブランチを明示的にfetchしてください(指定しないと unknown revision で失敗します)。

PRのターゲットブランチと比較する

もう1点、比較先を main に固定しないでください。git diff A...HEAD は「AとHEADの共通の祖先からの差分」を意味するため、develop やリリースブランチ宛のPRで origin/main...HEAD を使うと、そのPRが触っていない変更まで解析対象に入り、無関係なファイルの既存指摘でCIが落ちます。GitHub ActionsではPRのターゲットブランチが GITHUB_BASE_REF に入るので、これを使います。9

gitの失敗を「変更なし」にしない

さらに、git の失敗を必ず検知してください。PowerShellは既定では、外部コマンドがゼロ以外の終了コードを返しても終了エラーにしません。10 そのため、ベースrefが取得できていない状態では git diff が失敗して出力が空になるだけで、後続は「変更ファイルなし」と解釈し、1ファイルも解析しないままCIが緑になります。差分チェックで最も危険な壊れ方がこれです。実行直後に $LASTEXITCODE を確認して明示的に落としてください(PowerShell 7.3以降なら $PSNativeCommandUseErrorActionPreference = $true を設定する方法もあります)。10

      - uses: actions/checkout@v4
        with:
          fetch-depth: 0        # 差分を取るには履歴が必要
# 変更されたps1/psm1だけを解析する(CIでの差分チェック)
# 比較先をmainに固定しない。developやリリースブランチ宛のPRでは、
# mainとの差分に無関係な変更まで含まれ、触っていないファイルで落ちる。
# 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..." のように引用符+8進エスケープで返り、拡張子の判定から漏れる
$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は単一のパスを受け取るパラメーターなので、配列をそのまま渡すと
# パラメーターバインドで失敗する。1ファイルずつ解析して結果を集約する
$issues = foreach ($file in $changed) {
    Invoke-ScriptAnalyzer -Path $file -Settings .\PSScriptAnalyzerSettings.psd1
}

この例では.ps1・.psm1・.psd1を対象にし、Test-Pathで存在するファイルを選びます。-Pathには配列をまとめて渡さず、1ファイルずつ解析して結果を集めます。その$issuesに対して、前節と同じ失敗条件を適用します。

6.3. 第3段階: 範囲を広げる(継続)

ExcludeRules を1つずつ外し、対応した分だけ厳格にしていきます。リファクタリングの機会に既存ファイルを直し、負債を減らしていきます。

6.4. 自動修正は、差分の確認とセットにする

自動修正が効く指摘は-Fixでまとめて直せます。1 適用前に作業中の差分を確認し、適用後もgit diffで何が変わったかを確認してください。整形だけを行う場合はInvoke-Formatterを使います。4

Invoke-ScriptAnalyzer -Path .\Scripts -Recurse -Fix -Settings .\PSScriptAnalyzerSettings.psd1
git diff        # 何が変わったかを必ず目視する

7. CIで自動化する

7.1. 全件解析の結果から、失敗させる指摘を選ぶ

以下はWindowsランナーで、リポジトリ全体を解析する例です。失敗条件は「Error、または$mustFixに挙げたルール」で、それ以外のWarningはログに残して可視化します。Warningをすべて許容するという意味ではありません。

処理は「設定を指定して解析 → 全指摘をログへ表示 → 要修正の指摘を選択 → 件数に応じて失敗」の順です。変更ファイルだけに絞る段階では、解析結果を取得する部分を6.2節の処理に置き換えます。

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) 件の要修正指摘があります"
          }

BlockingとWarningの件数は、互いに重ならない分類ではありません。Warningである平文パスワードの指摘を$mustFixで選ぶと、両方に数えられます。合否を決めるのは$blocking.Countです。

7.2. Pesterと組み合わせて、守る範囲を広げる

Pesterのテストと同じワークフローにまとめると、「lintが通る → テストが通る → マージできる」という流れが作れます。WindowsアプリのCI/CD全般の組み方は「WinForms / WPFアプリのCI/CD実践」を参照してください。

7.3. CIがなくても、月次の棚卸しから始める

CIがない環境でも、月次でInvoke-ScriptAnalyzerを回し、結果をCSVに残せば資産の状態を可視化できます。次の例は-Encoding utf8BOMを使うPowerShell 7系向けです。第2章の件数とあわせて、どの指摘が減ったかを追います。

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 diff を見る1
編集時のフィードバック 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導入、属人化した運用スクリプトの保守性改善を扱っています。

参考リンク

  1. Microsoft Learn, PSScriptAnalyzer モジュールの概要. PSScriptAnalyzerがPowerShellスクリプト・モジュール向けの静的解析ツールであること、Invoke-ScriptAnalyzerによる解析と-Path / -Recurse / -Settings / -Fix / -ExcludeRule などのパラメーター、Get-ScriptAnalyzerRuleによるルール一覧の取得、診断結果が重大度(Error / Warning / Information)を持つことについて。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8

  2. Microsoft Learn, PSScriptAnalyzer の設定ファイル. 設定ファイル(.psd1)でSeverity・IncludeRules・ExcludeRules・IncludeDefaultRules・Rulesなどを指定できること、-Settingsパラメーターで設定ファイルを渡せること、ルールごとの詳細設定(PSUseCompatibleSyntaxのTargetVersionsや整形系ルールのオプション)について。 ↩ ↩2 ↩3 ↩4

  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のターゲットブランチ名が入ること(それ以外のイベントでは空であること)について。三点リーダー記法の意味(明示した2つのrefのマージベースからの差分)はGit公式のgit diffを参照。 ↩

  10. Microsoft Learn, about_Preference_Variables ─ $PSNativeCommandUseErrorActionPreference. ネイティブコマンドのゼロ以外の終了コードが既定では終了エラーにならないこと、PowerShell 7.3で導入されたこの設定を$trueにすると$ErrorActionPreferenceに従って終了エラーになること、直近の外部コマンドの終了コードが$LASTEXITCODEで取得できることについて。 ↩ ↩2

同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。

このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。

よくある質問

この記事のテーマについて、相談時によくある質問をまとめています。

PSScriptAnalyzerを既存スクリプトに掛けたら、警告が数百件出ました。どこから手を付ければよいですか?
いきなり全部を直そうとしないでください。実務的な進め方は、まず重大度Errorのものだけを対象にして、それをゼロにすることです。なお、重大度はルールごとに決まっていて、たとえば平文パスワードを検出するPSAvoidUsingPlainTextForPasswordはWarningです。credentialまわりのように重大度に関係なく落としたいルールがある場合は、CIの失敗条件にルール名を明示して加えてください。次に、これから変更するファイルだけを解析対象にするルールをCIに入れ、新しく増える問題を止めます。既存の警告は「今は許容する」と決めて設定ファイルで除外し、リファクタリングのついでに1つずつ減らしていくのが現実的です。
特定の箇所だけ警告を抑止したいのですが、どうすればよいですか?
その関数やスクリプトにSuppressMessageAttributeを付けます。System.Diagnostics.CodeAnalysis.SuppressMessageAttributeにルール名を指定し、Justificationに理由を書いてください。理由を書くことが重要で、後から読んだ人が「なぜ例外なのか」を判断できます。ルール全体を無効にしたいなら設定ファイルのExcludeRulesに書きますが、こちらは影響範囲が広いので、まず個別抑止で足りないかを検討してください。
Write-Hostを使うと警告が出ます。使ってはいけないのですか?
PSAvoidUsingWriteHostは、値を返すべき場面でWrite-Hostを使うと出力を取り出せなくなる、という設計上の指摘です。対話的なツールで装飾表示を行う目的なら、SuppressMessageAttributeに理由を書いて抑止するのが妥当です。一方、無人実行のスクリプトでWrite-Hostしか使っていない場合は、警告のとおり見直す価値があります。ルールに機械的に従うのではなく、指摘の意図を理解して判断してください。
PesterとPSScriptAnalyzerは、どちらを先に導入すべきですか?
PSScriptAnalyzerを先に入れるほうが、導入コストに対する効果が大きいです。テストコードを一切書かずに、コマンド1つで全スクリプトを解析でき、初日から効果が出ます。Pesterはテストを書く作業が必要なぶん立ち上がりに時間がかかりますが、ロジックの正しさを守れるのはテストだけです。順序としては、まず静的解析をCIに入れて明らかな問題を止め、次に壊れると困る処理からPesterのテストを足していく流れをおすすめします。
CIサーバーがない小さなチームでも導入する意味はありますか?
あります。GitやCIがなくても、共有フォルダーのスクリプト一式に対してInvoke-ScriptAnalyzer -Path . -Recurseを実行するだけで棚卸しになります。結果をCSVに出して「重大度Errorが何件あるか」を月次で見るだけでも、資産の状態が可視化されます。加えて、VS CodeのPowerShell拡張機能はPSScriptAnalyzerを内蔵しているため、編集中にその場で警告が出ます。これだけでも書き方の癖は着実に改善します。

著者プロフィール

記事の著者プロフィールページです。

小村 豪

合同会社小村ソフト 代表

Windows ソフト開発、技術相談、不具合調査を中心に、既存資産が残る案件や原因が見えにくい障害調査に強みがあります。

ブログ一覧に戻る