I'm not quite sure if this exact question is a duplicate, but I couldn't find my specific question on stackoverflow, so I guess it's not.
$true is a boolean type:
($true).getType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Boolean System.ValueType
'true' is a string:
('true').gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
why is this condition true?
PS C:\WINDOWS\system32> if ('True' -eq $true) {'yes'} else {'no'}
yes
only because the string is called True just like the boolean true? If I compare it with any other string, there is a different outcome:
PS C:\WINDOWS\system32> 'hello' -eq $true
False
PS C:\WINDOWS\system32> 'true' -eq $true
True
if you do 'true' == true in C# it prints an cannot convert string to bool error and both C# and PS are based on .net and have quite the same syntax. why is there a different outcome?
this seems very strange to me.
How is PowerShell evaluating 'true' to $true?
In PowerShell, operator overloading is determined by the left-hand side (lhs) argument.
This means that when you supply a string as the lhs argument, the parser will attempt to convert the right-hand side (rhs) to a string as well.
When cast to a string, $true comes out as the string "True" - which, since PowerShell's string comparison operators are all case-insensitive by default, happens to equal "true".
This operation:
'true' -eq $true
Is interpreted as
'true' -eq 'True'
Which happens to be $true, satisfying your if condition.
With $true, this also happens to work the other way around, because a non-empty string, when converted to a boolean, evaluates to $true:
In other words
$true -eq 'true'
Is interpreted as
$true -eq $true
This can lead to confusion, since the string "false" will incidentally also be evaluated as $true, simply because the string "false" is not empty:
PS C:\> $true -eq "true"
True
PS C:\> $true -eq "false"
True
PS C:\> $true -eq ""
False
Related
The command $output = Invoke-Expression "cfn-lint *.yaml" gives me an array for $output:
$output.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
Write-Output $output gives me this nice output:
Write-Output $output
W2506 Parameter AMIID should be of type [AWS::EC2::Image::Id,
AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>]
trace3-ec2-virtual-machine-creation.yaml:27:3
W2001 Parameter VpcId not used.
trace3-ec2-virtual-machine-creation.yaml:32:3
But Write-Host $output gives me a version with all the newlines stripped out:
Write-Host $output
W2506 Parameter AMIID should be of type [AWS::EC2::Image::Id, AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>] trace3-ec2-virtual-machine-creation.yaml:27:3 W2001 Parameter VpcId not used. trace3-ec2-virtual-machine-creation.yaml:32:3
Why is it doing this?
I know that I can get around this with the call Write-Host ($output | Out-String), but I don't understand why that works.
Write-Output takes [-InputObject] <PSObject[]> as argument while Write-Host takes [[-Object] <Object>] as argument.
Write-Output returns the objects that are submitted as input.
Write-Host sends the objects to the host. It does not return any objects. However, the host displays the objects that Write-Host sends to it.
As you can see, Write-Ouput takes an array of PSObject as Input parameter and returns the same array as Output.
Out-String works fine with Write-Host because it is converting your array to a multiline string hence Write-Host will return it as literal to the Information Stream while without the Out-String it is taking all lines of your array and joining them into a single line string.
PS /> $test = #(
'this is a'
'test'
)
$writeHost = Write-Host -Object $test 6>&1
$writeOutput = Write-Output -Object $test
$writeHost.GetType()
$writeOutput.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False InformationRecord System.Object
True True Object[] System.Array
Edit
Following your comment, I don't know for sure why, I couldn't find any relevant information on MS Docs for the cmdlet but we can assume it is doing some sort of manipulation on the input objects. We do know that Write-Host is a wrapper for Write-Information.
Take this function as an example, it receives a [object] in this case it receives an array with 2 items as input and writes to the information stream a string representation of it's input:
$test = #(
'this is a'
'test'
)
function TestString ([object]$String) {
Write-Information ([string]$String) -InformationAction Continue
}
PS /> TestString $test
this is a test
I ran into something strange, and I don't understand what is happening:
PS C:\> $env:x = $false
PS C:\> if($env:x){'what'}
what
PS C:\> $env:x
False
So the value is actually false, but what does the if check? Clearly not the value of x. What is happening?
The Environment provider (which implements the env: drive) only supports string items, and will coerce any assigned value to a [string]:
PS C:\> $env:x = $false
PS C:\> $env:x.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
... and the default [string]-to-[bool] conversion rules in PowerShell holds that non-empty strings are converted to $true:
PS C:\> $true -eq 'false'
True
PS C:\> [bool]'false'
True
PS C:\> [bool]''
False
To parse a string value (like "false") to its equivalent [bool] value, you need to either call [bool]::Parse():
if([bool]::Parse($env:x)){
'what'
}
... or, if the string value might not be a valid truthy/falsy value, use [bool]::TryParse():
$env:x = $false
$result = $false
if([bool]::TryParse($env:x, [ref]$result) -and $result){
'what'
}
I'm new to Powershell and I'm confused about something, more specifically null and $null.
For example. let's say you have a function:
function myFunction([ref]$foo){
if($foo -ne $null){
...do stuff
}
}
And when you call this function, you do so like:
[ref]$foo = $null
myFunction $foo
If you execute the code above, the condition if($foo -ne $null) will return true.
However, if you call the function like:
$foo = $null
myFunction $foo
the condition if($foo -ne $null) will return false.
Why is this? Why is it that when you assign a ref variable $null, it isn't considered null when it is checked?
PowerShell seems to cast $null to some non-null value when you attempt to store it in a strongly typed variable. I believe this is the cause. Notice the following:
PS C:\> [ref]$foo = $null
PS C:\> $foo
Value
-----
PS C:\> $foo.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False PSReference System.Object
It appears that PowerShell is casting $null to a PSReference. I've seen similar behavior with strings:
PS C:\> [String]$str = $null
PS C:\> $str -eq $null
False
PS C:\> $str -eq [String]::Empty
True
It confused the heck out of me when I had a [String] parameter on a function that I was defaulting to $null and my null check was never true.
Why would they do this? I wonder the same thing.
For strings, I ended up using [String]::IsNullOrEmpty (although there may be a more PowerShell-ish way). For PSReference, Value might suit your needs for the null check:
PS C:\> [ref]$foo = $null
PS C:\> $foo.Value -eq $null
True
Note that we have just proven you won't run into a null reference error by accessing Value.
Because $null is a special variable which is actually null, where as [ref]$foo is a strongly typed variable containing a reference to a location, which just so happens to be null.
So of course $ref -ne $null. $null is the null, $ref is a reference which could be null later.
The only method I have found is a direct cast:
> $numberAsString = "10"
> [int]$numberAsString
10
Is this the standard approach in Powershell? Is it expected that a test will be done before to ensure that the conversion will succeed and if so how?
You can use the -as operator. If casting succeed you get back a number:
$numberAsString -as [int]
Using .net
[int]$b = $null #used after as refence
$b
0
[int32]::TryParse($a , [ref]$b ) # test if is possible to cast and put parsed value in reference variable
True
$b
10
$b.gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32 System.ValueType
note this (powershell coercing feature)
$a = "10"
$a + 1 #second value is evaluated as [string]
101
11 + $a # second value is evaluated as [int]
21
A quick true/false test of whether it will cast to [int]
[bool]($var -as [int] -is [int])
For me $numberAsString -as [int] of #Shay Levy is the best practice, I also use [type]::Parse(...) or [type]::TryParse(...)
But, depending on what you need you can just put a string containing a number on the right of an arithmetic operator with a int on the left the result will be an Int32:
PS > $b = "10"
PS > $a = 0 + $b
PS > $a.gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32 System.ValueType
You can use Exception (try/parse) to behave in case of Problem
I'd probably do something like that :
[int]::Parse("35")
But I'm not really a Powershell guy. It uses the static Parse method from System.Int32. It should throw an exception if the string can't be parsed.
$source = "number35"
$number=$null
$result = foreach ($_ in $source.ToCharArray()){$digit="0123456789".IndexOf($\_,0);if($digit -ne -1){$number +=$\_}}[int32]$number
Just feed it digits and it wil convert to an Int32
Building up on Shavy Levy answer:
[bool]($var -as [int])
Because $null is evaluated to false (in bool), this statement Will give you true or false depending if the casting succeeds or not.
I'm using an array variable in PowerShell 2.0. If it does not have a value, it will be $null, which I can test for successfully:
PS C:\> [array]$foo = $null
PS C:\> $foo -eq $null
True
But when I give it a value, the test for $null does not return anything:
PS C:\> [array]$foo = #("bar")
PS C:\> $foo -eq $null
PS C:\>
How can "-eq $null" give no results? It's either $null or it's not.
What is the correct way to determine if an array is populated vs. $null?
It's an array, so you're looking for Count to test for contents.
I'd recommend
$foo.count -gt 0
The "why" of this is related to how PSH handles comparison of collection objects
You can reorder the operands:
$null -eq $foo
Note that -eq in PowerShell is not an equivalence relation.
if($foo -eq $null) { "yes" } else { "no" }
help about_comparison_operators
displays help and includes this text:
All comparison operators except the
containment operators (-contains,
-notcontains) and type operators (-is, -isnot) return a Boolean value when the input to the operator (the value
on the left side of the operator) is a
single value (a scalar). When the
input is a collection of values, the
containment operators and the type
operators return any matching values.
If there are no matches in a
collection, these operators do not
return anything. The containment
operators and type operators always
return a Boolean value.
If your solution requires returning 0 instead of true/false, I've found this to be useful:
PS C:\> [array]$foo = $null
PS C:\> ($foo | Measure-Object).Count
0
This operation is different from the count property of the array, because Measure-Object is counting objects. Since there are none, it will return 0.
The other answers address the main thrust of the question, but just to comment on this part...
PS C:\> [array]$foo = #("bar")
PS C:\> $foo -eq $null
PS C:\>
How can "-eq $null" give no results? It's either $null or it's not.
It's confusing at first, but that is giving you the result of $foo -eq $null, it's just that the result has no displayable representation.
Since $foo holds an array, $foo -eq $null means "return an array containing the elements of $foo that are equal to $null". Are there any elements of $foo that are equal to $null? No, so $foo -eq $null should return an empty array. That's exactly what it does, the problem is that when an empty array is displayed at the console you see...nothing...
PS> #()
PS>
The array is still there, even if you can't see its elements...
PS> #().GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
PS> #().Length
0
We can use similar commands to confirm that $foo -eq $null is returning an array that we're not able to "see"...
PS> $foo -eq $null
PS> ($foo -eq $null).GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
PS> ($foo -eq $null).Length
0
PS> ($foo -eq $null).GetValue(0)
Exception calling "GetValue" with "1" argument(s): "Index was outside the bounds of the array."
At line:1 char:1
+ ($foo -eq $null).GetValue(0)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : IndexOutOfRangeException
Note that I am calling the Array.GetValue method instead of using the indexer (i.e. ($foo -eq $null)[0]) because the latter returns $null for invalid indices and there's no way to distinguish them from a valid index that happens to contain $null.
We see similar behavior if we test for $null in/against an array that contains $null elements...
PS> $bar = #($null)
PS> $bar -eq $null
PS> ($bar -eq $null).GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
PS> ($bar -eq $null).Length
1
PS> ($bar -eq $null).GetValue(0)
PS> $null -eq ($bar -eq $null).GetValue(0)
True
PS> ($bar -eq $null).GetValue(0) -eq $null
True
PS> ($bar -eq $null).GetValue(1)
Exception calling "GetValue" with "1" argument(s): "Index was outside the bounds of the array."
At line:1 char:1
+ ($bar -eq $null).GetValue(1)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : IndexOutOfRangeException
In this case, $bar -eq $null returns an array containing one element, $null, which has no visual representation at the console...
PS> #($null)
PS> #($null).GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
PS> #($null).Length
1
How do you want things to behave?
If you want arrays with no elements to be treated the same as unassigned arrays, use:
[array]$foo = #() #example where we'd want TRUE to be returned
#($foo).Count -eq 0
If you want a blank array to be seen as having a value (albeit an empty one), use:
[array]$foo = #() #example where we'd want FALSE to be returned
$foo.PSObject -eq $null
If you want an array which is populated with only null values to be treated as null:
[array]$foo = $null,$null
#($foo | ?{$_.PSObject}).Count -eq 0
NB: In the above I use $_.PSObject over $_ to avoid [bool]$false, [int]0, [string]'', etc from being filtered out; since here we're focussed solely on nulls.
Watch out for switch. It will never run with a null array, for example as the output of an empty directory.
switch ( $null ) { default { 'yes' } }
yes
switch ( #() ) { default { 'yes' } } # no output
mkdir foo
switch ( dir foo ) { default { 'yes' } } # no output
Not all of these answers work for me. I ended up doing this, treating it like a boolean.
$foo = #()
if (! $foo) { 'empty array' }
empty array
Actually, I came across an arraylist inside an object.
[pscustomobject]#{config = [Collections.ArrayList]#()} | ? { ! $_.config }
config
------
{}