PowerShell Pipeline

Better Script Building Using PSScriptAnalyzer

Check to see if your code adheres to the PowerShell community best practices with this tool.

In the quest for writing better code in PowerShell, we have a great tool that can help you to write better code by pointing out areas that can be improved to better match a 'best practices' approach. That tool is PSScriptAnalyzer and is available to download from the PowerShellGallery. Now you might be asking yourself (or me), what is PSScriptAnalyzer and why should I even care about this?

Well, this module is a static code checker that utilizes many best practices as defined by the PowerShell team as well as the PowerShell community. Warnings and error are generated during a run of the Invoke-ScriptAnalyzer command to help identify areas of concern in a script file. This can range from cmdlet aliases being used to global variables and even variables that have not been initialized. Keep in mind that these are guidelines and everything should be reviewed to determine if they really benefit from making a change in your code to satisfy a warning.

You can download this module from the PowerShellGallery using Install-Module.

 

Install-Module  PSScriptAnalyzer -Verbose –Force
[Click on image for larger view.] Figure 1. Downloading PSScriptAnalyzer using Install-Module.

If you do not have PowerShell V5 and want this great tool, you can pick it up from the GitHub repo here. Note: PSScriptAnalyzer will only run on Windows 8.1/Windows Server 2012 R2 at a minimum OS and PowerShell V3+. Anything below this will unfortunately cause issues.

When you think of a tool to analyze your scripts, you might think that this is probably some sort of ISE-only utility, but actually this is available to run on the PowerShell console! Let's take a look at the available commands in this module;

Get-Command -Module PSScriptAnalyzer
[Click on image for larger view.] Figure 2. Available commands with PSScriptAnalyzer.

Before we start looking at running the analyzer, we can first check out all of the rules that come with the initial module using Get-ScriptAnalyzerRule.

[Click on image for larger view.] Figure 3. A look at all of the rules.

It turns out that we have 27 rules that come with this module. That is quite a bit to look at our scripts to see where we might be lacking in meeting the best practices.

For some fun, let's take the following function, called Get-Disks and run it through the analyzer and see what kind of damage we are dealt.

Function Get-Disks  {
Param (
[parameter()]
$Computername = $env:COMPUTERNAME
)
Begin {
$WMIParams = @{
Filter = "DriveType=3 AND NOT Caption LIKE '\\\\%'"
Class = 'Win32_Volume'
ErrorAction = 'Stop'
}
}
Process {
$Computername | ForEach-Object {
$Computer = $_
$WMIParams.Computername = $Computer
Try {
Get-WMIObject @WMIParams  | ForEach-Object {
[pscustomobject]@{
Computername = $_.PSComputername
DriveLetter = $_.DriveLetter
Label = $_.Label
FreeSpace = $_.FreeSpace/1GB
Capacity = $_.Capacity/1GB
PercentFree = "{0:P2}" -f ($_.FreeSpace / $_.Capacity)
}
}
}
Catch {
Write-Warning "[$Computer] $_"
}
}
}
}

 

It looks somewhat OK, right? If you work with PowerShell frequently, I have no doubt that you have already picked out a few things that do not seem quite right and should probably throw some warnings or errors when we run the script analyzer. So without further ado, let's kick it off and see what we come back with!

Invoke-ScriptAnalyzer -Path .\Get-Disks.ps1  | Select "RuleName",  Severity,  Line,  Column 

I've left out the description part in the output to keep all of the results on a single page without having to scroll down on the console. In all, we have over 20 items which may be an issue as called out by the script analyzer.

[Click on image for larger view.] Figure 4. A look at the first run of the analyzer on a script.

Let's see what we can fix and what we aren't really that concerned about.

AvoidUsingCmdletAliases: These two happen to be the ForEach that we are piping data into for further processing. The full cmdlet name of this is ForEach-Object, not to be confused with the ForEach language keyword that you can also use. Simple enough fix for me to knock out so I will do just that!

AvoidUsingEmptyCatchBlock: I should at least throw some sort of warning stream to alert the user whenever the catch block is hit on an error. Another easy fix.

AvoidUnitializedVariable: I do not really consider this an issue unless the variables are never going to be used. Every variable name is used and this even flags on my $Computername parameter as well as the automatic variable $_. Something like this is definitely a case by case basis.

AvoidUsingPositionalParameters: Here I should avoid adding the parameter values after a cmdlet/function and instead use the full parameter name along with the value to make it easier to understand what each value is being used for in the command. A definite fix! Fun fact, two of the hits come from ForEach-Object. Can you guess what the parameter is? If you said –Process, then you are doing great!

ProvideVerboseMessage: Some verbose messaging might be a good idea to help with troubleshooting various parts of the command. Fix!

ProvideCommentHelp: This is a must in any function or script that you write! Not having help is a big deal if you expect someone outside of the current you to fully understand what to do with the command. A must fix!

AvoidUnloadableModule: This is not a part of any module so this will be ignored.

UseSingularNouns: Whoops! Remember that your function name should be Verb-SingularNoun. Definitely a must fix!

Now let's take a look at our new and improved function after all of the refactoring by using PSScriptAnalyzer.

Function Get-Disk  {
<#
.SYNOPSIS
Used to view disk space on a system

        .DESCRIPTION
Used to view disk space on a system

        .PARAMETER Computername
The computername to view disk space against

        .NOTES
Name: Get-Disk
Author: Boe Prox

        .EXAMPLE
Get-Disk -Computername $Env:Computername

            Computername : BOE-PC
DriveLetter  : C:
Label        : OS Disk
FreeSpace    : 29.2846488952637
Capacity     : 170.458980560303
PercentFree  : 17.18 %

            Description
-----------
Shows the disk space for local system.
#>
Param (
[parameter()]
$Computername = $env:COMPUTERNAME
)
Begin {
Write-Verbose -Message "Creating splatting table for cmdlet."
$WMIParams = @{
Filter = "DriveType=3 AND NOT Caption LIKE '\\\\%'"
Class = 'Win32_Volume'
ErrorAction = 'Stop'
}
}
Process {
$Computername | ForEach-Object -Process {           
$Computer = $_
$WMIParams.Computername = $Computer
Write-Verbose -Message "Processing computer: $Computer"
Try {
Get-WMIObject @WMIParams  | ForEach-Object -Process {
[pscustomobject]@{
Computername = $_.PSComputername
DriveLetter = $_.DriveLetter
Label = $_.Label
FreeSpace = $_.FreeSpace/1GB
Capacity = $_.Capacity/1GB
PercentFree = "{0:P2}" -f ($_.FreeSpace / $_.Capacity)
}
}
}
Catch {
Write-Warning -Message "[$Computer] $_"
}
}
}
}

And let's see what things look like by re-running the analyzer to see the results.

Invoke-ScriptAnalyzer -Path .\Get-Disks.ps1  -ExcludeRule AvoidUnloadableModule,AvoidUnitializedVariable  |
Select "RuleName", Severity, Line, Column

Note the use of the –ExcludeRule parameter. I have decided to ignore the Variable and Module rules as I do not believe that they are valid enough to warrant any action. The result is that nothing is returned from the command meaning that I have satisfied the 'best practices' scan!

Now it is your turn to take PSScriptAnalyzer for a spin and see how your code stacks up to the scan! Just be sure to evaluate each item to make the proper determination if you should update your code based on each of the results that are found.

About the Author

Boe Prox is a Microsoft MVP in Windows PowerShell and a Senior Windows System Administrator. He has worked in the IT field since 2003, and he supports a variety of different platforms. He is a contributing author in PowerShell Deep Dives with chapters about WSUS and TCP communication. He is a moderator on the Hey, Scripting Guy! forum, and he has been a judge for the Scripting Games. He has presented talks on the topics of WSUS and PowerShell as well as runspaces to PowerShell user groups. He is an Honorary Scripting Guy, and he has submitted a number of posts as a to Microsoft's Hey, Scripting Guy! He also has a number of open source projects available on Codeplex and GitHub. His personal blog is at http://learn-powershell.net.

comments powered by Disqus
Most   Popular