当我的 powershell cmdlet 参数接受 ValueFromPipelineByPropertyName 并且我有别名时,如何获取原始 属性 名称?
When my powershell cmdlet parameter accepts ValueFromPipelineByPropertyName and I have an alias, how can I get the original property name?
函数如何判断参数是作为别名传入的,还是管道 属性 中的对象作为别名匹配的?怎么才能取到原来的名字呢?
假设我的 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 不仅通过别名将参数无声地按摩到正确的参数而对用户 'helpful',而且还通过将所有别名输入折叠到主要参数名称。很好,但我不知道如何确定传递给 Cmdlet 的实际原始参数(或通过管道传入的对象 属性)
函数如何判断参数是作为别名传入的,还是管道 属性 中的对象作为别名匹配的?怎么才能取到原来的名字呢?
我认为函数无法知道是否使用了别名,但关键是这无关紧要。在函数内部,您应该始终引用参数,就好像它是由它的主名称使用的一样。
如果您需要参数根据它是否使用了一个不是别名的别名来执行不同的操作,那么您应该使用不同的参数,或者作为开关的第二个参数。
顺便说一句,如果你这样做是因为你想使用多个参数作为 ValueFromPipelineByPropertyName
,你已经可以使用单独的参数并且你不需要使用别名来实现这一点。
对于每种不同的输入类型,按值从管道接受值确实需要是唯一的(例如,只有一个字符串可以按值,一个整数可以按值等)。但是可以为每个参数启用按名称接受管道(因为每个参数名称都是唯一的)。
我在这上面撞得头破血流,所以我想写下我的理解状态。解决方案在底部(就是这样)。
首先,快速:如果您为 命令 添加别名,您可以使用 $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
这将显示别名。您可以使用正则表达式解析它们,但我建议使用语言解析器:
$InvocationAst = [Management.Automation.Language.Parser]::ParseInput($InvocationLine, [ref]$null, [ref]$null)
$InvocationAst.EndBlock.Statements[0].PipelineElements[0].CommandElements.ParameterName
这将为您提供调用时的参数列表。然而,它很脆弱:
- 不适用于 splats
- 不适用于
ValueFromPipelineByPropertyName
- 缩写的参数名称会引起额外的头痛
- 只在函数体内有效;在
dynamicparam
块中,$MyInvocation
属性尚未填充
不起作用
我深入研究了 ParameterBinderController
- 感谢 Rohn Edwards 的一些 。
这不会让你到任何地方。为什么不?因为 relevant method 没有副作用——它只是从规范的参数名称无缝地移动到别名。反思是不够的;您需要附加一个调试器,我认为这不是代码解决方案。
这就是为什么 Trace-Command
从不显示别名解析的原因。如果是这样,您也许可以挂钩跟踪提供程序。
不起作用
Register-ArgumentCompleter
采用接受 CommandAst
的脚本块。此 AST 将别名参数名称作为标记。但是您不会在脚本中走得太远,因为仅当您以交互方式完成参数时才会调用参数完成器。
有几个完整的 class 您可以挂钩;此限制适用于所有人。
不起作用
我搞砸了自定义参数属性,例如class HookAttribute : System.Management.Automation.ArgumentTransformationAttribute
。这些接收一个 EngineIntrinsics
参数。不幸的是,您没有得到新的上下文;调用属性时已经完成参数绑定,您将通过反射找到的绑定都引用规范参数名称。
Alias
属性本身是一个密封的class。
有效
PreCommandLookupAction
钩子能让您获得快乐。这使您可以拦截命令解析。在这一点上,你有他们写的 args。
此示例 returns 字符串 AliasedParam
每当您使用参数别名时。它适用于缩写的参数名称、冒号语法和拼写。
$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
.
中干调用参数绑定机制来进一步开发此方法。运行
函数如何判断参数是作为别名传入的,还是管道 属性 中的对象作为别名匹配的?怎么才能取到原来的名字呢?
假设我的 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 不仅通过别名将参数无声地按摩到正确的参数而对用户 'helpful',而且还通过将所有别名输入折叠到主要参数名称。很好,但我不知道如何确定传递给 Cmdlet 的实际原始参数(或通过管道传入的对象 属性)
函数如何判断参数是作为别名传入的,还是管道 属性 中的对象作为别名匹配的?怎么才能取到原来的名字呢?
我认为函数无法知道是否使用了别名,但关键是这无关紧要。在函数内部,您应该始终引用参数,就好像它是由它的主名称使用的一样。
如果您需要参数根据它是否使用了一个不是别名的别名来执行不同的操作,那么您应该使用不同的参数,或者作为开关的第二个参数。
顺便说一句,如果你这样做是因为你想使用多个参数作为 ValueFromPipelineByPropertyName
,你已经可以使用单独的参数并且你不需要使用别名来实现这一点。
对于每种不同的输入类型,按值从管道接受值确实需要是唯一的(例如,只有一个字符串可以按值,一个整数可以按值等)。但是可以为每个参数启用按名称接受管道(因为每个参数名称都是唯一的)。
我在这上面撞得头破血流,所以我想写下我的理解状态。解决方案在底部(就是这样)。
首先,快速:如果您为 命令 添加别名,您可以使用 $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
这将显示别名。您可以使用正则表达式解析它们,但我建议使用语言解析器:
$InvocationAst = [Management.Automation.Language.Parser]::ParseInput($InvocationLine, [ref]$null, [ref]$null)
$InvocationAst.EndBlock.Statements[0].PipelineElements[0].CommandElements.ParameterName
这将为您提供调用时的参数列表。然而,它很脆弱:
- 不适用于 splats
- 不适用于
ValueFromPipelineByPropertyName
- 缩写的参数名称会引起额外的头痛
- 只在函数体内有效;在
dynamicparam
块中,$MyInvocation
属性尚未填充
不起作用
我深入研究了 ParameterBinderController
- 感谢 Rohn Edwards 的一些
这不会让你到任何地方。为什么不?因为 relevant method 没有副作用——它只是从规范的参数名称无缝地移动到别名。反思是不够的;您需要附加一个调试器,我认为这不是代码解决方案。
这就是为什么 Trace-Command
从不显示别名解析的原因。如果是这样,您也许可以挂钩跟踪提供程序。
不起作用
Register-ArgumentCompleter
采用接受 CommandAst
的脚本块。此 AST 将别名参数名称作为标记。但是您不会在脚本中走得太远,因为仅当您以交互方式完成参数时才会调用参数完成器。
有几个完整的 class 您可以挂钩;此限制适用于所有人。
不起作用
我搞砸了自定义参数属性,例如class HookAttribute : System.Management.Automation.ArgumentTransformationAttribute
。这些接收一个 EngineIntrinsics
参数。不幸的是,您没有得到新的上下文;调用属性时已经完成参数绑定,您将通过反射找到的绑定都引用规范参数名称。
Alias
属性本身是一个密封的class。
有效
PreCommandLookupAction
钩子能让您获得快乐。这使您可以拦截命令解析。在这一点上,你有他们写的 args。
此示例 returns 字符串 AliasedParam
每当您使用参数别名时。它适用于缩写的参数名称、冒号语法和拼写。
$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
.