Multiple conditions on parameterset - powershell

In a function, I have 2 [String] parameters, $DBServer and $ServiceServer
Both should be allowed to be blank or filled, but nothing inbetween.
Is this possible to do with parametersets?
Or should I include a check in my PowerShell function which makes sure that my conditions are made?

Yes, you do this with Parameter Sets. So you'd have 2 sets. One in which both parameters are Mandatory, and one in which both parameters are absent.
function Ruin-Database {
[CmdletBinding(DefaultParameterSetName='Blank')]
param(
[Parameter(
ParameterSetName='Filled',
Mandatory=$true
)]
[ValidateNotNullOrEmpty()]
[String]
$DBServer ,
[Parameter(
ParameterSetName='Filled',
Mandatory=$true
)]
[ValidateNotNullOrEmpty()]
[String]
$ServiceServer
)
If ($PSCmdlet.ParameterSetName -eq 'Blank') {
Write-Verbose 'Blank' -Verbose
} else {
Write-Verbose 'Filled' -Verbose
}
}
Once you define the function this way, look at the help to see both parameter sets:
Get-Help Ruin-Database
Explanation
If a parameter specifies one or more parameter sets, then it will only be included in those sets. If it doesn't specify, it will be included in all sets.
PowerShell must be able to unambiguously resolve the set if you want it to work, so sometimes it takes some trial and error.
One thing you can do to help this is to specify a DefaultParameterSetName in the CmdletBinding attribute. But you can also specify a parameter set name there that you haven't used in any other parameter. That means the default set would only include parameters that don't specify any set (are available in all), even if there are none of those.
Running help on a function/cmdlet is a good way to see what sets PowerShell is interpreting, as it can get complex.
You can specify multiple [Parameter()] attributes on a single parameter to define different characteristics in different sets, for example, a parameter may be mandatory in one set, but optional in another. It may be specified via pipeline in one set, but not in another.

Related

Single parameter argument with position equal to 1 or no argument value?

I have stumbled upon a function with single parameter, and there are 2 things I do not understand simply because documentation does not mention them.
Here is a function:
function Some-Function
{
[CmdletBinding()]
param (
[Parameter(Mandatory,
Position = 1, ValueFromPipeline)]
[string] $Input
)
}
Question 1:
Since this is single parameter function, why is Position set to 1? what does that mean? I don't see any point here, what is wrong with the default value of Position = 0?
Question 2:
ValueFromPipeline and Mandatory arguments are not set to any value ie, = $true or = $false
what is the default value of these arguments if not set to value?
I can't find anywhere in the docs to explain this, and none of the examples on docs are like this one.
Since this is single parameter function, why is Position set to 1?
There is no strict need to set a Position property at all in this case.
While it is a good convention to start Position properties with 0, it isn't technically necessary - all that matters is the relative ordering of all parameters with Position attributes.
In the absence of any Position properties, it is the order of parameter declarations (other than [switch] parameters) that implicitly defines their positional ordering, EXCEPT if you use [CmdletBinding(PositionalBinding=$false)], in which case only explicit Position properties matter.
Note: The presence of [CmdletBinding(PositionalBinding=$false)] or at least one [Parameter(Position=...)] attribute implicitly switch the default to non-positional: that is, all parameters that do not explicitly specify a Position value become non-positional, meaning they can only be passed arguments if preceded by the parameter's name (e.g., -foo bar instead of just bar).
ValueFromPipeline and Mandatory arguments are not set to any value
In PowerShell v3+, omitting an attribute's property value defaults to $true, in the interest of brevity.

Allowing extra parameters on a Powershell advanced function

Environmental note: I'm currently targetting PowerShell 5.1 because 6 has unrelated limitations I can't work around yet.
In the Powershell module I'm writing, there is one main function that's sort of a conglomeration of a bunch of the smaller functions. The main function has a superset of the smaller function's parameters. The idea is that calling the main function will call each smaller function with the necessary parameters specified on the main. So for example:
function Main { [CmdletBinding()] param($A,$B,$C,$D)
Sub1 -A $A -B $B
Sub2 -C $C -D $D
}
function Sub1 { [CmdletBinding()] param($A,$B)
"$A $B"
}
function Sub2 { [CmdletBinding()] param($C,$D)
"$C $D"
}
Explicitly specifying the sub-function parameters is both tedious and error prone particularly with things like [switch] parameters. So I wanted to use splatting to make things easier. Instead of specifying each parameter on the sub-function, I'll just splat $PSBoundParameters from the parent onto each sub-function like this:
function Main { [CmdletBinding()] param($A,$B,$C,$D)
Sub1 #PSBoundParameters
Sub2 #PSBoundParameters
}
The immediate problem with doing this is that the sub-functions then start throwing an error for any parameter they don't have defined such as, "Sub1 : A parameter cannot be found that matches parameter name 'C'." If I remove the [CmdletBinding()] declaration, things work but I lose all the benefits of those subs being advanced functions.
So my current workaround is to add and additional parameter on each sub-function that uses the ValueFromRemainingArguments parameter attribute like this:
function Sub1 { [CmdletBinding()]
param($A,$B,[Parameter(ValueFromRemainingArguments)]$Extra)
"$A $B"
}
function Sub2 { [CmdletBinding()]
param($C,$D,[Parameter(ValueFromRemainingArguments)]$Extra)
"$C $D"
}
Technically, this works well enough. The sub-functions get their specific params and the extras just get ignored. If I was writing this just for me, I'd move on with my life and be done with it.
But for a module intended for public consumption, there's an annoyance factor with that -Extra parameter being there. Primarily, it shows up in Get-Help output which means I have to document it even if just to say, "Ignore this."
Is there an extra step I can take to make that extra parameter effectively invisible to end users? Or am I going about this all wrong and there's a better way to allow for extra parameters on an advanced function?
My usual approach is to export only "wrapper" functions that call internal (i.e., not user-facing) functions in the module.

Is there some way to define a parameterless powershell parameter set?

I've got some fairly complex functions that I'm writing for a library module, with lots of different ways it can be called. However, it is in fact possible to default all of them, but when I try to call my function with no parameters the call fails because the parameter set cannot be determined.
I would like to define a parameter set that contains no parameters whatsoever, such that calls with no parameters succeed. This is difficult to do since ParameterSetName is a property of the Parameter attribute though, and it's not possible to attribute nothing.
I experimented with the DefaultParameterSet property of the CmdletBinding attribute that is placed on the param block, however nothing seemed to work. It seems that the parameter set name defined there must actually exist in order for Powershell to default to it.
Currently my best approximation of this use case is to define one of the parameter sets to have no Mandatory parameters, however these fail when empty strings or nulls are piped in, and I would like for this not to be the case.
Is this possible?
Sure is. Just specify a default parameter set name that isn't used otherwise:
function Foo {
[CmdletBinding(DefaultParameterSetName='x')]
Param(
[Parameter(Mandatory=$true, ParameterSetName='y')]$a,
[Parameter(Mandatory=$false, ParameterSetName='y')]$b,
[Parameter(Mandatory=$true, ParameterSetName='z')]$c,
[Parameter(Mandatory=$false)]$d
)
"`$a: $a"
"`$b: $b"
"`$c: $c"
"`$d: $d"
}

How does ValidateSet stop a variable being changed to a non-set value?

I've just noticed that if you are using ValidateSet for a parameter variable in a function you cannot within that function change the parameter variable to a value that is not in the set.
Here is a simple example to demonstrate:
Function Test {
[cmdletbinding()]
Param(
[ValidateSet(1,2,3)]
[int]$Number
)
$Number = 4
}
Test 3
Returns:
The variable cannot be validated because the value 4 is not a valid value for the Number variable.
I've used Get-Member to explore $Number and I can't see any indication of how (or why) it restricts the variable like this. I assume it's some sort of custom object or strong typing but the variable looks to be a System.Int32. Does anyone know how/why this happens?
This isn't unique to Int variables, this is just a simple example. I have found the same true for a String Array parameter.
I discovered from this related question: Find the values in ValidateSet that if I do the following inside my function:
(Get-Variable 'Number').Attributes.ValidValues
this lists the defined ValidateSet values. I therefore assume setting this attribute is how ValidateSet works, with the side effect being that it is then in effect throughout the life of the variable.

not using [parameter()] on some parameters in a PowerShell advanced function

If you are doing advanced functions and have the parameters decorated [parameter()], would there be any reason to NOT decorate a parameter with [Parameter()] . I've seen this a few times and don't know whether its just a case of laziness, oversight, or purposeful design.
Laziness I think but to be fair, it isn't needed if you aren't using any special parameter attibutes like Mandatory, Position, etc. As long is one parameter is marked [Parameter(...)] or the param block is marked [CmdletBinding(...)] PowerShell will interpret that function as an advanced function. OTOH there is no harm in adding the empty [Parameter()] to parameters.