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,
)
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] []
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.
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
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.
Related
Im wondering if I am tackling this in the best way or if there is a better way of achieving my task.
I've written a function in PowerShell which takes different parameters, but some of the parameters won't work together.
So for example if I'm running the function and specifying a computerName parameter then I can't also pass list of multiple computer names.
I know I can write multiple If statements as along the lines if If(($computerName) - and ($computerList)){Then write and error}
but there are several parameters not just two, so do I need to do an if for each set of parameters someone could type in, or is there a better way of me tackling this?
currently I have multiple Ifs like If $computerName -and !(log file) and $computerlist) then write an error etc.
The PowerShell-idiomatic solution here is to declare a parameter that accepts either:
function Get-Stuff
{
param(
[Parameter(ValueFromPipeline = $true)]
[string[]]$ComputerName
)
process {
foreach($Computer in $ComputerName){
# do stuff with each individual -ComputerName argument
}
}
}
Now the user can do both
Get-Stuff -ComputerName oneComputerName
... and
"many","computer","names" |Get-Stuff
or for that matter
$computers = Get-Content .\Computers.txt
Get-Stuff -ComputerName $computers
# or
$computers |Get-Stuff
I know I can write multiple If statements as along the lines of If(($computerName) -and ($computerList)){Then write and error}
You can, but this is generally a bad idea - to test whether an argument value was passed to a parameter is better done through the automatic variable $PSBoundParameters:
function Get-Stuff
{
param(
[string]$AParameter,
[string]$ADifferentOne
)
if($PSBoundParameters.ContainsKey('AParameter')){
# an argument was definitely provided to $AParameter
}
if($PSBoundParameters.ContainsKey('ADifferentOne')){
# an argument was definitely provided to $ADifferentOne
}
}
The answer to the implied question of "how do I declare and work with mutually exclusive parameters" is parameter sets:
function Verb-Noun
{
param(
[Parameter(Mandatory = $true, ParameterSetName = 'SingleComputer')]
[string]$ComputerName,
[Parameter(Mandatory = $true, ParameterSetName = 'MultipleComputers')]
[string[]]$ComputerList
)
if($PSCmdlet.ParameterSetName -eq 'SingleComputer'){
# just one, we can deal with $ComputerName
}
else {
# we got multiple names via $ComputerList
}
}
PowerShell now recognizes two distinct parameter sets, each of which only accepts one of our parameters:
PS ~> Get-Command Verb-Noun -Syntax
Verb-Noun -ComputerName <string> [<CommonParameters>]
Verb-Noun -ComputerList <string[]> [<CommonParameters>]
The function has one required parameter, -Path, and two other mutually exclusive switches. This is not the real function, but a MRE (Minimal Reproducable Example). The default operation is to copy the file to a known location and then remove it.
Do-TheFile [-Path] <String[]> [[-Copy] | [-Remove]]
-Path = filename is mandatory
-CopyOnly = only copy the file, cannot be used with -Remove
-RemoveOnly = only remove the file, cannot be used with -Copy
This is the current code.
param (
[Parameter(Mandatory=$true, Position=0)]
[string[]]$Path
,[Parameter(Mandatory=$false, ParameterSetName='CopyOnly')]
[switch]$CopyOnly
,[Parameter(Mandatory=$false ,ParameterSetName='RemoveOnly')]
[switch]$RemoveOnly
)
The console allows me to specify both -CopyOnly and -RemoveOnly. My expectation was that the console would not permit me to enter both -CopyOnly and -RemoveOnly because they are in different ParameterSets. How can I specify these ParameterSets so that -Copy and -Remove are mutually exclusive?
PS C:\src\t> Do-TheFile -Path t.txt -CopyOnly -RemoveOnly
Do-TheFile: Parameter set cannot be resolved using the specified named parameters. One or more parameters issued cannot be used together or an insufficient number of parameters were provided.
Agree with the others here.
Your code works, as written when using IntelliSense, but PowerShell will not stop you from typing in other valid switches/variables/property names (in either the consolehost, ISE, VSCode, Visual Studio, etc...), that does not mean it would work just because you typed both.
Why make two switches, when you only want to use one option at a time, no matter what.
Just use a simple validation set.
Function Test-MyFunctionTest
{
[cmdletbinding()]
param
(
[Parameter(Mandatory = $true, Position = 0)]
[string[]]$Path,
[Parameter(Mandatory)][ValidateSet('CopyOnly', 'RemoveOnly')]
[string]$FileAction
)
}
# Results
<#
Test-MyFunctionTest -Path $PWD -FileAction CopyOnly
Test-MyFunctionTest -Path $PWD -FileAction RemoveOnly
#>
Otherwise, as you have discovered, you have to code this up yourself. For example:
Function Test-MyFunctionTestAgain
{
[cmdletbinding()]
param
(
[Parameter(Mandatory=$true, Position=0)]
[string[]]$Path,
[switch]$RemoveOnly
)
If($RemoveOnly.IsPresent)
{'Do the remove action'}
Else {'Do the copy action'}
}
Test-MyFunctionTestAgain -Path $PWD
# Results
<#
Do the copy action
#>
Test-MyFunctionTestAgain -Path $PWD -RemoveOnly
# Results
<#
Do the remove action
#>
Update
As for this...
"I agree that this could work. Although, the default operation (using
no switches) is to both Copy and Remove."
... then this...
Function Test-MyFunctionTestMore
{
[cmdletbinding()]
param
(
[Parameter(Mandatory = $true, Position = 0)]
[string[]]$Path,
[Parameter(Mandatory = $false)][ValidateSet('CopyAndRemove', 'CopyOnly', 'RemoveOnly')]
[string]$FileAction = 'CopyAndRemove'
)
Switch ($FileAction)
{
CopyAndRemove {'Do the copy and remove action'}
CopyOnly {'Do the copy only action'}
RemoveOnly {'Do the remove only action'}
}
}
Test-MyFunctionTestMore -Path $PWD
# Results
<#
Do the copy and remove action
#>
Test-MyFunctionTestMore -Path $PWD -FileAction CopyOnly
# Results
<#
Do the copy only action
#>
Test-MyFunctionTestMore -Path $PWD -FileAction RemoveOnly
# Results
<#
Do the remove only action
#>
Or this way, if you are really yearning just to have a switch ;-} ...
Function Test-MyFunctionTestSwitch
{
[cmdletbinding()]
param
(
[Parameter(Mandatory=$true, Position=0)]
[string[]]$Path,
[Parameter(Mandatory = $false)][ValidateSet('CopyAndRemove', 'CopyOnly', 'RemoveOnly')]
[string]$FileAction = 'CopyAndRemove',
[switch]$RemoveOnly
)
If($RemoveOnly.IsPresent)
{
$FileAction = 'RemoveOnly'
'Do the remove only action'
}
ElseIf ($FileAction -eq 'CopyOnly')
{'Do the copy only action'}
Else{'Do the copy and remove action'}
}
Test-MyFunctionTestSwitch -Path $PWD
# Results
<#
Do the copy and remove action
#>
Test-MyFunctionTestSwitch -Path $PWD -FileAction CopyOnly
# Results
<#
Do the copy only action
#>
Test-MyFunctionTestSwitch -Path $PWD -RemoveOnly
# Results
<#
Do the remove only action
#>
Lastly as a point of note:
Trying to emulate some other tools actions, or expecting PowerShell to natively emulate some other tools actions, params, etc., really should not be an expectation.
If you believe PowerShell should have a specific feature, then the option is to submit it to the PowerShell team, to have it upvoted by others for work/inclusion or since PowerShell is open-sourced, you can tool it up and submit it for review/approval of commit.
In contemporary versions of PowerShell, ParameterSets are mutually exclusive.
function greet {
Param(
[String]
$Name = "World",
[Parameter(ParameterSetName="intro")]
[Switch]
$Hello,
[Parameter(ParameterSetName="outro")]
[Switch]
$Farewell
)
if ($Hello) {
echo "Hello, $Name!"
} elseif ($Farewell) {
echo "Farewell, $Name!"
} else {
echo "What's up, $Name"
}
}
This results in the split-groupings that you often see in MS cmdlets:
> greet -?
NAME
greet
SYNTAX
greet [-Name <string>] [-Hello] [<CommonParameters>]
greet [-Name <string>] [-Farewell] [<CommonParameters>]
Doing this requires that the user or the script identify which ParameterSet should be used.
greet -Name "Bob"
> greet -Name "Bob"
greet: Parameter set cannot be resolved using the specified named parameters. One or more parameters issued cannot be used together or an insufficient number of parameters were provided.
This is trying to tell the user they weren't specific enough. See DefaultParameterSetName for how to set it from the script:
There is a limit of 32 parameter sets. When multiple parameter sets are defined, the DefaultParameterSetName keyword of the CmdletBinding attribute specifies the default parameter set. PowerShell uses the default parameter set when it can't determine the parameter set to use based on the information provided to the command.
#Postanote's answer is great and I will prefer it.
However, as #kfsone underlined, DefaultParameterSetName can achieve this with your two switches if you add a ParameterSetName for $Path only and set it as default :
[CmdletBinding(DefaultParameterSetName='CopyAndRemove')]
param (
[Parameter(Mandatory=$true, Position=0)]
[Parameter(ParameterSetName='CopyAndRemove')]
[Parameter(ParameterSetName='CopyOnly')]
[Parameter(ParameterSetName='RemoveOnly')]
[string[]]$Path,
[Parameter(Mandatory=$false, ParameterSetName='CopyOnly')]
[switch]$CopyOnly,
[Parameter(Mandatory=$false ,ParameterSetName='RemoveOnly')]
[switch]$RemoveOnly
)
$Path
$PSCmdlet.ParameterSetName
In a PowerShell script, I want to read a CSV file that contains something like this:
Type Title Param1 Param2
---- ----- ------ ------
Type1 Foo type 1 ValueForType1
Type2 Foo type 2 ValueForType2
When type is Type1, I have to call a function named New-FooType1, when type is Type2, the funcation is named New-FooType2, and so on:
function New-FooType1{
param(
[Parameter(Mandatory=$true, ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[string]$Title,
[Parameter(Mandatory=$true, ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[string]$Param1
)
Write-Host "New-FooType1 $Title with $Param1"
}
function New-FooType2{
param(
[Parameter(Mandatory=$true, ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[string]$Title,
[Parameter(Mandatory=$true, ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[string]$Param2
)
Write-Host "New-FooType2 $Title with $Param2"
}
I'm trying to route the call to either of the functions, using a dynamic invocation:
$csv | % {
$cmdName = "New-Foo$($_.Type)"
Invoke-Command (gcm $cmdName) -InputObject $_
}
However, I always get an error:
Parameter set cannot be resolved using the specified named parameters
As you can see, different type mean different parameters set.
How can I solve this? I would like to avoid manipulating properties manually, because in my real life script, I have a dozen of different types, with up to 6 parameters.
Here is a complete repro sample of the issue:
$csvData = "Type;Title;Param1;Param2`nType1;Foo type 1;ValueForType1;;`nType2;Foo type 2;;ValueForType2"
$csv = ConvertFrom-csv $csvData -Delimiter ';'
$csv | ft -AutoSize
function New-FooType1{
param(
[Parameter(Mandatory=$true, ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[string]$Title,
[Parameter(Mandatory=$true, ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[string]$Param1
)
Write-Host "New-FooType1 $Title with $Param1"
}
function New-FooType2{
param(
[Parameter(Mandatory=$true, ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[string]$Title,
[Parameter(Mandatory=$true, ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[string]$Param2
)
Write-Host "New-FooType2 $Title with $Param2"
}
$csv | % {
$cmdName = "New-Foo$($_.Type)"
Invoke-Command (gcm $cmdName) -InputObject $_
}
The expected output of this script is:
New-FooType1 Foo type 1 with ValueForType1
New-FooType2 Foo type 2 with ValueForType2
Use the call operator &:
$CmdName = "New-FooType1"
$Arguments = "type1"
& $CmdName $Arguments
the call operator also supports splatting if you want the arguments bound to specific named parameters:
$Arguments = #{
"title" = "type1"
}
& $CmdName #Arguments
To invoke command by name you should use invoke operator &. Invoke-Command cmdlet support only ScriptBlock and file invocation, and file invocation only supported for remote calls.
For dynamic parameter binding you can use spatting, but in that case you have to convert PSCustomObjects, returned by ConvertFrom-Csv cmdlet, to Hashtable. You also have to strip any extra parameters from Hashtable because splatting will fail if you try to bind non-existing parameter.
Another approach for dynamic parameter binding would be to use binding from pipeline object. It looks like it is what you want to do, since you mark all your parameters with ValueFromPipelineByPropertyName option. And this approach will just ignore any extra property it can not bind to parameter. I recommend you to remove ValueFromPipeline option, because with this option in case of absence of property with parameter name PowerShell will just convert PSCustomObject to string (or to whatever type you use for parameter) and pass it as value for parameter.
So, all you need is to pass object by pipeline and use invoke operator for invocation of command with dynamic name:
$_ | & "New-Foo$($_.Type)"
dont know exactly what your trying to do, but
Invoke-Command (gcm $cmdName) ?
Try invoke-expression $cmdname
For the following function, it must have a parameter of Group of type int or a parameter of Items of type int array. How to define the function? It must have one of these parameters but cannot have both.
Start-Execute -Group 1
Start-Execute -Items 100,200,300
You want to use Parameter Sets, which are a feature of Advanced Functions.
function Start-Execute {
[CmdletBinding()]
param(
[Parameter(
ParameterSetName='ByGroup',
Mandatory=$true
)]
[int]
$Group ,
[Parameter(
ParameterSetName='ByItems',
Mandatory=$true
)]
[int[]]
$Items
)
# function code
}
Inside the function, you can determine which parameter set was specified by testing the value of $PSCmdlet.ParameterSetName to see which parameter set it matches.
To see that your parameter sets were created correctly, run the following after the function definition has been executed:
Get-Help Start-Execute
That will show you separate invocations for each parameter set.
That being said, we can't tell what your function does. If $Items is just meant to be an array of multiple $Groups (that is, a single item is the same as a group), then your function should accept a single int array [int[]] and then just always process it with foreach, because that will work correctly even with a single value supplied.
Adding a parameter to multiple parameter sets.
You asked about adding a parameter called -Debug. I just want to point out that -Debug is a Common Parameter, so you probably shouldn't use that name. I'll show an example using a parameter named -Test:
function Start-Execute {
[CmdletBinding()]
param(
[Parameter(
ParameterSetName='ByGroup',
Mandatory=$true
)]
[int]
$Group ,
[Parameter(
ParameterSetName='ByItems',
Mandatory=$true
)]
[int[]]
$Items ,
[Switch]
$Test
)
)
# function code
}
This is one way to do it: don't provide any parameter set names. It will be available in all sets.
Another way is to provide a separate [Parameter()] attribute for each parameter set:
[Parameter(
ParameterSetName='ByItems',
Mandatory=$true
)]
[Parameter(
ParameterSetName='ByGroup',
Mandatory=$false
)]
[Switch]
$Test
This is useful when you want to use different settings for different sets, such as to make this parameter mandatory in one parameter set but optional in another, or to make the parameter available to multiple but not all sets.
I have this script that can be called in two ways:
MyScript -foo path\to\folder
or
MyScript -bar path\to\folder
(That is, I can either pass a switch plus a folder or a string argument plus a folder.)
I have tried to put parameter declarations into my script as to reflect that syntax:
param(
[parameter(Mandatory=$false)] [switch]$foo,
[parameter(Mandatory=$false)] [String]$bar,
[parameter(Mandatory=$true)] [System.IO.FileInfo]$path
)
But then I have to pass path explicitly to invoke the script:
MyScript -l -path path\to\folder
So (how) can I do that making both bar and path positional parameters?
Note: If I have picked an extraordinarily stupid syntax for invoking the script, I can still change it.
A couple of things: You need to use parameter sets to tell PowerShell that there are mutually exclusive ways to invoke your script; that is to say, you cannot use the switch and the string at the same time. The sets also serve to allow you to set the position of both $bar and $filepath to be at index 0. Switches don't need to be positionally placed as they are not ambiguous to the binder and be placed anywhere. Also, at least one parameter in each set should be mandatory.
function test-set {
[CmdletBinding(DefaultParameterSetName = "BarSet")]
param(
[parameter(
mandatory=$true,
parametersetname="FooSet"
)]
[switch]$Foo,
[parameter(
mandatory=$true,
position=0,
parametersetname="BarSet"
)]
[string]$Bar,
[parameter(
mandatory=$true,
position=1
)]
[io.fileinfo]$FilePath
)
#"
Parameterset is: {0}
Bar is: '{1}'
-Foo present: {2}
FilePath: {3}
"# -f $PSCmdlet.ParameterSetName, $bar, $foo.IsPresent, $FilePath
}
The CmdletBinding attribute is needed to specify which parameter set should be the default if the function is invoked without parameters.
Here's the syntax help for the above configuration:
PS> test-set -?
NAME
test-set
SYNTAX
test-set [-Bar] <string> [-FilePath] <FileInfo> [<CommonParameters>]
test-set [-FilePath] <FileInfo> -Foo [<CommonParameters>]
And here's the output for various invocations:
PS> test-set barval C:\temp\foo.zip
Parameterset is: BarSet
Bar is: 'barval'
-Foo present: False
FilePath: C:\temp\foo.zip
PS> test-set -foo c:\temp\foo.zip
Parameterset is: FooSet
Bar is: ''
-Foo present: True
FilePath: c:\temp\foo.zip
Hope this helps.
As explained here you can specify the parameter position of your parameter.
[parameter(Position=0)]