Getty Images/iStockphoto

Try PSScriptAnalyzer to check PowerShell code best practices

Meeting best practices can be a tough feat. Thankfully, PowerShell Script Analyzer checks the code for you. Follow these examples to learn how it works and get started.

When it comes to defining best practice code in PowerShell, there are as many opinions as there are people online -- and not all of them are valuable. However, if you write PowerShell code, for business or for pleasure, and want to check for best practice, take the time to learn about PowerShell Script Analyzer.

PowerShell Script Analyzer, commonly written PSScriptAnalyzer, is a static code analysis tool, which examines written code and evaluates it for various machine-measurable best practices. The module accomplishes this through rules, each of which defines a best practice and tells the engine how to scan for it. This does sound complicated, so let's get the module set up and then dive into an example.

Install PSScriptAnalyzer

Even though PowerShell Script Analyzer is included in the Windows Management Framework, the first thing we need to do for this tutorial is to install the latest version of the PSScriptAnalyzer module. We can do this with the standard Install-Module cmdlet in both Windows PowerShell and PowerShell:

Install-Module PSScriptAnalyzer -Force

We use the -Force parameter to ensure the latest version is installed, even if we have an older version installed on the machine already.

With the PSScriptAnalyzer module installed, we can look through the common names of the available rules with the following command:

Get-ScriptAnalyzerRule | Sort-Object RuleName | Select-Object CommonName

This will give you a list that should look something like Figure 1.

List the common names of the available rules
Figure 1. List of available rules.

These rules are listed under their common names because these are, for the most part, easy to understand as written. For more details about rules, omit the sort and select.

Each rule represents a best practice that PowerShell Script Analyzer can measure in a quantitative way. Easy examples include 'Avoid using Write-Host' or 'Avoid trailing whitespace.' Examples that I wouldn't understand by reading the common name include things like 'Align assignment statement'. We can use the following command to look at the full rule to get a better understanding:

Get-ScriptAnalyzerRule | ?{$_.CommonName -eq 'Align assignment statement'}

This will produce the outcome seen in Figure 2.

The 'Align assignment statement' rule lines up assignment statements so the assignment operators are aligned
Figure 2. Description of the 'Align assignment statement' rule.

Get started with PowerShell Script Analyzer

To understand how rules work, let's run through an example.

Here, we have a function that was written to violate several PSScriptAnalyzer rules intentionally. This function will reset the password for an Active Directory user by connecting to a domain controller:

Function Reset-AdPassword {
    param (
        [Parameter( Mandatory )]
        [string]$User,
        [Parameter( Mandatory )]
        [string]$Password = 'Password1234!'
    )
    Write-Host "Resetting password for '$user'"
    icm -ComputerName 'DC01' -ScriptBlock {
 
        Set-ADAccountPassword $args[0] -NewPassword (ConvertTo-SecureString $args[1] -AsPlainText -Force) -Reset
 
        Try {
            Unlock-ADAccount $args[0]
        } catch {}
    } -ArgumentList $User,$Password
   
}

If you've read through many of the rules, you can probably spot several violations without needing to use PSScriptAnalyzer.

With the file saved as PSSA.ps1, we scan it with PSScriptAnalyzer by running the Invoke-ScriptAnalyzer .\PSSA.ps1 command:

This command produces the rather informative output seen in Figure 3.

List of the violated rules and descriptions as to why
Figure 3. List of violated rules.

Because it is so verbose, we get a lot of information about each violated rule and on which line said violation occurred, as well as additional information that will help us resolve this issue. Let's sort them by line and go through them in a list.

1 – ShouldProcess tells PowerShell whether to prompt for action. You'll see this on cmdlets that use Remove, Reset, or Disable -- in short, destructive or changing verbs.

3 – When passing usernames and passwords into a function, best practice is to request a credential object, rather than the username and password separately.

5 – An alternative to the solution presented on line 3, you could set the password as a secure string. In fact, secure strings are always advised for sensitive data such as passwords.

6 – When you set a parameter to Mandatory, it can't use a default value -- it's pointless to set a parameter as Mandatory and to also have a default value.

8 – Using Write-Host limits the functionality of your script as noted in the message. If you write a message as was the intention in this function, use the Write-Information command instead. If you write values to the pipeline, use the Write-Output command.

9 – My use of aliases is one of the most commonly flagged rules in my scripts. You should always specify the complete name of the cmdlet or function to avoid possible problems and difficulties in script maintenance.

9 – Hardcoded computer names make the script less useful; instead, pass that value as a parameter with a default value.

11 – Much like line 5, converting a string to a secure string inline does not protect the data in the string.

15 – Using a try block with an empty catch misses the point of try-catch. If you need a cmdlet to run and it doesn't matter if it fails, use -ErrorAction SilentlyContinue.

17 – Trailing whitespaces bloat script files -- remove them.

Fortunately, all these identified issues are straightforward fixes. If I were to rewrite the function, it might look like this:

Function Reset-AdPassword {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter( Mandatory )]
        [string]$User,
        [Parameter()]
        [securestring]$Password,<
        [Parameter()]
        [string]$DomainController = 'DC01'
    )
    Write-Information "Resetting password for '$user'"
    if ($PSCmdlet.ShouldProcess("User $user",'Reset password')) {
        Invoke-Command -ComputerName $DomainController -ScriptBlock {
            Set-ADAccountPassword $args[0] -NewPassword $args[1] -Reset
            Unlock-ADAccount $args[0] -ErrorAction SilentlyContinue
        } -ArgumentList $User,$Password
    }
}

If we scan it with PowerShell Script Analyzer, we receive no output, which displays as follows:

PS>Invoke-ScriptAnalyzer .\PSSA_fixed.ps1 | sort line

And in this case, no output is good.

Skipping rules

Occasionally, you will come across a legitimate reason to ignore a rule in PSScriptAnalyzer. For instance, maybe you are writing a function for a less technically inclined individual and you want to accept a password as a plain string. To avoid PSScriptAnalyzer pointing that out as an error, add a SuppressMessageAttribute to the top of the function or script. For our example, we can do that with the following code:

Function Reset-AdPassword {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter( Mandatory )]
        [string]$User,
        [Parameter()]
        [securestring]$Password,
        [Parameter()]
        [string]$DomainController = 'DC01'
    )
    Write-Information "Resetting password for '$user'"
    if ($PSCmdlet.ShouldProcess("User $user",'Reset password')) {
        Invoke-Command -ComputerName $DomainController -ScriptBlock {
            Set-ADAccountPassword $args[0] -NewPassword $args[1] -Reset
            Unlock-ADAccount $args[0] -ErrorAction SilentlyContinue
        } -ArgumentList $User,$Password
    }
}

Notice that I switched the password back to a normal string. If we run PSScriptAnalyzer against it, we still pass:

PS>Invoke-ScriptAnalyzer .\PSSA_fixed.ps1 | sort line

This feature is useful if you have a build pipeline that scans code before publishing it; violations of best practice are ignored because they are intentional. However, use caution with the SuppressMessageAttribute because best practices are best practices for a reason. This workaround is not generally recommended.

PowerShell Script Analyzer measures your code against industry best practices quickly and easily. Instead of posting code on Reddit or StackOverflow and receiving little to no positive feedback, run Invoke-ScriptAnalyzer to illuminate the issue and receive some information on how to fix it.

Dig Deeper on IT systems management and monitoring