In PowerShell, I want to write a function, that accepts different options as parameters. It is OK, if it receives more than one parameter, but it has to receive at least one parameter. I want to enforce it through the parameter definition and not through code afterwards. I can get it to work with the following code:
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
}
But as you can see, it scales badly. For each additional option, I have to add a line to all other options. Can this be done in a more efficient and a more maintainable way?
As I already said, I don't want to check the parameters in the code, e. g. through evaluating $PSBoundParameters
. I want it to happen in the parameter definition for auto-doc reasons.
If you need a real world example, have a look at Set-DhcpServerv4OptionValue
which accepts many different options (-DnsDomain
, -DnsServer
, -Router
, ...), where it is OK to have them all, but it makes no sense to have none.
Note: After several answers have already been provided, I just realized that my code is actually not working, if you provide more than one option.
The following isn't a great solution - and depending on what you mean by auto-doc, it may not work for you - but it scales well, as you'll only ever need one additional parameter set:
function Set-Option {
[CmdletBinding(DefaultParameterSetName='Fail')]
Param(
[Parameter(ParameterSetName='AtLeastOne')]
$Option1,
[Parameter(ParameterSetName='AtLeastOne')]
$Option2,
[Parameter(ParameterSetName='AtLeastOne')]
$Option3,
# Declare a dummy parameter whose default value throws an error
# if no other parameter is passed.
# Note: All that 'DontShow' does is to exclude the parameter
# from tab completion; doesn't hide it from syntax diagram.
[Parameter(ParameterSetName='Fail', DontShow)]
${-} = $(
if ($PScmdlet.ParameterSetName -eq 'Fail') {
throw "Please specify at least one option."
}
)
)
# Do stuff, without needing to evaluate the plausibility of
# the given parameters here.
}
All real parameters are optional and belong to the same parameter set that is not the default.
The purpose of dummy parameter ${-}
, which is the only one in the default parameter set, is solely to throw an error via its default value.
Since the default value is always evaluated, a conditional is needed to throw only if the default parameter set has been selected ($PScmdlet.ParameterSetName -eq 'Fail'
).
Due to the parameter's irregular name, you actually cannot pass an explicit value to it (which is desirable here, because it is purely auxiliary and not meant for direct use): you'd have to use -- <value>
, but --
has special meaning to the parameter binder (deactivates named parameter binding for the subsequent arguments).
Unfortunately, property DontShow
(e.g. [Parameter(DontShow)]
) only hides the parameter from tab-completion, not also from the syntax diagrams.
Thus, unfortunately, the dummy parameter set and its parameter appear in the syntax diagram, so that Set-Option -?
shows the following:
SYNTAX
Set-Option [-- <Object>] [<CommonParameters>]
Set-Option [-Option1 <Object>] [-Option2 <Object>] [-Option3 <Object>] [<CommonParameters>]
Note that syntax diagrams lack a notation for your desired logic.