Use the Abstract Syntax Tree (AST) to list parameters and variables in PowerShell functions

One thing I've missed during the past couple of years with virtual-only conferences is the hallway track. While at the PowerShell + DevOps Global Summit 2022, there was a discussion about using PascalCase for parameter names and camelCase for user-defined variables in your PowerShell functions.

Specifying different casings depending on the usage seems like a great idea. Determining where you defined your variables would be self-explanatory. The only problem is you need something to verify that you've specified them in the correct case.

This scenario is a perfect opportunity to use the Abstract Syntax Tree (AST) to verify the casing is correct depending on usage.

I started using the Get-MrAst function in my MrModuleTools PowerShell module from my ModuleTools GitHub repo to retrieve the AST from a file or script block.

1Get-MrAst -Path C:\MrGit\Inspector\MrInspector\public\Get-MrSyntax.ps1

variable-type1a.jpg

I used another one of my functions named Get-MrAstType in the same module to determine the different options for filtering the AST. This part was straightforward since they're named ParameterAst and VariableExpressionAst.

1Get-MrAstType | Format-Wide -Property {$_} -Column 3 -Force

variable-type2a.jpg

There are two different ways to obtain the parameters and variables. One contains the dollar sign and the other, only the variable name. I opted for the one with only the variable name. I filtered out the underscore to prevent current object variables from showing up in the list unless you're using PSItem.

The list of variables also contains the parameters because they're specified in the body of the function. I used Compare-Object to determine which items are duplicated in both lists. An If statement identifies the duplicates as parameters and the non-duplicated items as user-defined variables.

I included the line and column numbers in the output to know where the variables are used. Sometimes they're specified more than once on the same line.

 1#Requires -Version 4.0
 2function Get-MrVariableType {
 3
 4<#
 5.SYNOPSIS
 6    List variables and whether they're defined as parameters or in the body of a function.
 7
 8.DESCRIPTION
 9    Get-MrVariableType is an advanced function that returns a list of variables defined in a
10    function and whether they are parameters or user defined within the body of the function.
11
12 .PARAMETER Ast
13    Provide a ScriptBlockAst object via parameter or pipeline input. Use Get-MrAst to create this
14    object.
15
16.EXAMPLE
17     Get-MrAST -Path 'C:\Scripts' | Get-MrVariableType
18
19.EXAMPLE
20     Get-MrVariableType -Ast (Get-MrAST -Path 'C:\Scripts')
21
22.NOTES
23    Author:  Mike F Robbins
24    Website: http://mikefrobbins.com
25    Twitter: @mikefrobbins
26#>
27
28    [CmdletBinding()]
29    param (
30        [Parameter(Mandatory,
31                   ValueFromPipeline)]
32        [System.Management.Automation.Language.ScriptBlockAst]$Ast
33    )
34
35    PROCESS {
36
37        $variables = $Ast.FindAll({$args[0].GetType().Name -like 'VariableExpressionAst'}, $true).Where({$_.VariablePath.UserPath -ne '_'})
38
39        $parameters = $Ast.FindAll({$args[0].GetType().Name -like 'ParameterAst'}, $true)
40
41        $diff = Compare-Object -ReferenceObject $parameters.Name.VariablePath.UserPath -DifferenceObject $variables.VariablePath.UserPath -IncludeEqual
42
43        foreach ($variable in $variables) {
44
45            [pscustomobject]@{
46                Name = $variable.VariablePath.UserPath
47                Type = if ($variable.VariablePath.UserPath -in $diff.Where({$_.SideIndicator -eq '=='}).InputObject) {
48                           'Parameter'
49                       } else {
50                           'UserDefined'
51                       }
52                LineNumber = $variable.Extent.StartLineNumber
53                Column = $variable.Extent.StartColumnNumber
54            }
55
56        }
57
58    }
59
60}

You can pipe the output of Get-MrAst to Get-MrVariableType or provide the results via parameter input with the Ast parameter.

1Get-MrAst -Path C:\MrGit\Inspector\MrInspector\public\Get-MrSyntax.ps1 |
2Get-MrVariableType

variable-type3a.jpg

You can also sort the output with Sort-Object to see where you've specified variables in a different case. Notice the differences with the file and true variables in the following results.

1Get-MrAst -Path C:\MrGit\Inspector\MrInspector\public\Get-MrSyntax.ps1 |
2Get-MrVariableType |
3Sort-Object -Property Name

variable-type4a.jpg

You could write a unit test using Pester to identity when parameters and variables don't adhere to your standards. You could also write code that uses the output of Get-MrVariableType to automate the remediation of variable casing.

The Get-MrVariableType function shown in this blog article is part of my MrModuleTools PowerShell module. You can install it from the PowerShell Gallery using the following command:

1Install-Module -Name MrModuleTools

The video shown below demonstrates how to use the Get-MrVariableType function.

You can download the source code from my ModuleTools GitHub repo. Feel free to submit a GitHub issue if you have suggestions or find problems. I accept pull requests if you would like to contribute.

µ