DarkLite1
DarkLite1

Reputation: 14735

How to return the default value of a parameter in the correct parameter type?

The code below will retrieve the default values of the parameters used in a function or scriptfile:

Function Get-DefaultParameterValuesHC {
    [OutputType([hashtable])]
    Param (
        [Parameter(Mandatory)]
        [String]$Path
    )
    $ast = (Get-Command $Path).ScriptBlock.Ast

    $selectParams = @{
        Property = @{ 
            Name       = 'Name'; 
            Expression = { $_.Name.VariablePath.UserPath } 
        },
        @{ 
            Name       = 'Value'; 
            Expression = { 
                if ($_.DefaultValue.StaticType.IsArray) {
                    $_.DefaultValue.SubExpression.Statements.PipelineElements.Expression.Elements.Extent.Text
                }
                else {
                    if ($_.DefaultValue.Value) { $_.DefaultValue.Value }
                    else { $_.DefaultValue.Extent.Text }
                }
            }
        }
    }
    
    $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) {
            # Convert '$env:' variables
            $ExecutionContext.InvokeCommand.ExpandString($value) -replace 
            "^`"|`"$|'$|^'" 
        }
    }
    $result
}

However, all values are of type string. How is it possible to get them in the correct type? The example below illustrates the issue with a hash table being returned as a string.

Function Test-Function {
    Param (
        [String]$ScriptName = 'Get printers',
        [HashTable]$Settings = @{ Duplex = 'Yes'}
    )
}
Get-DefaultParameterValuesHC -Path 'Test-Function'

# Output:
Name         Value
----         -----
ScriptName   'Get printers'
PaperSize    '@{ Duplex = 'Yes'}'  # String not hashTable

The SafeGetValue() works but only if it's not an expression. Is there a way to have it work for hash tables too?

Upvotes: 1

Views: 240

Answers (1)

TheFreeman193
TheFreeman193

Reputation: 527

Firstly, it is possible to extract the parameters directly with:

$ast.Body.ParamBlock.Parameters

FindAll() is not needed in this case.

A quick-and-dirty way of achieving this would be to have the Select-Object parameter splat return the text representation regardless of data type, and use the InvokeScript() method to parse it when you process the results:

Name = 'Value';
Expression = {
    $_.DefaultValue.Extent.Text
}
$result[$d.Name] = foreach ($value in $d.Value) {
    $ExecutionContext.InvokeCommand.InvokeScript($value, $true)
}

Be aware that InvokeScript() will execute whatever string passed to it. Therefore, like with Invoke-Expression, it's important to be careful whenever it is used. For example, if Test-Function had this in its param() block:

[String]$ScriptName = (& cmd /c "echo This ran >%USERPROFILE%\Desktop\OhNo.txt"),

Running the analysis function as above would run cmd and write a file, even though it didn't explicitly call the target function. Therefore, it's risky to use this method with functions that run commands in the param() block. It would be necessary to look inside any Pipeline and SubExpression properties for each DefaultValue recursively, to filter out anything that shouldn't be parsed.

It is possible to recognise most types based on which properties exist for each DefaultValue, and customise the behaviour for each data type to recreate the original object. Similarly to how the code in the question already differentiates arrays from other types, exquisite type handling is the safest approach by far.

Upvotes: 1

Related Questions