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,
)
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:
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.New-ConnectionString
in favor of doing it themselves if they need to use features you didn't provide access toIf you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With