In my previous post about automating PowerShell module creation I've mentioned usage of AST for code analysis. Mainly for extracting information like function definition, aliases etc.
So today I will show you some basics, plus give you several real world examples, so you can start your own AST journey :).
Table of contents
Introduction
AST is abbreviation of Abstract Syntax Tree and it is built-in PowerShell feature for code analysis. In general it will create hierarchic tree of AST objects, representing given code. You can use it for example to extract various information from the code like variables, function definitions, function parameters, aliases, begin/process/end blocks or to self-aware code analysis (code checks itself).
What are the necessary steps, to use AST?
1. Get AST object for your code
- get AST object from script file
$script = 'C:\Scripts\SomeScript.ps1'
$AST = [System.Management.Automation.Language.Parser]::ParseFile($script, [ref]$null, [ref]$null)
- get AST object from PowerShell code
$code = @'
some powershell code here
'@
$AST = [System.Management.Automation.Language.Parser]::ParseInput($code, [ref]$null, [ref]$null)
2. Decide what AST class type you are looking for
- Btw there is also more universal tool called Show-Object, that can visualize any PowerShell object
Anyway I've chosen to find function parameters i.e. ParameterAst, so lets move on to...
3. Find specific type of AST object
- there are two main methods to search the AST object:
Find() (return just first result) and FindAll() (return all results)
- So how to use
FindAll()? This method needs two parameters. First one (predicate) is for filtering the results and second one (recurse) is just switch for searching nested scriptBlocks too.
$predicate = {param($astObject) $astObject -is [System.Management.Automation.Language.<placeASTTypeHere>] }
$recurse = $true
$AST.FindAll($predicate, $recurse)
$AST.FindAll($true, $true)
$AST.FindAll({ param($astObject) $astObject -is [System.Management.Automation.Language.ParameterAst] }, $true)
So our search for ParameterAst in Hello-World.ps1 will return
In similar way, you can search for anything you like (anything AST has class for, to be more precise). So now is the right time to give you some examples, how I use AST in my projects.
Real world examples
I will use this script Hello-World.ps1 for all examples bellow!
function Hello-World {
[Alias("Invoke-Something")]
param (
[string] $name = "Karel"
,
[switch] $force
)
$someInnerVariable = "homeSweetHome"
Get-Process -Name $name
}
Set-Alias -Name Invoke-SomethingElse -Value Hello-World
And create AST object like this
$AST = [System.Management.Automation.Language.Parser]::ParseFile('C:\Scripts\Hello-World.ps1', [ref]$null, [ref]$null)
1. How to get variables
- I am using this for pre-commit validations of changes made in our company central Variables.psm1 PowerShell module. Specifically to cancel commit, in case, when newly created variable name, doesn't start with underscore. Or to warn the user, if he modify or delete variable, that is used elsewhere in our central repository.
function _getVariableAST {
param ($AST, $varToExclude, [switch] $raw)
$variable = $AST.FindAll( { $args[0] -is [System.Management.Automation.Language.VariableExpressionAst ] }, $true)
$variable = $variable | Where-Object { $_.parent.left -or $_.parent.type -and ($_.parent.operator -eq 'Equals' -or $_.parent.parent.operator -eq 'Equals') }
if ($raw) {
return $variable
}
$variable = $variable | Select-Object @{n = "name"; e = { $_.variablepath.userPath } }, @{n = "value"; e = {
if ($value = $_.parent.right.extent.text) {
$value
} else {
$_.parent.parent.right.extent.text
}
}
}
$variable = $variable | Select-Object name, @{n = "value"; e = { $_.value.Replace("`r`n", "`n") } }
if ($varToExclude) {
$variable = $variable | Where-Object { $_.name -notmatch $varToExclude }
}
return $variable
}
And the result will be

2. How to get aliases
This is essential for my Export-ScriptToModule function to get all aliases defined in script file, to be able to later export them in generated PowerShell module using Export-ModuleMember.
function _getAliasAST {
param ($AST, $functionName)
$alias = @()
$AST.EndBlock.Statements | ? { $_ -match "^\s*Set-Alias .+" -and $_ -match [regex]::Escape($functionName) } | % { $_.extent.text } | % {
$parts = $_ -split "\s+"
$content += "`n$_"
if ($_ -match "-na") {
$i = 0
$parPosition
$parts | % {
if ($_ -match "-na") {
$parPosition = $i
}
++$i
}
$alias += $parts[$parPosition + 1]
} else {
$alias += $parts[1]
}
}
$AST.FindAll( {
param([System.Management.Automation.Language.Ast] $AST)
$AST -is [System.Management.Automation.Language.AttributeAst]
}, $true) | ? { $_.parent.extent.text -match '^param' } | Select-Object -ExpandProperty PositionalArguments | Select-Object -ExpandProperty Value -ErrorAction SilentlyContinue | % { $alias += $_ }
return $alias
}
And the result will be

3. How to get function definition
This is also essential for my Export-ScriptToModule function to validate number of defined functions in script file (it has to be exactly one), defined function name (it has to match with ps1 name and will be used in Export-ModuleMember) and content (it will be used to generate PowerShell module).
function _getFunctionAST {
param ($AST)
$AST.FindAll( {
param([System.Management.Automation.Language.Ast] $AST)
$AST -is [System.Management.Automation.Language.FunctionDefinitionAst] -and
($PSVersionTable.PSVersion.Major -lt 5 -or
$AST.Parent -isnot [System.Management.Automation.Language.FunctionMemberAst])
}, $true)
}
And the result will be

4. How to get function parameters
I am using this for pre-commit validations. In particular to warn the user, in case he made change in parameter of function, that is used elsewhere in our repository. So he can check, this commit won't break anything.
function _getParameterAST {
param ($AST, $functionName)
$parameter = $AST.FindAll( { $args[0] -is [System.Management.Automation.Language.ParamBlockAst] }, $true) | Where-Object { $_.parent.parent.name -eq $functionName }
$parameter.parameters | Select-Object @{n = 'name'; e = { $_.name.variablepath.userpath } }, @{n = 'value'; e = { $_.defaultvalue.extent.text } }, @{ n = 'type'; e = { $_.staticType.name } }
}
And the result will be

5. How to get script requirements
Again, this is used in Export-ScriptToModule function to have option to exclude requirements from generated PowerShell module.
$AST.scriptRequirements.requiredModules.name
And the result will be

Summary
I hope that you've find some useful new information here. In case of any questions, don't hesitate to write me some comments!
And for those, who want to go deeper into AST, check this detailed article about AST.
PS: I will be talking about GIT hooks automation, and my CI/CD solution in some of upcoming blog posts, don't worry :)