Ironman Software Forums
Continue the conversion on the Ironman Software forums. Chat with over 1000 users about PowerShell, PowerShell Universal, and PowerShell Pro Tools.
As a part of our PowerShell Universal v5 release, we are adding some custom PSScriptAnalyzer rules to the platform to make it easier to spot problems when building out scripts. In this post, we’ll walk through how to create custom PSScriptAnalyzer rules.
PSScriptAnalyzer
PSScriptAnalyzer
is a static code analysis tool for PowerShell scripts. It checks the quality of the code based on a set of rules. The built in rules check all kinds of things like alias usage, spaces at the ends of lines, $null
checks and more. When using editors, like VS Code, PSScriptAnalyzer runs in the background to provide real-time feedback on your scripts.
Internally, the PowerShell extension is running Invoke-ScriptAnalyzer
to provide this feedback. You can run this command yourself to see the results of the analysis. Running with just the path will run all the default rules.
Invoke-ScriptAnalyzer -Path .\MyScript.ps1
You can also exclude rules using parameters on the cmdlet.
Invoke-ScriptAnalyzer -Path .\MyScript.ps1 -ExcludeRule PSAvoidUsingCmdletAliases
In order to customize PSScriptAnalyzer
behavior without having to modify the command line, you can also use a configuration file. This file does everything the different parameters on Invoke-ScriptAnalyzer
.
# PSScriptAnalyzerSettings.psd1
@{
Severity=@('Error','Warning')
ExcludeRules=@('PSAvoidUsingCmdletAliases', 'PSAvoidUsingWriteHost')
}
You can use script analyzer settings files by passing the -Settings
parameter to Invoke-ScriptAnalyzer
.
Invoke-ScriptAnalyzer -Path MyScript.ps1 -Settings PSScriptAnalyzerSettings.psd1
You can also use this settings file with VS Code by configuring this setting.
Custom rules are defined using PowerShell modules. Custom rules can accept a list of tokens or an abstract syntax tree (AST) and return diagnostic records. For example, in PowerShell Universal, we have a rule that checks for the use of built in variables like $PSUEnvironment
or $UniversalClient
. Setting these items would cause undefined behaviors so we want users to know up front when they attempt to override them.
The first step is to create a new .psm1
module file and define a function that will be used to analyze the script. In this example, we’ve chosen Universal.Rules.psm1
as the file name and defined the Measure-PSUBuiltInVariables
function. Comment-based help is required and there are some specific details of this function that should be noted.
First, you need to include the OutputType
attribute to specify the type of object that the function will return. In this case, it will return an array of Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord
objects. You also need to accept a ScriptBlockAst
object as a parameter. This object represents the script block of the script that is being analyzed.
<#
.SYNOPSIS
Locates built in PowerShell Universal variables
.DESCRIPTION
Checks to make sure that built in PowerShell Universal variables are not being used in the script.
.EXAMPLE
Measure-PSUBuiltInVariables -ScriptBlockAst $ScriptBlockAst
.INPUTS
[System.Management.Automation.Language.ScriptBlockAst]
.OUTPUTS
[Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]
.NOTES
None
#>
function Measure-PSUBuiltInVariables {
[CmdletBinding()]
[OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
Param
(
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[System.Management.Automation.Language.ScriptBlockAst]
$ScriptBlockAst
)
The body of this function does the heavy lifting of analyzing the script block. In our example, we check the AST to try to find assignment statements. If we find assignment statements, we then check to see if the left side of the assignment is a variable. If that’s the case, we then using a static method to verify whether it’s one of the built in variables in PowerShell Universal.
If the conditions are met, we create new DiagnosticRecord
objects and populate the necessary information. $_.Extent
is important because it tells editors, like VS Code or Monaco, what part of the script to highlight with the warning or error.
Process {
$results = @()
try {
#region Define predicates to find ASTs.
[ScriptBlock]$predicate1 = {
param ([System.Management.Automation.Language.Ast]$Ast)
if ($Ast -is [System.Management.Automation.Language.AssignmentStatementAst]) {
if ($Ast.Left -is [System.Management.Automation.Language.VariableExpressionAst]) {
return [PowerShellUniversal.BuiltInVariables]::IsBuiltInVariable($Ast.Left.VariablePath.UserPath)
}
}
return $false
}
#endregion
#region Finds ASTs that match the predicate.
[System.Management.Automation.Language.Ast[]]$methodAst = $ScriptBlockAst.FindAll($predicate1, $true)
$methodAst | ForEach-Object {
$result = New-Object `
-Typename "Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord" `
-ArgumentList "Overriding built in PowerShell Universal variables can cause undefined behavior.", $_.Extent, $PSCmdlet.MyInvocation.InvocationName, Warning, $null
$results += $result
}
return $results
#endregion
}
catch {
$PSCmdlet.ThrowTerminatingError($PSItem)
}
}
Finally, we make sure to export the function from our module.
Export-ModuleMember -Function Measure-PSUBuiltInVariables
Inside PowerShell Universal, we use Invoke-ScriptAnalyzer
to run code analysis. As you type in the editor, this function is called to provide real-time feedback. In order to use our custom rule, we pass the -CustomRulePath
parameter to Invoke-ScriptAnalyzer
.
Invoke-ScriptAnalyzer -ScriptDefinition $code -CustomRulePath $InstallDir\Modules\Universal.Rules\Universal.Rules.psm1 -IncludeDefaultRules
We then process the output of PSScriptAnalyzer
to provide feedback to the user.
Have some ideas for custom rules for PowerShell Universal? Let us know.
Continue the conversion on the Ironman Software forums. Chat with over 1000 users about PowerShell, PowerShell Universal, and PowerShell Pro Tools.
Receive once-a-month updates about Ironman Software. You'll learn about our product updates and blogs related to PowerShell.