考虑以下简单函数:
function Write-HostIfNotVerbose()
{
if ($VerbosePreference -eq 'SilentlyContinue')
{
Write-Host @args
}
}
而且效果很好:
现在我想将其设为高级功能,因为我希望它继承详细程度首选项:
function Write-HostIfNotVerbose([Parameter(ValueFromRemainingArguments)]$MyArgs)
{
if ($VerbosePreference -eq 'SilentlyContinue')
{
Write-Host @MyArgs
}
}
但是不起作用:
令我抓狂的是,我无法识别第一个示例中的
$args
与第二个示例中的 $args
有何不同。
我知道原生
@args
splatting 默认情况下不适用于高级功能 - https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_splatting?view= powershell-7.2#笔记
但我希望可以模拟,但也行不通。我的问题是 - 我尝试模拟它的方式有什么问题,以及是否可以修复我的代码而不在
Write-Host
处显示所有
Write-HostIfNotVerbose
参数
Santiago Squarzon 的有用答案包含一些出色的调查,揭示了
@args
背后隐藏的魔力,即使用自动$args
变量进行splatting,该变量仅在simple(非高级)函数中可用。
圣地亚哥答案中的解决方案不仅复杂,而且并不完全稳健,因为它无法区分
-ForegroundColor
(参数名称)与'-ForegroundColor'
参数值它看起来像参数名称,但通过 quoting 来区分。
@args
魔法也有一个局限性:它无法正确传递指定为 且具有显式值的
[switch]
参数,例如 -NoNewLine:$false
[1]一个鲁棒解决方案需要通过自动
$PSBoundParameters
变量进行展开,这反过来要求包装函数本身也声明所有潜在的传递参数。
这样的包装函数称为 代理函数,PowerShell SDK 可以通过 PowerShell SDK 搭建此类函数的脚手架,如此答案中所述。
在您的情况下,您必须按如下方式定义函数:
function Write-HostIfNotVerbose {
[CmdletBinding()]
param(
[Parameter(Position = 0, ValueFromPipeline, ValueFromRemainingArguments)]
[Alias('Msg', 'Message')]
$Object,
[switch] $NoNewline,
$Separator,
[System.ConsoleColor] $ForegroundColor,
[System.ConsoleColor] $BackgroundColor
)
begin {
$scriptCmd =
if ($VerbosePreference -eq 'SilentlyContinue') { { Write-Host @PSBoundParameters } }
else { { Out-Null } }
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
}
process {
$steppablePipeline.Process($_)
}
end {
$steppablePipeline.End()
}
}
[1] 这样的参数总是作为 two 参数传递,即作为参数名称
-NoNewLine
本身,后跟一个单独的参数 $false
。问题是,当原始参数被解析为 $args
时,尚不知道它们将绑定到什么正式声明的参数。应用于 NoteProperty
用于将元素标记为参数 names的
$args
标记不会保留有关后续参数是否用 :
与参数名称分隔的信息,这对于 [switch]
参数是必需的将该参数标识为属于开关。如果没有此信息,则在splatting期间始终传递两个单独的参数。
这对我来说太晦涩难懂了,无法解释,但为了回答 PowerShell 可以用
$args
做什么,你可以测试一下:
function Write-HostIfNotVerbose {
param(
[parameter(ValueFromRemainingArguments)]
[psobject[]] $MagicArgs
)
$params = foreach ($arg in $MagicArgs) {
if ($arg.StartsWith('-')) {
$arg.PSObject.Properties.Add(
[psnoteproperty]::new('<CommandParameterName>', $arg))
}
$arg
}
if ($VerbosePreference -eq 'SilentlyContinue') {
Write-Host @params
}
}
Write-HostIfNotVerbose -ForegroundColor Green Hello world! -BackgroundColor Yellow
查看
$args
为我们自动做什么的一种方法可能是序列化变量:
function Test-Args {
[System.Management.Automation.PSSerializer]::Serialize($args)
}
Test-Args -Argument1 Hello -Argument2 World
上面将为我们提供 $args
的序列化表示,我们将在其中观察到以下内容:
<LST>
<Obj RefId="1">
<S>-Argument1</S>
<MS>
<S N="<CommandParameterName>">Argument1</S>
</MS>
</Obj>
<S>Hello</S>
<Obj RefId="2">
<S>-Argument2</S>
<MS>
<S N="<CommandParameterName>">Argument2</S>
</MS>
</Obj>
<S>World</S>
</LST>