I have a calculated PowerShell variable that may be set to $True (Boolean), or 'True' (String), or $False (Boolean), or 'False' (String), and I want to test if it's "True" (Boolean or string).
While I can easily frame the successfully test as below:
$MyVar = $false
If (($MyVar -eq $True) -or ($MyVar -eq 'True')) {1} Else {0}
0
If I change this to the more succinct format below, I get the unexpected result:
If ($MyVar -in @($True,'True')) {1} Else {0}
1
Can anyone explain why the collection operators -in and -contains behave this way? I've observed this behavior with PowerShell 5.1 and PWSH 7.4.1
I've tried numerous variations of this, and the same unexpected behavior is observed.
If ('False' -in @($true)) {1} else {0}
1
If (@($true) -contains 'False') {1} else {0}
1
This stackoverflow article: https://stackoverflow.com/questions/38002509/powershell-why-does-truth-contain-lies
says: 'What's happening is that it is converting the right hand side of the operation to match the type of the left hand side';e.g.
$true -eq "Lies" ~> $true -eq [Bool]"Lies"
True
But that doesn't seem to be happening when I reverse the comparison elements here:
If ('False' -in @($true)) {1} else {0}
1
tl;dr
To avoid the pitfall described in the next section, use:
That is, stringify your variable using an expandable (interpolating), double-quoted string (
"...") and perform string comparison:$MyVaris a[bool]instance, it stringifies to either'True'or'False'irrespective of the current culture$MyVaralready is a string, the enclosure in"..."is in effect a no-op.Note that the
-eqoperator is case-insensitive by default; if you want to case-exact matching, use its case-sensitive variant,-ceq.Type coercion in
-in/-containsoperations:'False' -in @($true)is equivalent to@($true) -contains 'False'.That is, the two operators involved,
-inand-contains, are equivalent and differ only in which operand is the scalar vs. the collection to test containment in.In both cases, it is the elements of the collection operand that effectively act as the LHS of the implied
-eqcomparison performed for each collection element against the scalar operand.Therefore, both of the above are the effective equivalent of:
Given that it is generally[1] the LHS of operations that drives implicit type coercions in PowerShell, with
-inand-containsit is therefore each collection element that drives type coercions.With a
[bool]instance as the LHS, as in the case at hand, the RHS is coerced to that type based on the rules described in about_Booleans. These rules notably include considering any nonempty string to be$true, irrespective of the string's content.Thus,
$true -eq 'False'is - perhaps surprisingly -$true, given that'False'is a nonempty string.[1] There are exceptions:
-and/invariably interpret coerce both operands to numbers; e.g.,'10' - '2'yields[int] 8. The reason is that with these specific operators interpretation as numbers is the only sensible interpretation - unlike with polymorphic operators such as+and*.