PSScriptAnalyzer로 PowerShell 스크립트의 품질을 지키기 ── 규칙 선정과 CI 도입

· · PowerShell, 정적 분석, CI/CD, GitHub Actions, 품질 관리, 유지보수성, 운영 개선, 스크립트

사내 PowerShell 스크립트가 늘어나면 반드시 ‘품질 편차’라는 문제가 나타납니다. 별칭투성이라 읽을 수 없는 스크립트, 평문 비밀번호가 적힌 스크립트, 오타 난 변수명이 아무에게도 발견되지 않은 채 남아 있는 스크립트. 작성한 본인이 없어진 뒤 그것을 읽는 사람이 곤란해지는 형태로 문제가 드러납니다.

이러한 문제의 상당 부분은 정적 분석 도구로 기계적으로 검출할 수 있습니다. PowerShell에는 공식 정적 분석 모듈인 PSScriptAnalyzer가 있으며, 명령 하나로 스크립트 묶음을 분석할 수 있습니다. 테스트 코드를 한 줄도 쓰지 않고도 첫날부터 효과가 나오는 것이 가장 큰 장점입니다.

이 글에서는 PSScriptAnalyzer를 사내 스크립트 자산에 도입하는 실무 절차를, ‘먼저 활성화해야 할 규칙’ → ‘기존 자산에 대한 단계적 도입’ → ‘CI를 통한 자동 점검’의 순서로 정리합니다. 테스트를 통한 품질 확보에 대해서는 「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의 3단계이며, 먼저 Error만 0으로 만드는 것이 현실적인 출발점입니다.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를 0으로’ → ‘변경 파일만 엄격하게’ → ‘범위를 넓힌다’ 순서입니다.

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는 지적 사항 1건을 객체 1개로 반환하며, 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건 중 Error는 1건뿐이고, 나머지는 전부 Warning입니다.7 평문 비밀번호 검출(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(Architecture Decision Record) 입문」과 통합니다.

6. 기존 자산에 대한 단계적 도입

수백 건의 경고를 앞에 두고 ‘전부 고치고 나서 도입’이라고 생각하면 먼저 좌절합니다. 단계를 나눕니다.

1단계: 출혈을 멈춘다(1일) Severity = 'Error'만을 대상으로 CI에 넣어, 이를 0으로 만듭니다. 여기서 주의해야 할 것은 중대도는 규칙별로 정해져 있으며 직관과 일치하지 않는다는 점입니다. 예를 들어 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으로 실패합니다).

한 가지 더, 비교 대상을 main으로 고정하지 마십시오. git diff A...HEAD는 ‘A와 HEAD의 공통 조상으로부터의 차분’을 의미하므로, develop이나 릴리스 브랜치로 향하는 PR에서 origin/main...HEAD를 사용하면 그 PR이 건드리지 않은 변경까지 분석 대상에 들어가, 관계없는 파일의 기존 지적 때문에 CI가 떨어집니다. GitHub Actions에서는 PR의 대상 브랜치가 GITHUB_BASE_REF에 들어오므로 이를 사용합니다.9

또한 git의 실패를 반드시 감지하십시오. PowerShell은 기본적으로 외부 명령이 0이 아닌 종료 코드를 반환해도 종료 오류로 처리하지 않습니다.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과의 차분에 관계없는 변경까지 포함되어, 건드리지 않은
# 파일에서 실패한다.
# 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이 실패하여 출력이 비게 되고, "변경 없음 = 분석 대상
# 0건 = 합격"으로 그대로 통과해 버린다.
# 직후에 $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-Formatter를 사용할 수 있습니다.4

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 실전」을 참조하십시오.

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 diff를 확인1
편집 시 피드백 CI만 / VS Code 확장 기능 그 자리에서 고칠 수 있는 것이 가장 저렴함5

9. 정리

  • PSScriptAnalyzer는 공식 정적 분석 모듈로, 테스트를 작성하지 않고도 도입할 수 있으며 첫날부터 효과가 납니다.
  • 먼저 Error를 0으로 만들고, 다음으로 변경된 파일만 엄격하게 점검하는 단계적 도입이 현실적입니다. 중대도는 규칙별로 정해져 있으므로, 평문 비밀번호(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

  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. 네이티브 명령의 0이 아닌 종료 코드가 기본적으로는 종료 오류가 되지 않는다는 점, PowerShell 7.3에서 도입된 이 설정을 $true로 하면 $ErrorActionPreference에 따라 종료 오류가 된다는 점, 가장 최근 외부 명령의 종료 코드를 $LASTEXITCODE로 가져올 수 있다는 점에 대해.  2

같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.

이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.

자주 묻는 질문

이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.

PSScriptAnalyzer를 기존 스크립트에 실행했더니 경고가 수백 건 나왔습니다. 어디서부터 손을 대면 좋을까요?
갑자기 전부 고치려고 하지 마십시오. 실무적인 진행 방법은 먼저 중대도 Error인 것만을 대상으로 하여 이를 0으로 만드는 것입니다. 참고로 중대도는 규칙별로 정해져 있어서, 예를 들어 평문 비밀번호를 검출하는 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 소프트웨어 개발, 기술 상담, 장애 조사를 중심으로 재현이 어려운 장애 조사와 기존 자산이 남아 있는 프로젝트에 강점이 있습니다.

블로그 목록으로 돌아가기