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

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

수정 이력(6건, 최종 수정 2026년 09월 03일)

이 글에 적용한 변경 사항의 기록입니다. 보관해 둔 수정 전 버전은 DOI가 부여된 고정 URL에서 읽을 수 있습니다.

Codex 리뷰에 따라 상담·문의 링크에 /ko/ 로케일 접두를 붙였습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
permalink·저자 표기·지식 맵 래퍼·깨진 내부 링크 등 CI가 지적한 표시용 수정을 반영했습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
기사 맨 앞에 「이 기사의 지식 맵」 절을 추가했습니다. 본문에서 다루는 개념과 그 관계를 요약·그림·상세 페이지 링크로 정리한 것입니다. 본문의 주장은 바꾸지 않았습니다.
외부 리뷰(1283건)에 대응해 본문을 갱신했습니다. 개별 변경 내용은 아래 이력을 참조하세요.
우선 활성화할 규칙 표에 심각도 열을 추가하고, Error만 실패 조건으로 두면 그냥 통과하는 규칙이 있다는 점을 명시했습니다. 대상 독자와 전제 환경 표, 분석 결과로 무엇이 반환되는지, VS Code 설정 파일의 기본값도 추가했습니다.
CI 실패 조건을 만드는 코드 예에서 $issues가 어디에도 정의되지 않은 채 쓰이고 있어, 분석 결과를 가져오는 행을 보완했습니다. 관련 기사 링크 문구도 링크 대상의 현재 제목에 맞췄습니다.
최초 공개
이 글을 인용하기(DOI(등록된 아카이브): 10.5281/zenodo.22175039)

아래 DOI는 이전에 등록된 아카이브를 가리키며 현재 본문과 다를 수 있습니다. 현재 본문을 참조할 때는 이 페이지의 URL을 사용하세요.

Go Komura (2026). 「PSScriptAnalyzer로 PowerShell 스크립트 품질을 지키는 방법 ── 규칙 선정과 CI 도입」. 합동회사 코무라소프트. https://comcomponent.com/ko/blog/powershell-psscriptanalyzer-ci/

DOI(등록된 아카이브)
10.5281/zenodo.22175039
DOI(마지막 등록 버전)
10.5281/zenodo.22175040

사내 PowerShell 스크립트가 늘어나면 반드시 「품질 편차」 문제가 나옵니다. alias로 가득해 읽기 어려운 스크립트, 평문 비밀번호가 적힌 스크립트, 오타가 난 변수 이름이 아무도 눈치채지 못한 채 남아 있는 스크립트. 작성자 본인이 떠난 뒤에, 그 코드를 읽는 사람이 곤란해지는 형태로 문제가 드러납니다.

이런 문제의 상당 부분은 정적 분석 도구로 기계적으로 검출할 수 있습니다. 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 runner를 전제로 합니다. Invoke-ScriptAnalyzer를 호출하는 부분은 다른 CI에서도 같고, 분석 자체는 Windows 이외의 runner에서도 실행할 수 있습니다
필요한 권한 도입은 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 실패 조건은 「심각도 Error + 이름을 지정한 중요 규칙」으로 둡니다. 심각도는 규칙마다 정해져 있으며, 평문 비밀번호 검출(PSAvoidUsingPlainTextForPassword)은 Warning입니다. Error만 조건으로 두면 그냥 통과합니다.6
  • 기존 자산에는 단계적 도입. 「Error를 제로로」→「변경 파일만 엄격하게」→「범위를 넓힌다」 순서입니다.

그림의 실선은 항상 성립하는 관계, 점선은 조건이 붙는 관계입니다(성립 조건은 상세 페이지의 관계별 설명에 적혀 있습니다). 관계 전체 목록(총 22건, 근거와 확신도 포함)과 주요 개념의 정의는 지식 맵 상세 페이지에 정리되어 있습니다(일본어). 데이터: JSON-LD / Turtle

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건을 객체 하나로 반환하며, 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 매개변수로 평문 비밀번호를 받고 있다 credential을 평문으로 유지하는 것은 감사에서도 지적된다6
PSAvoidUsingConvertToSecureStringWithPlainText Error 평문에서 SecureString을 만들고 있다 위와 같은 뿌리. 암호화의 의미가 없어진다
PSUseDeclaredVarsMoreThanAssignments Warning 대입되었지만 한 번도 쓰이지 않은 변수 변수 이름 오타를 검출할 수 있다. 실질적인 버그 검출
PSAvoidUsingInvokeExpression Warning Invoke-Expression 사용 문자열을 코드로 실행하므로 주입의 온상이 된다
PSUseShouldProcessForStateChangingFunctions Warning 상태 변경을 수반하는 함수에 -WhatIf가 없다 위험한 조작을 사전 확인할 수 없는 설계를 검출
PSAvoidUsingCmdletAliases Warning ls % ? 등의 alias 대화에서는 편리해도, 스크립트에서는 가독성을 해친다
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(아키텍처 결정 기록)을 작은 팀에서 쓰기」와 통합니다.

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

수백 건의 경고를 앞에 두고 「전부 고친 뒤에 도입」이라고 생각하면, 우선 좌초합니다. 단계를 나눕니다.

1단계: 출혈을 멈춘다(1일) Severity = 'Error'만 대상으로 CI에 넣고, 이를 제로로 만듭니다. 여기서 주의할 점은 심각도는 규칙마다 정해져 있으며, 직관과 일치하지 않는다는 것입니다. 예를 들어 PSAvoidUsingPlainTextForPassword의 심각도는 Warning이며, Error만 실패 조건으로 두면 걸리지 않습니다.6 credential 관련처럼 「심각도와 관계없이 막고 싶은」 규칙은 다음과 같이 규칙 이름으로 명시해 실패 조건에 더합니다.

# 먼저 분석 결과를 가져온다
$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에서 실행할 경우 비교 대상 commit이 이미 가져와져 있어야 합니다. actions/checkout은 기본으로 commit을 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이 실패해 출력이 비고, 「변경 없음 = 분석 대상 제로 = 합격」으로 그냥 통과한다.
# 직후에 $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 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 + credential 계열 규칙을 이름으로 지정 전부를 조건에 넣으면 아무도 통과하지 못한다. 심각도는 규칙마다 정해져 있다는 점에 주의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

  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의 merge base로부터의 차이)는 Git 공식 git diff를 참조. 

  10. Microsoft Learn, about_Preference_Variables ─ $PSNativeCommandUseErrorActionPreference. 네이티브 명령의 0이 아닌 종료 코드가 기본으로는 종료 오류가 되지 않는다는 점, PowerShell 7.3에서 도입된 이 설정을 $true로 하면 $ErrorActionPreference에 따라 종료 오류가 된다는 점, 직전 외부 명령의 종료 코드를 $LASTEXITCODE로 가져올 수 있다는 점에 대해.  2

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

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

자주 묻는 질문

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

PSScriptAnalyzer를 기존 스크립트에 적용했더니 경고가 수백 건 나왔습니다. 어디서부터 손대면 되나요?
한꺼번에 전부 고치려 하지 마세요. 실무적인 진행 방법은 먼저 심각도 Error인 항목만 대상으로 삼아 이를 제로로 만드는 것입니다. 다만 심각도는 규칙마다 정해져 있으며, 예를 들어 평문 비밀번호를 검출하는 PSAvoidUsingPlainTextForPassword는 Warning입니다. credential 관련처럼 심각도와 관계없이 반드시 막고 싶은 규칙이 있으면 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 소프트웨어 개발, 기술 상담, 장애 조사를 중심으로 재현이 어려운 장애 조사와 기존 자산이 남아 있는 프로젝트에 강점이 있습니다.

블로그 목록으로 돌아가기