制作 PowerShell 所需的众多参数中的至少一个
Making at least one of many parameters required in PowerShell
在 PowerShell 中,我想编写一个函数,它接受不同的选项作为参数。没关系,如果它接收多个参数,但它必须接收至少一个参数。我想通过参数定义而不是之后的代码来强制执行它。我可以使用以下代码让它工作:
function Set-Option {
Param(
[Parameter(Mandatory, ParameterSetName="AtLeastOption1")]
[Parameter(Mandatory=$false, ParameterSetName="AtLeastOption2")]
[Parameter(Mandatory=$false, ParameterSetName="AtLeastOption3")]
$Option1,
[Parameter(Mandatory=$false, ParameterSetName="AtLeastOption1")]
[Parameter(Mandatory, ParameterSetName="AtLeastOption2")]
[Parameter(Mandatory=$false, ParameterSetName="AtLeastOption3")]
$Option2,
[Parameter(Mandatory=$false, ParameterSetName="AtLeastOption1")]
[Parameter(Mandatory=$false, ParameterSetName="AtLeastOption2")]
[Parameter(Mandatory, ParameterSetName="AtLeastOption3")]
$Option3
)
# Do stuff, but don't evaluate the plausibility of the given parameters here
}
但如您所见,它的扩展性很差。对于每个附加选项,我必须在所有其他选项中添加一行。能否以更高效和更易于维护的方式完成此操作?
正如我已经说过的,我不想检查代码中的参数,e。 G。通过评估 $PSBoundParameters
。出于自动文档的原因,我希望它发生在参数定义中。
如果您需要真实世界的示例,请查看 Set-DhcpServerv4OptionValue
,它接受许多不同的选项(-DnsDomain
、-DnsServer
、-Router
、...) ,其中可以全部拥有它们,但是拥有 none.
是没有意义的
注意:在已经提供了几个答案之后,我才意识到我的代码实际上不工作,如果你提供更多不止一种选择。
如果参数都是开关(即,您将它们指定为 -Option1
而不是 -Option1 SomeValue
),请在实际代码的开头包含一个测试,以检查它们是否全部false,如果是,则拒绝调用。如果它们是值参数(即 -Option1 SomeValue
),您必须针对 $null
测试它们中的每一个,如果它们都是 $null
,则拒绝调用。
function Set-Option {
param (
[switch]$Option1,
[switch]$Option2,
...
)
if (!($Option1 -or $Option2 -or ...)) {
# reject the invocation and abort
}
...
}
以下不是一个很好的解决方案 - 根据您所说的 auto-doc 的含义,它可能不适合您 - 但它可以很好地扩展,因为您只需要 一个 附加参数集:
function Set-Option {
[CmdletBinding(DefaultParameterSetName='Fail')]
Param(
[Parameter(ParameterSetName='AtLeastOne')]
$Option1,
[Parameter(ParameterSetName='AtLeastOne')]
$Option2,
[Parameter(ParameterSetName='AtLeastOne')]
$Option3,
# Note: All that 'DontShow' does is to exclude the param. from tab completion.
[Parameter(ParameterSetName='Fail', DontShow)]
${-} = $(throw "Please specify at least one option.")
)
# Do stuff, but don't evaluate the plausibility of the given parameters here
}
所有实际参数都是可选的并且属于相同的参数集不是默认值。
虚拟参数 ${-}
的目的,它是默认参数集中唯一的一个,只是通过其 default[=48= 抛出错误]值。
由于它的名字不规则,你实际上不能给它一个明确的值(这里是可取的,因为它纯粹是辅助而不是直接使用):你必须使用-- <value>
,但 --
对参数绑定器有特殊意义(为后续参数停用命名参数绑定)。
不幸的是,属性DontShow
(例如[Parameter(DontShow)]
)只隐藏了来自tab-completion的参数, 也不来自 语法图 .
- GitHub issue #7868 提议引入一种从语法图中隐藏(过时的)参数的方法。
因此,不幸的是,虚拟参数集及其参数出现在语法图中,因此Set-Option -?
显示如下:
SYNTAX
Set-Option [-- <Object>] [<CommonParameters>]
Set-Option [-Option1 <Object>] [-Option2 <Object>] [-Option3 <Object>] [<CommonParameters>]
请注意,语法图缺少您所需逻辑的符号。
编辑: 正如 OP 所指出的,此处提供的解决方案 在传递了多个参数 时不起作用:
Set-Option -Option1 foo -Option2 42
Parameter set cannot be resolved using the specified named parameters. One or more
parameters issued cannot be used together or an insufficient number of parameters were
provided.
我将保留它作为使用 DynamicParam
的示例。
这是一个使用 DynamicParam
自动生成您手动创建的相同参数集的解决方案。尽管不是“code-free”解决方案,它仍然显示预期的语法图(当像 Set-Option -?
那样调用时),因为 PowerShell 从 DynamicParam
块中获取所有必要的信息。
首先我们定义一个可重用的辅助函数来编写一个DRYDynamicParam
块:
Function Add-ParamGroupAtLeastOne {
<#
.SYNOPSIS
Define a group of parameters from which at least one must be passed.
#>
Param(
[Parameter(Mandatory)] [Management.Automation.RuntimeDefinedParameterDictionary] $Params,
[Parameter(Mandatory)] [Collections.Specialized.IOrderedDictionary] $ParamDefinitions
)
foreach( $paramDef in $ParamDefinitions.GetEnumerator() ) {
$attributes = [Collections.ObjectModel.Collection[Attribute]]::new()
# Generate parameter sets for one parameter
foreach( $groupItem in $ParamDefinitions.Keys ) {
$attr = [Management.Automation.ParameterAttribute]@{
Mandatory = $paramDef.Key -eq $groupItem
ParameterSetName = "AtLeastOne$groupItem"
}
if( $paramDef.Value.HelpMessage ) {
$attr.HelpMessage = $paramDef.Value.HelpMessage
}
$attributes.Add( $attr )
}
# Add one parameter
$Params.Add( $paramDef.Key, [Management.Automation.RuntimeDefinedParameter]::new( $paramDef.Key, $paramDef.Value.Type, $attributes ))
}
}
Set-Option
函数现在可以这样写:
Function Set-Option {
[CmdletBinding()]
Param() # Still required
DynamicParam {
$parameters = [Management.Automation.RuntimeDefinedParameterDictionary]::new()
Add-ParamGroupAtLeastOne -Params $parameters -ParamDefinitions ([ordered] @{
Option1 = @{ Type = 'string'; HelpMessage = 'the 1st option' }
Option2 = @{ Type = 'int'; HelpMessage = 'the 2nd option' }
Option3 = @{ Type = 'bool'; HelpMessage = 'the 3rd option' }
})
$parameters
}
process {
# Do stuff
}
}
Set-Option -?
按预期输出此语法图:
SYNTAX
Set-Option -Option1 <string> [-Option2 <int>] [-Option3 <bool>] [<CommonParameters>]
Set-Option -Option2 <int> [-Option1 <string>] [-Option3 <bool>] [<CommonParameters>]
Set-Option -Option3 <bool> [-Option1 <string>] [-Option2 <int>] [<CommonParameters>]
如果您想添加更多参数属性,请查看 ParameterAttribute
class 并在函数 Add-ParamGroupAtLeastOne
中添加所需的属性,因为我已经为 HelpMessage
.
在 PowerShell 中,我想编写一个函数,它接受不同的选项作为参数。没关系,如果它接收多个参数,但它必须接收至少一个参数。我想通过参数定义而不是之后的代码来强制执行它。我可以使用以下代码让它工作:
function Set-Option {
Param(
[Parameter(Mandatory, ParameterSetName="AtLeastOption1")]
[Parameter(Mandatory=$false, ParameterSetName="AtLeastOption2")]
[Parameter(Mandatory=$false, ParameterSetName="AtLeastOption3")]
$Option1,
[Parameter(Mandatory=$false, ParameterSetName="AtLeastOption1")]
[Parameter(Mandatory, ParameterSetName="AtLeastOption2")]
[Parameter(Mandatory=$false, ParameterSetName="AtLeastOption3")]
$Option2,
[Parameter(Mandatory=$false, ParameterSetName="AtLeastOption1")]
[Parameter(Mandatory=$false, ParameterSetName="AtLeastOption2")]
[Parameter(Mandatory, ParameterSetName="AtLeastOption3")]
$Option3
)
# Do stuff, but don't evaluate the plausibility of the given parameters here
}
但如您所见,它的扩展性很差。对于每个附加选项,我必须在所有其他选项中添加一行。能否以更高效和更易于维护的方式完成此操作?
正如我已经说过的,我不想检查代码中的参数,e。 G。通过评估 $PSBoundParameters
。出于自动文档的原因,我希望它发生在参数定义中。
如果您需要真实世界的示例,请查看 Set-DhcpServerv4OptionValue
,它接受许多不同的选项(-DnsDomain
、-DnsServer
、-Router
、...) ,其中可以全部拥有它们,但是拥有 none.
注意:在已经提供了几个答案之后,我才意识到我的代码实际上不工作,如果你提供更多不止一种选择。
如果参数都是开关(即,您将它们指定为 -Option1
而不是 -Option1 SomeValue
),请在实际代码的开头包含一个测试,以检查它们是否全部false,如果是,则拒绝调用。如果它们是值参数(即 -Option1 SomeValue
),您必须针对 $null
测试它们中的每一个,如果它们都是 $null
,则拒绝调用。
function Set-Option {
param (
[switch]$Option1,
[switch]$Option2,
...
)
if (!($Option1 -or $Option2 -or ...)) {
# reject the invocation and abort
}
...
}
以下不是一个很好的解决方案 - 根据您所说的 auto-doc 的含义,它可能不适合您 - 但它可以很好地扩展,因为您只需要 一个 附加参数集:
function Set-Option {
[CmdletBinding(DefaultParameterSetName='Fail')]
Param(
[Parameter(ParameterSetName='AtLeastOne')]
$Option1,
[Parameter(ParameterSetName='AtLeastOne')]
$Option2,
[Parameter(ParameterSetName='AtLeastOne')]
$Option3,
# Note: All that 'DontShow' does is to exclude the param. from tab completion.
[Parameter(ParameterSetName='Fail', DontShow)]
${-} = $(throw "Please specify at least one option.")
)
# Do stuff, but don't evaluate the plausibility of the given parameters here
}
所有实际参数都是可选的并且属于相同的参数集不是默认值。
虚拟参数
${-}
的目的,它是默认参数集中唯一的一个,只是通过其 default[=48= 抛出错误]值。由于它的名字不规则,你实际上不能给它一个明确的值(这里是可取的,因为它纯粹是辅助而不是直接使用):你必须使用
-- <value>
,但--
对参数绑定器有特殊意义(为后续参数停用命名参数绑定)。不幸的是,属性
DontShow
(例如[Parameter(DontShow)]
)只隐藏了来自tab-completion的参数, 也不来自 语法图 .- GitHub issue #7868 提议引入一种从语法图中隐藏(过时的)参数的方法。
因此,不幸的是,虚拟参数集及其参数出现在语法图中,因此Set-Option -?
显示如下:
SYNTAX
Set-Option [-- <Object>] [<CommonParameters>]
Set-Option [-Option1 <Object>] [-Option2 <Object>] [-Option3 <Object>] [<CommonParameters>]
请注意,语法图缺少您所需逻辑的符号。
编辑: 正如 OP 所指出的,此处提供的解决方案 在传递了多个参数 时不起作用:
Set-Option -Option1 foo -Option2 42
Parameter set cannot be resolved using the specified named parameters. One or more
parameters issued cannot be used together or an insufficient number of parameters were
provided.
我将保留它作为使用 DynamicParam
的示例。
这是一个使用 DynamicParam
自动生成您手动创建的相同参数集的解决方案。尽管不是“code-free”解决方案,它仍然显示预期的语法图(当像 Set-Option -?
那样调用时),因为 PowerShell 从 DynamicParam
块中获取所有必要的信息。
首先我们定义一个可重用的辅助函数来编写一个DRYDynamicParam
块:
Function Add-ParamGroupAtLeastOne {
<#
.SYNOPSIS
Define a group of parameters from which at least one must be passed.
#>
Param(
[Parameter(Mandatory)] [Management.Automation.RuntimeDefinedParameterDictionary] $Params,
[Parameter(Mandatory)] [Collections.Specialized.IOrderedDictionary] $ParamDefinitions
)
foreach( $paramDef in $ParamDefinitions.GetEnumerator() ) {
$attributes = [Collections.ObjectModel.Collection[Attribute]]::new()
# Generate parameter sets for one parameter
foreach( $groupItem in $ParamDefinitions.Keys ) {
$attr = [Management.Automation.ParameterAttribute]@{
Mandatory = $paramDef.Key -eq $groupItem
ParameterSetName = "AtLeastOne$groupItem"
}
if( $paramDef.Value.HelpMessage ) {
$attr.HelpMessage = $paramDef.Value.HelpMessage
}
$attributes.Add( $attr )
}
# Add one parameter
$Params.Add( $paramDef.Key, [Management.Automation.RuntimeDefinedParameter]::new( $paramDef.Key, $paramDef.Value.Type, $attributes ))
}
}
Set-Option
函数现在可以这样写:
Function Set-Option {
[CmdletBinding()]
Param() # Still required
DynamicParam {
$parameters = [Management.Automation.RuntimeDefinedParameterDictionary]::new()
Add-ParamGroupAtLeastOne -Params $parameters -ParamDefinitions ([ordered] @{
Option1 = @{ Type = 'string'; HelpMessage = 'the 1st option' }
Option2 = @{ Type = 'int'; HelpMessage = 'the 2nd option' }
Option3 = @{ Type = 'bool'; HelpMessage = 'the 3rd option' }
})
$parameters
}
process {
# Do stuff
}
}
Set-Option -?
按预期输出此语法图:
SYNTAX
Set-Option -Option1 <string> [-Option2 <int>] [-Option3 <bool>] [<CommonParameters>]
Set-Option -Option2 <int> [-Option1 <string>] [-Option3 <bool>] [<CommonParameters>]
Set-Option -Option3 <bool> [-Option1 <string>] [-Option2 <int>] [<CommonParameters>]
如果您想添加更多参数属性,请查看 ParameterAttribute
class 并在函数 Add-ParamGroupAtLeastOne
中添加所需的属性,因为我已经为 HelpMessage
.