当省略必需参数时,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小时才能获得结果。所以现在你的迭代速度较慢。
答案 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
会导致错误,而不是有问题的用户提示。如果$ErrorActionPreference
是Stop
,它会抛出一个你可以捕获的异常。
我已经重构了一些使用这种技术的代码,我很乐观这是我尝试过的最不好的解决方法。 @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."
}
这有效。我收到的唯一消息是“必须输入文件名”。