Search code examples
powershellstring-comparison

Strange behaviour of string comparison in Powershell


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..


Solution

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