Colin
Colin

Reputation: 2743

Is there a better way to declare multiple parameter sets?

I'm writing a cmdlet (in PowerShell) that is responsible for writing a record into a database. With the conditional command line, it seems like I have to define four different parameter sets.

Is there a more succient way of doing this?

DETAILS

The parameters of the cmdlet are:

Path and Xml are mutually exclusive, and UserName/Password and UseIntegratedSecurity are mutually exclusive.

To get this wired up correctly, it seems like I have to define four different parameter sets, e.g.:


function Install-WidgetData
{
    [CmdletBinding()]
    PARAM
    (
        [Parameter(ParameterSetName="Xml_AutoConnect", Mandatory=$True)]
        [Parameter(ParameterSetName="Xml_ManualConnect", Mandatory=$True)]
        [Parameter(ParameterSetName="Path_AutoConnect", Mandatory=$True, )]
        [Parameter(ParameterSetName="Path_ManualConnect", Mandatory=$True)]
        [ValidateNotNullOrEmpty()]
        [string[]] $ComputerName,

        [Parameter(ParameterSetName="Path_AutoConnect", Mandatory=$True)]
        [Parameter(ParameterSetName="Path_ManualConnect", Mandatory=$True)]
        [ValidateNotNullOrEmpty()]
        [string] $Path,

        [Parameter(ParameterSetName="Xml_AutoConnect", Mandatory=$True)]
        [Parameter(ParameterSetName="Xml_ManualConnect", Mandatory=$True)]
        [ValidateNotNullOrEmpty()]
        [string[]] $Xml,

        [Parameter(ParameterSetName="Xml_AutoConnect")]
        [Parameter(ParameterSetName="Path_AutoConnect")]
        [switch] $UseIntegratedSecurity,

        [Parameter(ParameterSetName="Xml_ManualConnect")]
        [Parameter(ParameterSetName="Path_ManualConnect")]
        [ValidateNotNullOrEmpty()]
        [string] $UserName,

        [Parameter(ParameterSetName="Xml_ManualConnect")]
        [Parameter(ParameterSetName="Path_ManualConnect")]
        [ValidateNotNullOrEmpty()]
        [string] $Password,
    )

Upvotes: 16

Views: 17506

Answers (4)

oɔɯǝɹ
oɔɯǝɹ

Reputation: 7625

If you want a fast sanity check on your parameter sets, you can use Show-Command

This will display a form with multiple tabs, one for each parameter set. For example:

Show-Command Get-ChildItem

Will show this:

enter image description here

Or; If you want a Command Line alternative, you can use Get-Command -Syntax

Get-Command Get-ChildItem -Syntax

Will show you this:

Get-ChildItem [[-Path] ] [[-Filter] ] [-Include ] [-Exclude ] [-Recurse] [-Depth ] [-Force] [-Name] [-UseTransaction] [-Attributes ] [-Directory] [-File] [-Hidden] [-ReadOnly] [-System] []

Get-ChildItem [[-Filter] ] -LiteralPath [-Include ] [-Exclude ] [-Recurse] [-Depth ] [-Force] [-Name] [-UseTransaction] [-Attributes ] [-Directory] [-File] [-Hidden] [-ReadOnly] [-System] []

Upvotes: 15

jpmc26
jpmc26

Reputation: 29856

There is a better way, but it's a design solution rather than a technical one.

The problem is actually that your function is doing too many things. One might say it's violating the single responsibility principle. Each task it performs has two separate parameter sets. The tasks and their parameter sets are:

  • Build a connection string
    • Manual (user name and password)
    • Auto (OS account authentication)
  • Sending a query to the database
    • XML data
    • Path to XML file containing data

Since each task has its own different parameters sets, your function ends up needing the Cartesian product of them (Manual & XML, Auto & XML, Manual & path, Auto & path).

Any time you find yourself in one of these "Cartesian product" parameter situations, it's almost always a sign that you can move one piece of functionality into a separate function and make the new function's result a parameter to the original. In this case, you can split it up into New-ConnectionString and Install-WidgetData, and Install-WidgetData can accept a full connection string as a parameter. This removes the logic of building the connection string from Install-WidgetData, condensing several parameters into one and halving the number of parameter sets needed.

function New-ConnectionString(
    [Parameter(Mandatory=$True, Position=0)] # Makes it mandatory for all parameter sets
    [ValidateNotNullOrEmpty()]
    [string[]]$ComputerName,

    [Parameter(ParameterSetName="AutoConnect", Mandatory=$True)]
    [switch]$UseIntegratedSecurity,

    [Parameter(ParameterSetName="ManualConnect", Mandatory=$True, Position=1)]
    [ValidateNotNullOrEmpty()]
    [string]$UserName,

    [Parameter(ParameterSetName="ManualConnect", Mandatory=$True, Position=2)]
    [ValidateNotNullOrEmpty()]
    [string]$Password
) {
    # ... Build connection string up
    return $connString
}


function Install-WidgetData(
    [Parameter(Mandatory=$True, Position=0)]
    [ValidateNotNullOrEmpty()]
    [string]$ConnectionString,

    [Parameter(ParameterSetName="Path", Mandatory=$True, Position=1)]
    [ValidateNotNullOrEmpty()]
    [string]$Path,

    [Parameter(ParameterSetName="Xml", Mandatory=$True)]
    [ValidateNotNullOrEmpty()]
    [string[]]$Xml
) {
     # Do installation
}

You can see that this did what you want by invoking help on the commands:

PS C:\> help New-ConnectionString

NAME
    New-ConnectionString

SYNTAX
    New-ConnectionString [-ComputerName] <string[]> -UseIntegratedSecurity  [<CommonParameters>]

    New-ConnectionString [-ComputerName] <string[]> [-UserName] <string> [-Password] <string>  [<CommonParameters>]

...

PS C:\> help Install-WidgetData

NAME
    Install-WidgetData

SYNTAX
    Install-WidgetData [-ConnectionString] <string> [-Path] <string>  [<CommonParameters>]

    Install-WidgetData [-ConnectionString] <string> -Xml <string[]>  [<CommonParameters>]

...

Then you call them something like this:

Install-WidgetData (New-ConnectionString 'myserver.example.com' -UseIntegratedSecurity) `
    -Path '.\my-widget-data.xml'

You can store the result of New-ConnectionString in a variable if you want, of course. You also get some additional features from doing this refactor:

  • New-ConnectionString's return value can be reused for any number of functions that require a connection string.
  • Callers can obtain connection strings from other sources if they prefer
  • Callers can forego your New-ConnectionString in favor of doing it themselves if they need to use features you didn't provide access to

Upvotes: 6

chamele0n
chamele0n

Reputation: 105

Sadly, that is the only way to do it, according to about_Functions_Advanced_Parameters

Here is an excerpt:

    You can specify only one ParameterSetName value in each argument and only
    one ParameterSetName argument in each Parameter attribute. To indicate that
    a parameter appears in more than one parameter set, add additional Parameter        
    attributes.

    The following example explicitly adds the Summary parameter to the Computer
    and User parameter sets. The Summary parameter is mandatory in one parameter
    set and optional in the other.

    Param
      (
        [parameter(Mandatory=$true,
                  ParameterSetName="Computer")]
        [String[]]
        $ComputerName,

        [parameter(Mandatory=$true,
                  ParameterSetName="User")]
        [String[]]
        $UserName

        [parameter(Mandatory=$false, ParameterSetName="Computer")]
        [parameter(Mandatory=$true, ParameterSetName="User")]
        [Switch]
        $Summary
      )

For more information about parameter sets, see Cmdlet Parameter Sets in the MSDN library.

Upvotes: 6

Serjx86
Serjx86

Reputation: 530

Well, this is the most succinct way. More succinct then the horrors of switch/case or if/then traps to account for all possible parameter sets!

However, your other option is to write different commandlest for mutually exclusive parameter sets, for example

Install-WidgetDataFromPath
Install-WidgetDataFromXml

Both can call Install-WidgetData script commandlet which you can keep hidden inside the module or using scope modifier to hide it from global scope if you are using only a script file. The internal commandlet can implement shared code for both (or more) user-facing wrappers. Judging from your code I don't think you need to be explained how to implement this.

Upvotes: 0

Related Questions