- Interact with Azure Cosmos DB with PowerShell - Tue, Sep 14 2021
- Azure health services: Track Microsoft cloud outages and maintenance - Wed, Sep 8 2021
- Powerline: Customize your PowerShell console - Tue, Aug 31 2021
Static code analysis refers to reviewing and refactoring code without actually running it. Specifically, the Windows PowerShell team at Microsoft and the PowerShell community at large have developed a body of best practices for quality PowerShell scripting.
You can learn more about this collection of PowerShell scripting best practices by examining the following resources:
- The Community Book of PowerShell Practices
- Book: Windows PowerShell Best Practices
- Course: Windows PowerShell Best Practices and Patterns
Anyway, the PowerShell development team created the PowerShell Script Analyzer (PSScriptAnalyzer) module as a way to help us administrative scripters check our code against best practices. As has become their custom of late, the project is open-source and hosted at GitHub. The open-source nature of the project means that you can clone the project and contribute changes and additional features yourself via Git pull requests.
Here are three good reasons for you to consider running the Script Analyzer against all your PowerShell scripts from now on:
- PSScriptAnalyzer is included in Windows Management Framework (WMF) v5.
- Writing community-approved code makes your code easier to understand and maintain by you and others.
- Any module you upload to the PowerShell Gallery is automatically scanned with PSScriptAnalyzer.
With all that said, let’s use the PowerShell Script Analyzer.
Installing PSScriptAnalyzer ^
Although PSScriptAnalyzer is (supposedly) included in WMF v5, we’ll use PowerShellGet to install the latest and greatest version from The PowerShell Gallery. I say “supposedly” because some WMF v5 preview builds include the module, and some don’t. Welcome to rapid deployment cycles! J
Install-Module -Name PSScriptAnalyzer -Force
Let’s examine the contents of the PSScriptAnalyzer module:
Get-Command -Module PSScriptAnalyzer | Select-Object -Property CommandType,Name,Version | Format-Table -AutoSize CommandType Name Version ----------- ---- ------- Cmdlet Get-ScriptAnalyzerRule 1.2.0 Cmdlet Invoke-ScriptAnalyzer 1.2.0
I’m a big fan of reading PowerShell help with the -ShowWindow switch so I can put my console/ISE and help file side by side. Don’t forget to run Update-Help first!
Get-Help Invoke-ScriptAnalyzer -ShowWindow Get-Help Get-ScriptAnalyzerRule -ShowWindow Get-Help about_PSScriptAnalyzer -ShowWindow
Now, let’s scan the built-in ruleset:
Get-ScriptAnalyzerRule | Select-Object -Property CommonName | Sort-Object -Property CommonName CommonName ---------- Avoid Default Value For Mandatory Parameter Avoid Invoking Empty Members Avoid Using Cmdlet Aliases Avoid Using ComputerName Hardcoded Avoid Using Deprecated Manifest Fields Avoid Using Empty Catch Block Avoid Using Get-WMIObject, Remove-WMIObject, Invoke-WmiMethod, Register-WmiEvent, Set-WmiInstance Avoid Using Invoke-Expression Avoid Using Plain Text For Password Parameter Avoid Using Positional Parameters Avoid Using SecureString With Plain Text Avoid Using ShouldContinue Without Boolean Force Parameter Avoid Using Username and Password Parameters Avoid Using Write-Host Basic Comment Help Cmdlet Singular Noun Cmdlet Verbs DSC examples are present Dsc tests are present Extra Variables Module Manifest Fields No Global Variables Null Comparison PSCredential Reserved Cmdlet Chars Reserved Parameters Return Correct Types For DSC Functions Should Process Switch Parameters Should Not Default To True Use BOM encoding for non-ASCII files Use Cmdlet Correctly Use identical mandatory parameters for DSC Get/Test/Set TargetResource functions Use Identical Parameters For DSC Test and Set Functions Use OutputType Correctly Use ShouldProcess For State Changing Functions Use Standard Get/Set/Test TargetResource functions in DSC Resource Use UTF8 Encoding For Help File Use verbose message in DSC resource
Each of those rules is explained, to a greater or lesser degree, in the project’s GitHub repository.
Running a scan ^
Take a look at the following screenshot. In it, I wrote a PowerShell script called bad-script.ps1 that intentionally violates several PSScriptAnalyzer rules.
Our poorly coded PowerShell script
At base, my Retrieve-UptimeValues script actually does something quite useful; it reports system uptime in a reader-friendly format.
Invoke-PSScriptAnalyzer -Path ‘.\bad-script.ps1’
The following screenshot shows the results of my best-practices scan. I extended the pipeline to format the results in a more eye-friendly manner.
The results of our best-practices scan
What’s good is that we have no errors—only warnings. That said, I was surprised to see that some of my code didn’t trigger errors that I know are tracked in the default ruleset. Bugs are part of any open-source, community project.
At any rate, here’s the Retrieve-UptimeValues script in the Windows PowerShell ISE with the detected violations highlighted in yellow:
I highlighted the PSScriptAnalyzer rule violations.
Now, I’ll explain my detected mistakes by line number:
- 1: We have two problems here. First, we’re using an unapproved verb (use Get-Verb to see the full list of approved command verbs). Second, command nouns should be singular, not plural.
- 10: To improve our code readability, we should avoid command aliases. Instead, we expand command names and parameter names. Also, we use named parameters instead of positional parameters wherever possible.
- 13: Once again, we want to avoid the use of command aliases (here, cls is a built-in alias for Clear-Host).
- 14: Unless we’re actually formatting output for the screen, we should use Write-Output so we have string data remaining in the pipeline for potential future processing.
- 16: If we use Try/Catch blocks, then we need to actually code an exception handler in our Catch block.
- 18: What’s the point of writing PowerShell functions unless they are dynamic? To that point, we need to remove hard-coded references and instead define parameter variables.
As I said, there’s some other wacky stuff in my script that wasn’t flagged by the script analyzer:
- I defined an optional input parameter named $futureParam that’s actually not used in the function body. In fact, with a simple script such as this, it’s probably overkill to use the CmdletBinding directive at all.
- On line 12, I’m using double quotes. Technically, we can enclose string data with single or double quotes. PowerShell best practice specifies that we use single quotes unless we need to automatically expand variables.
- On line 11, I use get-date in lowercase. This is a picky detail, perhaps, but your command/function calls should use title case (Get-Date).
Here’s my slimmed-down, refactored code:
Our function is in much better shape now.
Working with PSScriptAnalyzer rules ^
Your IT department may have its own set of priorities in terms of quality PowerShell scripting. We can play around with Invoke-ScriptAnalyzer to, for instance, exclude one or more default rules:
Invoke-ScriptAnalyzer -Path .\bad-script.ps1 -ExcludeRule PSUsesSingularNouns
Or we can do just the opposite, specifying only some rules:
Invoke-ScriptAnalyzer -Path .\bad-script.ps1 -IncludeRule PSUseApprovedVerbs,PSAvoidUsingCmdletAliases
Finally, if you prefer to do static code analysis with the PowerShell Script Analyzer from the PowerShell ISE or another scripting tool, consider the following integration points:
- Windows PowerShell ISE Script Analyzer Add-in
- ISESteroids Script Analyzer integration
- Visual Studio Code Script Analyzer add-in