I am migrating a very large function based script to classes, and trying to up my error trapping game at the same time. The code runs either on remote machines by way of jobs, or automatically & transparently at user logon, with log files the only means of communicating issues. The current (function based) version has some bugs that result in unhandled exceptions, which means the issues never make it into a log. The code reads a user provided XML file for data to process, and I need to provide meaningful feedback so that the data or data structure can be addressed. My goals in general are... 1: Catch ALL exceptions. 2: Visually differentiate between "expected" exceptions; where I provide some useful information in the log to allow the user to troubleshoot, and "unexpected" exceptions; where I provide script name and line number to help me track down bugs. 3: Over time move as many items as possible from "unexpected" to "expected".
Given those goals, there are a couple of things I am contemplating, and I feel like I don't know enough to be sure I am correct in my decision making.
1: $ErrorActionPreference=Stop Historically the advice has been NOT to set a global error action, and do it instead on a case by case basis. But given that every single situation where I CAN set it to stop I will, it seems like maybe this advice is really more about quick "scripts", whereas I am basically using PS to write a "program", and a global stop is the better answer?
2: Given a scenario like this, where I have an exception in a utility method (no I am not implementing a class here, I am trying to keep things simple for the example, but hopefully the concept is still clear)...
$path = '\\Server\InvalidFolder|'
try {
Test-Path $path -errorAction:stop
} catch [System.ArgumentException] {
throw [Custom.ExpectedException] "Error: Invalid characters in path - $invalidCharacters"
} catch {
# Generic error trap
throw [Custom.UnexpectedException] "Unexpected Error: $($_.ScriptStackTrace) $($_.Exception.Message)"
}
I feel like what I want/need to do is create some custom error types, so I can catch the different Powershell errors and convert them to my types, which I then throw, and the calling code has its own try/catch that then formats and pushes to the log. Is that the right approach, or am I missing some better option? 3: Assuming this is even possible/advisable, how does one create custom exception types? My Google-Fu has failed me and that has me wondering if I can't find anything because it can't/shouldn't be done.
EDIT: As far as the custom exception types go, this looks to work fine.
class PxException : Exception {
PxException() {
}
PxException([string] $message): base($message) {
}
PxException([string] $message, [Exception] $inner) : base($message, $inner) {
}
}
$path = '\\Server\InvalidFolder|'
try {
Test-Path $path -errorAction:stop
} catch [System.ArgumentException] {
Write-Host "Error: $($_.Exception.Message)"
throw [PxException] "Px expected exception"
} catch {
# Standard Px error trap
Write-Host "Unexpected Error: $($_.ScriptStackTrace)"
Write-Host "$($_.Exception.Message)"
}
Still wondering it it's the RIGHT way, but it is obviously A way, and that's a good sign.
EDIT 2: So, this works
class PxException : Exception {
PxException() {}
PxException([string] $message) : base($message) {}
PxException([string] $message, [Exception] $inner) : base($message, $inner) {}
}
But when I try to implement InvocationInfo in my custom type, it barfs...
class PxException : Exception {
PxException() {}
PxException([string] $message) : base($message) {}
PxException([string] $message, [InvocationInfo] $InvocationInfo) : base($message, $InvocationInfo) {}
PxException([string] $message, [Exception] $inner) : base($message, $inner) {}
}
[InvocationInfo] is not recognized as a type. Even if I have using namespace System;, which is where I think InvocationInfo is defined.
EDIT 3: GetType() to the rescue. This works.
PxException([string] $message, [System.Management.Automation.InvocationInfo] $InvocationInfo) : base($message, $InvocationInfo) {}
No using required. At least as far as getting rid of the error in the exception class definition. But
$InvocationInfo = $_.InvocationInfo
throw [PxException] "Px expected exception" $InvocationInfo
Does,'t work. Gotta figure out why I can't throw an exception with two arguments. But... progress.