My PowerShell exceptions aren't being caught - powershell

Powershell v2:
try { Remove-Item C:\hiberfil.sys -ErrorAction Stop }
catch [System.IO.IOException]
{ "problem" }
catch [System.Exception]
{ "other" }
I'm using the hibernation file as an example, of course. There's actually another file that I expect I may not have permission to delete sometimes, and I want to catch this exceptional situation.
Output:
output
and yet $error[0] | fl * -Force outputs System.IO.IOException: Not Enough permission to perform operation.
Problem: I don't see why I'm not catching this exception with my first catch block, since this matches the exception type.

When you use the Stop action PowerShell changes the type of the exception to:
[System.Management.Automation.ActionPreferenceStopException]
So this is the what you should catch. You can also leave it out and catch all types.
Give this a try if you want to opearte on different excdeptions:
try
{
Remove-Item C:\pagefile.sys -ErrorAction Stop
}
catch
{
$e = $_.Exception.GetType().Name
if($e -eq 'ItemNotFoundException' {...}
if($e -eq 'IOException' {...}
}

I like the Trap method for global error handling, as I wanted to stop my scripts if unhandled exceptions were occurring to avoid corrupting my data. I set ErrorActionPreference to stop across the board. I then use Try/Catch in places where I may expect to see errors in order to stop them halting the scripts. See my question here Send an email if a PowerShell script gets any errors at all and terminate the script

Related

PowerShell Forms, Try/Catch Not Stopping Application Properly

Problem
I am working with PowerShell to create an application that uses a GUI created using Forms. While trying to maintain a modular programming style, I have more or less slammed into a wall that I cannot seem to find a way around, and was hoping the community could help.
In my application, I am trying to create custom error catching utilizing try/catch statements, but have found that when I call upon a factory function and that function has an error, the application will display said error as intended, but will continue to process as if it was successful...
I have even tried using exit and break, but end up with an "Unhandled Exception" error that immediately follows my custom error. I have even set $ErrorActionPreference = "Stop" but still get this problem.
Here is a simplified sample of my code so that you can see what is happening:
Button_Click = {
ValidatePathing -BackupPath $BackupPath.Text
If (!(Test-Path $BackupPath.Text)) {
# Attempt to Create it
}
# bunch of code
}
Function ValidatePathing ([string]$BackupPath){
Try {
If (!$BackupPath) {
throw "customError1"
}
If ($BackupPath -match "\|") {
throw "customError2"
}
}
Catch {
If ($_.Exception.Message -eq "customError1") {
[System.Windows.Forms.MessageBox]::Show("some message")
}
ElseIf ($_.Exception.Message -eq "customError2") {
[System.Windows.Forms.MessageBox]::Show("some message")
}
}
}
Unfortunately, when the code hits a section like ValidatePathing and a custom error happens, the error message will display as expected, but the code will then step over/through the section and continues to process the rest of the code, despite the error.
Question
Does anyone know PowerShell well enough to explain how to properly handle a situation like this so that the code stops running after hitting a nested error like this?
To be sure your Try is being handed a terminating error as it's designed to handle, utilize $PSCmdlet.ThrowTerminatingError(). This can take an ErrorRecord object as a constructor, allowing you to catch specific exception types.

Determine if an exception was thrown from try block when you execute the finally

UPDATE May 2021 - When I originally asked this question, the core thing that made this question relevant (for me) was that when rethrowing an exception from a catch via a simple throw (by itself), the original exception stack was lost. So that made using a catch to detect if an exception was thrown off-limits.
This incorrect loss-of-stack behavior was fixed sometime between when the question was asked (2017) and now. So a simple catch and rethrow (call throw with no other arguments) is now the most straightforward way to detect an exception was thrown from the finally block. Thanks to #JohnLBevan for his answer letting me know that rethrowing from the catch was no longer problematic.
ORIGINAL QUESTION:
I've got some code structured like this
try{
...
}
finally{
...
<code that may throw>
}
Of course one should generally avoid code that throws in a finally. But it can happen. And when it does, one unfortunate side effect is that the original exception is lost. So the first thing I'd like to do in the finally is log information about the exception thrown in the try, if one was thrown.
But how can I determine if an exception did occur in the try block, once I'm in the finally? Is there a slick way? I don't want to catch the exception in a catch. I can set a boolean at the end of the try which would indicate an exception was not thrown, but I'm not a big fan of having to do that every time. That would look like this:
$exceptionThrown = $true
try{
...
$exceptionThrown = $false
}
finally{
<if $exceptionThrown log info about it>
...
<code that may throw>
}
Can I do better?
If the only reason you're avoiding the catch block is because you don't want to affect the stack trace, you can use it then rethrow the error with the original line number by using throw with no arguments; thus rethrowing the original exactly as if you'd not used the catch block. For example:
$exceptionInfo = $null
try {
1/0 # cause some error
} catch {
$exceptionInfo = $_.Exception # you could set a flag / whatever here; without knowing your requirement I can't advise further
throw # you said you didn't want to catch it... but if you just don't want to impact the stack trace this is fine as we're rethrowing the original exception; not throwing a new one
} finally {
if ($null -eq $exceptionInfo) {
Write-Information 'Completed Successfully' -InformationAction Continue
} else {
Write-Warning "An error occurred $exceptionInfo"
}
}
If you don't want to use a catch block and don't want to use some variable you've defined to flag whether an exception's occurred, you could use $Error; though you may need to clear it first as it will contain all errors which have been raised in the current session...
$Error.Clear()
try {
1/0
} finally {
if ($Error.Count) {
Write-Warning "An error occurred $($Error[0])"
} else {
Write-Information 'Completed Successfully' -InformationAction Continue
}
}
Generally you shouldn't need to determine whether something was successful in a finally block though; rather:
If you have logic that you only want to occur if the command is successful, place it in the TRY block after the line that may cause an exception.
If you have logic that you only want to occur if the command hits an error, place it in the catch block (and use throw if you want to rethrow the original exception so that it still bubbles up afterwards).
If you have logic that should run in either case, that's where the finally block comes in. Sometimes you may need to know the state of some component here (e.g. is a connection to your database still open) and that state may have changed due to the exception... If that's the case, normally such components should provide their own flags.
Below's a rough illustration; the actual example's a bit poor as I couldn't think of a good & succinct real world example scenario; but hopefully you get the idea.
try {
$con = Get-MyDbConnection
New-DbRecord -Connection $con -Data $data
} catch {
Write-Log $_.Exception
} finally {
if (($null -ne $con) -and ($con.IsConnected)) {
$con.Disconnect()
}
}
This page about Powershell 5.1 doesn't explicitly cover the case of throwing an exception inside a "finally" block, but says that Powershell behavior is similar to C# behavior. And the accepted answer to this SO question about C# behavior indicates that:
the code in the "finally" block beyond the point where the exception was thrown is not executed, and
if the "finally" block was executed during the handling of an earlier exception, then that first exception is lost
So I think what you really want is something like this:
try { ... set flag ... }
catch { ... adjust flag ... }
finally { ... check flag ... }
The catch block only executes if there was an exception thrown in the "try" block, but the "finally" block happens in every case (and can tell whether or not an exception was thrown in the original "try" block by checking the value of the flag).
Of course if an exception can be thrown in the "finally" block and you want to handle that, then you're going to need to wrap that whole business in another "try".

How can I remove a FileTransferred event handler from a WinSCP Session object?

I made a small PowerShell wrapping script for the WinSCP module that suites my needs. Like in the example for synchronizing local and remote folders mine uses a file transferred event to log session events.
$fileTransferedEvent = {Receive-FileTransferredEvent $_}
$session.add_FileTransferred($fileTransferedEvent)
The example disposes the session when it is done. My logic keeps the session open until I choose to be done with it so I can use the same session in other cmdlet call.
This creates an issue if a other cmdlets (or even calling the same one again) also use add_FileTransferred(). The event gets added again which triggers X times the output where X is how many times the method is called.
I found a partner method called remove_FileTransferred() but I do not know how to use it
Name MemberType Definition
---- ---------- ----------
add_FileTransferred Method void add_FileTransferred(WinSCP.FileTransferredEventHandler value)
remove_FileTransferred Method void remove_FileTransferred(WinSCP.FileTransferredEventHandler value)
They both accept the same values so I thought maybe I could pass it a definition of the the handler but that does not seem to work i.e. $session.remove_FileTransferred($fileTransferedEvent) did not do the job. Nor does it error either.
How can I remove file transferred event handlers on Winscp.Session objects?
This works for me perfectly:
$fileTransferedEvent = { Write-Host "Upload of $($_.FileName) succeeded" }
$session.add_FileTransferred($fileTransferedEvent)
$session.PutFiles("C:\test.txt", "/").Check()
$session.remove_FileTransferred($fileTransferedEvent)
$session.PutFiles("C:\test.txt", "/").Check()
I get:
Upload of C:\test.txt succeeded
While if I do not call remove_FileTransferred, I get the output twice, as expected:
Upload of C:\test.txt succeeded
Upload of C:\test.txt succeeded
Refer to Event handlers in PowerShell.
If you add the same event handler multiple times, the handler will be called that many times each time the event is raised. If you want to cancel an event subscription, you have to remove the handler the same number of time. Removing handler, which is not (or no longer) added is noop. All these are general rules, not limited to Session.FileTransferred or WinSCP .NET assembly.
Example:
$fileTransferedEvent = { Write-Host "Upload of $($_.FileName) succeeded" }
$session.add_FileTransferred($fileTransferedEvent)
$session.add_FileTransferred($fileTransferedEvent)
Write-Host "two handlers:"
$session.PutFiles("C:\test.txt", "/").Check()
$session.remove_FileTransferred($fileTransferedEvent)
Write-Host "one handler:"
$session.PutFiles("C:\test.txt", "/").Check()
$session.remove_FileTransferred($fileTransferedEvent)
Write-Host "zero handlers:"
$session.PutFiles("C:\test.txt", "/").Check()
Write-Host "done"
This will get you:
two handlers:
Upload of C:\test.txt succeeded
Upload of C:\test.txt succeeded
one handler:
Upload of C:\test.txt succeeded
zero handlers:
done
Though, what may not be obvious is, that the following two code snippets are not identical:
$fileTransferedEvent = { Write-Host "Upload of $($_.FileName) succeeded" }
$session.add_FileTransferred($fileTransferedEvent)
$fileTransferedEvent = { Write-Host "Upload of $($_.FileName) succeeded" }
$session.add_FileTransferred($fileTransferedEvent)
$fileTransferedEvent = { Write-Host "Upload of $($_.FileName) succeeded" }
$session.add_FileTransferred($fileTransferedEvent)
$session.add_FileTransferred($fileTransferedEvent)
The first snippet adds two distinct event handlers, while the latter adds two identical handlers.
After the first snippet, you cannot remove the two registrations by calling this:
$session.remove_FileTransferred($fileTransferedEvent)
$session.remove_FileTransferred($fileTransferedEvent)
The first "remove" will remove the last/second added handler. The second "remove" will do nothing, as it tries to remove the same handler, which was already cancelled. As you do not have any reference to the first handler, you cannot cancel it anymore.

What is the difference between the Throw keyword and the ThrowTerminatingError method in Powershell? [duplicate]

I'm trying to get my script to stop if it hits an error, and use try... catch to give me an easy way to handle the error. The easiest thing in the world I'd have thought, but I'm obviously doing something stupid. I have read for a couple of hours and I'm stuck, any help would be very handy, thanks!
Here's some example code, I've put erroraction all over the place, can't seem to stop the damn thing!
$ErrorActionPreference = "Stop"
try {
get-content "c:\GarbageFileName.txt" -ErrorAction stop
}
catch {
write-output "in catch, I want it to stop now"
}
write-output "try-catch finished, script is continuing"
this text added next day *
Fantastic answers from people, I wish I could choose more than 1 answer or had enough reputation to vote for the ones which I reluctantly didn't choose as the answer, or say thanks somehow!
The whole idea of a try/catch control is that you tell the script what to do if it encounters a terminating error, instead of the default action of throwing the error and stopping the script. If your catch block just displays a message to the terminal with Write-Host, that's all the error handling there will be, and the script will continue from there. If you think about it, it would partially defeat the purpose of try/catch if the script were stopped automatically whenever an error is caught.
In the catch block, $_ will be set to the ErrorRecord object representing the terminating error from the try block (the same one that gets stored in $error[0]). So the simplest way to end the script is to rethrow the error that would have been thrown if you hadn't used a try/catch:
try {
Get-Content "c:\GarbageFileName.txt" -ErrorAction stop
} catch {
# Custom action to perform before terminating
throw $_
}
Or, if you want to display a custom message instead of the default ErrorRecord:
try {
Get-Content "c:\GarbageFileName.txt" -ErrorAction stop
} catch {
throw 'Custom error message'
}
Or you could use break as suggested in Joost's answer if you want to just quit after you're finished with your custom error handling without throwing an error to the error stream.
Or you could get more sophisticated and create your own ErrorRecord object. There's a lot you can do with that, it's too big a topic to cover comprehensively here, but you can get more info about the syntax by googling System.Management.Automation.ErrorRecord. Here's an example from one of my scripts to get you started (from a function that executes a SQL query defined in the $query variable against a SQL Server database):
} catch {
$ErrorRecord = New-Object System.Management.Automation.ErrorRecord(
(New-Object Exception("Exception executing the query: $($_.Exception.InnerException.Message)")),
$query,
[System.Management.Automation.ErrorCategory]::InvalidArgument,
$null
)
$ErrorRecord.CategoryInfo.Reason = $_.CategoryInfo.Reason;
$ErrorRecord.CategoryInfo.Activity = $_.InvocationInfo.InvocationName;
$PSCmdlet.ThrowTerminatingError($ErrorRecord);
}
A couple of notes:
You'll see that in creating my custom ErrorRecord, I'm using $_, which I just said contains the ErrorRecord object associated by the terminating error that was caught in the try block. The idea is to customize some of the error output, while using parts of the default ErrorRecord by assigning them to the corresponding properties for the custom ErrorRecord.
$PSCmdlet is only available if you declare [CmdletBinding()] at the beginning of the function or script. Otherwise, you can just use throw $ErrorRecord to throw your custom error. However, the result will be more Cmdlet-style if you use $PSCmdlet.ThrowTerminatingError. (throw will spit back the line from the function that generated the error, whereas $PSCmdlet.ThrowTerminatingError will give you the line from the calling context where the function was used. It's hard to describe in a way that makes sense without getting too elaborate, but if you experiment with it you'll see what I mean.)
BTW, it's redundant to set $ErrorActionPreference = "Stop", and then use -ErrorAction Stop. The preference variable sets the default action for all cmdlets, and the -ErrorAction switch overrides the default action for a particular cmdlet, so there's no need to first specify the default, then use -ErrorAction to specify the same action you just set as the default. What you probably want to do is just leave out $ErrorActionPreference = "Stop".
The only thing missing is your break-statement in the Catch-block. Powershell won't stop the script if you don't instruct it to.
try {
get-content "c:\GarbageFileName.txt" -ErrorAction stop
}
catch {
write-output "in catch, I want it to stop now"
break
}
write-output "try-catch finished, script is continuing"
And a small addendum, in case it helps you: with finally, you can add some lines of code that are always executed, regardless of wether an exception was thrown or not.
try {
get-content "c:\GarbageFileName.txt" -ErrorAction stop
}
catch {
write-output "in catch, I want it to stop now"
break
}
finally {
#do some stuff here that is executed even after the break-statement, for example:
Set-Content -Path "f:\GarbageFileName.txt" -Value $null
}
#the line below is executed only if the error didn't happen
write-output "try-catch finished, script is continuing"
Try-Catch will catch an exception and allow you to handle it, and perhaps handling it means to stop execution... but it won't do that implicitly. It will actually consume the exception, unless you rethrow it. But your issue is simpler than that -- the try block takes precedence over the -ErrorAction stop in your get-content cmdlet. So instead of stopping execution, you get taken to the Catch block and continue on because there is no error handling in the catch block.
Try removing the try-catch logic from your script, and allow the cmdlet to error out:
get-content "c:\GarbageFileName.txt" -ErrorAction stop
write-output "You won't reach me if GarbageFileName doesn't exist."
And you should get the desired result of execution not reaching write-output:
PS C:\> .\so-test.ps1
Get-Content : Cannot find path 'C:\GarbageFileName.txt' because it does not exist.
At C:\so-test.ps1:2 char:12
+ get-content <<<< "c:\GarbageFileName.txt" -ErrorAction stop
+ CategoryInfo : ObjectNotFound: (C:\GarbageFileName.txt:String) [Get-Content], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand

How can I use try... catch and get my script to stop if there's an error?

I'm trying to get my script to stop if it hits an error, and use try... catch to give me an easy way to handle the error. The easiest thing in the world I'd have thought, but I'm obviously doing something stupid. I have read for a couple of hours and I'm stuck, any help would be very handy, thanks!
Here's some example code, I've put erroraction all over the place, can't seem to stop the damn thing!
$ErrorActionPreference = "Stop"
try {
get-content "c:\GarbageFileName.txt" -ErrorAction stop
}
catch {
write-output "in catch, I want it to stop now"
}
write-output "try-catch finished, script is continuing"
this text added next day *
Fantastic answers from people, I wish I could choose more than 1 answer or had enough reputation to vote for the ones which I reluctantly didn't choose as the answer, or say thanks somehow!
The whole idea of a try/catch control is that you tell the script what to do if it encounters a terminating error, instead of the default action of throwing the error and stopping the script. If your catch block just displays a message to the terminal with Write-Host, that's all the error handling there will be, and the script will continue from there. If you think about it, it would partially defeat the purpose of try/catch if the script were stopped automatically whenever an error is caught.
In the catch block, $_ will be set to the ErrorRecord object representing the terminating error from the try block (the same one that gets stored in $error[0]). So the simplest way to end the script is to rethrow the error that would have been thrown if you hadn't used a try/catch:
try {
Get-Content "c:\GarbageFileName.txt" -ErrorAction stop
} catch {
# Custom action to perform before terminating
throw $_
}
Or, if you want to display a custom message instead of the default ErrorRecord:
try {
Get-Content "c:\GarbageFileName.txt" -ErrorAction stop
} catch {
throw 'Custom error message'
}
Or you could use break as suggested in Joost's answer if you want to just quit after you're finished with your custom error handling without throwing an error to the error stream.
Or you could get more sophisticated and create your own ErrorRecord object. There's a lot you can do with that, it's too big a topic to cover comprehensively here, but you can get more info about the syntax by googling System.Management.Automation.ErrorRecord. Here's an example from one of my scripts to get you started (from a function that executes a SQL query defined in the $query variable against a SQL Server database):
} catch {
$ErrorRecord = New-Object System.Management.Automation.ErrorRecord(
(New-Object Exception("Exception executing the query: $($_.Exception.InnerException.Message)")),
$query,
[System.Management.Automation.ErrorCategory]::InvalidArgument,
$null
)
$ErrorRecord.CategoryInfo.Reason = $_.CategoryInfo.Reason;
$ErrorRecord.CategoryInfo.Activity = $_.InvocationInfo.InvocationName;
$PSCmdlet.ThrowTerminatingError($ErrorRecord);
}
A couple of notes:
You'll see that in creating my custom ErrorRecord, I'm using $_, which I just said contains the ErrorRecord object associated by the terminating error that was caught in the try block. The idea is to customize some of the error output, while using parts of the default ErrorRecord by assigning them to the corresponding properties for the custom ErrorRecord.
$PSCmdlet is only available if you declare [CmdletBinding()] at the beginning of the function or script. Otherwise, you can just use throw $ErrorRecord to throw your custom error. However, the result will be more Cmdlet-style if you use $PSCmdlet.ThrowTerminatingError. (throw will spit back the line from the function that generated the error, whereas $PSCmdlet.ThrowTerminatingError will give you the line from the calling context where the function was used. It's hard to describe in a way that makes sense without getting too elaborate, but if you experiment with it you'll see what I mean.)
BTW, it's redundant to set $ErrorActionPreference = "Stop", and then use -ErrorAction Stop. The preference variable sets the default action for all cmdlets, and the -ErrorAction switch overrides the default action for a particular cmdlet, so there's no need to first specify the default, then use -ErrorAction to specify the same action you just set as the default. What you probably want to do is just leave out $ErrorActionPreference = "Stop".
The only thing missing is your break-statement in the Catch-block. Powershell won't stop the script if you don't instruct it to.
try {
get-content "c:\GarbageFileName.txt" -ErrorAction stop
}
catch {
write-output "in catch, I want it to stop now"
break
}
write-output "try-catch finished, script is continuing"
And a small addendum, in case it helps you: with finally, you can add some lines of code that are always executed, regardless of wether an exception was thrown or not.
try {
get-content "c:\GarbageFileName.txt" -ErrorAction stop
}
catch {
write-output "in catch, I want it to stop now"
break
}
finally {
#do some stuff here that is executed even after the break-statement, for example:
Set-Content -Path "f:\GarbageFileName.txt" -Value $null
}
#the line below is executed only if the error didn't happen
write-output "try-catch finished, script is continuing"
Try-Catch will catch an exception and allow you to handle it, and perhaps handling it means to stop execution... but it won't do that implicitly. It will actually consume the exception, unless you rethrow it. But your issue is simpler than that -- the try block takes precedence over the -ErrorAction stop in your get-content cmdlet. So instead of stopping execution, you get taken to the Catch block and continue on because there is no error handling in the catch block.
Try removing the try-catch logic from your script, and allow the cmdlet to error out:
get-content "c:\GarbageFileName.txt" -ErrorAction stop
write-output "You won't reach me if GarbageFileName doesn't exist."
And you should get the desired result of execution not reaching write-output:
PS C:\> .\so-test.ps1
Get-Content : Cannot find path 'C:\GarbageFileName.txt' because it does not exist.
At C:\so-test.ps1:2 char:12
+ get-content <<<< "c:\GarbageFileName.txt" -ErrorAction stop
+ CategoryInfo : ObjectNotFound: (C:\GarbageFileName.txt:String) [Get-Content], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand