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

问题描述 投票:0回答:2

考虑以下简单函数:

function Write-HostIfNotVerbose()
{
    if ($VerbosePreference -eq 'SilentlyContinue')
    {
        Write-Host @args
    }
}

而且效果很好:

enter image description here

现在我想将其设为高级功能,因为我希望它继承详细程度首选项:

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

但是不起作用:

enter image description here

令我抓狂的是,我无法识别第一个示例中的

$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

参数
function powershell arguments function-call parameter-splatting
2个回答
3
投票

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期间始终传递两个单独的参数。


3
投票

这对我来说太晦涩难懂了,无法解释,但为了回答 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="&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>
    
© www.soinside.com 2019 - 2024. All rights reserved.