JNK
JNK

Reputation: 65187

$Error variable is $Null but $_ contains error in Catch

I have PS module that contains a number of scripts for individual functions. There is also a "library" script with a number of helper functions that get called by the functions used in the module.

Let's call the outer function ReadWeb, and it uses a helper function ParseXML.

I encountered an issue this week with error handling in the inner helper ParseXML function. That function contains a try/catch, and in the catch I interrogate:

$Error[0].Exception.InnerException.Message

...in order to pass the error back to the outer scope as a variable and determine if ParseXML worked.

For a particular case, I was getting an indexing error when I called ReadWeb. The root cause turned out to be the $Error object in the Catch block in ParseXML was coming back $Null.

I changed the error handling to check for a $Error -eq $Null and if it is, use $_ in the Catch to determine what the error message is.

My question is: what would cause $Error to be $null inside the Catch?

Upvotes: 9

Views: 3118

Answers (3)

mklement0
mklement0

Reputation: 439317

Note: The following applies to Windows PowerShell as well as to PowerShell (Core) versions up to a least v7.3.7 (current as of this writing).

  • Accessing the error at hand inside a catch block should indeed be done via the automatic $_ variable.

  • Inside a module, $Error isn't $null, but surprisingly is an always-empty collection (of type System.Collections.ArrayList); therefore, $Error[0] - which in catch blocks outside modules is the same as $_ - is unexpectedly $null in modules:

    • What technically happens is that modules have an unused, module-local copy of $Error, which shadows (hides) the true $Error variable that is located in the global scope.

      • When an error is automatically recorded from inside a module, however, it is added to the global $Error collection - just like in code running outside of modules.

      • As it turns out, the unused, useless module-level copy of $Error is an accidental remnant of functionality that was never implemented: see this answer for the history, and GitHub issue #20458 for a discussion about fixing the problem.

    • Therefore, a workaround is to use $global:Error instead (which is only necessary if you need access to previous errors; as stated, for the current one, use $_).

The following sample code illustrates the problem:

$Error.Clear()

# Define a dynamic module that exports sample function 'foo'.
$null = New-Module {

  Function foo {
    try {
      1 / 0     # Provoke an error that can be caught.
    }
    catch {
      $varNames =   '$Error', '$global:Error', '$_'
      $varValues =   $Error,   $global:Error,   $_
      foreach ($i in 0..($varNames.Count-1)) {
        [pscustomobject] @{
          Name = $varNames[$i]
          Type = $varValues[$i].GetType().FullName
          Value = $varValues[$i]
        }
      }
    }
  }

}

foo

Output; note how the value of $Error is {}, indicating an empty collection:


Name          Type                                     Value
----          ----                                     -----
$Error        System.Collections.ArrayList             {}
$global:Error System.Collections.ArrayList             {Attempted to divide by zero.}
$_            System.Management.Automation.ErrorRecord Attempted to divide by zero.

Upvotes: 4

Mat M
Mat M

Reputation: 1894

Edit: answer based on Powershell 3.

$error is an automatic variable handled by Powershell: 3rd § of the LONG DESCRIPTION in about_Try_Catch_Finally.

It is considered as the context of the Catch block, thus being available as $_. Since Catch block is a different block than Try, the $error automatic variable is reset and valued $null.

Upvotes: 0

Start-Automating
Start-Automating

Reputation: 8367

$error and try / catch are different beasts in PowerShell.

try / catch catches terminating errors, but won't catch a Write-Error (cause it's non terminating).

$error is a list of all errors encountered (including ones swallowed up when -ErrorAction silentlycontinue is used).

$_ is the current error in a try/catch block.

I'd guess that your underlying function calls Write-Error, and you want that to go cleanly into a try/catch. To make this be a terminating error as well, use -ErrorAction Stop.

Upvotes: 0

Related Questions