Chapter 18Lesson 03~230 minutes

PSScriptAnalyzer, Rules, Style, and Static Diagnostics

Use current PSScriptAnalyzer tooling to discover rules, analyze PowerShell safely, define repository policy, handle suppressions, check compatibility, and refactor diagnostics without confusing linting with correctness.

PSScriptAnalyzerStatic analysisRulesStyle

Learning objectives

  • Explain static analysis and how it complements runtime tests.
  • Use PSScriptAnalyzer 1.24.0 tooling on the PowerShell 7.6 baseline.
  • Discover rules and run structured analysis with Invoke-ScriptAnalyzer.
  • Create repeatable project settings for security, safety, maintainability, and compatibility rules.
  • Handle false positives and suppressions with narrow documented justification.
  • Refactor an intentionally noncompliant script and rerun analysis/tests.

1. Static analysis inspects code without running every behavior

Static analysis examines source code and its parsed structure for patterns associated with defects, portability problems, security risks, or maintainability issues. It can find problems before a particular execution path is exercised.

Static analysis complements testing. A test can prove that one scenario produced the expected result; an analyzer can flag a risky command or unused variable in a path that your tests never executed. Neither one replaces the other.

Static analysis inspects code without running every behavior
flowchart LR S[Source code] --> P[PSScriptAnalyzer rules] S --> T[Pester tests] P --> D[Diagnostics] T --> R[Behavior results] D --> Q[Quality decision] R --> Q

2. Current baseline: PSScriptAnalyzer 1.24.0

The verified current Microsoft documentation describes PSScriptAnalyzer 1.24.0 as the latest documented release. It supports Windows PowerShell 5.1 and PowerShell 7.2.11 or later on Windows, Linux, and macOS. This course uses PowerShell 7.6.x as its main baseline.

$analyzer = Get-Module -ListAvailable PSScriptAnalyzer |
    Sort-Object Version -Descending |
    Select-Object -First 1

$analyzer | Select-Object Name,Version,Path

# If missing and PSResourceGet from Chapter 15 is available:
# Install-PSResource -Name PSScriptAnalyzer -Scope CurrentUser
# Import-Module PSScriptAnalyzer

3. Discover rules instead of memorizing a stale list

Get-ScriptAnalyzerRule exposes the rules available in the installed module. Rule names, defaults, and capabilities can evolve, so discovery is more reliable than copying an old blog post.

Get-ScriptAnalyzerRule |
    Sort-Object Severity,RuleName |
    Select-Object RuleName,Severity,SourceName |
    Format-Table -AutoSize

Get-ScriptAnalyzerRule -Name PSAvoidUsingCmdletAliases,PSUseApprovedVerbs |
    Select-Object RuleName,Severity,Description

Formatting is appropriate here because the output is for human inspection. In automation, keep the diagnostic objects structured until the final reporting boundary.

4. Invoke-ScriptAnalyzer returns diagnostic objects

Invoke-ScriptAnalyzer analyzes .ps1, .psm1, and .psd1 content and returns diagnostic records. You can analyze one file or recurse through a project.

$diagnostics = Invoke-ScriptAnalyzer -Path ./src -Recurse
$diagnostics |
    Select-Object RuleName,Severity,ScriptName,Line,Column,Message

# A focused CI view:
$blocking = Invoke-ScriptAnalyzer -Path ./src -Recurse -Severity Error,Warning
[pscustomobject]@{
    DiagnosticCount = @($blocking).Count
    HasBlockingIssues = @($blocking).Count -gt 0
}

Severity helps triage, but a team still chooses policy. A warning can be release-blocking if it represents a class of risk your organization has decided to forbid.

5. Useful rule categories map to real engineering risks

PSScriptAnalyzer rules are not merely style preferences. Many describe operational failure modes: ambiguous aliases in shared scripts, plaintext credential parameters, state-changing functions without ShouldProcess, or compatibility issues on another PowerShell host.

CategoryExample current ruleWhy it matters
NamingPSUseApprovedVerbsPredictable command discovery and consistent semantics.
MaintainabilityPSUseDeclaredVarsMoreThanAssignmentsAssigned-but-unused values often reveal dead or mistaken logic.
PortabilityPSAvoidUsingCmdletAliasesAliases can reduce clarity and differ across environments.
SecurityPSAvoidUsingPlainTextForPasswordPlain string password parameters increase exposure risk.
SafetyPSUseShouldProcessForStateChangingFunctionsState-changing functions should expose preview/confirmation patterns.
CompatibilityPSUseCompatibleCommands / Syntax / TypesDetect dependencies unavailable on target PowerShell environments.

6. Analyze an intentionally noncompliant script before refactoring it

The following file is intentionally poor training code. Do not run it as a deployment tool. The point is to let the analyzer identify multiple independent concerns.

# bad-training-script.ps1 -- analyze, do not use in production
function Delete-TrainingCache {
    param(
        [string]$Path,
        [string]$Password
    )

    $unused = 'never read again'
    gci $Path
    Invoke-Expression "Remove-Item -Recurse -Force '$Path'"
}

Invoke-ScriptAnalyzer -Path ./bad-training-script.ps1 |
    Sort-Object Severity,RuleName |
    Select-Object RuleName,Severity,Line,Message

Likely diagnostics include the unapproved Delete verb, alias use, plaintext password parameter, unused variable, Invoke-Expression, and state-change design concerns. The exact set depends on the installed rule version and syntax.

7. A repository settings file makes analysis policy repeatable

A PSScriptAnalyzerSettings.psd1 file is a PowerShell data file that records team policy beside the code. This avoids developers running different rule subsets by memory.

# PSScriptAnalyzerSettings.psd1
@{
    Severity = @('Error','Warning')
    IncludeRules = @(
        'PSUseApprovedVerbs'
        'PSAvoidUsingCmdletAliases'
        'PSUseDeclaredVarsMoreThanAssignments'
        'PSAvoidUsingPlainTextForPassword'
        'PSAvoidUsingInvokeExpression'
        'PSUseShouldProcessForStateChangingFunctions'
    )
}

# Run explicitly with the project policy:
Invoke-ScriptAnalyzer -Path ./src -Recurse -Settings ./PSScriptAnalyzerSettings.psd1

PSScriptAnalyzer can also discover a settings file named PSScriptAnalyzerSettings.psd1 at the project root when you analyze that project. Explicit paths are useful in CI because the policy input is unambiguous.

8. Compatibility rules require an explicit target

“Cross-platform” is not one environment. A script can target PowerShell 7.6 on Linux, Windows PowerShell 5.1 on Windows, or a specific server image. Compatibility analysis is meaningful only when you configure the environment you actually support.

Get-ScriptAnalyzerRule -Name PSUseCompatibleCommands,PSUseCompatibleSyntax,PSUseCompatibleTypes |
    Select-Object RuleName,Severity,Description

# Inspect the shipped settings/examples before enabling a compatibility rule:
$module = Get-Module -ListAvailable PSScriptAnalyzer |
    Sort-Object Version -Descending |
    Select-Object -First 1
$module.ModuleBase
Policy lesson: do not turn on a compatibility profile you do not understand and then suppress every result. Define supported platforms first, then configure analysis to match them.

9. Suppress a diagnostic only with a narrow, documented reason

Static analyzers can produce false positives or flag a pattern that is intentionally acceptable at one boundary. PSScriptAnalyzer supports SuppressMessageAttribute. A suppression should be narrow, reviewable, and justified—not a blanket way to make the dashboard green.

function Show-InteractiveTrainingBanner {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSAvoidUsingWriteHost',
        '',
        Justification='This function is explicitly a human-only host adapter; it returns no reusable data.'
    )]
    param([string]$Message)

    Write-Host $Message
}

Invoke-ScriptAnalyzer -Path ./src -SuppressedOnly |
    Select-Object RuleName,ScriptName,Line,Message

Parser errors cannot be suppressed. More generally, if the code can be improved cheaply, prefer fixing the code over suppressing the rule.

10. Automatic fixes need source control and review

Some analyzer diagnostics offer suggested corrections. Invoke-ScriptAnalyzer -Fix can apply supported fixes, and the cmdlet supports -WhatIf. Automatic rewriting is still a code change: review the diff, rerun tests, and verify file encoding when encoding matters.

# Preview supported corrections first:
Invoke-ScriptAnalyzer -Path ./src -Recurse -Fix -WhatIf

# If the preview is acceptable, apply on a clean source-control branch/worktree:
# Invoke-ScriptAnalyzer -Path ./src -Recurse -Fix
# git diff
# Invoke-Pester -Path ./tests

11. Lab — refactor the intentionally bad script

A safer rewrite removes the unused value and dynamic evaluation, uses an approved verb, treats the path literally, and exposes SupportsShouldProcess. The password parameter disappears because the operation did not actually need a credential.

function Remove-TrainingCache {
    [CmdletBinding(SupportsShouldProcess,ConfirmImpact='Medium')]
    param(
        [Parameter(Mandatory)]
        [string]$Path
    )

    if (-not (Test-Path -LiteralPath $Path)) {
        return [pscustomobject]@{ Path=$Path; Changed=$false; Reason='NotFound' }
    }

    if ($PSCmdlet.ShouldProcess($Path,'Remove training cache')) {
        Remove-Item -LiteralPath $Path -Recurse -Force
        return [pscustomobject]@{ Path=$Path; Changed=$true; Reason='Removed' }
    }

    [pscustomobject]@{ Path=$Path; Changed=$false; Reason='WhatIfOrDeclined' }
}

Invoke-ScriptAnalyzer -Path ./src/TrainingCache.ps1
Remove-TrainingCache -Path ./disposable-cache -WhatIf

Static analysis does not prove this function is correct. Pester tests should still verify NotFound, WhatIf, successful removal in TestDrive, and error behavior.

12. Common static-analysis mistakes

MistakeWhy it failsBetter pattern
Treat zero diagnostics as proofRules cannot cover every business requirement or runtime state.Combine analysis with tests and review.
Enable every rule without a target policyNoise produces mass suppression.Choose rules based on supported environments and risks.
Suppress without justificationFuture reviewers cannot tell intentional design from hidden debt.Use narrow documented suppression.
Auto-fix without diff/test reviewAutomated edits can alter semantics or encoding.Preview, diff, test, then commit.
Only run analyzer locallyDeveloper environments drift.Run the same policy in CI.

13. Verification checklist

  • You can explain what static analysis can and cannot prove.
  • You can discover rules with Get-ScriptAnalyzerRule.
  • You can run Invoke-ScriptAnalyzer and preserve diagnostic objects.
  • You can create a repository settings file with explicit rule/severity policy.
  • You can justify a narrow suppression and inspect suppressed diagnostics.
  • You can refactor analyzer findings without replacing tests.

14. Knowledge check

Question 1. What is static analysis?

Question 2. Does a clean PSScriptAnalyzer run prove business correctness?

Question 3. Why use a settings file?

Question 4. When is suppression acceptable?

Question 5. What should follow an automatic -Fix operation?

15. Summary and next bridge

PSScriptAnalyzer provides fast feedback on risky patterns, style, safety, security, and compatibility. Its value comes from explicit project policy and disciplined handling of findings—not from chasing a zero count at any cost. The next lesson combines tests and analysis with coverage data, machine-readable reports, and quality gates that a CI runner can enforce.

16. Authoritative references

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.