Powershell not passing Parameter to If Statement - powershell

I have this
Param (
[Parameter(Mandatory=$true)]
[ValidateSet('Yes','No')]
[string]$ContinueSetup,
[Parameter(Mandatory=$true)]
[Validateset('yes', 'no')]
[string]$InstallDropbox = 'Yes',
[Parameter(Mandatory=$true)]
[Validateset('yes', 'no')]
[string]$InstallSlack,
[Parameter(Mandatory=$true)]
[Validateset('yes', 'no')]
[string]$InstallOffice
)
if ($ContinueSetup -eq 'yes'){
if ($InstallDropbox = 'yes'){
write-host 'install dropbox'
}
else
{write-host 'dropbox not selected'}
if ($InstallSlack = 'yes'){
write-host 'install slack'
}
else
{write-host 'slack not selected'}
if ($InstallOffice = 'yes'){
write-host 'install office'
}
else
{write-host 'Office not selected'}
}
if ($continuesetup -eq 'no') {write-host 'no setup'; break}
It asks for my parameters as I want, but doesn't pass the parameters on, instead it just sets them all to 'yes'. Are parameters inherent? How should I set this so that it stops at each if statements, checks the parameter and does one of two actions, yes/no, and then moves onto the next if statement?

Comparison in PowerShell is done using -eq, not =.
Also, do not make parameters yes and no. There is already a built-in type that's immune to typos: bool. $true and $false are what you're after

As others have said, the bool type is designed for this sort of thing. Since they're only true or false, you don't have to include equality statements, which makes the code much more readable. Instead of
if( $InstallSlack -eq 'yes' ) # Note the -eq comparison is used in Powershell, not '=' or '=='
with a bool, you can just write
if( $InstallSlack )
which I think is much clearer.
To do the opposite, you put a -not in front of the bool:
if( -not $InstallSlack )
Here's your code rewritten with bool's (and with me changing the formatting a bit by adding some whitespace and aligning the code block indentation).
Param(
[Parameter(Mandatory=$true)]
[bool]$ContinueSetup,
[Parameter(Mandatory=$true)]
[bool]$InstallDropbox = 'Yes',
[Parameter(Mandatory=$true)]
[bool]$InstallSlack,
[Parameter(Mandatory=$true)]
[bool]$InstallOffice
)
if ($ContinueSetup){
if ($InstallDropbox){
write-host 'install dropbox'
}
else{
write-host 'dropbox not selected'
}
if ($InstallSlack){
write-host 'install slack'
}
else {
write-host 'slack not selected'
}
if ($InstallOffice){
write-host 'install office'
}
else {
write-host 'Office not selected'
}
}
else{
write-host 'no setup'
break
}
Bools are a very powerful programming concept. In Powershell, they can be combined/modified with -not, -or, -and, and a few others. This link has the details.
(Just for completeness, I'll note that some people (including me) don't like seeing [bool] parameters in Powershell. There's something called a [switch] parameter that can be used instead, but I wouldn't worry about it too much. Here's a link.)

I'd also recommend using [switch] instead of [bool] for your parameter types and not specifying $true as the default. SwitchParameter parameters make invocation simplier and more straightforward.
Param (
[switch]$Setup,
[switch]$NoDropboxInstall,
[switch]$NoSlackInstall,
[switch]$NoOfficeInstall
)
if ($Setup) {
"NoDropboxInstall: $NoDropboxInstall"
"NoSlackInstall: $NoSlackInstall"
"NoOfficeInstall: $NoOfficeInstall"
}
if (!$Setup) {
write-host 'no setup'
return
}
Invoking the script is simpler:
.\parameters.ps1 -Setup -NoDropboxInstall
Output:
NoDropboxInstall: True
NoSlackInstall: False
NoOfficeInstall: False

Yes, an assignment statement can be an expression. Occasionally useful, but confusing. Any non-null result will be true.
if ($a = 'yes') {'yes'}
yes
if ($a = 'no') {'yes'}
yes
if ($a = '') {'yes'}
# nothing

Related

PowerShell booleans -- How to handle null differently from false?

I'm trying to write a server build script that includes Boolean parameters for various tasks, e.g. whether to install IIS. If the user does not specify this parameter one way or the other, I want the script to prompt for a decision, but for convenience and unattended execution I want the user to be able to explicitly choose to install IIS or NOT install IIS by setting the value to True or False on the command line and therefore avoid being prompted. My issue is that when I create a Boolean parameter, PowerShell automatically sets it to False, rather than leaving it null, if it wasn't specified on the command line. Here is the design that I THOUGHT would've worked:
param(
[bool]$IIS
)
if ($IIS -eq $null) {
$InstallIIS = Read-Host "Do you want to install IIS? (Y/N)"
if ($InstallIIS -eq "Y") {$IIS = $true}
}
if ($IIS) {Do stuff here}
Any suggestions for how to achieve my desired outcome would be most appreciated. Then if this changes anything, what I'd REALLY like to do is leverage PSRemoting to accept these build decision parameters on the user's system host and then pass them to the targets as an ArgumentList, and I'm wondering if that will affect how these Booleans are handled. For example:
param (
[string[]]$Computers
[bool]$IIS
)
$Computers | Foreach-Object {
Invoke-Command -ComputerName $_ -ArgumentList $IIS -ScriptBlock {
param(
[bool]$IIS
)
if ($IIS -eq $null) {
$InstallIIS = Read-Host "Do you want to install IIS? (Y/N)"
if ($InstallIIS -eq "Y") {$IIS = $true}
}
if ($IIS) {Do stuff here}
Ideas?
The way to accomplish this is with Parameter Sets:
[CmdletBinding()]
param (
[Parameter()]
[string[]]$Computers ,
[Parameter(ParameterSetName = 'DoSomethingWithIIS', Mandatory = $true)]
[bool]$IIS
)
$Computers | Foreach-Object {
Invoke-Command -ArgumentList $IIS -ScriptBlock {
param(
[bool]$IIS
)
if ($PSCmdlet.ParameterSetName -ne 'DoSomethingWithIIS') {
$InstallIIS = Read-Host "Do you want to install IIS? (Y/N)"
if ($InstallIIS -eq "Y") {$IIS = $true}
}
if ($IIS) {Do stuff here}
Well of course even though I Googled about this quite a bit before posting here, including discovering the [AllowNull()] parameter and finding that it did NOT help in my use case, I ended up finding the answer in the first Google search AFTER posting. This is what worked:
[nullable[bool]]$IIS
My only gripe with that syntax is that running Get-Help against the script now returns shows this for the IIS parameter:
-IIS <Nullable`1>
instead of:
-IIS <Boolean>
But unless there's a more elegant way to achieve what I need, I think I can live with that by adding a useful description for that parameter as well as Examples.
Even though boolean operators handle $null, $False, '', "", and 0 the same, you can do an equality comparison to see which is which.
If ($Value -eq $Null) {}
ElseIf ($Value -eq $False) {}
..etc..
In your situation, you want to use [Switch]$IIS. This will be $False by default, or $True if entered with the command a la Command -IIS, then you can handle it in your code like:
If ($IIS) {}
Which will only be $True if entered at the command line with -IIS
Instead of using an equality test that's going to try to coerce the value to make the test work:
if ($IIS -eq $Null)
Use -is to check the type directly:
PS C:\> $iis = $null
PS C:\> $iis -is [bool]
False

Inconsistent behavior in powershell with null parameters

I need to write a function in powershell that tells apart a 'parameter not being passed' from one passed with string empty (or any other string)
I wrote it like this:
function Set-X {
param(
[AllowNull()][string]$MyParam = [System.Management.Automation.Language.NullString]::Value
)
if ($null -ne $MyParam) { write-host 'oops' }
else { write-host 'ok' }
}
If I call Set-X without parameters from ISE, it works as I expect and prints 'ok'.
But if I do that from the normal console, it prints 'oops'.
What is going on? What is the proper way to do it?
Allowing the user to pass in a parameter argument value of $null does not change the fact that powershell will attempt to convert it to a [string].
Converting a $null value in powershell to a string results in an empty string:
$str = [string]$null
$null -eq $str # False
'' -eq $str # True
(same goes for $null -as [string] and "$null")
Remove the type constraint on the MyParam parameter if you not only want to allow $null but also accept $null as a parameter value:
function Set-X {
param(
[AllowNull()]$MyParam = [System.Management.Automation.Language.NullString]::Value
)
if ($null -ne $MyParam) { write-host 'oops' }
else { write-host 'ok' }
}
As Mathias and BenH have written, the culprit is casting $null to the [string] type, which results in an empty string:
[string]$null -eq '' #This is True
But for the sample code in Mathias answer to work correctly we also have to replace
[System.Management.Automation.Language.NullString]::Value
with $null
function Set-X {
param(
[AllowNull()]$MyParam = $null
)
if ($null -ne $MyParam) { write-host 'oops' }
else { write-host 'ok' }
}

convert "Yes" or "No" to boolean

I want to parse user values contained in .CSV file. I don't want my users to enter "Yes" or "No" but instead enter "True" or "False". In each case I want to convert to the equivalent boolean values: $true or $false. Ideally I would like a default value, so if there's misspelt "Yes or "No" I would return my default value: $true or $false.
Hence, I wondered if there is a neat way of doing this other than
if(){} else (){}
One way is a switch statement:
$bool = switch ($string) {
'yes' { $true }
'no' { $false }
}
Add a clause default if you want to handle values that are neither "yes" nor "no":
$bool = switch ($string) {
'yes' { $true }
'no' { $false }
default { 'neither yes nor no' }
}
Another option might be a simple comparison:
$string -eq 'yes' # matches just "yes"
or
$string -match '^y(es)?$' # matches "y" or "yes"
These expressions would evaluate to $true if the string is matched, otherwise to $false.
Ah, the magic of powershell functions, and invoke expression.
function Yes { $true }
function No { $false }
$magicBool = & $answer
Note: This is case insensitive, but will not handle misspellings
If the only possible values are "Yes" and "No" then probably the simplest way is
$result = $value -eq 'Yes'
With misspelled values and the default $false the above will do as well.
With misspelled values and the default $true this will work
$result = $value -ne 'No'
All of these are valid approaches. If you are looking for a one liner, this will validate it is an acceptable value and set to boolean true if in the 'true' value set. This will also give you a default $false value.
$result = #("true","false","yes","no") -contains $value -and #("true","yes") -contains $value
For a default $true value you would need something like so.
$result = $true
if (#("true","false","yes","no") -contains $value) {
$result = #("true","yes") -contains $value
}
Without a full snippet of your existing code, something like this would probably be an alternative path to take, as opposed to a string of IF statements.
NOTE: This will not handle simple 'Y' or 'N' input, but is case insensitive. So, you should be able to see 'yes' or 'YES' working, as well.
$myVar = Read-Host 'What is your answer?'
switch ($myVar)
{
Yes {$myVarConverted = $true; break}
True {$myVarConverted = $true; break}
No {$myVarConverted = $false; break}
False {$myVarConverted = $false; break}
default {"Invalid Input"; break}
}
Write-Host $myVarConverted
Please see my additional comment on your question about the 'misspelling' caveat. That's difficult to code around without specific restrictions or requirements.
Here's the way I do Yes-No answers:
function ask-user
{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string] $question
)
Process
{ $answer = read-Host $question
if ("yes" -match $answer) {$true}
elseif ("no" -match $answer) {$false}
else {ask-user $question}
}
}
You can easily substitute true and false for yes and no.
This one is case insensitive, and will match valid abbreviations. (Y or N).
In the case of misspellings, it asks again. Yeah, I could have done it without recursion, but I'm lazy.
These are great solutions above, but let me just say that this whole topic just proves the vast shortcomings of Powershell...
[System.Convert]::ToBoolean("False") -eq $true ?
[System.Convert]::ToBoolean("0") -eq $true ?
Really?
Give me a f--kin break.
For me :-
Function convert2Bool($this) { return ($("False","0","","N","No",'$False',"Off") -notcontains [string]$this) }
can adjust if you don't want $null blank-string going to $false, else fine.

Ternary operator in PowerShell

From what I know, PowerShell doesn't seem to have a built-in expression for the so-called ternary operator.
For example, in the C language, which supports the ternary operator, I could write something like:
<condition> ? <condition-is-true> : <condition-is-false>;
If that doesn't really exist in PowerShell, what would be the best way (i.e. easy to read and to maintain) to accomplish the same result?
$result = If ($condition) {"true"} Else {"false"}
For use in or as an expression, not just an assignment, wrap it in $(), thus:
write-host $(If ($condition) {"true"} Else {"false"})
Powershell 7 has it.
PS C:\Users\js> 0 ? 'yes' : 'no'
no
PS C:\Users\js> 1 ? 'yes' : 'no'
yes
The closest PowerShell construct I've been able to come up with to emulate that is:
#({'condition is false'},{'condition is true'})[$condition]
Try powershell's switch statement as an alternative, especially for variable assignment - multiple lines, but readable.
Example,
$WinVer = switch ( Test-Path -Path "$Env:windir\SysWOW64" ) {
$true { "64-bit" }
$false { "32-bit" }
}
"This version of Windows is $WinVer"
Per this PowerShell blog post, you can create an alias to define a ?: operator:
set-alias ?: Invoke-Ternary -Option AllScope -Description "PSCX filter alias"
filter Invoke-Ternary ([scriptblock]$decider, [scriptblock]$ifTrue, [scriptblock]$ifFalse)
{
if (&$decider) {
&$ifTrue
} else {
&$ifFalse
}
}
Use it like this:
$total = ($quantity * $price ) * (?: {$quantity -le 10} {.9} {.75})
As of PowerShell version 7, the ternary operator is built into PowerShell.
1 -gt 2 ? "Yes" : "No"
# Returns "No"
1 -gt 2 ? 'Yes' : $null
# Get a $null response for false-y return value
I too, looked for a better answer, and while the solution in Edward's post is "ok", I came up with a far more natural solution in this blog post
Short and sweet:
# ---------------------------------------------------------------------------
# Name: Invoke-Assignment
# Alias: =
# Author: Garrett Serack (#FearTheCowboy)
# Desc: Enables expressions like the C# operators:
# Ternary:
# <condition> ? <trueresult> : <falseresult>
# e.g.
# status = (age > 50) ? "old" : "young";
# Null-Coalescing
# <value> ?? <value-if-value-is-null>
# e.g.
# name = GetName() ?? "No Name";
#
# Ternary Usage:
# $status == ($age > 50) ? "old" : "young"
#
# Null Coalescing Usage:
# $name = (get-name) ? "No Name"
# ---------------------------------------------------------------------------
# returns the evaluated value of the parameter passed in,
# executing it, if it is a scriptblock
function eval($item) {
if( $item -ne $null ) {
if( $item -is "ScriptBlock" ) {
return & $item
}
return $item
}
return $null
}
# an extended assignment function; implements logic for Ternarys and Null-Coalescing expressions
function Invoke-Assignment {
if( $args ) {
# ternary
if ($p = [array]::IndexOf($args,'?' )+1) {
if (eval($args[0])) {
return eval($args[$p])
}
return eval($args[([array]::IndexOf($args,':',$p))+1])
}
# null-coalescing
if ($p = ([array]::IndexOf($args,'??',$p)+1)) {
if ($result = eval($args[0])) {
return $result
}
return eval($args[$p])
}
# neither ternary or null-coalescing, just a value
return eval($args[0])
}
return $null
}
# alias the function to the equals sign (which doesn't impede the normal use of = )
set-alias = Invoke-Assignment -Option AllScope -Description "FearTheCowboy's Invoke-Assignment."
Which makes it easy to do stuff like (more examples in blog post):
$message == ($age > 50) ? "Old Man" :"Young Dude"
Since a ternary operator is usually used when assigning value, it should return a value. This is the way that can work:
$var=#("value if false","value if true")[[byte](condition)]
Stupid, but working. Also this construction can be used to quickly turn an int into another value, just add array elements and specify an expression that returns 0-based non-negative values.
The ternary operator in PowerShell was introduced with the PowerShell version7.0.
[Condition] ? (output if True) : (output if False)
Example 01
$a = 5; $b = 6
($a -gt $b) ? "True" : "False"
Output
False
Example 02
($a -gt $b) ? ("$a is greater than $b") : ("$a is less than $b")
Output
5 is less than 6
more information
https://www.tutorialspoint.com/how-ternary-operator-in-powershell-works
Since I have used this many times already and didn't see it listed here, I'll add my piece :
$var = #{$true="this is true";$false="this is false"}[1 -eq 1]
ugliest of all !
kinda source
I've recently improved (open PullRequest) the ternary conditional and null-coalescing operators in the PoweShell lib 'Pscx'
Pls have a look for my solution.
My github topic branch: UtilityModule_Invoke-Operators
Functions:
Invoke-Ternary
Invoke-TernaryAsPipe
Invoke-NullCoalescing
NullCoalescingAsPipe
Aliases
Set-Alias :?: Pscx\Invoke-Ternary -Description "PSCX alias"
Set-Alias ?: Pscx\Invoke-TernaryAsPipe -Description "PSCX alias"
Set-Alias :?? Pscx\Invoke-NullCoalescing -Description "PSCX alias"
Set-Alias ?? Pscx\Invoke-NullCoalescingAsPipe -Description "PSCX alias"
Usage
<condition_expression> |?: <true_expression> <false_expression>
<variable_expression> |?? <alternate_expression>
As expression you can pass:
$null, a literal, a variable, an 'external' expression ($b -eq 4) or a scriptblock {$b -eq 4}
If a variable in the variable expression is $null or not existing, the alternate expression is evaluated as output.
PowerShell currently doesn't didn't have a native Inline If (or ternary If) but you could consider to use the custom cmdlet:
IIf <condition> <condition-is-true> <condition-is-false>
See: PowerShell inline If (IIf)
If you're just looking for a syntactically simple way to assign/return a string or numeric based on a boolean condition, you can use the multiplication operator like this:
"Condition is "+("true"*$condition)+("false"*!$condition)
(12.34*$condition)+(56.78*!$condition)
If you're only ever interested in the result when something is true, you can just omit the false part entirely (or vice versa), e.g. a simple scoring system:
$isTall = $true
$isDark = $false
$isHandsome = $true
$score = (2*$isTall)+(4*$isDark)+(10*$isHandsome)
"Score = $score"
# or
# "Score = $((2*$isTall)+(4*$isDark)+(10*$isHandsome))"
Note that the boolean value should not be the leading term in the multiplication, i.e. $condition*"true" etc. won't work.
Here's an alternative custom function approach:
function Test-TernaryOperatorCondition {
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline = $true, Mandatory = $true)]
[bool]$ConditionResult
,
[Parameter(Mandatory = $true, Position = 0)]
[PSObject]$ValueIfTrue
,
[Parameter(Mandatory = $true, Position = 1)]
[ValidateSet(':')]
[char]$Colon
,
[Parameter(Mandatory = $true, Position = 2)]
[PSObject]$ValueIfFalse
)
process {
if ($ConditionResult) {
$ValueIfTrue
}
else {
$ValueIfFalse
}
}
}
set-alias -Name '???' -Value 'Test-TernaryOperatorCondition'
Example
1 -eq 1 |??? 'match' : 'nomatch'
1 -eq 2 |??? 'match' : 'nomatch'
Differences Explained
Why is it 3 question marks instead of 1?
The ? character is already an alias for Where-Object.
?? is used in other languages as a null coalescing operator, and I wanted to avoid confusion.
Why do we need the pipe before the command?
Since I'm utilising the pipeline to evaluate this, we still need this character to pipe the condition into our function
What happens if I pass in an array?
We get a result for each value; i.e. -2..2 |??? 'match' : 'nomatch' gives: match, match, nomatch, match, match (i.e. since any non-zero int evaluates to true; whilst zero evaluates to false).
If you don't want that, convert the array to a bool; ([bool](-2..2)) |??? 'match' : 'nomatch' (or simply: [bool](-2..2) |??? 'match' : 'nomatch')

PowerShell string default parameter value does not work as expected

#Requires -Version 2.0
[CmdletBinding()]
Param(
[Parameter()] [string] $MyParam = $null
)
if($MyParam -eq $null) {
Write-Host 'works'
} else {
Write-Host 'does not work'
}
Outputs "does not work" => looks like strings are converted from null to empty string implicitly? Why? And how to test if a string is empty or really $null? This should be two different values!
Okay, found the answer # https://www.codykonior.com/2013/10/17/checking-for-null-in-powershell/
Assuming:
Param(
[string] $stringParam = $null
)
And the parameter was not specified (is using default value):
# will NOT work
if ($null -eq $stringParam)
{
}
# WILL work:
if ($stringParam -eq "" -and $stringParam -eq [String]::Empty)
{
}
Alternatively, you can specify a special null type:
Param(
[string] $stringParam = [System.Management.Automation.Language.NullString]::Value
)
In which case the $null -eq $stringParam will work as expected.
Weird!
You will need to use the AllowNull attribute if you want to allow $null for string parameters:
[CmdletBinding()]
Param (
[Parameter()]
[AllowNull()]
[string] $MyParam
)
And note that you should use $null on the left-hand side of the comparison:
if ($null -eq $MyParam)
if you want it to work predictably
seeing many equality comparisons with [String]::Empty, you could use the [String]::IsNullOrWhiteSpace or [String]::IsNullOrEmpty static methods, like the following:
param(
[string]$parameter = $null
)
# we know this is false
($null -eq $parameter)
[String]::IsNullOrWhiteSpace($parameter)
[String]::IsNullOrEmpty($parameter)
('' -eq $parameter)
("" -eq $parameter)
which yields:
PS C:\...> .\foo.ps1
False
True
True
True
True
Simply do not declare the param's type if you want a $null value to remain:
Param(
$stringParam
)
(None of the other solutions worked for me when declaring the type.)
So it seems a default value of $null for parameters of type [string] defaults to empty string, for whatever reason.
Option 1
if ($stringParam) { ... }
Option 2
if ($stringParam -eq "") { ... }
Option 3
if ($stringParam -eq [String]::Empty) { ... }