What options are available for handling exceptions from class methods?
i.e. with advanced functions we can do this:
function Test-Error {
[CmdletBinding()]
Param()
throw "Error!"
}
"1. Error's about to happen"
Test-Error -ErrorAction SilentlyContinue
"1. Error just happenned (you didn't see it)"
"2. Error's about to happen"
Test-Error -ErrorAction Stop
"2. Error just happened (you saw it; but you won't see this)"
To get the same behaviour when calling a class method, I could do this:
class ErrorTest
{
ErrorTest(){}
TestError(){ throw "Error!" }
}
[ErrorTest]$e = [ErrorTest]::new()
"1. Error's about to happen"
$ErrorActionPreference = 'SilentlyContinue'
$e.TestError()
"1. Error just happened (you didn't see it)"
"2. Error's about to happen"
$ErrorActionPreference = 'Stop'
$e.TestError()
"2. Error just happened (you saw it; but you won't see this)"
However I don't like changing ErrorActionPreference. Is there a better solution / a standard way of dealing with such scenarios?
Update
Per TheIncorrigible1's comment, also tried using this method of throwing exceptions:
function Test-Error {
[CmdletBinding()]
Param()
$badObject = 'whatever object caused the issue'
$actualException = [System.NotSupportedException]::new('I take exception to your use of this function')
$errorRecord = [System.Management.Automation.ErrorRecord]::new($actualException, 'ExampleException1', [System.Management.Automation.ErrorCategory]::InvalidData, $badObject)
$PSCmdlet.ThrowTerminatingError($errorRecord)
}
Related
I have a quite big project with many functions in there.
Just 2 questions:
What would be here the "best practice" regarding Error-Handling? To use a local handling per Function, or use only one Error-Logging in the Main-Section?
The tricky part (!), let's have a look at the strange Error behaviour in the function F_XXX: only $_ delivers an error message, $Error[0] is here empty! Strange enough, when I start the function F_XXX separately (cut out from the module), it behaves as expected, it means: $Error[0] gives an error back. The code:
Blockquote
$ErrorActionPreference = "Stop"
Function F1
{
try
{
# do something
}
catch
{
# 1. cascade Error to Main?
# throw $Error[0].InnerException
# or
# local Error-Logging?
write-MyErrorLogging -message $Error[0].InnerException
}
}
Function F2
{
try
{
# do something
}
catch
{
# 1. cascade Error to Main?
# throw $Error[0].InnerException
# or
# local Error-Logging?
write-MyErrorLogging -message $Error[0].InnerException
}
}
Function F_XXXXXX
{
try
{
cls
write-host "The install data is copied.."
$share = "\\my_wrong_path\sql_sources\"
Copy-Item $share -Destination $installDrive -Force -Recurse
}
catch
{
$Error[0] #here is nothing!
$null -eq $Error[0] # here true
$_.Exception # only here the error-message: wrong path!
}
}
Blockquote
# here Main
try
{
F1
F2
F_XXXXXX
}
catch
{
write-MyErrorLogging -message $Error[0].InnerException
}
Blockquote
Inside a catch block, it's best to avoid $Error[0], given that the error at hand is reliably reflected in the automatic $_ variable.
If you do need access to previous errors via the automatic $Error variable, use $global:Error inside modules - see the bottom section for details.
Unless you need to perform additional actions when an error occurs, you can let a script-terminating (fatal) error (which your $ErrorActionPreference = "Stop" statement turns all errors in your code into) bubble up the call stack until it is either caught by a try / catch statement or, in the absence of one, terminates the entire call stack (i.e., the scripts and its callers).
If you do need to perform additional actions, use try / catch, and place the actions inside the catch block (as well as potential cleanup actions in a finally block), followed by re-throwing the error simply by calling throw without an argument.
Thus, you can make do with a single try / catch statement in the top-level scope of your script:
# Turn all errors in this and descendant scopes into
# script-terminating (fatal) ones.
$ErrorActionPreference = 'Stop'
# ... other function definitions, without try / catch
# Top-level code that calls the other functions and catches
# any errors.
try
{
F1
F2
F_XXXXXX
}
catch
{
write-MyErrorLogging -message $_.InnerException
}
The automatic $Error variable in modules:
Strangely, up to at least PowerShell 7.2.3 (current as of this writing):
Errors occurring in modules - just like ones occurring outside modules - are recorded in the $Error variable that exists in the global scope.
However, a seemingly unused, module-local copy of $Error exists, which shadows the global variable.
The workaround is to use use $global:Error from inside modules.
The behavior suggests a bug, given that the module-local copy is seemingly never touched and serves no apparent purpose.
I know, there are hundreds of pages that address the -in my opinion- strange way that Powershell handles return values from functions, and I must have visited about half of them ;=)
This particular one drives me nuts.
Consider the following:
I'm doing a call to a function with one parameter (call is not done from within another function):
$result = getVMinfo($vm)
The function getVMinfo looks like this :
function getVMinfo {
param (
[string]$vm
)
try {
Get-WmiObject -Class Win32_DiskDrive -Computername $vm -ErrorAction Stop
}
catch {
$ErrorReturn = $_.Exception.Message
}
if ($ErrorReturn) {
Write-Host "Error =" $ErrorReturn
return $ErrorReturn
}
}
Looks simple enough, and other functions do work when returning a value in this way.
Now, if I run the script, The write-Host bit in the catch does show me that $ErrorReturn is filled with a string (tested that with $ErrorReturn.GetType() ).
However, $result in the calling statement is always empty.
I have tried many suggestions, like creating an array, and use the .Add() to add the errorstring to the array, and then return the array. Nothing seems to work.
I am really at a loss here. Don't understand what I am doing wrong.
Please, help me !
I need an example that use try..catch..finally clause where the finally is NECESSARY vs try..catch clause where finally is optional. The example below only demonstrated that finally is optional because with or without it, it won't make any different to my output.
My Example (note: $ErrorActionPreference is Continue):
try {
$value = 5 / 0
} catch {
Write-Output "illegal operation"
}
$t = Get-Date
Write-Output ("operation is done at " + "$t")
The reason is I need to know where finally clause become necessary vs just put finally clause no matter what.
A finally clause is just a logical construct saying "this statement or group of statements should always be run at the end of the try block, regardless of whether there was an error or not". It tells people reading the code that there is a logical connection between the code in the try and finally blocks (e.g. opening and closing a database connection). However, beyond that there is no essential difference between
try {
5 / 0
} catch {
'illegal operation'
}
'continued'
and
try {
5 / 0
} catch {
'illegal operation'
} finally {
'continued'
}
You can find some discussion of the subject here.
I think the only way it would make a difference is if you return or exit in the try block:
try {
'foo' # <-- displayed
exit
} finally {
'bar' # <-- displayed
}
'baz' # <-- not displayed
but maybe something like that is just bad design.
I have a hashtable and I'm trying to make an if statement right now that will check to see if what went through the hashtable matched anything within it.
$netVerConv = #{
'v2.0' = "lib\net20";
'v3.0' = "lib\net30";
'v3.5' = "lib\net35";
'v4.0' = "lib\net40";
'v4.5' = "lib\net45";
}
$target = $netVerConv.Get_Item($netVerShort)
if () {
}
Above is the area of my code I'm working with, the target variable runs $netVerShort through the $netVerConv hashtable using a Get_Item command. The if statement that I've laid the framework for would check to see if netVerShort matched anything within the hashtable and if it didn't it will stop the program, which I know how to do with a simple exit command.
The other suggestions will work in your specific scenario but in general you should use the ContainsKey() method to see if a key exists in the hashtable. For instance the hashtable value could be $null or $false in which case, testing via the result of Get_Item() or more simply Item[$netVerShort], will return a false negative. So I recommend this approach for testing existence of a key in a hashtable. It is also more obvious what your intent is:
if (!$netVerConv.ContainsKey($netVerShort) {
...
}
How about this:
if( $target -eq $null ) {
echo "Didn't Match"
exit
}
Another option:
if (-not ($target = $netVerConv.Get_Item($netVerShort)))
{
Write-Error "Version $netVerShort not found"
Exit
}
You could also re-factor that as a Switch
$target =
Switch ($netVerShort)
{
'v2.0' {"lib\net20"}
'v3.0' {"lib\net30"}
'v3.5' {"lib\net35"}
'v4.0' {"lib\net40"}
'v4.5' {"lib\net45"}
Default {
Write-Error "Version $netVerShort not found"
Exit
}
}
I use trap to write errors to file, and want write line number where error ocured.
$_.Exception.StackTrace is not answer.
Where I can get line number of error? Maybe some predefined variable?
You can retrieve the line number from the InvocationInfo object on $_. For example, the script...
"Hello, World!"
function foo() {
trap [Exception] {
$_.InvocationInfo.ScriptLineNumber
$_.InvocationInfo.OffsetInLine
continue;
}
[reflection.assembly]::loadfrom("C:\")
}
foo
... generates the output:
Hello, World!
10
34
You should use $_.InvocationInfo properties, for example: ScriptName, ScriptLineNumber, OffsetInLine, Line.
For example to format position info in Visual Studio style:
trap {
Write-Host "$($_.InvocationInfo.ScriptName)($($_.InvocationInfo.ScriptLineNumber)): $($_.InvocationInfo.Line)"
}
It will write something like:
C:\TEMP\test2.ps1(8): Get-Item missing
Also, you can just use $_.InvocationInfo.PositionMessage, see this post:
How can I get powershell exception descriptions into a string?
If you just want to find an error line after the script has been executed, you can view $Error array. $Error[0] corresponds to the last error.
More details here.