Detecting a .NET exception in PS - powershell

I am making several calls to .NET classes in order in my PS script like this:
[class1]::MethodA()
[class1]::MethodB()
if(/*check if last method threw an error*/)
{
"MethodB failed! Exiting."
return
}
[class2]::MethodC()
[class2]::MethodD()
I really want to check whether there was an exception from MethodB before moving on. How do check for this?

Use a try/catch block:
try
{
[class1]::MethodB()
}
catch
{
Write-Host "MethodB failed!"
exit
}

Related

How to use variable to store error type for catch block?

I want to handle exceptions for every command in my script. To that end I'm writing a function for try..catch. This function has two parameters: $command, the command to be executed, and $errorType, the optional error type specified in the catch block.
function tryCatch ($command, $errorType) {
try {
$command
} catch [$errorType] {
# function to be called if this error type occurs
catchError
}
}
However I can't figure out how to pass the error type to the catch block as a variable. I get this error:
At \script.ps1:233 char:25
+ try {$command} catch [$errorType] {catchError}
+ ~
Missing type name after '['.
I've tried to get around it but nothing seems to work. Is there a way to do this?
I don't think you can use a variable to specify a type to catch. What you can do is use a condition inside the catch block:
function Invoke-Something($command, [Object]$errorType) {
try {
$command
} catch {
if ($_.Exception -is $errorType) {
catchError
} else {
# do something else
}
}
}
Invoke-Something 'whatever the command' ([System.IO.IOException])
Short answer, I don't believe you can do what you're trying. Lemme walkthrough just to make sure I understand the scenario.
The parameter for the catch block is one or more exception types like System.Net.WebException:
try {
$wc = new-object System.Net.WebClient
$wc.DownloadFile("http://www.contoso.com/MyDoc.doc")
} catch [System.Net.WebException], [System.IO.IOException] {
"Unable to download MyDoc.doc from http://www.contoso.com."
} catch {
"An error occurred that could not be resolved."
}
Saying this just to level set.
Now, we typically see those types hard coded, but you'd like to assign the type in the catch block dynamically as a variable:
try {
...
} catch $exceptionType {
catchError
}
The problem is catch needs to be followed by an exception type not a variable. That variable will be (if it hosts an exception type) of type RuntimeType. You can try to finagle the exception type out of the variable with GetType() or something like that. Net-net, it just won't work.
Put a generic catch block (with no type) in your script-function-whatever then pass the values to your catch function, and have the branching logic in there do whatever you're trying to do.
try { ... } catch { catchError -Command $command -Exception $_ }
And, if you don't want to pass the whole exception object, you can use...
$_.FullyQualifiedErrorId

Why doesn't Pester catch errors using a trap

I'm wondering why I get the following behaviour when running this script. I have the script loaded in PowerShell ISE (v4 host) and have the Pester module loaded. I run the script by pressing F5.
function Test-Pester {
throw("An error")
}
Describe "what happens when a function throws an error" {
Context "we test with Should Throw" {
It "Throws an error" {
{ Test-Pester } | Should Throw
}
}
Context "we test using a try-catch construct" {
$ErrorSeen = $false
try {
Test-Pester
}
catch {
$ErrorSeen = $true
}
It "is handled by try-catch" {
$ErrorSeen | Should Be $true
}
}
Context "we test using trap" {
trap {
$ErrorSeen = $true
}
$ErrorSeen = $false
Test-Pester
It "is handled by trap" {
$ErrorSeen | Should Be $true
}
}
}
I then get the following output:
Describing what happens when a function throws an error
Context we test with Should Throw
[+] Throws an error 536ms
Context we test using a try-catch construct
[+] is handled by try-catch 246ms
Context we test using trap
An error
At C:\Test-Pester.ps1:2 char:7
+ throw("An error")
+ ~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (An error:String) [], RuntimeException
+ FullyQualifiedErrorId : An error
[-] is handled by trap 702ms
Expected: {True}
But was: {False}
at line: 40 in C:\Test-Pester.ps1
40: $ErrorSeen | Should Be $true
Question
Why is the trap{} apparently not running in the final test?
Here are two solutions to the problem based on the comments/suggested answers from #PetSerAl and #Eris. I have also read and appreciated the answer given to this question:
Why are variable assignments within a Trap block not visible outside it?
Solution 1
Although variables set in the script can be read within the trap, whatever you do within the trap happens to a copy of that variable, i.e. only in the scope that is local to the trap. In this solution we evaluate a reference to $ErrorSeen so that when we set the value of the variable, we are actually setting the value of the variable that exists in the parent scope.
Adding a continue to the trap suppresses the ErrorRecord details, cleaning up the output of the test.
Describe "what happens when a function throws an error" {
Context "we test using trap" {
$ErrorSeen = $false
trap {
Write-Warning "Error trapped"
([Ref]$ErrorSeen).Value = $true
continue
}
Test-Pester
It "is handled by trap" {
$ErrorSeen | Should Be $true
}
}
}
Solution 2
Along the same lines as the first solution, the problem can be solved by explicitly setting the scope of the $ErrorSeen variable (1) when it is first created and (2) when used within the trap {}. I have used the Script scope here, but Global also seems to work.
Same principle applies here: we need to avoid the problem where changes to the variable within the trap only happen to a local copy of the variable.
Describe "what happens when a function throws an error" {
Context "we test using trap" {
$Script:ErrorSeen = $false
trap {
Write-Warning "Error trapped"
$Script:ErrorSeen = $true
continue
}
Test-Pester
It "is handled by trap" {
$ErrorSeen | Should Be $true
}
}
}
According to this blog, you need to tell your trap to do something to the control flow:
The [...] thing you notice is that when you run this as script, you will receive both your error message and the red PowerShell error message.
. 'C:\Scripts\test.ps1'
Something terrible happened!
Attempted to divide by zero.
At C:\Scripts\test.ps1:2 Char:3
+ 1/ <<<< null
This is because your Trap did not really handle the exception. To handle an exception, you need to add the "Continue" statement to your trap:
trap { 'Something terrible happened!'; continue }
1/$null
Now, the trap works as expected. It does whatever you specified in the trap script block, and PowerShell does not get to see the exception anymore. You no longer get the red error message.

Switching on SqlException.Number in Powershell

I have this catch block in my Powershell script.
catch [System.Data.SqlClient.SqlException]
{
Write-Host "$_"
Exit 2
}
I would really like to be able to switch on the error number.
I know atleast in C# there's a property on the SqlException called number. Isn't that also true for Powershell?
If the property is there, how do I access it?
Thank you very much in advance
You should be able to access it in your catch block using:
$_.Exception.Number
i.e.
catch [System.Data.SqlClient.SqlException]
{
Write-Host $_.Exception.Number
Exit 2
}

Catch all Powershell errors using Try/Catch/Finally

I have a 1500+ lines Powershell script that has been enhanced to log progress and errors to Azure Table Storage.
The problem is that not all errors are logged.
Is this the easiest way to accomplish this without changing the script too much and catching all errors?
$ErrorActionPreference = "Stop"
function a
{
"a"
}
function b
{
"b"
Get-Content foo
}
function c
{
"c"
}
Try
{
a
b
c
}
Catch
{
"Catch & Log Error"
$Error[0].Exception
}
Finally
{
"The End"
}
UPDATE
The output is:
a
b
Catch & Log Error
Cannot find path 'C:\foo' because it does not exist.
The End
You can place this at the top of your script:
$ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
... all errors will then 'act' like terminating errors & be caught by a try...catch block.

Powershell try/catch rethrow not propagating error (Powershell 2.0)

I have a try-catch statement within a try-catch statement. The inner catch catches the error, but the throw does not cause the error to be caught in the out catch statement. Breifly, my script is formatted similar to:
$ErrorPreference = "Stop"
try
{
getStuffFromDB
putStuffInDB
}
catch
{
write-host ("Error: " + $error[0])
}
function getStuffFromDB
{
try
{
-- database query statement
}
catch
{
throw
}
finally
{
close connection and clean up
}
}
function putStuffInDB
{
try
{
-- database insert statements statement
}
catch
{
throw
}
finally
{
close connection and clean up
}
}
When I ran the script there were no errors, but I noticed the SQL Server database that I was attempting to populate was missing data. When I re-ran the script in debug, the function 'putStuffInDB' had an error that was caught in the catch block. But when I stepped the message did not get 'thrown' to the outer catch block, but processed the finally block and terminated.
I am obviously missing something that I am not seeing. I have used the construct in C# in the past and never had issues with errors being 'passed' to the outer catch block.
I am not seeing that behavior. I ran the following in PowerShell ISE and it produces the expected results. Is it possible that the errors in the database were not in fact thrown as exceptions? I believe in SQL Server for example, certain errors under a given error level are not thrown as exceptions back to the ADO.NET provider.
$ErrorActionPreference = 'Stop'
function Throw1 {
try {
Write-Host "Throw1.Try"
throw "Error from Throw1"
}
catch {
Write-Host "Throw1.Catch"
throw
}
finally {
Write-Host "Throw1.Finally"
}
}
function Throw2 {
try {
Write-Host "Throw2.Try"
throw "Error from Throw2"
}
catch {
Write-Host "Throw2.Catch"
throw
}
finally {
Write-Host "Throw2.Finally"
}
}
function Test {
try {
Throw1
Throw2
}
catch {
Write-Host $error[0]
}
}
Test
Produces the following:
Throw1.Try
Throw1.Catch
Throw1.Finally
Error from Throw1
The variable you want to set is $ErrorActionPreference, not $ErrorPreference.
(Josh did set the right variable.)
I realized that the problem was of my own doing. In the POSH functions to create the SQLServer entries I returned the primary key of the data set created. The design of the functions was such that the function would return the primary key. The design mistake was that I put a return statement in the finally block which superceded the throw back to the outer catch. I have changed the design removing the return statement. The try/catch now works correctly.