powershelldesign-patternsparameter-sets

Using parametersets to constrain more than one mutually exclusive dependency


When a script's signature is differentiated (singularly) by an argument, parameter sets make sense to me.

Example:

.\myscript.ps1 -InputFile [-Optional1] [-Optional2]...
.\myscript.ps1 -ArrayOfNames [-Optional1] [-Optional2]...

My question is: Are parameter sets the logical choice when you wish to support parallel (or multiple) dependencies as explained below?

Here is my current scenario. I'm adding support for an existing script that queries logs containing time stamps. The script should accept a csv file or an array of smtp addresses to identify which users to query.

The script should also support begin and end date parameters or an integer value to facilitate reporting n number of days in the past, calculated from the current date.

The outcome I wish to support is:

.\myScript -InputFile -StartDate -EndDate [-Optional1] [-Optional2]...
.\myScript -InputFile -LastNumDays [-Optional1] [-Optional2]...
.\myScript -Smtp -StartDate -EndDate [-Optional1] [-Optional2]...
.\myScript -Smtp -LastNumDays [-Optional1] [-Optional2]...

Either of the following two parameter definitions work well if I don't attempt to combine my two requirements:

[Parameter(Mandatory=$true, ParameterSetName="Input")]
[ValidateScript({Test-Path -Path $_ -PathType Leaf})][string] $InputFile,

[Parameter(Mandatory=$true, ParameterSetName="NoInput")]
[ValidateNotNullOrEmpty()][String[]] $Smtp

Get-Help displays expected usage as:

.\myScript.ps1 -InputFile <String> [<CommonParameters>]
.\myScript.ps1 -Smtp <String[]> [<CommonParameters>]

If I configure the following instead:

[Parameter(Mandatory=$true, ParameterSetName="NotRange")]
[ValidateNotNullOrEmpty()][int] $LastNumDays = 30, # init 30 days default

[Parameter(Mandatory=$true, ParameterSetName="Range")]
[ValidateNotNullOrEmpty()][Alias("Start")] [DateTime] $StartDate,

[Parameter(Mandatory=$true, ParameterSetName="Range")]
[ValidateNotNullOrEmpty()][Alias("End")] [DateTime] $EndDate

Get-Help displays expected usage as:

.\myScript.ps1 -LastNumDays <Int32> [<CommonParameters>]
.\myScript.ps1 -StartDate <DateTime> -EndDate <DateTime> [<CommonParameters>]

The problem is that I can't seem to incorporate both my dependencies as described at the beginning of this post. An example of just one of my unsuccessful attempts to combine these two logical dependencies using parameter sets is as follows:

[Parameter(Mandatory=$true, ParameterSetName="Input")]
[ValidateScript({Test-Path -Path $_ -PathType Leaf})][string] $InputFile,

[Parameter(Mandatory=$true, ParameterSetName="NoInput")]
[ValidateNotNullOrEmpty()][String[]] $Smtp,

[Parameter(Mandatory=$true, ParameterSetName="NotRange")]
[Parameter(Mandatory=$true, ParameterSetName="Input")]
[Parameter(Mandatory=$true, ParameterSetName="NoInput")]
[ValidateNotNullOrEmpty()][int] $LastNumDays = 30, # init 30 days default

[Parameter(Mandatory=$true, ParameterSetName="Range")]
[Parameter(Mandatory=$true, ParameterSetName="Input")]
[Parameter(Mandatory=$true, ParameterSetName="NoInput")]
[ValidateNotNullOrEmpty()][Alias("Start")] [DateTime] $StartDate,

[Parameter(Mandatory=$true, ParameterSetName="Range")]
[Parameter(Mandatory=$true, ParameterSetName="Input")]
[Parameter(Mandatory=$true, ParameterSetName="NoInput")]
[ValidateNotNullOrEmpty()][Alias("End")] [DateTime] $EndDate

Get-Help results are incorrect b/c the first two usage statements allow LastNumDays and Start/EndDate parameters to be used at the same time:

.\myScript.ps1 -InputFile <String> -LastNumDays <Int32> -StartDate <DateTime> -EndDate <DateTime> [<CommonParameters>]

.\myScript.ps1 -Smtp <String[]> -LastNumDays <Int32> -StartDate <DateTime> -EndDate <DateTime> [<CommonParameters>]

.\myScript.ps1 -LastNumDays <Int32> [<CommonParameters>]
.\myScript.ps1 -StartDate <DateTime> -EndDate <DateTime> [<CommonParameters>]

I've tested different combinations of mandatory true/false and including/omitting my named parameter sets with no success.

I'm suspecting now that my requirements may not be fitting for the use case parameter sets were intended to support but am left to wonder what pattern and practice I should be using instead.

How can I properly define usage syntax for these two dependencies if not using parameter sets? I feel that I must avoid resorting to tests in my code that announce dependencies that are not defined in Get-Help.

Thank you!


Solution

  • You need to make each parameter set unique, so PowerShell can distinguish one from another. For simplicity reasons I'll name the parameter sets A through D:

    A: -InputFile -StartDate -EndDate
    B: -InputFile -LastNumDays
    C: -Smtp -StartDate -EndDate
    D: -Smtp -LastNumDays

    Now associate each parameter with each parameter set it appears in:

    Param(
      [Parameter(Mandatory=$true, ParameterSetName="A")]
      [Parameter(Mandatory=$true, ParameterSetName="B")]
      [string]$InputFile,
    
      [Parameter(Mandatory=$true, ParameterSetName="C")]
      [Parameter(Mandatory=$true, ParameterSetName="D")]
      [String[]]$Smtp,
    
      [Parameter(Mandatory=$true, ParameterSetName="B")]
      [Parameter(Mandatory=$true, ParameterSetName="D")]
      [int]$LastNumDays,
    
      [Parameter(Mandatory=$true, ParameterSetName="A")]
      [Parameter(Mandatory=$true, ParameterSetName="C")]
      [DateTime]$StartDate,
    
      [Parameter(Mandatory=$true, ParameterSetName="A")]
      [Parameter(Mandatory=$true, ParameterSetName="C")]
      [DateTime]$EndDate
    )
    

    Output:

    PS C:\> .\test.ps1 -?
    test.ps1 -InputFile <string> -LastNumDays <int> [<CommonParameters>]
    test.ps1 -InputFile <string> -StartDate <datetime> -EndDate <datetime> [<CommonParameters>]
    test.ps1 -Smtp <string[]> -LastNumDays <int> [<CommonParameters>]
    test.ps1 -Smtp <string[]> -StartDate <datetime> -EndDate <datetime> [<CommonParameters>]

    Note that it's pointless to provide a default value for a mandatory parameter (-LastNumDays), because you're required to provide a value anyway.