Reputation: 2743
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,
)
Upvotes: 16
Views: 17506
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:
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
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:
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 toUpvotes: 6
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
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