With writing custom PSSA rules and just parsing PowerShell scripts, I find myself often in the same use case where I would like to resolve command name (which is not that difficult) and the parameters of a CommandAst
.
For my current particular case, I would like the write a rule to Avoid New-Object Cmdlet. To prevent any false positives, I would like to exclude any command with New-Object -ComObject
parameters, and to write an automated -Fix
I would like to retrieve parameters along with the -TypeName
parameter. The "problem" is that the parameters could be provided several ways: named, unnamed (by position) or elastic (e.g. -Type
) or an alias as -Args
.
I presume that the PowerShell engine somehow needs to deal with the same command line resolving and I wonder whether this could be invoked from PowerShell itself.
In other words, I have the following command line:
New-Object -TypeName System.Version -ArgumentList "1.2.3.4"
which I can parse with the Abstract Syntax Tree (AST) class.
To resolve the command name (regardsless I might use an alias), I might do this:
$Ast = [System.Management.Automation.Language.Parser]::ParseInput(
{ New-Object -TypeName System.Version -ArgumentList "1.2.3.4" },
[ref]$null, [ref]$null
)
$CommandAst = $Ast.Endblock.Statements.PipelineElements
$CommandAst.GetCommandName()
New-Object
But how do I (easily) resolve the parameters (regardless they are provided named, unnamed, elastic or via an alias)?
You can use StaticParameterBinder.BindCommand
, this of course works as long as the command exist / can be resolved:
using namespace System.Management.Automation.Language
$command = { New-Object System.Version '1.2.3.4' }
$commandAst = $command.Ast.Find({ $args[0] -is [CommandAst] }, $false)
$bindingResult = [StaticParameterBinder]::BindCommand($commandAst)
foreach ($result in $bindingResult.BoundParameters.GetEnumerator()) {
[pscustomobject]@{
Parameter = $result.Key
Argument = $result.Value.Value.SafeGetValue()
}
}
There is a caveat with .SafeGetValue()
, if the dynamic expression cannot be resolved, you will get an error, so for example if your command was:
$command = {
$foo = '1.2.3.4'
New-Object System.Version $foo
}
Then, it would fail, if you want to resolve the value of $foo
, in PowerShell 7+ you can use .SafeGetvalue($true)
, however this overload doesn't exist in in older versions, there you will require a more manual approach, an example here.
In this case the output you'd get from the above would be:
Parameter Argument
--------- --------
TypeName System.Version
ArgumentList 1.2.3.4
If however you want to see the argument as-is, without resolving it, then you can use .ToString()
instead of .SafeGetValue()
, in which case the output would be:
Parameter Argument
--------- --------
TypeName System.Version
ArgumentList $foo