Get params by paramset? - powershell

In Powershell, you can assign params to paramsets. You can check which paramset using $PSCmdlet.ParameterSetName. Is there anything more intelligent you can do? Easily get all the params in that paramset that were passed?
I want to require that one and only one param in a paramset was passed. Wondering if there's a more elegant, built-in solution than checking and counting $PSBoundParams against a static list of keys.

Use $PSBoundParameters to find the parameters that are user-populated. It is a [HashTable] with a couple bonuses.
Ex.ps1:
[CmdletBinding()]
Param([Parameter(ParameterSetName='Custom1',Position=0)]
[String]$ex)
$PSBoundParameters.Keys
PS C:\>ex.ps1 Example!
> ex
Alternatively, you can use a Switch on $PSCmdlet.ParameterSetName to do things with the parameters.
Switch ($PSCmdlet.ParameterSetName)
{
'__AllParameterSets' { }
'Custom1' { }
}

Related

In powershell is there a difference between having param in a function or putting parameters in the function?

In powershell you can make functions with function name {commands} and make those functions take arguments with this:
function myFunction {
param($var1, $var2)
}
but you can also accomplish this with
function myFunction($var1, $var2) {}
and they would be the same.
For example, if I made a function func1 be:
function func1 {
param($var1, $var2)
echo "$var1 $var2"
}
I would call it by using func1 1 2 where $var1 would be equal to 1 and $var2 would be equal to 2.
Input:
PS C:\Users\Neko> func1 1 2
Output:
1 2
However, if I do the same thing but instead I did the other method of passing arguments to functions:
function func2($var1, $var2) {
echo "$var1 $var2"
}
I would also call it the same exact way, calling it by using func2 1 2 where $var1 would be equal to 1 and $var2 would be equal to 2 like the previous function.
Input:
PS C:\Users\Neko> func2 1 2
Output:
1 2
So everything seems the same and constant between the two renditions of the function, so my question is, is there a difference between the two methods of passing arguments to functions or are they both actually the same? Even if it is the most minor of details, or just a parsing difference, I would like to know any differences between the two in functions specifically since param has other uses as well.
UPDATE: The arguments you can do in param like [parameter(Mandatory=$true, ValueFromPipeline=$true)] and [String[]] are not unique to param. You can also accomplish this in the other 'non-param' example by doing:
function func2(
[parameter(Mandatory=$true, ValueFromPipeline=$true, etc)]
[String[]]
$var1, $var2
) {
echo "$var1 $var2"
}
To complement 7cc's helpful answer:
While the two syntax forms are mostly interchangeable when you define a function's parameters, only the param(...) block syntax works in the following circumstances:
If you want to use a [CmdletBinding()] attribute and its properties to (explicitly) make your function or script an advanced function or script.[1]
If you're writing a script file(*.ps1) or script block ({ ... }): the only way to declare parameters for them is is by placing a param(...) block at the beginning.
Therefore, you may opt to always use the param(...) block syntax, for consistency across function and script parameter definitions.
If a [CmdletBinding(...)]) attribute is used, it must directly precede the param(...) block.
As for:
I would call it by using func1(1)(2)
No, you would call it as follows:
func1 1 2
That is, PowerShell functions are called like shell commands: without parentheses, separated by whitespace; while your invocation happens to work too, the use of (...) around the arguments can change their interpretation:
without the enclosing (...) the arguments are parsed in argument mode, where, notably, strings needn't be quoted
with the enclosing (...), are parsed in expression mode, where strings do need to be quoted.
See this answer for more information.
[1] While you can place a [CmdletBinding(...)] attribute inside the parentheses with the function Foo (...) { ... } syntax without provoking an error, doing so is effectively ignored. Separately, in the absence of an (effective) explicit [CmdletBinding(...)] attribute, with either syntax, if you happen to decorate at least one parameter with a [Parameter()] attribute, you get the default behaviors of an advanced function (e.g., support for automatic common parameters such as -Verbose), because using [Parameter()] implicitly makes a function an advanced one (as if a [CmdletBinding()] attribute - without explicit property values - were in effect). However, if you need an explicit [CmdletBinding(...)] attribute, so as to opt into non-default advanced-function behaviors, via property values such as PositionalBinding=$false or SupportsShouldProcess=$true, use of a param(...) block is your only option.
One thing is that the CmdletBinding attribute requires Param
function Echo-Confirm
{
# Here
[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="High")]
Param ($val=1)
if ($PSCmdlet.ShouldProcess($val) -eq $true) {
Write-Output "Confirmed $val"
}
}
Edit after this comment
The syntax is fine, but CmdletBinding has no effect
Function foo (
[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="High")]
[Parameter()]$val=1
) {
# never confirm
if ($PSCmdlet.ShouldProcess($val) -eq $true) {
Write-Output "always here"
}
else {
Write-Output "never here"
}
}
foo -Confirm
# throws an error
foo: A parameter cannot be found that matches parameter name 'confirm'.
From About Functions - Functions with Parameters - Named Parameters:
You can define any number of named parameters. You can include a
default value for named parameters, as described later in this topic.
You can define parameters inside the braces using the Param keyword,
as shown in the following sample syntax:
function <name> {
param ([type]$parameter1[,[type]$parameter2])
<statement list>
}
You can also define parameters outside the braces without the Param
keyword, as shown in the following sample syntax:
function <name> [([type]$parameter1[,[type]$parameter2])] {
<statement list>
}
…
While the first method is preferred, there is no difference between
these two methods.
Edit
#mklement0 thanks for helpful explication of the latter (emphasized) statement.
This statement (there is no difference between these two methods) is valid despite of 7cc's improper guesswork.
7cc's answer is right, explained in mklement0's comments below and his updated answer.
In About Functions Advanced Parameters => Attributes of parameters, there are some allusions to the relation of CmdletBinding and Parameter attributes and advanced functions (advanced functions use the CmdletBinding attribute to identify them as functions that act similar to cmdlets):
… if you omit the CmdletBinding attribute, then to be recognized
as an advanced function, the function must include the Parameter
attribute…
… to be recognized as an advanced function, rather than a simple
function, a function must have either the CmdletBinding attribute
or the Parameter attribute, or both.
I can't comprehend PowerShell inventors' motivation for such (confusing for me) design…

PowerShell parameter sets or dynamic parameters

I'm trying to figure out which method would work best for the following situation.
Example function:
Set-APICredentials {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$APIUser,
[Parameter(Mandatory)]
[string]$APIKey,
[Parameter(Mandatory)]
[string]$PFXFile,
[Parameter(Mandatory)]
[string]$PFXPassword,
[switch]$AsVariable
)
begin{
$PFXPath = (Get-ChildItem -Name $PFXFile).FullName
}
process{
#create basic auth header from APIUser and APIKey
$basicAuthHeader
#create certificate object, verify private key, convert back into PFX Collection as bytes string variable
$clientAuthCertRaw
#create hashtable with credentials
$credentials = #{
basicAuthHeader = $basicAuthHeader
clienAuthCertRaw = $clientAuthCertRaw
}
}
end{
if ($AsVariable) {
Sglobal:APICreds = $credentials
} else {
Export-Clixml -InputObject $credentials -Path $PSScriptRoot\APICredentials.xml
}
}
}
If (Test-Path -Path $PSScriptRoot\APICredentials.xml) is true and -AsVariable is specified then no other parameters are needed/used.
Otherwise if (Test-Path -Path $PSScriptRoot\APICredentials.xml) is false then everything previously stated as mandatory are required.
Is there some way to create a conditional parameter set?
Should I just create two parameter sets and error out if the previously stated logic is false? Or should I set -AsVariable as a parameter and handle the rest with dynamic parameters?
Because in most cases everything is mandatory and its only under special circumstances that -AsVariable is used on its own. I figured that configuring everything else as dynamic parameters would be wrong.
Dynamic Parameters are the strict way to handle exactly what you want to do, but I don't think they're worth the effort in most cases.
The most straightforward way to handle this is not to have the string params mandatory and just do the check at the beginning of the function.
You have a process block but the example params don't take pipeline input. If the function is taking pipeline input consider where you put that check; probably in the begin block but it will depend on the params maybe?
I was also toying around with abusing [ValidateScript({})] for this, but it's not quite going to work, because if you add it on the [switch], you can't access/check the other params to check their values, and if you put it on the conditionally mandatory values (to check for the existence of the file) it will only run the validation when the parameter is bound.
This also feels a little bit like a code smell? Curious about your use case.

Can I use a constant in the ValidateSet attribute of a PowerShell function parameter?

I am using the ValidateSet attribute on one of my PowerShell function parameters like so:
[ValidateSet('Development','Test','Production')]
[string]$Context
I have repeated this is many places throughout a scripting project. Can these literal strings be replaced with a constant?
No, it has to be a literal or a scriptblock. The scriptblock option seems pointless since it seems to use the literal (string) value of the scriptblock instead of executing it.
So effectively, from my testing, you must use literals.
If you use a dynamic parameter instead you could achieve this, but that's way overkill just to be DRY.
If you try to use a variable, it won't work (and ISE will give you the red squiggly). The help text erroneously says it must be a constant, but it means literal.
I created a constant with:
Set-Variable -Option Constant
And it still does not work.
Adding this to help others searching for a similar solution. I was looking for a way to validate parameters against the keys of a global hash table. This is what I ended up doing:
$global:MyHash = #{
"anyitem" = #{"name" = "somename1"; "count" = 42 };
"someitem" = #{"name" = "another name"; "count" = 1337 };
}
function Test-Hash
{
param
(
[Parameter(mandatory = $true)] [ValidateScript( { $_ -in $global:MyHash.Keys } )] [string[]] $items
)
}
Test-Hash -items anyitem, someitem
I ended up replacing ValidateSet with ValidateScript as I realized (as mentioned in this thread as well) that the code block in ValidateSet does not work at all. Instead validating against the keys of a hash table one could easily use something like
$validParams = #('opt1', 'opt2')
and in the ValidateScript codeblock
{ $_ -in $validParams }
This is basically what I assume should answer the question.

Default value of mandatory parameter in PowerShell

Is it possible to set a default value on a mandatory parameter in a function?
It works without having it set as an mandatory ...
Ie.
Function Get-Hello {
[CmdletBinding()]
Param([Parameter(Mandatory=$true)]
[String]$Text = $Script:Text
)
BEGIN {
}
PROCESS {
Write-Host "$Script:Text"
Write-Host "$Text"
}
END {
}
}
$Text = "hello!"
Get-Hello
Reason for asking this is because i have a function that has some required parameters and the function works perfect with calling it with the required parameters but i also want to make it possible for these variables to be defined in the scripts that use this function in a "better presentable & editable" way along with the function to be able to be run with defining the required parameters.
Hence if defined in the script scope it should take that as default else it should prompty for the value.
Thanks in Advance,
If you targeting to PowerShell V3+, then you can use $PSDefaultParameterValues preferences variable:
$PSDefaultParameterValues['Get-Hello:Text']={
if(Test-Path Variable::Script:Text){
# Checking that variable exists, so we does not return $null, or produce error in strict mode.
$Script:Text
}
}

Powershell param declaration alternatives

What's the difference between:
function foo ([int] i, [string] s) { ... }
And:
function foo {
param (
[int] i,
[string] s
)
{ ... }
}
The help on MSDN does not explain this. Thanks!
Except for convenience, there's no difference. Both ways are valid. Using param (in my opinion) is more readable especially in advanced functions where a parameter declaration may contain a few lines of code and you can use indentation and line breaks.
Param statements are needed in scripts and scriptblocks, where there's not a natural place to define parameters like there would be in a function. Manojlds is correct that you have a lot of opportunities to use advanced function "options" to supercharge your function parameters if you use a param statement that you can't use in the more traditional parameter list.
No difference in this case. You might need to use the param declaration for advanced function parameters