Powershell parameter sets and optional parameters - powershell

I'm writing a function for which two parameters should be exclusive and optional.
Here are valid inputs:
new-event -Title sometitle -Text sometext -TimestampHappened 1234567 -SomeOtherOptionalParam somestring
new-event -Title sometitle -Text sometext -DateHappened (get-date) -SomeOtherOptionalParam somestring
new-event -Title sometitle -Text sometext -SomeOtherOptionalParam somestring
new-event -Title sometitle -Text sometext
Here is an invalid input:
new-event -Title sometitle -Text sometext -DateHappened (get-date) -TimestampHappened 1234567 -SomeOtherOptionalParam somestring
Here is my code so far:
[CmdletBinding()]
# Most parameters belong to Default, New-Event:ByDate and New-Event:ByTimestamp parameter sets
param (
[Parameter(
Position=0,
Mandatory=$True,
ParameterSetName="Default"
)]
[Parameter(
Position=0,
Mandatory=$True,
ParameterSetName="New-Event:ByDate"
)]
[Parameter(
Position=0,
Mandatory=$True,
ParameterSetName="New-Event:ByTimestamp"
)]
[ValidateNotNullOrEmpty()]
[String]$Title,
[Parameter(
Position=1,
Mandatory=$True,
ParameterSetName="Default"
)]
[Parameter(
Position=1,
Mandatory=$True,
ParameterSetName="New-Event:ByDate"
)]
[Parameter(
Position=1,
Mandatory=$True,
ParameterSetName="New-Event:ByTimestamp"
)]
[ValidateNotNullOrEmpty()]
[String]$Text,
[Parameter(
Position=2,
Mandatory=$False,
ParameterSetName="New-Event:ByDate"
)]
[ValidateNotNullOrEmpty()]
[datetime]$DateHappened,
[Parameter(
Position=2,
Mandatory=$False,
ParameterSetName="New-Event:ByTimestamp"
)]
[ValidateNotNullOrEmpty()]
[Double]$TimestampHappened,
[Parameter(
Position=3,
Mandatory=$False,
ParameterSetName="Default"
)]
[Parameter(
Position=3,
Mandatory=$False,
ParameterSetName="New-Event:ByDate"
)]
[Parameter(
Position=3,
Mandatory=$False,
ParameterSetName="New-Event:ByTimestamp"
)]
[String]$SomeOtherParam,
...
Here is what I get when I call Get-Help:
PS> get-help New-Event
NAME
New-Event
SYNOPSIS
Post an event to the stream.
SYNTAX
New-Event [-Title] <String> [-Text] <String> [[-TimestampHappened] <Double>] [[-Priority] <String>] [[-Hostname] <String>] [[-Tags] <String[]>] [[-AlertType] <String>] [<CommonParameters>]
New-Event [-Title] <String> [-Text] <String> [[-DateHappened] <DateTime>] [[-Priority] <String>] [[-Hostname] <String>] [[-Tags] <String[]>] [[-AlertType] <String>] <String>] [<CommonParameters>]
New-Event [-Title] <String> [-Text] <String> [[-Priority] <String>] [[-Hostname] <String>] [[-Tags] <String[]>] [[-AlertType] <String>] [<CommonParameters>]
However here is the error I get when I try to call the function with only the two mandatory parameters:
New-Event -Title test -Text text
New-Event : Parameter set cannot be resolved using the specified named parameters.
At line:1 char:1
+ New-Event -Title test -Text text
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [New-Event], ParameterBindingException
+ FullyQualifiedErrorId : AmbiguousParameterSet,New-Event
I'm missing something here, but I can't figure out what...
How can I get two parameters that are mutually exclusive and optional?

This makes perfect sense. You have 3 parameter sets, and the 2 mandatory parameters are included on every set. How could PowerShell determine which set you meant to use?
Luckily the [CmdletBinding()] attribute can take a parameter that helps with this exact case: DefaultParameterSetName. Setting this allows PowerShell to use this set in the case of (certain) ambiguities. Use it like so:
[CmdletBinding(DefaultParameterSetName='Default')]
Note that in this case, you named it default; it could have been named anything.

Related

Levels of parameter sets for a cmdlet

What I want is to get-help to output the below for my cmdlet
SYNTAX
Get-Somehting -A <Object> [-Package <Object>] [<CommonParameters>]
Get-Somehting -A <Object> [-Names <string[]>] [<CommonParameters>]
Get-Somehting -B <Object> [-Package <Object>] [<CommonParameters>]
Get-Somehting -B <Object> [-Names <string[]>] [<CommonParameters>]
The following
Function Get-Somehting{
[CmdletBinding()]
Param(
[Parameter(Mandatory,
ParameterSetName='A')]
[System.Object]$A,
[Parameter(Mandatory,
ParameterSetName='B')]
[System.Object]$B,
[Parameter(Mandatory,
ParameterSetName='package')]
[System.Object]$Package,
[Parameter(Mandatory,
ParameterSetName='Names')]
[String[]]$Names)
Process{
}
}
gives me
SYNTAX
Get-Somehting -A <Object> [-Package <Object>] [-Names <string[]>] [<CommonParameters>]
Get-Somehting -B <Object> [-Package <Object>] [-Names <string[]>] [<CommonParameters>]
One of the most annoying and coolest features is parameter set names. What's cool is you can properly define your input for the 'path' you want your code to take, and rather check what 'path' was input instead of checking all the different input parameters. You want to be very careful when defining them, however, as pwsh REALLY wants to end up with just one invoked 'path', so you have to properly define your expected paths:
Function Get-Something {
[CmdletBinding()]
Param(
[Parameter(Mandatory,ParameterSetName = 'AName')]
[Parameter(Mandatory,ParameterSetName = 'APackage')]
[System.Object]$A,
[Parameter(Mandatory,ParameterSetName = 'BName')]
[Parameter(Mandatory,ParameterSetName = 'BPackage')]
[System.Object]$B,
[Parameter(Mandatory,ParameterSetName = 'AName')]
[Parameter(Mandatory,ParameterSetName = 'BName')]
[System.Object]$Package,
[Parameter(Mandatory,ParameterSetName = 'APackage')]
[Parameter(Mandatory,ParameterSetName = 'BPackage')]
[String[]]$Names)
Process {
$PSCmdlet.ParameterSetName
}
}
It's kind of messy, but it also makes sense as in your case you want to always have an "a or b" path and with that always define either names or packages, so you end up with 4 possible 'paths'.

Function's multiple ParameterSets work when being declared from a script, but not when being imported from a module

I wrote a 'wrapper' function for the Send-MailMessage function with a hard-coded SmtpServer argument (that's all it does). Here's the header declaring the sets:
function SendMessage {
[CmdletBinding(DefaultParameterSetName='Path')]
Param(
[Parameter(Mandatory,
ParameterSetName='Path',
HelpMessage='the path and filename to the message you would like to send')]
[String]$Path,
[Parameter(Mandatory,
HelpMessage="A string containing the message which you want to send",
ParameterSetName='Msg')]
[String]$Msg,
[Parameter(Mandatory,
HelpMessage='Your Admin Office 365 credentials',
ParameterSetName='Path')]
[Parameter(Mandatory,
ParameterSetName='Msg')]
[Alias('Credentials')]
[System.Management.Automation.PSCredential]$Cred,
[Parameter(Mandatory,
HelpMessage='The address to which you want to send the message',
ParameterSetName='Path')]
[Parameter(Mandatory,
ParameterSetName='Msg')]
[String[]]$To,
[Parameter(Mandatory,
HelpMessage='The email from which you want to send the message',
ParameterSetName='Path')]
[Parameter(Mandatory,
ParameterSetName='Msg')]
[String]$From,
[Parameter(Mandatory,
HelpMessage='The subject of the email to send out',
ParameterSetName='Path')]
[Parameter(Mandatory,
ParameterSetName='Msg')]
[ValidateNotNullOrEmpty()]
[String]$Subject,
[Parameter(ParameterSetName='Path',
HelpMessage='[Optional] If you want it cc''d to anyone')]
[Parameter(ParameterSetName='Msg')]
[String[]]$CC
)
# do function stuff
}
The function has two ParameterSets: Path and Msg.
When I run it as a .ps1 script, I can see both sets:
PS U:\> test.ps1
PS U:\> gcm SendMessage -Syntax
SendMessage -Path <string> -Cred <pscredential> -To <string[]> -From <string> -Subject <string> [-CC <string[]>] [<CommonParameters>]
SendMessage -Msg <string> -Cred <pscredential> -To <string[]> -From <string> -Subject <string> [-CC <string[]>] [<CommonParameters>]
But when I import it as a module, I can only see the Path set:
PS U:\> Import-Module -Name test.psm1
PS U:\> gcm SendMessage -Syntax
SendMessage [-Cred] <pscredential> [-Path] <string> [-To] <string> [-From] <string> [-Subject] <string> [[-CC] <string[]>] [<CommonParameters>]
PS U:\>
I would like to be able to import both sets when I import the module.
Is there anything obvious which I am missing?
Thank you.
EDIT: I am using PS version 5.1, and have been testing using the ISE.
Testing scripts and function with the ISE makes it so complex. variables and functions in ISE is always available in global scope. Test this in PowerShell console.
Open a new PowerShell console, and dot source test.ps1
. c:\test.ps1
Get-Command SendMessage -Syntax
Open another PowerShell console and run Import-Module
Import-Module c:\Test.ps.1 -Verbose
Get-Command SendMessage -syntax

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!
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.

Powershell: Seemingly valid parameter set doesn't work

I am putting together a set of tools for internal account management. This particular cmdlet will prep an account for a transfer out, which includes an option to remove or retain their groups. My Param statement has become quite lengthy and has ended up with 6 parameter sets to cover every scenario. All combinations of parameters are working except for one. Here is my code:
function Move-AccountOut {
[CmdletBinding(DefaultParameterSetName='NoTransferHomeDrive')]
Param(
[Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True,Position=1)]
[string]$Username,
[Parameter(ParameterSetName='RetainGroups',Position=2)]
[Parameter(ParameterSetName='RetainGroupsWTran',Position=2)]
[switch]$RetainGroups,
[Parameter(ParameterSetName='RemoveFromAllGroups',Position=2)]
[Parameter(ParameterSetName='RemoveFromAllGroupsWTran',Position=2)]
[switch]$RemoveFromAllGroups,
[Parameter(ParameterSetName='TransferHomeDrive', Position=3)]
[Parameter(ParameterSetName='RetainGroupsWTran', Position=3)]
[Parameter(ParameterSetName='RemoveFromAllGroupsWTran', Position=3)]
[switch]$TransferHomeDrive,
[Parameter(ParameterSetName='TransferHomeDrive', Mandatory=$True, Position=4)]
[Parameter(ParameterSetName='RetainGroupsWTran', Mandatory=$True, Position=4)]
[Parameter(ParameterSetName='RemoveFromAllGroupsWTran', Mandatory=$True, Position=4)]
[string]$OldServer,
[Parameter(ParameterSetName='TransferHomeDrive', Mandatory=$True, Position=5)]
[Parameter(ParameterSetName='RetainGroupsWTran', Mandatory=$True, Position=5)]
[Parameter(ParameterSetName='RemoveFromAllGroupsWTran', Mandatory=$True, Position=5)]
[string]$NewServer
)
}
Which will generate the following Get-Help output:
Move-AccountOut [-Username] <string> [<CommonParameters>]
Move-AccountOut [-Username] <string> [[-RetainGroups]] [[-TransferHomeDrive]] [-OldServer] <string> [-NewServer] <string> [<CommonParameters>]
Move-AccountOut [-Username] <string> [[-RetainGroups]] [<CommonParameters>]
Move-AccountOut [-Username] <string> [[-RemoveFromAllGroups]] [[-TransferHomeDrive]] [-OldServer] <string> [-NewServer] <string> [<CommonParameters>]
Move-AccountOut [-Username] <string> [[-RemoveFromAllGroups]] [<CommonParameters>]
Move-AccountOut [-Username] <string> [[-TransferHomeDrive]] [-OldServer] <string> [-NewServer] <string> [<CommonParameters>]
The parameter I am having trouble with is the bottom one (transfer only). I can run the command with username, username+retain, username+remove, username+retain+transfer and username+remove+transfer. But transfer without a retain or remove does not work. When run, it throws the following error:
Move-AccountOut : Parameter set cannot be resolved using the specified named parameters.
At line:33 char:1
+ Move-AccountOut -Username X -TransferHomeDrive -OldServer X -NewServer Y
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Move-AccountOut], ParameterBindingException
+ FullyQualifiedErrorId : AmbiguousParameterSet,Move-AccountOut
I can't figure out why, given that it appears to be a valid parameter set according to my Get-Help output.
Thank you for the output, here is the key (emphasis mine):
Remaining valid parameter set: RetainGroupsWTran
Remaining valid parameter set: RemoveFromAllGroupsWTran
Remaining valid parameter set: TransferHomeDrive
This happens after all parameters have been bound, and it shows that there are 3 valid parameter sets remaining.
TransferHomeDrive is the parameter set you want.
So why are the other ones there?
RetainGroupsWTran should only be possible if -RetainGroups is specified, and RemoveFromAllGroupsWTran should only be possible if -RemoveFromAllGroups is specified.
However, you can also see in the Get-Help output that there is no parameter set where those switches are mandatory, and this is the problem.
Your updated definition looks like the following:
function Move-AccountOut {
[CmdletBinding(DefaultParameterSetName='NoTransferHomeDrive')]
Param(
[Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True,Position=1)]
[string]$Username,
[Parameter(ParameterSetName='RetainGroups',Position=2)]
[Parameter(Mandatory=$true,ParameterSetName='RetainGroupsWTran',Position=2)]
[switch]$RetainGroups,
[Parameter(ParameterSetName='RemoveFromAllGroups',Position=2)]
[Parameter(Mandatory=$true,ParameterSetName='RemoveFromAllGroupsWTran',Position=2)]
[switch]$RemoveFromAllGroups,
[Parameter(ParameterSetName='TransferHomeDrive', Position=3)]
[Parameter(ParameterSetName='RetainGroupsWTran', Position=3)]
[Parameter(ParameterSetName='RemoveFromAllGroupsWTran', Position=3)]
[switch]$TransferHomeDrive,
[Parameter(ParameterSetName='TransferHomeDrive', Mandatory=$True, Position=4)]
[Parameter(ParameterSetName='RetainGroupsWTran', Mandatory=$True, Position=4)]
[Parameter(ParameterSetName='RemoveFromAllGroupsWTran', Mandatory=$True, Position=4)]
[string]$OldServer,
[Parameter(ParameterSetName='TransferHomeDrive', Mandatory=$True, Position=5)]
[Parameter(ParameterSetName='RetainGroupsWTran', Mandatory=$True, Position=5)]
[Parameter(ParameterSetName='RemoveFromAllGroupsWTran', Mandatory=$True, Position=5)]
[string]$NewServer
)
}
I've made them mandatory within the parameter sets that were showing up in the trace output.
I think that you should also make them mandatory in the RetainGroups and RemoveFromAllGroups parameter sets as well, but it's not shown above.

Multiple parameter sets and PowerShell

I am building a function which will have three distinct parameter sets, and two of those sets will overlap with the third. The options would look like this:
A B
A C
A (D E F)
A B (D E F)
A C (D E F)
To make it a little more clear, here is a partially completed version the function:
function Move-AccountOut {
[CmdletBinding(DefaultParameterSetName='NoTransferHomeDrive')]
Param(
[Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)]
[string]$Username,
[Parameter(ParameterSetName='RetainGroups')]
[switch]$RetainGroups,
[Parameter(ParameterSetName='RemoveFromAllGroups')]
[switch]$RemoveFromAllGroups,
[Parameter(ParameterSetName='TransferHomeDrive', Mandatory=$False)]
[switch]$TransferHomeDrive,
[Parameter(ParameterSetName='TransferHomeDrive', Mandatory=$True)]
[string]$OldServer,
[Parameter(ParameterSetName='TransferHomeDrive', Mandatory=$True)]
[string]$NewServer
)
}
The purpose of the function is to automate the process of transferring out an AD account to another location within the company. RetainGroups would automatically retain the users groups when set, and RemoveFromAllGroups would automatically remove the user from their groups. The two switches should not be able to be used together. Additionally, if TransferHomeDrive is set, it will call a function to schedule a transfer using an internal tool.
To put it another way, RetainGroups and RemoveFromAllGroups should be a member of all parameter sets (similar to Username), but should not be able to be used together.
I have tried two ways. The first:
function Move-AccountOut {
[CmdletBinding(DefaultParameterSetName='NoTransferHomeDrive')]
Param(
[Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)]
[string]$Username,
[Parameter(ParameterSetName='RetainGroups')]
[switch]$RetainGroups,
[Parameter(ParameterSetName='RemoveFromAllGroups')]
[switch]$RemoveFromAllGroups,
[Parameter(ParameterSetName='TransferHomeDrive', Mandatory=$False)]
[Parameter(ParameterSetName='RetainGroups')]
[Parameter(ParameterSetName='RemoveFromAllGroups')]
[switch]$TransferHomeDrive,
[Parameter(ParameterSetName='TransferHomeDrive', Mandatory=$True)]
[Parameter(ParameterSetName='RetainGroups')]
[Parameter(ParameterSetName='RemoveFromAllGroups')]
[string]$OldServer,
[Parameter(ParameterSetName='TransferHomeDrive', Mandatory=$True)]
[Parameter(ParameterSetName='RetainGroups')]
[Parameter(ParameterSetName='RemoveFromAllGroups')]
[string]$NewServer
)
}
Using this technique, retain and remove cannot be used together, but OldServer and NewServer are no longer mandatory. If I change them to:
[Parameter(ParameterSetName='TransferHomeDrive', Mandatory=$True)]
[Parameter(ParameterSetName='RetainGroups', Mandatory=$True)]
[string]$OldServer,
[Parameter(ParameterSetName='TransferHomeDrive', Mandatory=$True)]
[Parameter(ParameterSetName='RetainGroups', Mandatory=$True)]
[string]$NewServer
They will be mandatory, but it no longer cares whether TransferHomeDrive is set.
If I set it up the opposite way:
function Move-AccountOut {
[CmdletBinding(DefaultParameterSetName='NoTransferHomeDrive')]
Param(
[Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)]
[string]$Username,
[Parameter(ParameterSetName='RetainGroups')]
[Parameter(ParameterSetName='TransferHomeDrive')]
[switch]$RetainGroups,
[Parameter(ParameterSetName='RemoveFromAllGroups')]
[Parameter(ParameterSetName='TransferHomeDrive')]
[switch]$RemoveFromAllGroups,
[Parameter(ParameterSetName='TransferHomeDrive', Mandatory=$False)]
[switch]$TransferHomeDrive,
[Parameter(ParameterSetName='TransferHomeDrive', Mandatory=$True)]
[string]$OldServer,
[Parameter(ParameterSetName='TransferHomeDrive', Mandatory=$True)]
[string]$NewServer
)
}
Then OldServer and NewServer will be mandatory, but RetainGroups and RemoveFromAllGroups can be used together. Additionally, if I use retain and remove together, then OldServer and NewServer become mandatory, but not when they are used on their own.
How do I make this work?
Ok, I think I understand this. The possible combinations you want are:
-RetainGroups
-RemoveFromAllGroups
-RetainGroups plus -TransferHomeDrive
-RemoveFromAllGroups plus -TransferHomeDrive
Only -UserName
-UserName plus -TransferHomeDrive
I am assuming that -OldServer and -NewServer only apply when moving the home drive, so whenever you are moving the home drive, they are mandatory. If that is not the case, let me know.
So what you have here are 6 parameter sets. As powerful as powershell's parameter set magic is, there isn't a good way to say "these 2 switches are mutually exclusive but should also be available in all parameter sets" so you have to multiplex it and repeat every parameter set with one or the other.
function Move-AccountOut {
[CmdletBinding(DefaultParameterSetName='OnlyUser')]
Param(
[Parameter(
Mandatory=$True,
ValueFromPipeline=$True,
ValueFromPipelineByPropertyName=$True
)]
[string]
$Username,
[Parameter(
Mandatory=$True,
ParameterSetName='RetainOnly'
)]
[Parameter(
Mandatory=$True,
ParameterSetName='RetainAndTransfer'
)]
[switch]
$RetainGroups,
[Parameter(
Mandatory=$True,
ParameterSetName='RemoveOnly'
)]
[Parameter(
Mandatory=$True,
ParameterSetName='RemoveAndTransfer'
)]
[switch]
$RemoveFromAllGroups,
[Parameter(
Mandatory=$True,
ParameterSetName='RetainAndTransfer'
)]
[Parameter(
Mandatory=$True,
ParameterSetName='RemoveAndTransfer'
)]
[Parameter(
Mandatory=$True,
ParameterSetName='TransferOnly'
)]
[switch]
$TransferHomeDrive,
[Parameter(
Mandatory=$True,
ParameterSetName='RetainAndTransfer'
)]
[Parameter(
Mandatory=$True,
ParameterSetName='RemoveAndTransfer'
)]
[Parameter(
Mandatory=$True,
ParameterSetName='TransferOnly'
)]
[string]
$OldServer,
[Parameter(
Mandatory=$True,
ParameterSetName='RetainAndTransfer'
)]
[Parameter(
Mandatory=$True,
ParameterSetName='RemoveAndTransfer'
)]
[Parameter(
Mandatory=$True,
ParameterSetName='TransferOnly'
)]
[string]
$NewServer
)
}
The output of Get-Help Move-AccountOut:
Move-AccountOut -Username <string> [<CommonParameters>]
Move-AccountOut -Username <string> -RetainGroups -TransferHomeDrive -OldServer <string> -NewServer <string> [<CommonParameters>]
Move-AccountOut -Username <string> -RetainGroups [<CommonParameters>]
Move-AccountOut -Username <string> -RemoveFromAllGroups -TransferHomeDrive -OldServer <string> -NewServer <string> [<CommonParameters>]
Move-AccountOut -Username <string> -RemoveFromAllGroups [<CommonParameters>]
Move-AccountOut -Username <string> -TransferHomeDrive -OldServer <string> -NewServer <string> [<CommonParameters>]
Simplifying It
If you want to make it less daunting, you might consider consolidating the remove and retain switches into a single parameter, something like this:
[Parameter(
Mandatory=$false # you can leave this out
)]
[ValidateSet(
'None',
'Retain',
'RemoveAll'
)]
[String]
$GroupAction = 'None'
This would reduce your parameter sets down to 2, and make your entire definition look like this:
function Move-AccountOut {
[CmdletBinding(DefaultParameterSetName='OnlyUser')]
Param(
[Parameter(
Mandatory=$True,
ValueFromPipeline=$True,
ValueFromPipelineByPropertyName=$True
)]
[string]
$Username,
[ValidateSet(
'None',
'Retain',
'RemoveAll'
)]
[String]
$GroupAction = 'None' ,
[Parameter(
Mandatory=$True,
ParameterSetName='TransferOnly'
)]
[switch]
$TransferHomeDrive,
[Parameter(
Mandatory=$True,
ParameterSetName='TransferOnly'
)]
[string]
$OldServer,
[Parameter(
Mandatory=$True,
ParameterSetName='TransferOnly'
)]
[string]
$NewServer
)
}
With the following Get-Help output:
Move-AccountOut -Username <string> [-GroupAction <string> {None | Retain | RemoveAll}] [<CommonParameters>]
Move-AccountOut -Username <string> -TransferHomeDrive -OldServer <string> -NewServer <string> [-GroupAction <string> {None | Retain | RemoveAll}] [<CommonParameters>]
I do want to point out that although that's simpler to define that doesn't mean it's better. It may be that you want to optimize your parameter sets for the caller which can be especially important if this is a function you plan on using interactively a lot from the shell, rather than calling from other scripts (and it seems like this may be the case).
So adding some complexity in the definition to make it easier to use might be the right thing to do.
By adding two more Parameter Sets you can do what you want. This is needed because you have 3 sets now, plus a non-set parameter (which technically puts it in the __AllParameterSets set if I remember right). So that's 4 ways of doing it. You need 6 ways of doing it if I am reading your question correctly. You want all of the following options:
Move-AccountOut -Username <string> [<CommonParameters>]
Move-AccountOut -Username <string> [-RetainGroups] [-TransferHomeDrive] [-OldServer <string>] [-NewServer <string>] [<CommonParameters>]
Move-AccountOut -Username <string> [-RetainGroups] [<CommonParameters>]
Move-AccountOut -Username <string> [-RemoveFromAllGroups] [-TransferHomeDrive] [-OldServer <string>] [-NewServer <string>] [<CommonParameters>]
Move-AccountOut -Username <string> [-RemoveFromAllGroups] [<CommonParameters>]
Move-AccountOut -Username <string> -OldServer <string> -NewServer <string> [-TransferHomeDrive] [<CommonParameters>]
So we will add the RemoveFromAllGroupsWTran and RetainGroupsWTran Parameter Sets, add them both to $TransferHomeDrive, $OldServer, and $NewServer (removing the other related set names from them), then add each to its respective switch parameter. It ends up looking like this:
function Move-AccountOut {
[CmdletBinding(DefaultParameterSetName='NoTransferHomeDrive')]
Param(
[Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)]
[string]$Username,
[Parameter(ParameterSetName='RetainGroups')]
[Parameter(ParameterSetName='RetainGroupsWTran')]
[switch]$RetainGroups,
[Parameter(ParameterSetName='RemoveFromAllGroups')]
[Parameter(ParameterSetName='RemoveFromAllGroupsWTran')]
[switch]$RemoveFromAllGroups,
[Parameter(ParameterSetName='TransferHomeDrive', Mandatory=$False)]
[Parameter(ParameterSetName='RetainGroupsWTran')]
[Parameter(ParameterSetName='RemoveFromAllGroupsWTran')]
[switch]$TransferHomeDrive,
[Parameter(ParameterSetName='TransferHomeDrive', Mandatory=$True)]
[Parameter(ParameterSetName='RetainGroupsWTran')]
[Parameter(ParameterSetName='RemoveFromAllGroupsWTran')]
[string]$OldServer,
[Parameter(ParameterSetName='TransferHomeDrive', Mandatory=$True)]
[Parameter(ParameterSetName='RetainGroupsWTran')]
[Parameter(ParameterSetName='RemoveFromAllGroupsWTran')]
[string]$NewServer
)
}
You can also use [ValidateScript()], if have two Mandatory = false parameters in all parameter sets, and you need to use them only together, e.g.:
function SomeFunction {
[CmdletBinding()]
Param(
[Parameter (Mandatory = $true,
ParameterSetName = "A")]
[Parameter (Mandatory = $true,
ParameterSetName = "B")]
[Parameter (Mandatory = $true,
ParameterSetName = "C")]
[switch]$Param1,
[Parameter (Mandatory = $true,
ParameterSetName = "A")]
[switch]$Param2,
[Parameter (Mandatory = $true,
ParameterSetName = "B")]
[string]$Param3,
[Parameter (Mandatory = $true,
ParameterSetName = "C")]
[string]$Param4,
[Parameter (Mandatory = $false,]
[ValidateScript({
if ($Param6) {
$True
}
else {
throw "This parameter will work only with parameter [Param6]"
}
}
)]
[string]$Param5,
[Parameter (Mandatory = $false)]
[ValidateScript({
if ($Param5) {
$True
}
else {
throw "This parameter will work only with parameter [Param5]"
}
}
)]
[string]$Param6
...
}