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.
Related
I have a job that is throwing an exception and I'd like to have the calling process report the exception call stack in a debug log while presenting a useful error to the user.
Contents of test.ps1:
Function foo {
throw "bar"
}
Try {
foo
} catch {
throw
}
Calling code:
Start-job test -filepath test.ps1
Receive-job test
$error[0] | select *
I'd like the output to indicate that the error occurred in test.ps1, but it just says that it occurred in the scriptblock in the exception output for the call stack.
I could see this in 2 ways:
1) updating the call stack shown to indicate the script name instead of just scriptblock, or
2) updating the exception message to have the script name in the exception message while keeping the existing call stack in the exception.
it is not an exact answer, I know.
Here are some throws that helped me a lot so far. See if you can use them:
$PSItem.InvocationInfo
contains additional information collected by PowerShell about the function or script where the exception was thrown. EG:
$PSItem.InvocationInfo | Format-List *
Others:
$PSItem.Exception.StackTrace
$PSItem.Exception.Message
$PSItem.Exception.InnerException
update: to throw a custom message i did:
catch {
$message = "Something bad happened in the inner script. See stack: $PSItem.Exception.InnerException"
throw $message
}
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
Below code keep executing when error arises,
foreach($url in Get-Content $urlsDir)
{
try
{
// do something
// declare X
}
catch
{
// write host or soemthing with exception
continue
}
finally
{
// dispose X
}
}
but when I put this code in RunWithElevatedPrivileges, it completely stops on first error and won't continue execution,
[Microsoft.SharePoint.SPSecurity]::RunWithElevatedPrivileges({
# Iterate through all webs in a text file
foreach($url in Get-Content $urlsDir)
{
try
{
// do something
// declare X
}
catch
{
// write host or soemthing with exception
continue
}
finally
{
// dispose X
}
}
});
It could be related to whether or not you've specified an ErrorAction, I'm not sure how this relates to try catch though. I have done something similar where my foreach would not stop unless explicitly stated.
Basically you need to specify what should happen if an error occurs on each call, as so
Call-Something $SomeParam -ErrorAction Stop
and unless you specify it as above for each call or in the start of your script as below errors can be silently ignored.
// at the start of your script
$ErrorActionPreference = "Stop"
For more information you can read about ErrorAction in powershell here for instance
https://blogs.msdn.microsoft.com/kebab/2013/06/09/an-introduction-to-error-handling-in-powershell/
And according to msdn these are the valid values:
Stop: Displays the debug message and stops executing. Writes an error to the console.
Inquire: Displays the debug message and asks you whether you want to continue. Note that adding the Debug common parameter to a command--when the command is configured to generate a debugging message--changes the value of the $DebugPreference variable to Inquire.
Continue: Displays the debug message and continues with execution.
SilentlyContinue: No effect. The debug message is not (Default) displayed and execution continues without interruption.
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.
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.