question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

$? is not set to $False even command fails

See original GitHub issue

Prerequisites

Steps to reproduce

facing issue in version: Powershell version powershell-7.2 and on powershell-7.1.3 OS: centos 7 module used : ExchangeOnlineManagement Issue: I connected o365 via powershell using application. I connected succesfully. I tried to fetch any connector exists or not. That gets failed. But “$?” was not set to False. It always remains True. Earlier it was set to False in such a case. Same issue I faced when I create any connector. IF connector creations fails, “$?” remains True instead of False.

In below code sample:

  1. connector1 does not exist in my o365 exchange.
  2. “myrule” as not created as account has reached limit to no. of rules already.

code sample: $EncPassword = ConvertTo-SecureString -String ‘passowd’ -AsPlainText -Force Connect-ExchangeOnline -AppId ‘appid of mine’ -CertificateFilePath ‘/home/cert.pfx’ -CertificatePassword $EncPassword -Organization ‘myorgdomain.onmicrosoft.com’ write-host “connected” Get-InboundConnector ‘connector1’ if ($? -eq $True) { write-host “inbound connector exist” } else { write-host “inbound connector does not exist” } try { New-TransportRule -Name ‘myrule’ -FromScope NotInOrganization -SentToScope InOrganization -Enabled $true -Priority 0 -SetSCL -1 if ($? -eq $True) { write-host “inbound rule created” } else { write-host “inbound rule creation failed” } } catch { write-error “This is exception” }

Expected behavior

$? should be set to $False as both commands were actually failed. It was working before few days but suddenly behaviour looks changed.

Expected output:
connected
inbound connector does not exist
inbound rule creation failed

Actual behavior

$? always set to $True even both commands were actually failed. It was working before few days but suddenly behaviour looks changed.

actual output:
connected
inbound connector exist
inbound rule created

Error details

No response

Environment data

actual output:
connected
inbound connector exist
inbound rule created

Visuals

No response

Issue Analytics

  • State:open
  • Created a year ago
  • Comments:20 (14 by maintainers)

github_iconTop GitHub Comments

1reaction
mklement0commented, Oct 22, 2022

@jhoneill

Quick terminology note: There are two types of terminating errors:

  • A statement-terminating error (pipeline-terminating) occurs in an expression such as 1 / 0, an exception thrown by a .NET method, an reported by a compiled cmdlet with .ThrowTerminatingError() (which, as noted, you can also do in PowerShell code with $PSCmdlt.ThrowTerminatingError(), but it is cumbersome and rarely done in practice).

    • As the name implies, the scope of what is terminated by default is the current statement , meaning that execution continues with the next statement.
  • A script-terminating error (runspace-terminating, fatal) throw by default produces ; you can promote non-terminating errors to script-terminating ones with -ErrorAction Stop.

    • As the name (imperfectly) implies, the scope of what is terminated by default is the current script - at least - but actually the entire runspace the script and its callers). (In an earlier discussion, it was decided that “script” was good enough, and easier to understand than “runspace”).

    • You can also promote non-terminating errors to script-terminating ones with -ErrorAction Stop. You can additionally promote statement-terminating ones to script-terminating ones with $ErrorActionPreference = 'Stop.

This terminology isn’t official but it’s useful for making sense of what PowerShell does, and I’ve used it here and on Stack Overflow. (The names could be revisited, should they became part of the official docs; speaking of: A while back I’ve attempted a comprehensive overview of PowerShell’s error handling: Our Error Handing, Ourselves)

0reactions
mklement0commented, Oct 27, 2022

The bottom line with respect to authoring advanced functions or scripts is: If you want them to be well-behaved in terms of error handling (on par with binary cmdlets):

  • Only ever use $PSCmdlet.WriteError() and $PSCmdlet.ThrowTerminatingError() to report errors (non-terminating and statement-terminating ones, respectively).

    • This ensures that $? is set correctly (in the caller’s scope).
  • Silence or catch any errors that may result from calls to others commands in the implementation, and translate them into one of the above calls, as appropriate.

    • Failure to do so can make your function / script subject to premature termination when invoked with -ErrorAction Stop.

Note that while you’re still free to use throw in such a function in order to emit a script-terminating (fatal) error, this would amount to behavior that diverges from that of a binary cmdlet (which cannot emit such errors).


Here’s a sample function that demonstrates the necessary techniques:

Function Get-Foo {

  [CmdletBinding()]
  param(
    [string] $Path = '/',
    [string] $NumString = '0'
  )

  # Relay any non-terminating errors from PowerShell-native commands via
  # $PSCmdlet.WriteError(), and any terminating error (including exceptions
  # from expressions / .NET method calls) via $PSCmdlet.ThrowTerminatingError()

  try {

    # Stderr output need not necessarily be silenced - it isn't 
    # affected by -ErrorAction Stop / $ErrorActionPreference = 'Stop'
    & ($IsWindows ? 'cmd' : 'sh') ($IsWindows ? '/c' : '-c') 'echo stderr output >&2'

    # Handle *non-terminating* errors, as happens when $Path doesn't exist.
    # NOTE: 
    #  * If you don't care about any errors, use just -ErrorAction Ignore
    #  * 2>$null does NOT work, as it would abort processing right away
    #    when invoked with -ErrorAction Stop
    # Any cmdlet call that results in *statement-terminating* error would be 
    # handled in the `catch` block.
    (Get-Item $Path -ErrorVariable errs -ErrorAction SilentlyContinue).FullName
    # If errors were captured, relay them via $PSCmdlet.WriteError()
    if ($errs) {
      foreach ($err in $errs) { $PSCmdlet.WriteError($err) }
    }

    # Handle a potential terminating error.
    # If $NumString can't be parsed as an integer, the
    # resulting exception amounts to a statement-terminating error,
    # which is handled in the `catch` block.
    [int]::Parse($NumString)
    
  }
  catch {
    # Relay as a statement-terminating error.
    $PSCmdlet.ThrowTerminatingError($_)
    # Note: To emit a *script*-terminating error instead, use:
    #  throw $_
  }

  'Done.'

}

Some sample calls:

# No errors.
PS> Get-Foo; $?
stderr output
/
0
Done.
True
# Non-terminating error - note that $? is $False, function runs to completion.
PS> Get-Foo NoSuchFile; $?
stderr output
/
0
Done.
False
# Statement-terminating error - note that $? is $False and 'Done' doesn't get to print.
PS> Get-Foo / NotANumber; $?
stderr output
/
Get-Foo: Exception calling "Parse" with "1" argument(s): "The input string 'NotANumber' was not in a correct format."
False
# Both types of errors - note that $? is $False and 'Done' doesn't get to print.
PS> Get-Foo NoSuchFile NotANumber; $?
stderr output
Get-Foo: Cannot find path '/Users/mklement/Desktop/pg/NoSuchFile' because it does not exist.
Get-Foo: Exception calling "Parse" with "1" argument(s): "The input string 'NotANumber' was not in a correct format."
False
# Effect of -ErrorAction Stop on a *non-terminating* error:
#  Becomes *script*-terminating (fatal):
#  * Instantly aborts the function-internal processing.
#  * $? result does not get to print, because execution was aborted overall.
PS> Get-Foo NoSuchFile -ErrorAction Stop; $?
stderr output
Get-Foo: Cannot find path '/Users/mklement/Desktop/pg/NoSuchFile' because it does not exist.
# Effect of -ErrorAction Stop on a *statement-terminating* error:
#  NO effect - $? still gets to print - execution continues.
PS> Get-Foo / NotANumber -ErrorAction Stop; $?
stderr output
/
Get-Foo: Exception calling "Parse" with "1" argument(s): "The input string 'NotANumber' was not in a correct format."
False
Read more comments on GitHub >

github_iconTop Results From Across the Web

How do I get the effect and usefulness of "set -e" inside a ...
If a compound command or shell function executes in a context where -e is being ignored, none of the commands executed within the...
Read more >
Why does this compound command {...} not exit on error ...
You run with set -e active. This will cause the shell to exit if any command returns a non-zero exit status (broadly speaking)....
Read more >
How to stop the bash script when a condition fails?
Here it prints Obvious error because its false on the left and exits the console which is what I wanted. true || echo...
Read more >
Error handling in playbooks
When Ansible receives a non-zero return code from a command or a failure from a module, by default it stops executing on that...
Read more >
The truth about Linux true and false commands
In this post, we'll look at how the true and false commands work and how you might put them to use on the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found