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
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:
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:
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"
}
Import-Module .\PlaylistTools -Force
Remove-Duplicates -Path ..\playlists\party-mix.csv
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:
Install-PSResource -Name PSScriptAnalyzer
Then point it at the module file:
Invoke-ScriptAnalyzer -Path .\PlaylistTools\PlaylistTools.psm1
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) orInformation(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,
sortisn'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,ResetorUpdateshould offer-WhatIf, as in-WhatIfin 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-VerboseorWrite-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:
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
}
}
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
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:
@{
Severity = @('Error', 'Warning')
ExcludeRules = @('PSAvoidUsingWriteHost')
}
For one function only, put a SuppressMessageAttribute above its param():
[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.
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.