Преобразование значения параметра по умолчанию строки в тип array

#powershell

#powershell

Вопрос:

Предположим, у вас есть следующая функция:

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

Get-DefaultParameterValuesHC -Path 'Test-Function'
  

Теперь для получения значений по умолчанию в аргументах функции можно использовать 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
}
  

Проблема здесь в том, что аргумент for $ComputerNames читается как a string , в то время как на самом деле это массив string .

Есть ли способ, которым PowerShell может преобразовать строку в массив? Или, что еще лучше, сначала правильно прочитать значение?

Комментарии:

1. Потому что он возвращает строку @($env:COMPUTERNAME, 'PC2') , а не 2 отдельных элемента, как ожидалось.

Ответ №1:

Вам нужно глубже изучить структуру AST…
Я рекомендую вам поиграть с этим PowerShell: графический интерфейс AST Explorer:

введите описание изображения здесь

Для вашего конкретного примера:

 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'
  

Комментарии:

1. Это намного чище, чем то, что я сделал. Спасибо, железо, ценю помощь.

Ответ №2:

Это немного хакерское решение, но это то, что я придумал, чтобы решить проблему отсутствия возврата array 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
}
  

Ответ №3:

ExpandPath будет расширять переменные только внутри строк. Чтобы получить фактические значения (а не только определение), вы можете использовать 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
}
  

ПРИМЕЧАНИЕ: это фактически вызовет объявление по умолчанию, поэтому будет выполняться любая логика внутри этого выражения, как и при запуске функции. Например, значение по умолчанию $Parameter = (Get-Date) всегда будет вызываться Get-Date .

Было бы предпочтительнее создать функцию, которая возвращает только объявления по умолчанию и позволяет пользователю решать, вызывать выражение или нет:

 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}}