Why DefaultParameterSetName restrain me to input the parameter? - powershell

function CTC {
[CmdletBinding(DefaultParameterSetName = "LocalBox")]
param
(
[Alias("l")]
[Parameter(ParameterSetName = "LocalBox", Mandatory)]
[Switch]
$Local,
[Alias("b")]
[Parameter(ParameterSetName = "LocalBox")]
[Parameter(ParameterSetName = "CloudTest")]
[string]
$BranchName = "head",
[Parameter(ParameterSetName = "CloudTest")]
[string]
$Tenant = "bingadsucmaz_cu"
)
process {
}
}
I have a function named "CTC", it has two parameter sets. The first one is "LocalBox" and I set it as default parameter set, the second one is "CloudTest". When I choose to run "CTC" without any parameters, it remind me to input the value like :
cmdlet CTC at command pipeline position 1
Supply values for the following parameters:
Local:
But suppose the meaningful action is the cmd choose to run "CloudTest" parameter set, right ?

Related

POWERSHELL - Handling Parameter Sets Gracefully [duplicate]

This question already has answers here:
Which parameter set has been used?
(2 answers)
Closed last month.
I have a function:
Function Get-CMAPICollection {
[CmdletBinding()]
param (
[parameter(ValueFromPipeline = $true, Mandatory = $True, ParameterSetName = "ByName")]
[string]$collectionName,
[parameter(ValueFromPipeline = $true, Mandatory = $True, ParameterSetName = "ById")]
[string]$collectionId
)
If ($collectionName) {
$URI = "https://$($CMAPI.cmServer)/AdminService/wmi/SMS_Collection?`$filter=Name eq '$collectionName'"
}
If ($collectionId) {
$URI = "https://$($CMAPI.cmServer)/AdminService/wmi/SMS_Collection?`$filter=CollectionId eq '$collectionId'"
}
(Send-CMAPIRequest -method GET -URI $uri).Value
}
Which all works fine.
I'm wondering if there is a better way to dynamically handle the parameters in the function than a bunch of 'If' statements.
My concern is that if I need to add a whole lot of parameter sets, the functions itself is going to have a whole bunch of 'If' statements cluttering up the place.
No matter how far I abstract it out, there is still an 'If' collecionName or 'If' collectionId.
Thanks!
As always, literally minutes after posting I have found the answer. Provided by this answer here:
Which parameter set has been used?
Using the variable:
$PSCmdlet.ParameterSetName
and then a switch statement will reduce the requirement for IF statements.
I suppose this is a duplicate, however I think the question and answer provide some good examples and information.
Fixed function:
Function Get-CMAPICollection {
[CmdletBinding()]
param (
[parameter(ValueFromPipeline = $true, Mandatory = $True, ParameterSetName = "ByName")]
[string]$collectionName,
[parameter(ValueFromPipeline = $true, Mandatory = $True, ParameterSetName = "ById")]
[string]$collectionId
)
switch ($PSCmdlet.ParameterSetName) {
byName { $URI = "https://$($CMAPI.cmServer)/AdminService/wmi/SMS_Collection?`$filter=Name eq '$collectionName'" }
ById { $URI = "https://$($CMAPI.cmServer)/AdminService/wmi/SMS_Collection?`$filter=CollectionId eq '$collectionId'" }
}
(Send-CMAPIRequest -method GET -URI $uri).Value
}

Running PowerShell script that leverages Parameter Sets without any parameters

How do I allow the option to run a PowerShell script that leverages parameter sets to run without passing any parameters? Is this possible?
param(
[Parameter(Mandatory=$true,HelpMessage="GenSecureFile used to generate secure password file.", ParameterSetName = 'GenSecureFile')]
[switch]$GenSecureFile,
[Parameter(Mandatory=$true,HelpMessage="GenSettingsFile used to generate settings file.", ParameterSetName = 'GenSettingsFile')]
[switch]$GenSettingsFile
)
Attempted to use a default parameter but this does not let you run the script with no parameters.
For this to work properly the parameter being declared as DefaultParameterSetName shouldn't be flagged as Mandatory and possibly be set to $true which wouldn't make sense for a switch Parameter. switch Parameters are meant to be optional (should not be Mandatory) and should not have a default value.
For Example:
function Test-Parameter {
[CmdletBinding(DefaultParameterSetName = 'GenSecureFile')]
param(
[Parameter(ParameterSetName = 'GenSecureFile')]
[switch] $GenSecureFile = $true,
[Parameter(Mandatory, ParameterSetName = 'GenSettingsFile')]
[switch] $GenSettingsFile
)
end {
$PSCmdlet.ParameterSetName
}
}
Test-Parameter
What you should do instead is have only one parameter that uses a ValidateSet Attribute Declaration, by doing so there wouldn't be a need for Parameter Sets.
For Example:
function Test-Parameter {
[CmdletBinding()]
param(
[Parameter()]
[ValidateSet('SecureFile', 'SettingsFile')]
[string] $Gen = 'SecureFile'
)
end {
if($Gen -eq 'SecureFile') {
# do something here when `SecureFile`
return
}
# do something here when `SettingsFile`
}
}
Test-Parameter

Providing a default value to a parameter that uses ArgumentCompletions attribute

Here is a distilled version of my function:
function Get-CommandBar{
[CmdletBinding()]
[Alias("GCom")]
Param(
[Parameter()]
[string]$Path = $pwd,
[Parameter()]
[ArgumentCompletions('Path', 'Copy')]
$ActiveTab = 'Path',
[Switch]$ActiveVideo,
[Switch]$AllTabs
)
begin{
}
process {
Switch ($PSBoundParameters.Keys){
'ActiveTab'{
If ($ActiveTab -eq "Path"){
"Path"
}elseif($ActiveTab -eq "Copy"){
"Copy"
}
}
'AllTabs'{
"All Tabs"
}
'ActiveVideo'{
"Active Video"
}
}
}
}
get-CommandBar -ActiveTab Path outputs:
Path
get-CommandBar -ActiveTab Copy outputs:
Copy
But trying just get-CommandBar -ActiveTab, instead of Path, it outputs an error:
Get-CommandBar: Missing an argument for parameter 'ActiveTab'. Specify a parameter of type 'System.Object' and try again.
What gives? Shouldnt $ActiveTab = 'Path' be sufficient? Information on ArgumentCompletions seems to be sparse out here.m
Any help would be wonderfull.

Powershell function receiving multiple parameters from pipeline

I'm writing a function as follows:
Function Display-ItemLocation {
Param(
[ Parameter (
Mandatory = $True,
Valuefrompipeline = $True ) ]
[ String ]$stringItem,
[ Parameter (
Mandatory = $False,
Valuefrompipeline = $True ) ]
[ String ]$stringLocation = 'unknown'
)
Echo "The location of item $stringItem is $stringLocation."
}
Display-ItemLocation 'Illudium Q-36 Explosive Space Modulator' 'Mars'
Display-ItemLocation 'Plumbus'
It works fine as written.
The location of item Illudium Q-36 Explosive Space Modulator is Mars.
The location of item Plumbus is unknown.
I'd like to be able to pre-load an array with multiple data pairs and send it via pipeline into the function.
$Data = #(
#('Bucket','Aisle 1'),
#('Spinach Pie','Freezer 4')
)
$Data | Display-ItemLocation
I can't find the magic syntax to get this to work. Can the function accept a pair of values at the same time from the pipeline?
Define your pipeline-binding parameters as binding by property name - ValuefromPipelineByPropertyName - and then pipe (custom) objects that have such properties:
Function Display-ItemLocation {
Param(
[ Parameter (
Mandatory,
ValuefromPipelineByPropertyName ) ]
[ String ]$stringItem,
[ Parameter (
Mandatory = $False,
ValuefromPipelineByPropertyName ) ]
[ String ]$stringLocation = 'unknown'
)
process { # !! You need a `process` block to process *all* input objects.
Echo "The location of item $stringItem is $stringLocation."
}
}
As an aside: Display is not an approved verb in PowerShell.
Now you can pipe to the function as follows; note that the property names must match the parameter names:
$Data = [pscustomobject] #{ stringItem = 'Bucket'; stringLocation = 'Aisle 1' },
[pscustomobject] #{ stringItem = 'Spinach Pie'; stringLocation = 'Freezer 4' }
$Data | Display-ItemLocation
The above yields:
The location of item Bucket is Aisle 1.
The location of item Spinach Pie is Freezer 4.
The above uses [pscustomobject] instances, which are easy to construct ad hoc.
Note that hash tables (e.g., just #{ stringItem = 'Bucket'; stringLocation = 'Aisle 1' }) do not work - although changing that is being discussed in this GitHub issue.
In PSv5+ you could alternatively define a custom class:
# Define the class.
class Product {
[string] $stringItem
[string] $stringLocation
Product([object[]] $itemAndLocation) {
$this.stringItem = $itemAndLocation[0]
$this.stringLocation = $itemAndLocation[1]
}
}
# Same output as above.
[Product[]] (
('Bucket', 'Aisle 1'),
('Spinach Pie', 'Freezer 4')
) | Display-ItemLocation
Thanks to #mklement0 for leading me to this solution. I came up with two options to resolve my dilemma using an array.
Option 1: Use .ForEach to pass the parameters in the usual way.
$Data = #(
#('Bucket','Aisle 1'),
#('Spinach Pie','Freezer 4')
)
$Data.ForEach({Format-ItemLocation "$($_[0])" "$($_[1])"})
Option 2: Using the pipeline (which is what I was after), I modified the function as mklement0 suggested to enable the ValuefromPipelineByPropertyName and to include a Process { } block.
Function Format-ItemLocation {
Param (
[ Parameter (
Mandatory = $True,
ValuefromPipelineByPropertyName = $True ) ]
[ String ]$stringItem,
[ Parameter (
Mandatory = $False,
ValuefromPipelineByPropertyName = $True ) ]
[ String ]$stringLocation = 'unknown'
)
Process {
"The location of item $stringItem is $stringLocation."
}
}
I pass the array via pipeline to a middle step to assign the parameter names to a [PSCustomObject]. This greatly reduces the amount of text that would bulk up the code, and it's the reason I was searching for a more elegant solution.
$Data = #(
#('Bucket','Aisle 1'),
#('Spinach Pie','Freezer 4')
)
$Data |
ForEach-Object { [PSCustomObject]#{stringItem=$_[0];stringLocation=$_[1]} } |
Format-ItemLocation
I changed the function name to Format-* as recommended.

Powershell non-positional, optional params

I'm trying to create a powershell (2.0) script that will accept arguments that follow this basic pattern:
.\{script name} [options] PATH
Where options are any number of optional parameters - think along the lines of '-v' for verbose. The PATH argument will simply be whatever argument is passed in last, and is mandatory. One could call the script with no options and only one argument, and that argument would be assumed to be the Path. I'm having trouble setting up a parameters list that contains only optional parameters but is also non-positional.
This quick script demonstrates the problem I am having:
#param test script
Param(
$firstArg,
$secondArg,
[switch]$thirdArg,
[Parameter(ValueFromRemainingArguments = $true)]
$remainingArgs)
write-host "first arg is $firstArg"
write-host "second arg is $secondArg"
write-host "third arg is $thirdArg"
write-host "remaining: $remainingArgs"
When called like so:
.\param-test.ps1 firstValue secondValue
The script outputs:
first arg is firstValue
second arg is secondValue
third arg is False
remaining:
The behavior I am trying to create would have both arguments fall through the optional params and end up in the remainingArgs variable.
This question/answer helpfully provided a way to achieve the desired behavior, but it only seems to work if there is at least one mandatory parameter, and only if it comes before all of the other arguments.
I can demonstrate this behavior by making firstArg mandatory and specifying a position of 0:
#param test script
Param(
[Parameter(Mandatory=$true, Position = 0)]
$firstArg,
$secondArg,
[switch]$thirdArg,
[Parameter(ValueFromRemainingArguments = $true)]
$remainingArgs)
write-host "first arg is $firstArg"
write-host "second arg is $secondArg"
write-host "third arg is $thirdArg"
write-host "remaining: $remainingArgs"
Run with the same input as before:
.\param-test.ps1 firstValue secondValue
The output is as follows:
first arg is firstValue
second arg is
third arg is False
remaining: secondValue
The first, mandatory argument is assigned, and everything left falls all the way through.
The question is this: How can I set up a params list such that all of the params are optional, and none of them is positional?
How about this?
function test
{
param(
[string] $One,
[string] $Two,
[Parameter(Mandatory = $true, Position = 0)]
[string] $Three
)
"One = [$one] Two = [$two] Three = [$three]"
}
One and Two are optional, and may only be specified by name. Three is mandatory, and may be provided without a name.
These work:
test 'foo'
One = [] Two = [] Three = [foo]
test -One 'foo' 'bar'
One = [foo] Two = [] Three = [bar]
test 'foo' -Two 'bar'
One = [] Two = [bar] Three = [foo]
This will fail:
test 'foo' 'bar'
test : A positional parameter cannot be found that accepts argument 'bar'.
At line:1 char:1
+ test 'foo' 'bar'
+ ~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [test], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,test
This doesn't enforce that your mandatory arg is placed last, or that it's not named. But it allows for the basic usage pattern you want.
It also does not allow for more than one value in $Three. This might be what you want. But, if you want to treat multiple non-named params as being part of $Three, then add the ValueFromRemainingArguments attribute.
function test
{
param(
[string] $One,
[string] $Two,
[Parameter(Mandatory = $true, Position = 0, ValueFromRemainingArguments = $true)]
[string] $Three
)
"One = [$one] Two = [$two] Three = [$three]"
}
Now things like this work:
test -one 'foo' 'bar' 'baz'
One = [foo] Two = [] Three = [bar baz]
Or even
test 'foo' -one 'bar' 'baz'
One = [bar] Two = [] Three = [foo baz]