Reputation: 65187
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
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
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
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