Behind the PowerShell Pipeline logo

Behind the PowerShell Pipeline

Subscribe
Archives
January 7, 2025

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:\&gt; [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:\&gt; 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.

Want to read the full issue?
GitHub Bluesky LinkedIn About Jeff
Powered by Buttondown, the easiest way to start and grow your newsletter.