2

Suppose you have the following function:

Function Test-Function {
    Param (
        [String[]]$ComputerNames = @($env:COMPUTERNAME, 'PC2'),
        [String]$PaperSize = 'A4'
    )
}

Get-DefaultParameterValuesHC -Path 'Test-Function'

Now to get the default values in the function arguments one can use AST:

Function Get-DefaultParameterValuesHC {
    [OutputType([hashtable])]
    Param (
        [Parameter(Mandatory)]$Path
    )
    $ast = (Get-Command $Path).ScriptBlock.Ast
        
    $selectParams = @{
        Property = @{ 
            Name       = 'Name'; 
            Expression = { $_.Name.VariablePath.UserPath } 
        },
        @{ 
            Name       = 'Value'; 
            Expression = { $_.DefaultValue.Extent.Text -replace "`"|'" }
        }
    }
        
    $result = @{ }

    $defaultValueParameters = @($ast.FindAll( { 
                $args[0] -is [System.Management.Automation.Language.ParameterAst] }
            , $true) | 
        Where-Object { $_.DefaultValue } | 
        Select-Object @selectParams)
            
    foreach ($d in $defaultValueParameters) {
        $result[$d.Name] = foreach ($value in $d.Value) {
            $ExecutionContext.InvokeCommand.ExpandString($value)
        }
    }
    $result
}

The issue here is that the argument for $ComputerNames is read as a string while it is actually an array of string.

Is there a way that PowerShell can covnert a string to an array? Or even better, read the value correctly in the first place?

1
  • 1
    Because it returns the string @($env:COMPUTERNAME, 'PC2') and not 2 separate items as expected. Commented Oct 26, 2020 at 8:19

3 Answers 3

1

You need to look deeper into the AST structure...
I recommend you to play around with this PowerShell: AST Explorer GUI:

enter image description here

For your specific example:

Function Test-Function {
    Param (
        [String[]]$ComputerNames = @($env:COMPUTERNAME, 'PC2'),
        [String]$PaperSize = 'A4'
    )
}

$FunctionDefinitionAst = (Get-Command 'Test-Function').ScriptBlock.Ast
$Body = $FunctionDefinitionAst.Body
$ParamBlock = $Body.ParamBlock
$CNParameter = $ParamBlock.Parameters | Where-Object { $_.Name.VariablePath.UserPath -eq 'ComputerNames' }
$DefaultValue = $CNParameter.DefaultValue
$DefaultValue.SubExpression.Statements.PipelineElements.Expression.Elements

VariablePath : env:COMPUTERNAME
Splatted     : False
StaticType   : System.Object
Extent       : $env:COMPUTERNAME
Parent       : $env:COMPUTERNAME, 'PC2'

StringConstantType : SingleQuoted
Value              : PC2
StaticType         : System.String
Extent             : 'PC2'
Parent             : $env:COMPUTERNAME, 'PC2'
Sign up to request clarification or add additional context in comments.

1 Comment

That's a lot cleaner then what I did. Thank you iRon appreciate the help.
0

It's a bit of a hackish solution but this is what I came up with to solve the issue of not returning an array of string:

Function Get-DefaultParameterValuesHC {
    Param (
        [Parameter(Mandatory)]$Path
    )
    $ast = (Get-Command $Path).ScriptBlock.Ast
        
    $selectParams = @{
        Property = @{ 
            Name       = 'Name'; 
            Expression = { $_.Name.VariablePath.UserPath } 
        },
        @{ 
            Name       = 'Value'; 
            Expression = { 
                if ($_.DefaultValue.StaticType.BaseType.Name -eq 'Array') {
                    $_.DefaultValue.SubExpression.Extent.Text -split ',' | 
                    ForEach-Object { $_.trim() -replace "`"|'" }
                }
                else {
                    $_.DefaultValue.Extent.Text -replace "`"|'" 
                }
            }
        }
    }
        
    $result = @{ }

    $defaultValueParameters = @($ast.FindAll( { 
                $args[0] -is [System.Management.Automation.Language.ParameterAst] }
            , $true) | 
        Where-Object { $_.DefaultValue } | 
        Select-Object @selectParams)
            
    foreach ($d in $defaultValueParameters) {
        $result[$d.Name] = foreach ($value in $d.Value) {
            $ExecutionContext.InvokeCommand.ExpandString($value)
        }
    }
    $result
}

Comments

0

ExpandPath will only expand variables inside strings. To get the actual values (and not just the definition) you could use Invoke-Expression:

function Get-DefaultParameterValuesHC {
    [OutputType([hashtable])]
    Param (
        [Parameter(Mandatory)]$Path
    )
    $result = @{ }
    (Get-Command $Path).ScriptBlock.Ast.Body.ParamBlock.Parameters | where {$_.DefaultValue} | foreach {
        $result[$_.Name.VariablePath.UserPath] = Invoke-Expression $_.DefaultValue.Extent.Text
    }
    $result
}

NOTE: This will actually invoke the default declaration, so any logic inside that expression will be run, just as when running the function. For example, a default value of $Parameter = (Get-Date) will always invoke Get-Date.

It would be preferable to create a function, that only returns the default declarations, and let the user decide to invoke the expression or not:

function Get-DefaultParameterDeclarations {
    Param (
        [Parameter(Mandatory, Position = 0)]
        [string]$CommandName
    )
    (Get-Command $CommandName).ScriptBlock.Ast.Body.ParamBlock.Parameters | where {$_.DefaultValue} |
      foreach {
        [PSCustomObject]@{
            Name = $_.Name.VariablePath.UserPath
            Expression = $_.DefaultValue.Extent.Text
        }
    }
}

# get the declarations and (optionally) invoke the expressions:
Get-DefaultParameterDeclarations 'Test-Function' |
    select Name, @{n="DefaultValue"; e={Invoke-Expression $_.Expression}}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.