Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a better way to declare multiple parameter sets?

Tags:

powershell

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:

  • ComputerName (the SQL server to connect to)
  • Path (the location of the data)
  • Xml (the raw data itself)
  • UserName
  • Password
  • UseIntegratedSecurity (instead of username/password, use current credentials)

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,
    )
like image 226
Colin Avatar asked Feb 07 '13 19:02

Colin


1 Answers

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
like image 194
jpmc26 Avatar answered Nov 13 '22 01:11

jpmc26