The automatic variable $PSBoundParameters contains all bound functions parameters and allows you to pass parameters to another function without redefining them. But how can you pass default parameters? With the help of PowerShell Abstract Syntax Trees (ASTs).

When writing code in PowerShell, you'll occasionally have the need to pass all parameters from a function directly to another function reference inside. This is common in instances where you might have similar functions with similar parameters but not all parameters are the same.

For example, I might have two functions that look something like this:

function Get-Something
 {
     [CmdletBinding()]
     param
     (
         [Parameter(Mandatory)]
         [string]$Thing1,
         
         [Parameter(Mandatory)]
         [string]$Thing2,
         [Parameter()]
         [string]$Thing3 = 'adefaultvalue'
     )
     Set-Something –Thing1 $Thing1 –Thing2 $Thing2 –Thing3 $Thing3
     
 }
 function Set-Something
 {
     [CmdletBinding()]
     param
     (
         [Parameter(Mandatory)]
         [string]$Thing1,
         
         [Parameter(Mandatory)]
         [string]$Thing2,
         
         [Parameter(Mandatory)]
         [string]$Thing3
     )
     Write-Host "Setting something with Thing1: $($Thing1) – Thing2: $($Thing2) – Thing3: $($Thing3)"
 }

I can run this, and it performs as you'd expect.

Successful functions

Successful functions

All parameters of each function represent the same thing in both. Notice that the Set-Something function is referenced inside of the Get-Something function. Inside here, I am passing all of the variables (both bound and default) from Get-Something's parameters to the Set-Something function. This works fine, but it's not efficient coding. Savvy PowerShellers would know that parameters passed to a function are available inside of the $PSBoundParameters variable. If all of the parameters are inside of this variable already, there's no sense redefining each parameter and passing it to Set-Something. Instead, we can use splatting and do the following:

function Get-Something
 {
     [CmdletBinding()]
     param
     (
         [Parameter(Mandatory)]
         [string]$Thing1,
         
         [Parameter(Mandatory)]
         [string]$Thing2,
         [Parameter()]
         [string]$Thing3 = 'adefaultvalue'
     )
     Set-Something @PSBoundParameters
     
 }

Since Thing3 is a default parameter, when you call Get-Something, you'd expect the values of Thing1, Thing2, and Thing3 to be passed to Set-Something.

Get-Something –Thing1 'hello' –Thing2 'world'

Let's see what happens.

The values of Thing1, Thing2, and Thing3 to be passed to Set-Something

Set-Something is prompting for a value for Thing3. Why? Since I set the default value of Thing3 on Get-Something's parameter, it should have worked, right? No. The reason is that $PSBoundParameters includes only parameters that were bound, meaning it does not contain any parameter arguments defined with a default value.

I need to figure out a way to get the value of Get-Something's Thing3 variable other than explicitly calling $Thing3. We can do this by using the Abstract Syntax Tree (AST). The AST is a great way to gather all kinds of information about your PowerShell scripts and functions. By using the AST and looking for all of the System.Management.Automation.Language.ParameterAst objects inside of this function, we can discover this information.

To find that default value, I'll first need to convert the Get-Something function into an AST block. I can do this by calling Get-Command.

$ast = (Get-Command 'Get-Something').ScriptBlock.Ast

Once I have the function that represents as an AST block, I can now search for various components inside using the FindAll () method.

$ast.FindAll({ $args[0] -is [System.Management.Automation.Language.ParameterAst] }, $true)

Searching for various components inside using the FindAll () method

You can see that this returns a lot of things other than the parameter name and default value. We'll need to pare this down a little bit to get something more usable. I'd like to get an output that includes just all of the parameters, with a default value showing their name and value only.

I can do that by digging into the objects a little bit and creating my own calculated property with Select-Object.

$myProperties = @{ n = 'Name'; e = { $_.Name.VariablePath.UserPath } },
 @{ n = 'Value'; e = { $_.DefaultValue.Extent.Text -replace "`"|'" } }
 $params = $ast.FindAll({ $args[0] -is [System.Management.Automation.Language.ParameterAst] }, $true) | where { $_.DefaultValue } | select $myProperties

This will then give me an output that's much cleaner.

Cleaner output

Cleaner output

Great! We now have a way to find all of the bound and default values for the Get-Something parameter. Let's now take the

function Get-Something
 {
     [CmdletBinding()]
     param
     (
         [Parameter(Mandatory)]
         [string]$Thing1,
         
         [Parameter(Mandatory)]
         [string]$Thing2,
         [Parameter()]
         [string]$Thing3 = 'adefaultvalue'
     )
     $ast = (Get-Command 'Get-Something').ScriptBlock.Ast
 $myProperties = @{ n = 'Name'; e = { $_.Name.VariablePath.UserPath } },
 @{ n = 'Value'; e = { $_.DefaultValue.Extent.Text -replace "`"|'" } }
 $defaulParams = @{}
 @($ast.FindAll({ $args[0] -is 
  [System.Management.Automation.Language.ParameterAst] }, $true) | where { $_.DefaultValue } | select $myProperties).foreach({
     $defaultParams[$_.Name] = $_.Value
 })
     $setSomethingParams = $PSBoundParameters + $defaultParams
 Set-Something @setSomethingParams
     
 }

I can now run Get-Something again, and I now get the default value as well.

Running Get Something again and getting the default value

Running Get Something again and getting the default value

You might be thinking that this would be overkill, and you'd be right for this simple example. But if you find yourself working with functions that contains dozens of parameters with default values, this method will take a ton of time.

Subscribe to 4sysops newsletter!

If you'd like to save some lines, I've wrapped this up into a function called Get-DefaultFunctionParameter, which you can download at GitHub and reuse.

0 Comments

Leave a reply

Please enclose code in pre tags

Your email address will not be published.

*

© 4sysops 2006 - 2023

CONTACT US

Please ask IT administration questions in the forums. Any other messages are welcome.

Sending

Log in with your credentials

or    

Forgot your details?

Create Account