What is the right way to check a switch parameter - powershell

What is the right way to check value of a switch?
function testSwitch
{
Param(
[switch] $swth
)
Write-Host "Value of swth is $swth"
if($swth.IsPresent){
Write-host "Switch is present"
}
if($swth){
Write-Host "Switch is present"
}
}
testSwitch -swth
I know both if statement works fine, but how?

$swth.IsPresent and $swth can be used interchangeably, because in a Boolean context (such as an if conditional) $swth (an instance of type [switch] representing a switch parameter) effectively returns the value of the .IsPresent property.[1]
In fact, $swth is probably preferable[2], not just for concision, but because the .IsPresent property is somewhat confusingly named:
.IsPresent doesn't indicate the presence of the switch per se, but whether its value is $true.
While specifying a switch by itself - -swth - implies both, the same isn't true if you explicitly set it to $false: Passing -swth:$false makes .IsPresent return $false, even though the switch is clearly present.
Passing $false explicitly isn't common, but has its uses, such as when opting out of a confirmation prompt with -Confirm:$false, and when programmatically constructing arguments.
Therefore, if you want to distinguish between the user not passing the switch and the user passing it with value $false, .IsPresent won't help you - you'll have to use $PSBoundParameters.ContainsKey('swth')
[1] On conversion to Boolean, which happens in method LanguagePrimitives.IsTrue(), PowerShell calls a [switch] instance's .ToBool() method, which in turn returns the private backing variable behind the .IsPresent property.
[2] The only caveat is that you must be aware that $swth is not a Boolean ([bool]), but of type [switch], which may matter in other contexts.

It would be more simple to just use an if/else clause:
Function Get-FruitDetails {
[CmdLetBinding()]
Param (
[String]$Fruit,
[Switch]$Yellow
)
Write-Verbose "Get fruit details of $Fruit"
$Result = [PSCustomObject]#{
Fruit = $Fruit
Yellow = $null
}
if ($Yellow) {
Write-Verbose 'Color is yellow'
$Result.Yellow = $true
}
else {
Write-Verbose 'Color is not yellow'
$Result.Yellow = $false
}
$Result
}
Get-FruitDetails -Fruit 'Banana' -Yellow -Verbose
Get-FruitDetails -Fruit 'Kiwi' -Verbose
This will output the following:
Fruit Yellow
----- ------
Banana True
Kiwi False
Some tips:
Avoid using Write-Host, it's meant for console programs. You're better of using Write-Verbose and adding the -Verbose switch if you want to see the messages
Try to use PowerShell approved verbs for function names. These can be easily found using Get-Verb. It will make it easier for others to see what your function does Get, Set, Remove, ...

Related

Use function parameter as both a variable and switch [duplicate]

I have the powershell function below
Function Test
{
Param
(
[Parameter()]
[string]$Text = "default text"
)
Write-Host "Text : $($Text)"
}
And I would like to be able to call this function like below :
Test -Text : should display the default text on the host
Test -Text "another text" : should display the provided text on the host
My issue is that the first syntax is not allowed in powershell ..
Any ideas of how I can achieve this goal ?
I would like a kind of 'switch' parameter that can take values other than boolean.
Thanks
The problem you're running into is with parameter binding. PowerShell is seeing [string] $Text and expecting a value. You can work around this like so:
function Test {
param(
[switch]
$Text,
[Parameter(
DontShow = $true,
ValueFromRemainingArguments = $true
)]
[string]
$value
)
if ($Text.IsPresent -and [string]::IsNullOrWhiteSpace($value)) {
Write-Host 'Text : <default text here>'
}
elseif ($Text.IsPresent) {
Write-Host "Text : $value"
}
}
Note: this is a hacky solution and you should just have a default when parameters aren't passed.
tl;dr
PowerShell does not support parameters with optional values.
A workaround is possible, but only for a single parameter.
Maximilian Burszley's helpful answer provides a workaround for a single parameter, via a catch-all parameter that collects all positionally passed arguments via the ValueFromRemainingArguments parameter property.
Fundamentally, though, what you're asking for is unsupported in PowerShell:
PowerShell has no support for parameters with optional values as of 7.2 - except for [switch] parameters, which are limited to [bool] values.
That is:
Any parameter you declare with a type other than [switch] invariably requires a value (argument).
The only other option is to indiscriminately collect any unbound positional arguments in a ValueFromRemainingArguments-tagged parameter, but you won't be able to associate these with any particular other bound parameter.
In other words:
If you happen to need just one optional-argument parameter, the ValueFromRemainingArguments can work for you (except that you should manually handle the case of mistakenly receiving multiple values), as shown in Maximilian Burszley's answer.
If you have two or more such parameters, the approach becomes impractical: you'd have to know in which order the parameters were passed (which PowerShell doesn't tell you) in order to associate the remaining positional arguments with the right parameters.
With [switch] parameters (using an imagined -Quiet switch as an example):
The default value - if you just pass -Quiet -is $true.
$false is typically indicated by simply not specifying the switch at all (that is, omitting -Quiet)
However, you may specify a value explicitly by following the switch name with :, followed by the Boolean value:
-Quiet:$true is the same as just -Quiet
-Quiet:$false is typically the same as omitting -Quiet; in rare cases, though, commands distinguish between an omitted switch and one with an explicit $false value; notably, the common -Confirm parameter allows use of -Confirm:$false - as opposed to omission of -Confirm - to override the value of the $ConfirmPreference preference variable.
While : as the separator between the parameter name and its argument (as opposed to the usual space char.) is supported with all parameters, with [switch] parameters it is a must so as to unequivocally signal that what follows is an argument for the switch parameter (which by default needs no argument) rather than an independent, positional argument.
The above tells us that PowerShell already has the syntax for general support of optional-argument parameters, so at some point in the future it could support them with any data type, as suggested in GitHub issue #12104.
I like #Maximilian Burszley's answer (and his name!) for String, I tweaked it for Ints:
function Optional-SwitchValue {
[CmdletBinding()]
param (
[Switch]
$Bump,
[Int]
$BumpAmount
)
Begin {
# nifty pattern lifted from https://stackoverflow.com/questions/58838941/powershell-special-switch-parameter
# default Bump to 1
if ($Bump.IsPresent -and -not $BumpAmount) {
$BumpAmount = 1
}
}
Process {
if($Bump) {
#use $BumpAmount in some way
}
}
}

Powershell "special" switch parameter

I have the powershell function below
Function Test
{
Param
(
[Parameter()]
[string]$Text = "default text"
)
Write-Host "Text : $($Text)"
}
And I would like to be able to call this function like below :
Test -Text : should display the default text on the host
Test -Text "another text" : should display the provided text on the host
My issue is that the first syntax is not allowed in powershell ..
Any ideas of how I can achieve this goal ?
I would like a kind of 'switch' parameter that can take values other than boolean.
Thanks
The problem you're running into is with parameter binding. PowerShell is seeing [string] $Text and expecting a value. You can work around this like so:
function Test {
param(
[switch]
$Text,
[Parameter(
DontShow = $true,
ValueFromRemainingArguments = $true
)]
[string]
$value
)
if ($Text.IsPresent -and [string]::IsNullOrWhiteSpace($value)) {
Write-Host 'Text : <default text here>'
}
elseif ($Text.IsPresent) {
Write-Host "Text : $value"
}
}
Note: this is a hacky solution and you should just have a default when parameters aren't passed.
tl;dr
PowerShell does not support parameters with optional values.
A workaround is possible, but only for a single parameter.
Maximilian Burszley's helpful answer provides a workaround for a single parameter, via a catch-all parameter that collects all positionally passed arguments via the ValueFromRemainingArguments parameter property.
Fundamentally, though, what you're asking for is unsupported in PowerShell:
PowerShell has no support for parameters with optional values as of 7.2 - except for [switch] parameters, which are limited to [bool] values.
That is:
Any parameter you declare with a type other than [switch] invariably requires a value (argument).
The only other option is to indiscriminately collect any unbound positional arguments in a ValueFromRemainingArguments-tagged parameter, but you won't be able to associate these with any particular other bound parameter.
In other words:
If you happen to need just one optional-argument parameter, the ValueFromRemainingArguments can work for you (except that you should manually handle the case of mistakenly receiving multiple values), as shown in Maximilian Burszley's answer.
If you have two or more such parameters, the approach becomes impractical: you'd have to know in which order the parameters were passed (which PowerShell doesn't tell you) in order to associate the remaining positional arguments with the right parameters.
With [switch] parameters (using an imagined -Quiet switch as an example):
The default value - if you just pass -Quiet -is $true.
$false is typically indicated by simply not specifying the switch at all (that is, omitting -Quiet)
However, you may specify a value explicitly by following the switch name with :, followed by the Boolean value:
-Quiet:$true is the same as just -Quiet
-Quiet:$false is typically the same as omitting -Quiet; in rare cases, though, commands distinguish between an omitted switch and one with an explicit $false value; notably, the common -Confirm parameter allows use of -Confirm:$false - as opposed to omission of -Confirm - to override the value of the $ConfirmPreference preference variable.
While : as the separator between the parameter name and its argument (as opposed to the usual space char.) is supported with all parameters, with [switch] parameters it is a must so as to unequivocally signal that what follows is an argument for the switch parameter (which by default needs no argument) rather than an independent, positional argument.
The above tells us that PowerShell already has the syntax for general support of optional-argument parameters, so at some point in the future it could support them with any data type, as suggested in GitHub issue #12104.
I like #Maximilian Burszley's answer (and his name!) for String, I tweaked it for Ints:
function Optional-SwitchValue {
[CmdletBinding()]
param (
[Switch]
$Bump,
[Int]
$BumpAmount
)
Begin {
# nifty pattern lifted from https://stackoverflow.com/questions/58838941/powershell-special-switch-parameter
# default Bump to 1
if ($Bump.IsPresent -and -not $BumpAmount) {
$BumpAmount = 1
}
}
Process {
if($Bump) {
#use $BumpAmount in some way
}
}
}

Why does my mandatory hashtable parameter not take my value

I have a function with parameters like this:
function foo {
Param(
[Parameter(Mandatory)]
[string]$foo,
[Parameter(Mandatory)]
[hashtable]$bar
)
}
if i call it like this everything works fine:
foo -foo "abc" -bar #{"a"="b"}
but if I only call foo and PowerShell asks me to specify values for my mandatory parameters, it won't accept my hashtable.
PS C:\Users\abc> foo
cmdlet foo at command pipeline position 1
Supply values for the following parameters:
foo: "abc"
bar: #{"a"="b"}
Cannot convert the "#{"a"="b"}" value of type "System.String" to type "System.Collections.Hashtable".
why does PowerShell not take my specified value?
I also tried to write it different ways like #{a=b}, "a"="b", #{a=b;}, #{"a"="b";} etc.
When you mark a parameter as Mandatory and don't specify a value (argument) for it on invocation, PowerShell automatically prompts you for a value.
Unfortunately, this automatic prompting mechanism for missing mandatory parameter values is broken (it has never worked, and still doesn't as of PowerShell v7):
You currently cannot interactively supply a value for any of the following types:
[hashtable],[scriptblock], [bool], [switch] (the latter two being uncommon as mandatory parameters) - their literal forms #{ ... }, { ... } and $true / $false are simply not recognized.
There's also no way to interactively specify $null.
Additionally, the automatic prompts, when they do work, are inconvenient (no tab completion, no re-prompting on invalid input), and therefore currently of little use.
These problems are being discussed in this GitHub issue.
Given the above, it's better to bypass the automatic prompts altogether and instead throw an error when no value was provided:
function foo {
Param(
# Use a default value that throws an error.
[string] $foo = $(Throw "Please pass a value for -foo."),
# Use a default value that throws an error.
[hashtable] $bar = $(Throw "Please pass a value for -bar.")
)
}
Note that [Parameter(Mandatory)] then mustn't be used, as that would still trigger an automatic prompt, despite the presence of a default value.
Read-Host (which is invoked automatically when this happens) always returns strings. If you want to take hashtable input like that, you will need to process the input. I would do something like:
if ($foo -is [system.Collections.hashtable]) {
//proceed
} else if ($foo -is [system.array]) {
Foreach ($fooItem in $foo) {
$myhash.add($key, $fooItem)
}
} else if ($foo -is [system.string]) {
$fooItem = $foo.split(,)
...
} else {
//error
}

Why does [NullString]::Value evaluate differently with a breakpoint?

I've tried this code in PowerShell ISE and VS Code with the same strange result. Without a breakpoint, the output is EMPTY, but with a breakpoint in the line with "NULL", the output is NULL (as expected). Why?
function demo {
param(
[string] $value = [NullString]::Value
)
if ($null -eq $value) {
"NULL"
} elseif ($value -eq '') {
"EMPTY"
} else {
"$value"
}
}
demo
I know now, that PowerShell will always convert a non-string value (e.g. $null or [NullString]::Value) to an (empty) string, when you use the type modifier [string] for a parameter.
Fine, I can live with that, but it's hard to figure that out by yourself, if debugging is so weird in this case.
PetSerAl, as many times before, has provided the crucial pointer in a comment on the question:
A known optimization bug is the most likely cause (as of Windows PowerShell v5.1 / PowerShell Core v6.1.0), and that bug just happens to be masked when the code is run in the Visual Studio Code or ISE debugger.
You can therefore use the same workaround mentioned in the linked bug report: place a call to Remove-Variable anywhere in the function body (its presence is enough - the call needn't actually be made at runtime):
function demo {
param(
[string] $value = [NullString]::Value
)
# Workaround for optimization bug
if ($False) { Remove-Variable }
if ($null -eq $value) {
"NULL"
} elseif ($value -eq '') {
"EMPTY"
} else {
"$value"
}
}
demo
Now you consistently get "NULL" as the output, whether debugging or not.
However, it's best to restrict use of [NullString]::Value to what it was designed for: passing null to string-typed parameters of .NET methods - see below.
As for why use of [NullString]::Value is needed at all in order to pass $null to a string parameter / store $null in a [string] variable, given that .NET strings can normally store null ($null) directly:
By (historical) design, PowerShell converts $null to '' (the empty string) when you assign it to a [string] variable; here's the rationale:
From https://github.com/PowerShell/PowerShell/issues/4616#issuecomment-323530442:
The thinking behind the design was that in most ways, $null and the empty string both represent the same error condition and that in the rare case where a distinction was important, $PSBoundParameters would be sufficient to distinguish between knowing a value was provided or not.
Given that even passing $null directly performs conversion to '' when passing arguments to string-typed .NET methods, you couldn't pass null to such methods up to v2.
To remedy that, version 3 introduced [NullString]::Value, which explicitly signals the desire to pass $null in a string context.
(The alternative - making PowerShell strings default to $null and allowing direct assignment of $null - was considered a change that would break too many existing scripts.)
Using [NullString]::Value beyond its intended purpose - i.e., for passing null to string parameters in .NET methods - is problematic, given that PowerShell doesn't expect [string] variables to contain $null in other contexts.
Fixing the above-mentioned optimization bug would help in the scenario in the question, but there may be other pitfalls.
Difference between $null and ""
Empty string is not the same as null; you'd need to test specifically
for that. Null is the non-existence of an object, Whereas a string is
an object even if it's empty.
That's a common gotcha in PowerShell. For whatever reason, it won't
let you assign a $null value to a string variable (or a string
parameter to a .NET type); it gets converted to an empty string. This
can cause some headaches when calling .NET methods that treat null and
empty strings differently, which is why they later added (in v3, if I
remember correctly), the [System.Management.Automation.NullString]
class. If you want to call such a method, you do this:
[SomeClass]::SomeMethod([nullstring]::Value)
[string]$camp = $null
Will assign $Null to $Camp, but since the [string]-part forces $camp
to be of the type string, $Camp will be assigned the value of
[String]$Null
[String]$Null will force a conversion of $Null (which is basically a
non-existing object) to a string and in PowerShell that results in an
empty string.
As far as PowerShell is concerned, that's basically correct. However,
in the .NET Framework, strings really can be null, and there's a
difference between a null string reference and an empty string.
Sometimes that causes confusion when you're looking at .NET
documentation and wondering why it doesn't seem to work properly from
PowerShell.
Use [string]::IsNullOrEmpty($variable)
https://powershell.org/forums/topic/difference-between-null-and/
See also...
How can I check if a string is null or empty in PowerShell?
[string]::IsNullOrEmpty(...)
How can I check if a string is null or empty in PowerShell?
* Update *
Changing your code to this...
function demo {
param(
[string]$value
)
if ([string]::IsNullOrEmpty($value))
{
"NULL"
} elseif ($value -eq '') {
"EMPTY"
} else {
"$value"
}
}
With or without debug (breakpoint set on 'NULL' and 'EMPTY') effort, the results returned are the same on my system ISE/VSCode
# Results
demo
NULL
demo -value ''
NULL
demo -value ' '
demo -value test
test
* Modification to show the elseif handling whitespace *
With or with the conditions previously stated
function demo {
param(
[string]$value
)
if ([string]::IsNullOrEmpty($value))
{
"NULL"
} elseif ([string]::IsNullOrWhiteSpace($value)) {
"EMPTY or a White Space"
} else {
"$value"
}
}
# Results
demo
NULL
demo -value ''
NULL
demo -value ' '
EMPTY or a White Space
demo -value test
test

How to use a default value with parameter sets in PowerShell?

I have this function
Function Do-ThisOrThat
{
[cmdletbinding()]
param (
[Parameter(Mandatory = $false, ParameterSetName="First")]
[string]$FirstParam = "MyFirstParam",
[Parameter(Mandatory = $false, ParameterSetName="Second")]
[string]$SecondParam
)
Write-Output "Firstparam: $FirstParam. SecondParam $SecondParam"
}
If I call the function like this Do-ThisOrThat I want it to detect that the $FirstParam has a default value and use that. Is it possible? Running it as is only works if I specify a parameter.
e.g. this works: Do-ThisOrThat -FirstParam "Hello"
You need to tell PowerShell which of your parameter sets is the default one:
[cmdletbinding(DefaultParameterSetName="First")]
This will allow you to invoke Do-ThisOrThat without any parameters as well as with a -SecondParameter value, and $FirstParam will have its default value in both cases.
Note, however, that based on how your parameter sets are defined, if you do specify an argument, you can't do so positionally - you must use the parameter name (-FirstParam or -SecondParam).
This question is a bit ambiguous.
If you want to set the default parameter set, use the DefaultParameterSetName property in the [CmdletBinding()] attribute:
[CmdletBinding(DefaultParameterSetName='First')]
If, on the other hand you want to detect and infer whether $FirstParam has its default value inside the script, you can check which parameter set has been determined and whether FirstParam parameter was specified by the caller, like this:
if($PSCmdlet.ParameterSetName -eq 'First' -and -not $PSBoundParameters.ContainsKey('FirstParam')){
<# $FirstParam has default value #>
}