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.
Related
Consider the following function :
function myfunc()
{
if (condition1) {
return 'nowork'
} elseif (condition2) {
return $false
} elseif (condition3) {
return $true
}
Now if I call this function, and I know that condition3 is true, I can see that True is returned:
...
$result = myfunc
Write-Host $result
(this writes True to the console.)
The next statement in the calling function is an if statement to determine what was returned, and act upon that:
$result = myfunc
Write-Host $result
if ($result -eq 'nowork') {
do this..
} elseif ($result -eq $false) {
do that..
} elseif ($result -eq $true) {
do something else..
}
And this is where it gets strange (to me). Even though I can see that True is returned, the if statement decides to go do 'do this..', the first branch of the if statement, where I would have expected that 'do something else..' would have been done.
Another strange thing is that it sometimes works, sometimes not. I tried changing the if statement to:
if ('nowork' -eq $result)
and then what went wrong first now worked, but later on the same issue re-appeared.
I'm guessing there's something wrong with my first string comparison, but I can't figure out what. I'm used to writing scripts in Linux (bash), so Powershell must be acting differently.
Btw: script is run in Debian 10, Powershell 7, but the exact same problem also appears on a Windows machine with Powershell 5.0.
Please help..
You're comparing apples and oranges
PowerShell's comparison operator behavior depends on type of the left-hand side operand.
When your lhs ($result) is a [bool] (ie. $true or $false), PowerShell will attempt to convert the right-hand side operand to [bool] as well before comparing the two.
Converting a non-empty string (ie. 'nowork') to [bool] results in $true, so the if condition evaluates to $true -eq $true -> $true.
You can fix this by manually type checking:
if($result -is [bool]){
if($result){
# was $true
}
else {
# was $false
}
}
elseif($result -eq 'nowork'){
# was 'nowork'
}
The nicer way of solving this however would be to always return the same type of object. In your case where you have 3 different return options, consider an enum:
enum WorkAmount
{
None
Some
All
}
function myfunc()
{
if (condition1) {
return [WorkAmount]::None
} elseif (condition2) {
return [WorkAmount]::Some
} elseif (condition3) {
return [WorkAmount]::All
}
}
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
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' }
}
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')
In PowerShell, how can I test if a variable holds a numeric value?
Currently, I'm trying to do it like this, but it always seems to return false.
add-type -Language CSharpVersion3 #'
public class Helpers {
public static bool IsNumeric(object o) {
return o is byte || o is short || o is int || o is long
|| o is sbyte || o is ushort || o is uint || o is ulong
|| o is float || o is double || o is decimal
;
}
}
'#
filter isNumeric($InputObject) {
[Helpers]::IsNumeric($InputObject)
}
PS> 1 | isNumeric
False
You can check whether the variable is a number like this: $val -is [int]
This will work for numeric values, but not if the number is wrapped in quotes:
1 -is [int]
True
"1" -is [int]
False
If you are testing a string for a numeric value then you can use the a regular expression and the -match comparison. Otherwise Christian's answer is a good solution for type checking.
function Is-Numeric ($Value) {
return $Value -match "^[\d\.]+$"
}
Is-Numeric 1.23
True
Is-Numeric 123
True
Is-Numeric ""
False
Is-Numeric "asdf123"
False
Modify your filter like this:
filter isNumeric {
[Helpers]::IsNumeric($_)
}
function uses the $input variable to contain pipeline information whereas the filter uses the special variable $_ that contains the current pipeline object.
Edit:
For a powershell syntax way you can use just a filter (w/o add-type):
filter isNumeric($x) {
return $x -is [byte] -or $x -is [int16] -or $x -is [int32] -or $x -is [int64] `
-or $x -is [sbyte] -or $x -is [uint16] -or $x -is [uint32] -or $x -is [uint64] `
-or $x -is [float] -or $x -is [double] -or $x -is [decimal]
}
You can do something like :
$testvar -match '^[0-9]+$'
or
$testvar -match '^\d+$'
Returns True if $testvar is a number.
If you want to check if a string has a numeric value, use this code:
$a = "44.4"
$b = "ad"
$rtn = ""
[double]::TryParse($a,[ref]$rtn)
[double]::TryParse($b,[ref]$rtn)
Credits go here
PS> Add-Type -Assembly Microsoft.VisualBasic
PS> [Microsoft.VisualBasic.Information]::IsNumeric(1.5)
True
http://msdn.microsoft.com/en-us/library/microsoft.visualbasic.information.isnumeric.aspx
-is and -as operators requires a type you can compare against. If you're not sure what the type might be, try to evaluate the content (partial type list):
(Invoke-Expression '1.5').GetType().Name -match 'byte|short|int32|long|sbyte|ushort|uint32|ulong|float|double|decimal'
Good or bad, it can work against hex values as well (Invoke-Expression '0xA' ...)
filter isNumeric {
$_ -is [ValueType]
}
-
1 -is [ValueType]
True
"1" -is [ValueType]
False
-
function isNumeric ($Value) {
return $Value -is [ValueType]
}
isNumeric 1.23
True
isNumeric 123
True
isNumeric ""
False
isNumeric "asdf123"
False
-
(Invoke-Expression '1.5') -is [ValueType]
$itisint=$true
try{
[int]$vartotest
}catch{
"error converting to int"
$itisint=$false
}
this is more universal, because this way you can test also strings (read from a file for example) if they represent number. The other solutions using -is [int] result in false if you would have "123" as string in a variable. This also works on machines with older powershell then 5.1
If you know the numeric type you want to test against (such as int for example in the code below), you can do it like this:
> [bool]("42" -as [int])
True
> [bool](42 -as [int])
True
> [bool]("hi" -as [int])
False
But note:
> [bool](42.1 -as [int])
True
Careful!:
It was pointed out that the code above fails to identify 0 as an int. You would need to add a guard for 0:
> $n -eq 0 -or $n -as [int]
Where $n is the object you are testing.
Thank you all who contributed to this thread and helped me figure out how to test for numeric values. I wanted to post my results for how to handle negative numbers, for those who may also find this thread when searching...
Note: My function requires a string to be passed, due to using Trim().
function IsNumeric($value) {
# This function will test if a string value is numeric
#
# Parameters::
#
# $value - String to test
#
return ($($value.Trim()) -match "^[-]?[0-9.]+$")
}
I ran into this topic while working on input validation with read-host. If I tried to specify the data type for the variable as part of the read-host command and the user entered something other than that data type then read-host would error out. This is how I got around that and ensured that the user enters the data type I wanted:
do
{
try
{
[int]$thing = read-host -prompt "Enter a number or else"
$GotANumber = $true
}
catch
{
$GotANumber = $false
}
}
until
($gotanumber)
"-123.456e-789" -match "^\-?(\d+\.?\d*)(e\-?\d+)?$|^0x[0-9a-f]+$"
or
"0xab789" -match "^\-?(\d+\.?\d*)(e\-?\d+)?$|^0x[0-9a-f]+$"
will check for numbers (integers, floats and hex).
Please note that this does not cover the case of commas/dots being used as separators for thousands.
Each numeric type has its own value. See TypeCode enum definition:
https://learn.microsoft.com/en-us/dotnet/api/system.typecode?view=netframework-4.8
Based on this info, all your numeric type-values are in the range from 5 to 15.
This means, you can write the condition-check like this:
$typeValue = $x.getTypeCode().value__
if ($typeValue -ge 5 -and $typeValue -le 15) {"x has a numeric type!"}
Testing if a value is numeric or a string representation of a numeric value.
function Test-Number
{
Param
(
[Parameter(Mandatory=$true,
Position=0)]
[ValidatePattern("^[\d\.]+$")]
$Number
)
$Number -is [ValueType] -or [Double]::TryParse($Number,[ref]$null)
}
Testing if a value is numeric.
function Test-Number
{
Param
(
[Parameter(Mandatory=$true,
Position=0)]
[ValidatePattern("^[\d\.]+$")]
$Number
)
$Number -is [ValueType]
}