如何在Powershell中的高级函数中实现@args splatting?

mar*_*ark 5 powershell arguments function function-call parameter-splatting

考虑以下简单函数:

function Write-HostIfNotVerbose()
{
    if ($VerbosePreference -eq 'SilentlyContinue')
    {
        Write-Host @args
    }
}
Run Code Online (Sandbox Code Playgroud)

而且效果很好:

在此输入图像描述

现在我想让它成为一个高级函数,因为我希望它继承详细程度首选项:

function Write-HostIfNotVerbose([Parameter(ValueFromRemainingArguments)]$MyArgs)
{
    if ($VerbosePreference -eq 'SilentlyContinue')
    {
        Write-Host @MyArgs
    }
}
Run Code Online (Sandbox Code Playgroud)

但它不起作用:

在此输入图像描述

让我抓狂的是,我无法确定第一个示例与第二$args个示例有何不同。$args

我知道@args默认情况下本机泼溅不适用于高级功能 - https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_splatting?view=powershell-7.2#notes

但我希望可以模拟,但也行不通。我的问题是 - 我尝试模拟它的方式有什么问题,以及是否可以在不显示所有参数的Write-Host情况下修复我的代码Write-HostIfNotVerbose

mkl*_*nt0 3

Santiago Squarzon 的有用答案包含一些出色的调查,揭示了背后隐藏的魔法@args,即使用自动变量进行喷溅,该变量仅在简单(非高级)函数中可用。$args

圣地亚哥的答案中的解决方案不仅复杂,而且也不完全健壮,因为它无法区分-ForegroundColor(参数名称)与恰好看起来像参数名称的'-ForegroundColor'参数值,但又与通过引用它。

  • 顺便说一句:即使是内置的@args魔法也有一个限制:它不能正确传递用显式值[switch]指定的参数,例如[1]
    -NoNewLine:$false

强大的解决方案需要通过自动$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()
  }

}
Run Code Online (Sandbox Code Playgroud)

[1] 这样的参数总是作为两个参数传递,即作为参数名称-NoNewLine本身,后跟一个单独的参数$false。问题是,当原始参数被解析为 时$args,尚不知道它们将绑定到什么正式声明的参数。NoteProperty用于将元素标记为$args参数名称的标记不会保留有关后续参数是否与参数名称分隔的信息:,这对于[switch]参数来说是识别该参数属于 switch 所必需的。如果缺少此信息,则在展开过程中始终会传递两个单独的参数。