PowerShell PSScriptAnalyzer: Fix the Most Common Rule Violations
─□✕

PowerShell PSScriptAnalyzer: Fix the Most Common Rule Violations

PowerShell Tips Editor 3 min read

PSScriptAnalyzer is the de facto linter for PowerShell, and understanding powershell psscriptanalyzer common rule fixes is essential for anyone shipping scripts to production or publishing modules to the Gallery. Left unaddressed, analyzer warnings signal poor coding practices, hinder pipeline automation, and make peer review painful. This guide walks through the violations developers encounter most, explains exactly why each rule exists, and shows you how to fix them without breaking your existing scripts.

Installing PSScriptAnalyzer and Running Your First Scan

PSScriptAnalyzer is available from the PowerShell Gallery and works with both Windows PowerShell 5.1 and PowerShell 7+. Install it once and it’s available in any session.

Install-Module PSScriptAnalyzer -Scope CurrentUser -Force

# Scan a single script
$results = Invoke-ScriptAnalyzer -Path 'C:\Scripts\MyScript.ps1' -Severity Warning, Error
$results | Format-Table RuleName, Severity, Line, Message -AutoSize

# Scan an entire directory recursively
Invoke-ScriptAnalyzer -Path 'C:\Modules\MyModule' -Recurse |
    Group-Object RuleName |
    Sort-Object Count -Descending |
    Select-Object Name, Count |
    Format-Table -AutoSize

Running a grouped summary first gives you a prioritized fix list — address the rules with the highest counts first for the biggest impact. The -Severity Error filter is the minimum bar for CI gates.

PSAvoidUsingWriteHost — Why and How to Replace It

Write-Host writes directly to the console host and bypasses the pipeline entirely. Any output it produces cannot be captured, redirected, or tested. The rule fires whenever PSScriptAnalyzer sees it in a function or script that is not explicitly a UI helper.

# VIOLATION
function Get-Report {
    Write-Host "Generating report..."   # PSAvoidUsingWriteHost
    return $data
}

# FIX — use Write-Verbose for progress messages
function Get-Report {
    [CmdletBinding()]
    param()
    Write-Verbose "Generating report..."
    return $data
}

# Callers enable verbose output with the common parameter
Get-Report -Verbose

Use Write-Information when you need a message that users can optionally capture via the information stream (6>). Reserve Write-Host for intentional console UI (menus, progress bars) and suppress the rule there with SuppressMessageAttribute.

PSUseApprovedVerbs — Finding Approved Verbs for Custom Functions

PowerShell enforces a canonical list of approved verbs to ensure consistency across modules. Using a non-approved verb generates an import warning and confuses users who rely on verb-based discovery (Get-Command -Verb).

# See all approved verbs with their groups
Get-Verb | Sort-Object Group, Verb | Format-Table -AutoSize

# VIOLATION — 'Check' and 'Verify' are not approved
function Check-DiskSpace { ... }

# FIX — use an approved verb from the Diagnostic group
function Test-DiskSpace { ... }

# Common substitutions
# Check/Verify  →  Test
# Create/Make   →  New
# Delete/Remove →  Remove
# Fetch/Retrieve → Get
# Modify/Change →  Set or Update

When no approved verb is a perfect semantic match, choose the closest one and use the noun or a parameter to convey the nuance. The Use verb (Common group) is a valid catch-all for actions that don’t fit elsewhere.

PSUseShouldProcessForStateChangingFunctions Explained

Any function that changes system state (deletes files, modifies registry, creates users) should support -WhatIf and -Confirm so callers can safely test it. PSScriptAnalyzer fires this rule when a function uses a state-changing cmdlet but does not declare SupportsShouldProcess = $true.

# VIOLATION — modifies files but no ShouldProcess support
function Remove-OldLogs {
    param([string]$Path)
    Get-ChildItem $Path -Filter '*.log' | Remove-Item
}

# FIX
function Remove-OldLogs {
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    param([string]$Path)
    Get-ChildItem $Path -Filter '*.log' | ForEach-Object {
        if ($PSCmdlet.ShouldProcess($_.FullName, 'Remove log file')) {
            Remove-Item $_.FullName -ErrorAction Stop
        }
    }
}

# Now callers can safely dry-run
Remove-OldLogs -Path 'C:\Logs' -WhatIf

Suppressing False Positives with SuppressMessageAttribute

Occasionally PSScriptAnalyzer flags legitimate code. Use System.Diagnostics.CodeAnalysis.SuppressMessageAttribute to suppress a specific rule on a specific function or variable, and always add a justification comment.

[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
    'PSAvoidUsingWriteHost',
    '',
    Justification = 'Interactive menu function — Write-Host is intentional'
)]
function Show-MainMenu {
    Write-Host "=== Main Menu ===" -ForegroundColor Cyan
    Write-Host "1. Run Report"
    Write-Host "2. Exit"
}

Place the attribute immediately above the function declaration. Placing it on a variable or inside the function body will cause it to be silently ignored — a common mistake covered in the errors section below.

Integrating PSScriptAnalyzer into Azure DevOps Pipelines

Fail the build automatically when Error-severity violations are found by using a non-zero exit code check.

# azure-pipelines.yml task equivalent — run in a PowerShell task
$results = Invoke-ScriptAnalyzer -Path '$(Build.SourcesDirectory)' `
               -Recurse -Severity Error

if ($results) {
    $results | Format-Table RuleName, ScriptName, Line, Message -AutoSize
    Write-Error "PSScriptAnalyzer found $($results.Count) error(s). Build failed."
    exit 1
}

Write-Host "PSScriptAnalyzer: no errors found."

Add a separate step for -Severity Warning that reports but does not break the build, giving the team visibility without blocking deployments for informational violations.

Common Errors and Fixes

Suppression attribute is ignored when placed on the wrong scope. SuppressMessageAttribute must be placed directly on the function or script-level element it targets. Placing it inside a function body or on a variable within the function has no effect — the violation still fires. Always place it on the function keyword line.

Custom rules DLL not found when running in a non-interactive pipeline. Custom rules loaded from a DLL path work interactively but fail in CI because the pipeline agent has a different working directory. Use an absolute path or a path relative to $PSScriptRoot when calling Invoke-ScriptAnalyzer -CustomRulePath, and ensure the DLL is committed to the repo or installed as a pipeline dependency.

Related Cmdlets / See Also

Wrapping Up

PSScriptAnalyzer is a quick win for any PowerShell codebase. Fix PSAvoidUsingWriteHost and PSUseApprovedVerbs first — they have the widest blast radius. Add SupportsShouldProcess to state-changing functions, integrate the linter into CI at the Error severity, and use suppression attributes sparingly and with justification comments.

Send-Item -To