Reputation: 14735
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
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