# PSScriptAnalyzer: A Second Pair of Eyes

Invoke-ScriptAnalyzer reads a script or a whole module without running it and lists every likely problem with its rule, severity, line and message; the rules worth knowing ask for full command names, approved verbs, singular nouns, -WhatIf on commands that change things, results instead of Write-Host and no unused variables, and a settings file or SuppressMessageAttribute sets one aside on purpose.

Journeyman · PowerShell · Updated November 10, 2026

## After this lesson you can

- Run Invoke-ScriptAnalyzer on a script or module and read each finding's rule, severity, line and message
- Fix the problems behind the rules that matter most, and set one rule aside on purpose with a settings file or SuppressMessageAttribute

## Key takeaways

- PSScriptAnalyzer is PowerShell's linter: Invoke-ScriptAnalyzer -Path reads a script or module without running it and lists likely problems.
- Each finding has a RuleName, a Severity (Error, Warning or Information), the file, the Line and a Message that says what to change.
- The rules worth knowing ask for full names instead of aliases, approved verbs, singular nouns, SupportsShouldProcess on Remove, Set and New commands, and no Write-Host for results.
- VS Code's PowerShell extension runs a shorter list of rules as you type. A PSScriptAnalyzerSettings.psd1 in the folder you open gives it the same rules as the terminal.
- A settings file's ExcludeRules, or a SuppressMessageAttribute on one function, sets a rule aside on purpose. Write down why.

Before an essay goes in, a friend reads it and circles the sentence that doesn't say what you meant. Before you hand `PlaylistTools` to someone else, or put a module on a shared drive at work, it deserves the same read. You met **PSScriptAnalyzer** in *Writing Scripts in VS Code*: the **linter** behind the yellow wavy lines, a program that reads code without running it and points at lines that are likely to cause trouble. This lesson runs it from the terminal, with every rule it has. Teams that share PowerShell code run it on every change.

This lesson uses `PlaylistTools` from *Your Own Module*. Make a playlist with a song in it twice:

```powershell
Set-Content -Path "$HOME\terminal-practice\playlists\party-mix.csv" -Value 'Title,Artist,Length', 'Golden Hour Drive,The Night Owls,3:45', 'Rain on the Roof,Clara Moss,4:15', 'Golden Hour Drive,The Night Owls,3:45', 'Long Way Home,Harbor Lights,5:30'
Set-Location "$HOME\terminal-practice\modules"
```

## A function written in a hurry

Here's a new playlist tool, written fast. Add it to `PlaylistTools.psm1` above the `Export-ModuleMember` line, and add `Remove-Duplicates` to both export lists, as *Your Own Module* showed:

```powershell
function Remove-Duplicates {
    [CmdletBinding()]
    param([string] $Path)
    $songs = Import-Csv -Path $Path
    $before = $songs.Count
    $songs | sort Title -Unique | Export-Csv -Path $Path
    Write-Host "Removed the duplicates from $Path"
}
```

```powershell
Import-Module .\PlaylistTools -Force
Remove-Duplicates -Path ..\playlists\party-mix.csv
```

```text
Removed the duplicates from ..\playlists\party-mix.csv
```

It worked: the second Golden Hour Drive is gone. Nothing is broken today, and that's exactly when a second pair of eyes is cheapest.

## Run it: \`Invoke-ScriptAnalyzer\`

If you didn't install PSScriptAnalyzer in *Installing Apps and Modules: winget and the PowerShell Gallery*, install it once, for your account:

```powershell
Install-PSResource -Name PSScriptAnalyzer
```

Then point it at the module file:

```powershell
Invoke-ScriptAnalyzer -Path .\PlaylistTools\PlaylistTools.psm1
```

![A terminal showing Invoke-ScriptAnalyzer's report on PlaylistTools.psm1: a table with the columns RuleName, Severity, ScriptName, Line and Message, with Warning rows for PSUseShouldProcessForStateChangingFunctions, PSUseSingularNouns, PSUseDeclaredVarsMoreThanAssignments, PSAvoidUsingCmdletAliases and PSAvoidUsingWriteHost, all on the lines of Remove-Duplicates, and Information rows saying that commands have no help comment](https://www.ask-codey.com/lessons/TODO)

Each row is one **finding**, an object you can sort, filter or count:

- **RuleName**: the rule, such as `PSAvoidUsingCmdletAliases`. Search the name for its page in Microsoft's documentation.
- **Severity**: `Error` (it will go wrong), `Warning` (it can go wrong, or confuses readers) or `Information` (worth a look).
- **ScriptName** and **Line**: where to look.
- **Message**: what to change, in a sentence.

When it has nothing to say, it prints nothing. To see only the serious rows, add `-Severity Warning, Error`. To check every file in a module folder, give it the folder and `-Recurse`.

## The rules that matter most

The messages below are quoted from PSScriptAnalyzer's own text, with the names from `Remove-Duplicates` filled in:

- **PSAvoidUsingCmdletAliases**: *'sort' is an alias of 'Sort-Object'. Alias can introduce possible problems and make scripts hard to maintain. Please consider changing alias to its full content.* Full names in scripts. On a Mac or Linux, `sort` isn't even PowerShell's: it runs the system's own sort program.
- **PSUseApprovedVerbs**: *The cmdlet 'Clean-Playlist' uses an unapproved verb.* Pick a verb from `Get-Verb` (here, `Clear`).
- **PSUseSingularNouns**: *The cmdlet 'Remove-Duplicates' uses a plural noun. A singular noun should be used instead.* PowerShell's nouns are singular: `Get-Process`, even when it returns many.
- **PSUseShouldProcessForStateChangingFunctions**: *Function 'Remove-Duplicates' has verb that could change system state. Therefore, the function has to support 'ShouldProcess'.* A command that starts with `New`, `Set`, `Remove`, `Start`, `Stop`, `Restart`, `Reset` or `Update` should offer `-WhatIf`, as in *`-WhatIf` in Your Own Tools*.
- **PSAvoidUsingWriteHost**: its message begins *File 'PlaylistTools.psm1' uses Write-Host.* Results belong on the pipeline, and a message for the person watching belongs in `Write-Verbose` or `Write-Information`.
- **PSUseDeclaredVarsMoreThanAssignments**: *The variable 'before' is assigned but never used.* Often a leftover, and sometimes a typo: a variable set under one name and read under another.

## Fix it, or set one aside on purpose

Here's the function with the advice taken: a singular noun, `-WhatIf` support, the full name, no unused variable and no `Write-Host`. Rename it in both export lists too, load the module again and try a rehearsal:

```powershell
function Remove-DuplicateSong {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)]
        [string] $Path
    )
    $songs = Import-Csv -Path $Path
    $unique = $songs | Sort-Object -Property Title -Unique
    if ($PSCmdlet.ShouldProcess($Path, "Keep $($unique.Count) of $($songs.Count) songs")) {
        $unique | Export-Csv -Path $Path
    }
}
```

```powershell
Set-Content -Path ..\playlists\party-mix.csv -Value 'Title,Artist,Length', 'Golden Hour Drive,The Night Owls,3:45', 'Rain on the Roof,Clara Moss,4:15', 'Golden Hour Drive,The Night Owls,3:45', 'Long Way Home,Harbor Lights,5:30'
Import-Module .\PlaylistTools -Force
Remove-DuplicateSong -Path ..\playlists\party-mix.csv -WhatIf
```

```text
What if: Performing the operation "Keep 3 of 4 songs" on target "..\playlists\party-mix.csv".
```

Run the analyzer on the whole folder with `-Recurse` and it checks the manifest too: *Do not use wildcard or $null in this field. Explicitly specify a list for CmdletsToExport.* `New-ModuleManifest` left `'*'` in `CmdletsToExport`, `VariablesToExport` and `AliasesToExport`; change each to `@()`, an empty list, since the module hands out none.

Sometimes a rule doesn't fit, such as `Write-Host` in a function whose whole job is colored lines on the screen. Set it aside on purpose, and say why. For a whole project, save a **settings file**, `PSScriptAnalyzerSettings.psd1`, in the folder you check; `Invoke-ScriptAnalyzer -Path . -Recurse` finds it there by itself:

```powershell
@{
    Severity     = @('Error', 'Warning')
    ExcludeRules = @('PSAvoidUsingWriteHost')
}
```

For one function only, put a **SuppressMessageAttribute** above its `param()`:

```powershell
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Colored lines for the screen only')]
```

Use both rarely: most findings are right, even when the code happens to work today.

## In VS Code, as you type

The PowerShell extension runs PSScriptAnalyzer while you type, and shows each finding as a wavy line and in the **Problems** panel (Ctrl+Shift+M). Out of the box it uses a shorter list of rules, so it underlines `sort` and `$before` in `Remove-Duplicates` but says nothing about the plural noun or `Write-Host`. With `PSScriptAnalyzerSettings.psd1` in the folder you open in VS Code, the editor uses the same rules as `Invoke-ScriptAnalyzer` in the terminal. Click a lightbulb, or press Ctrl+., on an alias to replace it with the full name.

![VS Code with PlaylistTools.psm1 open and the Problems panel below it, listing PSScriptAnalyzer's findings for Remove-Duplicates with their line numbers, while sort on its line has a yellow wavy underline](https://www.ask-codey.com/lessons/TODO)

PSScriptAnalyzer works the same on a Mac or Linux, and in Windows PowerShell 5.1, where you install it with `Install-Module PSScriptAnalyzer`.

## Check yourself

2 questions to try on the lesson page.

## So what

PSScriptAnalyzer reads every line the way a careful colleague would, so the alias, the missing `-WhatIf` and the forgotten variable are fixed before anyone runs your tool on real files.

## Key terms

- [PSScriptAnalyzer](https://www.ask-codey.com/glossary/psscriptanalyzer)
- [Linter](https://www.ask-codey.com/glossary/linter)
- [Invoke-ScriptAnalyzer](https://www.ask-codey.com/glossary/invoke-scriptanalyzer)
- [Approved Verb](https://www.ask-codey.com/glossary/approved-verb)
- [SuppressMessageAttribute](https://www.ask-codey.com/glossary/suppressmessageattribute)

## Cheat sheets

- [PSScriptAnalyzer: A Second Pair of Eyes](https://www.ask-codey.com/cheat-sheets/powershell-script-analyzer-cheat-sheet)

## Related

- [Writing Scripts in VS Code](https://www.ask-codey.com/lessons/powershell-scripts-in-vs-code)
- [Your Own Module](https://www.ask-codey.com/lessons/powershell-modules)
- [\`-WhatIf\` in Your Own Tools](https://www.ask-codey.com/lessons/powershell-supports-should-process)
- [Testing with Pester](https://www.ask-codey.com/lessons/powershell-pester)

Source: https://www.ask-codey.com/lessons/powershell-script-analyzer
