4

考虑以下简单函数:

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默认情况下本机飞溅不适用于高级功能 - https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_splatting?view=powershell-7.2#notes

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

4

2 回答 2

3

这对我来说太模糊了,无法解释,但是为了回答 PowerShell可以做什么,$args您可以测试一下:

function Write-HostIfNotVerbose {
param(
    [parameter(ValueFromRemainingArguments)]
    [object[]]$MagicArgs
)
    $params = @{
        NotePropertyName = '<CommandParameterName>'
        PassThru = $true
        InputObject = ''
    }
    $z = foreach($i in $MagicArgs) {
        if($i.StartsWith('-')) {
            $params.NotePropertyValue = $i
            Add-Member @params
            continue
        }
        $i
    }

    if ($VerbosePreference -eq 'SilentlyContinue') {
        Write-Host @z
    }
}

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="&lt;CommandParameterName&gt;">Argument1</S>
    </MS>
  </Obj>
  <S>Hello</S>
  <Obj RefId="2">
    <S>-Argument2</S>
    <MS>
      <S N="&lt;CommandParameterName&gt;">Argument2</S>
    </MS>
  </Obj>
  <S>World</S>
</LST>
于 2022-02-10T22:39:53.313 回答
3

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

Santiago 答案中的解决方案不仅复杂,而且也不完全健壮,因为它无法将-ForegroundColor(a parameter name ) 与恰好看起来像参数名称'-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()
  }

}

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

于 2022-02-11T03:33:25.290 回答