Why does 'true' equal $true - powershell

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

Write-Host strips newline characters where Write-Output does not

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

powershell checking boolean environment variable

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'
}

Confused about Nulls in Powershell

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.

In Powershell what is the idiomatic way of converting a string to an int?

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.

How to test for $null array in PowerShell

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
------
{}