当我的powershell cmdlet参数接受ValueFromPipelineByPropertyName并且我有别



函数如何判断参数是作为别名传入的,还是管道属性中的对象作为别名匹配的?它怎么能得到原来的名字

假设我的Powershell cmdlet接受管道输入,并且我希望使用ValueFromPipelineByPropertyName。我设置了一个别名,因为我可能会得到一些不同类型的对象,我希望能够根据收到的内容做一些稍微不同的事情。

这不起作用

function Test-DogOrCitizenOrComputer
{
[CmdletBinding()]
Param
(
# Way Overloaded Example
[Parameter(Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0)]
[Alias("Country", "Manufacturer")] 
[string]$DogBreed,
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$true,
Position=1)]
[string]$Name
)
# For debugging purposes, since the debugger clobbers stuff
$foo = $MyInvocation
$bar = $PSBoundParameters
# This always matches.
if ($MyInvocation.BoundParameters.ContainsKey('DogBreed')) {
"Greetings, $Name, you are a good dog, you cute little $DogBreed"
}
# These never do.
if ($MyInvocation.BoundParameters.ContainsKey('Country')) {
"Greetings, $Name, proud citizen of $Country"
}
if ($MyInvocation.BoundParameters.ContainsKey('Manufacturer')) {
"Greetings, $Name, future ruler of earth, created by $Manufacturer"
}
}

执行它时,我们会发现问题

起初,它似乎起作用:

PS> Test-DogOrCitizenOrComputer -Name Keith -DogBreed Basset
Greetings, Keith, you are a good dog, you cute little Basset

当我们尝试Alias:时,问题就很明显了

PS> Test-DogOrCitizenOrComputer -Name Calculon -Manufacturer HP
Greetings, Calculon, you are a good dog, you cute little HP

奖金失败,无法通过管道工作:

PS> New-Object PSObject -Property @{'Name'='Fred'; 'Country'='USA'} | Test-DogOrCitizenOrComputer
Greetings, Fred, you are a good dog, you cute little USA
PS> New-Object PSObject -Property @{'Name'='HAL'; 'Manufacturer'='IBM'} | Test-DogOrCitizenOrComputer
Greetings, HAL, you are a good dog, you cute little IBM

$MyInvocation.BoundParameters和$PSBoundParameters都包含已定义的参数名称,而不是任何匹配的别名。我看不出有什么方法可以通过别名匹配参数的真实名称。

PowerShell似乎不仅通过别名无声地将参数按摩到正确的参数来对用户"有帮助",而且通过将所有别名输入折叠到主参数名称中来对程序员"有帮助。这很好,但我不知道如何确定传递给Cmdlet的实际原始参数(或通过管道传递的对象属性)

函数如何判断参数是作为别名传入的,还是管道属性中的对象作为别名匹配的?它怎么能得到原来的名字

我不认为函数有任何方法可以知道是否使用了Alias,但关键是这应该无关紧要。在函数内部,您应该始终引用参数,就好像它被它的主要名称使用一样。

如果您需要参数根据其使用的别名而有所不同,则应使用不同的参数,或者使用另一个用作开关的参数。

顺便说一句,如果你这样做是因为你想使用多个参数作为ValueFromPipelineByPropertyName,你已经可以使用单个参数了,你不需要使用别名来实现这一点。

对于每个不同的输入类型(例如,只有一个字符串可以按值,一个int可以按值等),按值接受管道中的值确实需要是唯一的。但是,可以为每个参数启用按名称接受管道(因为每个参数名称都是唯一的)。

我对此非常头疼,所以我想写下我的理解状态。解决方案在底部(事实上)。

首先,快速:如果您对命令进行别名,则可以使用$MyInvocation.InvocationName轻松获得别名。但这对参数别名没有帮助。


在某些情况下有效

你可以通过拉动调用你的命令行来获得一些乐趣:

function Do-Stuff {
[CmdletBinding()]param(
[Alias('AliasedParam')]$Param
)

$InvocationLine = $MyInvocation.Line.Substring($MyInvocation.OffsetInLine - 1)    
return $InvocationLine
}
$a = 42; Do-Stuff -AliasedParam $a; $b = 23
# Do-Stuff -AliasedParam $a; $b = 23

这将显示别名。您可以使用regex解析它们,但我建议使用语言解析器:

$InvocationAst = [Management.Automation.Language.Parser]::ParseInput($InvocationLine, [ref]$null, [ref]$null)                
$InvocationAst.EndBlock.Statements[0].PipelineElements[0].CommandElements.ParameterName

这将为您提供一个调用时的参数列表。然而,它是脆弱的:

  • 不适用于飞溅
  • 不适用于ValueFromPipelineByPropertyName
  • 缩写的参数名称会引起额外的头痛
  • 仅在函数体中工作;在dynamicparam块中,尚未填充$MyInvocation属性

不工作

我深入研究了ParameterBinderController——感谢罗恩·爱德华兹的一些反思片段。

这不会让你有任何进展。为什么不呢?因为相关的方法没有副作用——它只是从规范的参数名称无缝地移动到别名。反思是不够的;您需要附加一个调试器,我不认为这是一个代码解决方案。

这就是Trace-Command从不显示别名解析的原因。如果是这样,您可能能够挂接跟踪提供程序。


不工作

CCD_ 9采用接受CCD_ 10的脚本块。此AST将别名化的参数名称作为标记保存。但您不会在脚本中走得太远,因为只有当您以交互方式制表完成参数时,才会调用参数完成符。

有几个completer类可以挂接;这一限制适用于所有人。


不工作

我搞砸了自定义参数属性,例如class HookAttribute : System.Management.Automation.ArgumentTransformationAttribute。它们接收一个EngineIntrinsics自变量。不幸的是,你没有得到新的上下文;当调用属性时,参数绑定已经完成,您将在反射中找到的绑定都引用了规范的参数名称。

Alias属性本身是一个密封类。


工作

使用PreCommandLookupAction挂钩可以获得快乐。这样可以截取命令分辨率。在这一点上,你已经有了args的写法。

每当您使用param别名时,此示例都会返回字符串AliasedParam。它适用于缩写的参数名称、冒号语法和splatting。

$ExecutionContext.InvokeCommand.PreCommandLookupAction = {
param ($CommandName, $EventArgs)
if ($CommandName -eq 'Do-Stuff' -and $EventArgs.CommandOrigin -eq 'Runspace')
{
$EventArgs.CommandScriptBlock = {
# not sure why, but Global seems to be required
$Global:_args = $args
& $CommandName @args
Remove-Variable _args -Scope Global
}.GetNewClosure()
$EventArgs.StopSearch = $true
}
}

function Do-Stuff
{
[CmdletBinding()]
param
(
[Parameter()]
[Alias('AliasedParam')]
$Param
)
$CalledParamNames = @($_args) -match '^-' -replace '^-' -replace ':$'
$CanonParamNames = $MyInvocation.BoundParameters.Keys
$AliasParamNames = $CanonParamNames | ForEach-Object {$MyInvocation.MyCommand.Parameters[$_].Aliases}
# Filter out abbreviations that could match canonical param names (they take precedence over aliases)
$CalledParamNames = $CalledParamNames | Where-Object {
$CalledParamName = $_
-not ($CanonParamNames | Where-Object {$_.StartsWith($CalledParamName)} | Select-Object -First 1)
}
# Param aliases that would bind, so we infer that they were used
$BoundAliases = $AliasParamNames | Where-Object {
$AliasParamName = $_
$CalledParamNames | Where-Object {$AliasParamName.StartsWith($_)} | Select-Object -First 1
}
$BoundAliases
}
# Do-Stuff -AliasP 42
# AliasedParam

如果全局变量冒犯了你,你可以使用一个辅助参数:

$EventArgs.CommandScriptBlock = {
& $CommandName @args -_args $args
}.GetNewClosure()
[Parameter(DontShow)]
$_args

缺点是有些傻瓜可能会实际使用helper参数,即使它是用DontShow隐藏的。

您可以通过在函数体或CommandScriptBlock中进行参数绑定机制的试运行调用来进一步开发这种方法。

相关内容

  • 没有找到相关文章

最新更新