Going Out on a Limb on the Abstract Syntax Tree
I thought I'd start out the new year by revisiting a topic I haven't covered in quite awhile. This is a topic that definitely fits the theme of this newsletter which is to dive deep into PowerShell topics and get a peek under the hood. Today I want to start with a short introduction to the Abstract Syntax Tree, also referred to as the AST.
PowerShell, and related tools, can use the AST to analyze a PowerShell command or script file and break it down to its individual components. This is a powerful tool that can be used to analyze PowerShell code. You can use the AST to see how PowerShell interprets your code.
Parsing with the AST
The AST is a .NET class referenced as [System.Management.Automation.Language.Parser]
. I can use my Get-TypeMember
function from the PSScriptTools module to explore the class.
PS C:\> Get-TypeMember System.Management.Automation.Language.Parser
Type: System.Management.Automation.Language.Parser
Name MemberType ResultType IsStatic IsEnum
---- ---------- ---------- -------- ------
GetType Method Type
ParseFile Method ScriptBlockAst True
ParseInput Method ScriptBlockAst True
To demonstrate, I'm going to parse this demo PowerShell script file.
#requires -version 5.1
#requires -RunAsAdministrator
<#
Sample-Script.ps1
Get event log data from a specified computer.
This is a sample script intended to demonstrate
a variety of PowerShell scripting techniques
and concepts. This script is for educational
purposes and not intended for production use.
#>
Param(
[string]$Computername = $Env:COMPUTERNAME,
[PSCredential]$Credential
)
# define a list of event logs to query with Get-WinEvent
$logs = 'System', 'Application', 'Windows PowerShell'
# create a hashtable for log results
$logHash = [ordered]@{
Computername = $Computername
Date = Get-Date
}
# define the filter hashtable for Get-WinEvent
$filter = @{
LogName = $null
Level = 2, 3
}
Write-Host "Querying logs on $Computername" -ForegroundColor Cyan
foreach ($log in $logs) {
<#
I am splatting any bound PSParameters
to the Get-WinEvent cmdlet. Otherwise,
Get-WinEvent will use default values.
#>
$filter.LogName = $log
$entries = Get-WinEvent @PSBoundParameters -FilterHashtable $filter
Write-Host "Found $($entries.Count) entries in $log" -ForegroundColor Cyan
$logHash.Add($log, $entries)
} #close foreach
#write results to the pipeline as a custom object
[PSCustomObject]$logHash
I'll let you run the script to see what it does. The script works, which is the important part.
To parse the file, I will need to pass a few parameters.
PS C:\> [System.Management.Automation.Language.Parser]::ParseFile.OverloadDefinitions
static System.Management.Automation.Language.ScriptBlockAst ParseFile(string fileName, [ref] System.Management.Automation.Language.Token[] tokens, [ref] System.Management.Automation.Language.ParseError[] errors)
This critical step here is recognizing that some of the parameters are passed by reference ([ref]
). This means I need to create an empty array to hold the results. I need a variable for tokens and a variable for errors. Because these are defined a [ref]
type, you need to define them before you can use them.
New-Variable astTokens -Force
New-Variable astErr -Force
I might as well define the path to the script file as well.
$path = 'c:\scripts\sample-script.ps1'
Now, I can parse the file.
$AST = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$astTokens, [ref]$astErr)
If there were any errors, they would be stored in the $astErr
variable. I'll come back to $astTokens
in a little bit.
The AST Object
The value of $AST
is a ScriptBlockAst
object. This is a rich object.
PS C:\> Get-TypeMember System.Management.Automation.Language.ScriptBlockAst
Type: System.Management.Automation.Language.ScriptBlockAst
Name MemberType ResultType IsStatic IsEnum
---- ---------- ---------- -------- ------
Find Method Ast
FindAll Method IEnumerable`1
GetHelpContent Method CommentHelpInfo
GetScriptBlock Method ScriptBlock
GetType Method Type
SafeGetValue Method Object
Visit Method Object
Visit Method Void
Attributes Property ReadOnlyCollection`1
BeginBlock Property NamedBlockAst
CleanBlock Property NamedBlockAst
DynamicParamBlock Property NamedBlockAst
EndBlock Property NamedBlockAst
Extent Property IScriptExtent
ParamBlock Property ParamBlockAst
Parent Property Ast
ProcessBlock Property NamedBlockAst
ScriptRequirements Property ScriptRequirements
UsingStatements Property ReadOnlyCollection`1
I'm not going to cover all of these members today. Many of them aren't applicable unless processing an advanced PowerShell function. I'll save that for another day.
Requirements
One property that you might find of interest is ScriptRequirements
. This property returns a ScriptRequirements
object.