Search code examples
powershellparametersoptional-parameterspowershell-5.1

Making at least one of many parameters required in PowerShell


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.


Solution

  • 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.

        • GitHub issue #7868 proposes introducing a way to hide (obsolete) parameters from the syntax diagram.

    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.