Learn about the PowerShell Abstract Syntax Tree (AST) – Part 3

This blog article is the third in a series of learning about the PowerShell Abstract Syntax Tree (AST). Be sure to read the other two if you haven't already.

In this blog article, I'll be specifically focusing on finding the AST recursively.

I'll start off by storing the path to one of my functions in a variable.

1$FilePath = 'U:\GitHub\Hyper-V\MrHyperV\public\Get-MrVmHost.ps1'

part3-ast2a.jpg

I'll store the AST in a variable and output what's in the variable just to confirm that it contains the AST.

1$AST = [System.Management.Automation.Language.Parser]::ParseFile($FilePath, [ref]$null, [ref]$null)
2$AST

part3-ast3b.jpg

Now to return the AST recursively. Although this command appears to return the same results based on the following screenshot, it actually returns much more.

1$AST.FindAll({$true}, $true)

part3-ast4a.jpg

The following example gives a better idea of the results. Just returning the top level AST only returns one item, whereas querying the AST recursively returns 118 items, or 26 different unique types.

1($AST | Get-Member).TypeName | Sort-Object -Unique
2($AST.FindAll({$true}, $true) | Get-Member).TypeName | Sort-Object -Unique

part3-ast5a.jpg

You'll probably be overwhelmed by all of the AST that's returned when querying it recursively. The solution is to narrow down the results. What are the AST type possibilities that can be used to narrow down the results?

A list of them can be retrieved by querying the System.Management.Automation.Language.ArrayExpressionAst .NET class. I ended up using a regular expression to cleanup the results and while I’ve seen others do this without a regex, they typically end up removing Expression from the list which makes it incomplete.

1([System.Management.Automation.Language.ArrayExpressionAst].Assembly.GetTypes() |
2Where-Object {$_.Name.EndsWith('Ast') -and $_.Name -ne 'Ast'}).Name -replace '(?<!^)ExpressionAst$|Ast$' |
3Sort-Object -Unique

part3-ast1a.jpg

I'll use one of the items (Variable) from the previous list to return only the AST information for the variables used in my function.

1$AST.FindAll({$args[0].GetType().Name -like "*Variable*Ast"}, $true)

part3-ast6a.jpg

Finally, I'll return a unique list of only the variables themselves that are used in the function.

1$AST.FindAll({$args[0].GetType().Name -like "*Variable*Ast"}, $true) | Select-Object -Property Extent -Unique

part3-ast7a.jpg

As you can see, there's a lot of power in using the AST.

In my next blog article, I'll share and demonstrate functions that I've created that use the AST to help automate my script module build process.

µ