如果省略了必需的* pipeline *参数,是否可以强制抛出PowerShell脚本?

时间:2015-11-08 23:27:25

标签: powershell parameters pipeline interactive

当省略必需参数时,Interactive PowerShell会话会提示用户。 Shay Levy offers a workaround这个问题。问题是当您使用管道绑定参数时,解决方法不起作用。

考虑这个例子:

function f {
    [CmdletBinding()]
    param
    (
        [Parameter(ValueFromPipeLineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$a=$(throw "a is mandatory, please provide a value.")
    )
    process{}
}

$o = New-Object psobject -Property @{a=1}
$o | f

尽管$o.a是一个非常好的绑定到f -a的值,但仍会抛出异常。由于某种原因,PowerShell会评估参数$a的默认值,即使有$a的值必定要从管道绑定。

当交互式运行时缺少必需参数时,还有其他方法可以强制PowerShell抛出异常吗?

为什么这很重要?这浪费了程序员的时间。方法如下:

  • 堆栈跟踪深度为20次调用是很正常的。当调用堆栈深处的调用因为没有收到强制参数而阻塞时,调试的效率会非常低。没有堆栈跟踪,没有错误消息,也没有上下文。所有你看到的是参数值的提示。祝你好运猜测为什么会这样。您可以随时调试解决方案,它只需要花费更多的时间,因为您没有从抛出的异常中获取通常的信息。

  • 假设您正在运行一系列配置测试用例,而一个 1000则存在此问题。平均而言,其中500个测试用例没有运行。因此,您只能在此测试运行中获得一半案例的测试结果。如果这些测试运行过夜,您可能需要再等24小时才能获得结果。所以现在你的迭代速度较慢。

6 个答案:

答案 0 :(得分:2)

前言

我看到的所有解决方案仅仅是针对这个基本问题的解决方法:在非交互模式下,PowerShell会在缺少参数时抛出异常。在交互模式下,无法告诉PowerShell以相同的方式抛出异常。

Connect确实应该为这个问题打开一个问题。我还没能在Connect上进行正确的搜索。

使用管道绑定参数

只要您以任何方式涉及参数绑定的管道,缺少的参数就会产生错误。并且,如果$ErrorActionPreference -eq 'Stop'它会引发异常:

function f {
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true,
                   ValueFromPipeLineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$a,

        [Parameter(Mandatory = $true ,
                   ValueFromPipeLineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$b,

        [Parameter(ValueFromPipeLineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$c
    )
    process{}
}

$o = New-Object psobject -Property @{a=1}
$splat = @{c=1}
$o | f @splat

为参数ParameterBindingException抛出b因为它是强制性的。请注意there's some bizarreness related to catching that exception under PowerShell 2

I tested使用管道和splatted参数的一些不同变体,看起来像涉及管道绑定以任何方式避免提示用户。

不幸的是,这意味着每次调用可能缺少参数的命令时都会创建一个参数对象。这通常涉及对New-Object psobject -Property @{}的相当冗长的调用。由于我希望经常使用这种技术,因此我创建了ConvertTo-ParamObject(和别名>>)来将splat参数转换为参数对象。使用>>会产生如下所示的代码:

$UnvalidatedParams | >> | f

现在假设$UnvalidatedParams是来自某个地方的哈希表,可能已经省略了f的一个必需参数。使用上述方法调用f会导致错误,而不是有问题的用户提示。如果$ErrorActionPreferenceStop,它会抛出一个你可以捕获的异常。

我已经重构了一些使用这种技术的代码,我很乐观这是我尝试过的最不好的解决方法。 @Briantist的technique非常聪明,但如果你不能改变你正在调用的cmdlet,它就不起作用。

答案 1 :(得分:1)

这不起作用的原因是管道参数具有不同的值,具体取决于您是否在Begin {}Process {}End {}块中。在某些时候,默认值会被评估,因此会抛出异常。这是我不喜欢那种特殊黑客的原因之一。

合适的解决方案(我希望)

I liked it so much I wrote a blog post about it所以我希望你觉得它很有用。

function Validate-MandatoryOptionalParameters {
[CmdletBinding()]
param(
    [Parameter(
        Mandatory=$true
    )]
    [System.Management.Automation.CommandInfo]
    $Context ,

    [Parameter(
        Mandatory=$true,
        ValueFromPipeline=$true
    )]
    [System.Collections.Generic.Dictionary[System.String,System.Object]]
    $BoundParams ,

    [Switch]
    $SetBreakpoint
)

    Process {
        foreach($param in $Context.Parameters.GetEnumerator()) {
            if ($param.Value.Aliases.Where({$_ -imatch '^Required_'})) {
                if (!$BoundParams[$param.Key]) {
                    if ($SetBreakpoint) {
                        $stack = Get-PSCallStack | Select-Object -Index 1
                        Set-PSBreakpoint -Line $stack.ScriptLineNumber -Script $stack.ScriptName | Write-Debug
                    } else {
                        throw [System.ArgumentException]"'$($param.Key)' in command '$($Context.Name)' must be supplied by the caller."
                    }
                }
            }
        }
    }
}

我认为最大的优势在于,无论您拥有多少参数或名称是什么,它都会以相同的方式被调用。

关键是您只需为以Required_开头的每个参数添加别名。

实施例

function f {
[CmdletBinding()]
param(
    [Parameter(
        ValueFromPipeline=$true
    )]
    [Alias('Required_Param1')]
    $Param1
)

    Process {
        $PSBoundParameters | Validate-MandatoryOptionalParameters -Context $MyInvocation.MyCommand
    }
}

根据我们的聊天对话和您的用例,我搞砸了设置断点而不是抛出。似乎它可能有用,但不确定。更多信息在帖子中。

也可以GitHub Gist(包括基于评论的帮助)。

我认为您解决此问题的唯一方法是检查流程块中的值。

Process {
    if (!$a) {
        throw [System.ArgumentException]'You must supply a value for the -a parameter.'
    }
}

如果您控制脚本的调用,则可以使用powershell.exe -NonInteractive并且应该抛出(或至少退出)而不是提示。

验证功能示例

function Validate-Parameter {
[CmdletBinding()]
param(
    [Parameter(
        Mandatory=$true , #irony
        ValueFromPipeline=$true
    )]
    [object]
    $o ,

    [String]
    $Message
)

    Begin {
        if (!$Message) {
            $Message = 'The specified parameter is required.'
        }
    }

    Process {
        if (!$o) {
            throw [System.ArgumentException]$Message
        }
    }
}

# Usage

Process {
    $a | Validate-Parameter -Message "-a is a required parameter"
    $a,$b,$c,$d | Validate-Parameter
}

答案 2 :(得分:0)

首先,如果你的函数应该接受来自管道的值,你需要在"参数"中声明它。按ValueFromPipeline=$True。为什么PS首先评估参数的默认值?我没有解释。

但是你总是可以在函数内部使用If语句来评估参数是否为空,如果为真,则生成错误。

试试这个:

function f {
    [CmdletBinding()]
    param
    (
        [Parameter(ValueFromPipeline=$True,ValueFromPipelinebyPropertyName=$True)]
        [ValidateNotNullOrEmpty()]
        [string]$a
    )
    process{

        if (!($a)) {
            $(throw "a is mandatory, please provide a value.")
        }
    }
}

$o = New-Object psobject -Property @{a=1}
$o.a| f 

答案 3 :(得分:0)

您是否尝试过ValidateScript

[ValidateScript({
  if ($_ -eq $null -or $_ -eq '') {
    Throw "a is mandatory, please provide a value."
  }
  else {
    $True
  }
})]

答案 4 :(得分:0)

以非交互模式启动Powershell:

powershell -NonInteractive

答案 5 :(得分:0)

我尝试了多种上述方法,但是所有这些方法都没有满足简单的要求,即当缺少参数时,应该向用户提供明智的消息。使用ValidateScript Powershell总是在我自己的消息之前添加“无法验证参数'fileName'上的参数”,这是我不希望的。

我最终去了老学校,只是这样做了:

param(
    [Parameter()] 
    [string]$fileName=""
)

if([string]::IsNullOrEmpty($fileName)) {
    throw [System.ArgumentException] "File name is required."
}

这有效。我收到的唯一消息是“必须输入文件名”。