Positioning with the PowerShell AST
I've been slowly diving into the world of the PowerShell Abstract Syntax Tree (AST). In the last issue, we looked at parsing PowerShell input. I demonstrated how to take a command and expand truncated parameters using a custom function. This is handy for cleaning up and reformatting your code. However, we still need to address positional parameters.
To begin, I'll recreate the AST tokens and AST from my demo command.
$cmd = 'Get-Process s* -includeUserName | Where {$_.WS -ge 250MB} -ov w | Sort WS -Desc | Select-Object -Fi 5 -outv x'
New-Variable astTokens
New-Variable astErr
$AST = [System.Management.Automation.Language.Parser]::ParseInput($cmd, [ref]$astTokens, [ref]$astErr)
The $cmd
string is the version from the end of the last article with the first segment expanded.
To find positional parameters, I need to update my helper function to include position information.
Function Resolve-Parameter {
[CmdletBinding()]
[Alias('rpa')]
Param(
[Parameter(Mandatory, Position = 0)]
[ValidateScript({ Get-Command -Name $_ -CommandType Cmdlet, Function, Alias },
ErrorMessage = "Command '{0}' not found or is not a valid cmdlet, function, or alias.")]
[string]$Command,
[Parameter(Mandatory, Position = 1)]
[string]$Parameter
)
#escape the first character in the command name
#this solves a problem with using single character aliases like ?
if ($Command.Length -eq 1) {
$Command = "``{0}" -f $Command
}
Write-Verbose "Resolving parameter '$Parameter' for command '$Command'"
#resolve the full command name
$resolvedCommand = Get-Command -Name $Command -CommandType Cmdlet, Function, Alias
if ($resolvedCommand.CommandType -eq 'Alias') {
$Command = $resolvedCommand.ResolvedCommandName
}
else {
$Command = $resolvedCommand.Name
}
$cmdParams = (Get-Command -Name $Command).Parameters
$paramDetail = $cmdParams.GetEnumerator() |
ForEach-Object {
<#
Getting parameter position will be incorrect if a parameter has different
positions for different parameter sets.
Positions with negative numbers indicated Named parameters
#>
[PSCustomObject]@{
PSTypeName = 'ParameterDetail'
Command = $Command
ParameterName = $_.Key
Position = $cmdParams[$_.key].Attributes[0].position | Where-Object { $_ -ge 0 }
Aliases = $_.Value.Aliases
ParameterSet = $_.value.ParameterSets.Keys
}
} #foreach-object
$paramDetail |
Where-Object { $_.aliases -contains $Parameter -OR $_.ParameterName -match "^$Parameter" }
}
I think the comments should explain the changes.

There are a few limitations to this function. It doesn't handle dynamic parameters or parameters that are only available in certain PSProviders. And while the AST should be able to identify commands, unless the command is loaded into your PowerShell session or discoverable by Get-Command
, the parameter can't be resolved.
I also wrote a function to simply the process of identifying positional parameters.
Function Get-PositionalParameter {
[CmdletBinding()]
[Alias('gpp')]
Param(
[Parameter(Mandatory, Position = 0)]
[ValidateScript({ Get-Command -Name $_ -CommandType Cmdlet, Function, Alias },
ErrorMessage = "Command '{0}' not found or is not a valid cmdlet, function, or alias.")]
[string]$Command,
[ValidateRange(0, 5)]
[int]$Position = 0
)
#escape the first character in the command name
if ($Command.Length -eq 1) {
$Command = "``{0}" -f $Command
}
#resolve the full command name
$resolvedCommand = Get-Command -Name $Command -CommandType Cmdlet, Function, Alias
if ($resolvedCommand.CommandType -eq 'Alias') {
$Command = $resolvedCommand.ResolvedCommandName
}
else {
$Command = $resolvedCommand.Name
}
$cmdParams = (Get-Command -Name $Command).Parameters
$cmdParams.GetEnumerator() | Where-Object { $_.Value.Attributes.Position -eq $Position } |
ForEach-Object {
[PSCustomObject]@{
PSTypeName = 'PositionalParameter'
Command = $Command
ParameterName = $_.Key
Position = $Position
ParameterSet = $_.Value.ParameterSets.keys
}
}
}
Here's how I can find what parameter is at position 0 for the Get-Process
command.
PS C:\> Get-PositionalParameter Get-Process -Position 0
Command ParameterName Position ParameterSet
------- ------------- -------- ------------
Get-Process Name 0 {Name, NameWithUserName}
However, there may be challenges using this output when multiple parameter sets come into play.
PS C:\> Get-PositionalParameter Get-CimInstance -Position 0
Command ParameterName Position ParameterSet
------- ------------- -------- ------------
Get-CimInstance ClassName 0 {ClassNameSessionSet, ClassNameComputerSet}
Get-CimInstance InputObject 0 {CimInstanceComputerSet, CimInstanceSessionSet}
I'll use these commands to resolve parameters.
Finding AST Elements
Instead of parsing AST tokens, I can use the AST object to find specific AST elements.
$find = $AST.Find(
{ $args[0] -is [System.Management.Automation.Language.CommandAst] },
$true
)
I am telling the AST to find the first instance of a System.Management.Automation.Language.CommandAst
object. This is the AST element that represents a command. This is a common AST usage pattern. To see all of things you can search for run this: