Error action not stopping script - powershell

Consider this simple code:
Read-Host $path
try {
Get-ChildItem $Path -ErrorAction Continue
}
Catch {
Write-Error "Path does not exist: $path" -ErrorAction Stop
Throw
}
Write-Output "Testing"
Why is 'Testing' printed to the shell if an invalid path is specified?
The script does not stop in the catch block. What am I doing wrong?

In your Try Catch block, you need to set Get-ChildItem -ErrorAction Stop
so the exception is caught in the Catch block.
With continue, you are instructing the command to not produce a terminating error when an actual error occurs.
Edit:
Also, your throw statement is useless there and you do not need to specify an error-action for Write-Error.
Here's the modified code.
$path = Read-Host
try {
Get-ChildItem $Path -ErrorAction stop
}
Catch {
Write-Error "Path does not exist: $path"
}
Additional note
You could apply this default behavior (if that is what you want) to the entire script by setting the default action to stop using :
$ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop

I think this is what you need:
$path = Read-Host 'Enter a path'
try {
Get-ChildItem $Path -ErrorAction Stop
}
Catch {
Throw "Path does not exist: $path"
}
Write-Output "Testing"
Per Sage's answer, you need to change to -ErrorAction Stop in the Try block. This forces the Get-ChildItem cmdlet to throw a terminating error, which then triggers the Catch block. By default (and with the Continue ErrorAction option) it would have thrown a non-terminating error which are not caught by a try..catch.
If you then want your code to stop in the Catch block, use Throw with the message you want to return. This will produce a terminating error and stop the script (Write-Error -ErrorAction Stop will also achieve a terminating error, it's just a more complicated method. Typically you should use Write-Error when you want to return non-terminating error messages).

Related

Catching errors in Powershell

I have weird problem, when im using try/catch method for some cmdlets its working for some not.
Can you advice on that?
This one is working fine:
try
{
$LookingForRemoteMailboxOnPrem = Get-RemoteMailbox $info -ErrorAction Stop | select -ExpandProperty UserPrincipalName
}
catch
{
string]$t = $Error[0]
}
But this one is not:
try
{
$EnableRemoteMailbox = Enable-RemoteMailbox $info -RemoteRoutingAddress $remote -PrimarySmtpAddress $info2 -ErrorAction Stop
}
catch
{
[string]$t = $Error[0]
}
Not saving error to $t variable
The $ErrorActionPreference is set to Continue by default. This means if PowerShell can "recover" from an error it won't throw an exception. You can use the -ErrorAction parameter to change the behaviour at every cmdlet.
This link gives a good example:
Try {dir c:\missingFolder}
Catch [System.Exception] {"Caught the exception"}
Finally {$error.Clear() ; "errors cleared"}
The string "Caught the exception does not occur in PowerShell windows. If you set the -ErrorAction to Stop an exception is raised.
Details are described here.

I don't understand how trap works

I'm trying to make a trap command that traps the exception that a file is not found. This is the code:
Trap {
Clear-Host
Write-Host "The file you are looking for does not exist"
}
Get-ItemProperty C:\fake
Am I misunderstanding how to do this?
Traps only execute on terminating errors (errors that stop the pipeline), so first you'll need change the ErrorAction behavior of the offending cmdlet:
Trap {
Clear-Host
Write-Host "The file you are looking for does not exist"
}
Get-ItemProperty C:\fake -ErrorAction Stop
To suppress the error record from subsequently bubbling up to the caller, return from the current scope from inside the trap:
Trap {
Clear-Host
Write-Host "The file you are looking for does not exist"
return
}
Get-ItemProperty C:\fake -ErrorAction Stop
In order to not have to specify the -ErrorAction Stop parameter argument explicitly all the time, set the $ErrorActionPreference variable at the start of your script/function:
$ErrorActionPreference = 'Stop'
or use the $PSDefaultParameterValues hashtable to set it for specific cmdlets:
$PSDefaultParameterValues['Get-ItemProperty:ErrorAction'] = 'Stop'
$PSDefaultParameterValues['Do-OtherStuff:ErrorAction'] = 'Stop'
$PSDefaultParameterValues['Set-*:ErrorAction'] = 'Stop'

Unable to catch DriveNotFoundException from Get-PSDrive

I'm unable to catch the DriveNotFoundException being generated by Get-PSDrive in the following example:
try {
# Assumes no Q:\ drive connected.
$foo = Get-PSDrive -name 'Q' -ErrorAction Stop
}
catch [System.Management.Automation.DriveNotFoundException] {
Write-Output "Drive not found."
}
catch {
Write-Output "Something else went wrong."
}
This should print the following:
PS C:\temp> .\foo.ps1
Drive not found.
PS C:\temp>
Instead, I get:
PS C:\temp> .\foo.ps1
Something else went wrong.
PS C:\temp>
I'm using Powershell 2.0, if that's relevant.
The issue is because the -ErrorAction Stop is changing the Exception type that the try/catch block is seeing.
You can prove it by catching the ActionPreferenceStopException type. So let's run some troubleshooting code to see what is going on:
try {
# Assumes no Q:\ drive connected.
$foo = Get-PSDrive -name 'Q' -ErrorAction Stop
}
catch [System.Management.Automation.DriveNotFoundException] {
Write-Output "Drive not found."
}
catch [System.Management.Automation.ActionPreferenceStopException] {
Write-Output "Stop Exception."
write-host "Caught an exception:" -ForegroundColor Red
write-host "Exception Type: $($_.Exception.GetType().FullName)" -ForegroundColor Red
write-host "Exception Message: $($_.Exception.Message)" -ForegroundColor Red
}
catch
{
write-host "Caught an exception:" -ForegroundColor Red
write-host "Exception Type: $($_.Exception.GetType().FullName)" -ForegroundColor Red
write-host "Exception Message: $($_.Exception.Message)" -ForegroundColor Red
}
This returns the following output:
Stop Exception.
Caught an exception:
Exception Type: System.Management.Automation.DriveNotFoundException
Exception Message: Cannot find drive. A drive with the name 'Q' does not exist.
So you see that the try/catch caught the [System.Management.Automation.ActionPreferenceStopException] exception, Even Though the Exception type is [System.Management.Automation.DriveNotFoundException] inside the catch block.
So, we can handle it with a slightly modified version of #haliphax's solution, which is to check the error type inside the ActionPreferenceStopException catch block:
try {
# Assumes no Q:\ drive connected.
$foo = Get-PSDrive -name 'Q' -ErrorAction Stop
}
catch [System.Management.Automation.ActionPreferenceStopException] {
if ($Error[0].Exception.GetType().Name -eq 'DriveNotFoundException') {
Write-Output "Drive not found."
}
else {
Write-Output "Something else went wrong."
}
}
catch {
Write-Output "Something else went wrong."
}
To complement HAL9256's great answer:
Note: The following is in part speculative. Do let me know if I'm wrong.
The behavior observed is a presumably a bug in PowerShell v1 and v2 , where the internal exception of type [System.Management.Automation.ActionPreferenceStopException] accidentally masks the original exception in the matching logic of typed catch blocks in a try / catch statement.
The reason I suspect that [System.Management.Automation.ActionPreferenceStopException] is purely an internal exception that should never have been exposed, is that both $Error[0].Exception and its alias inside a catch block, $_.Exception, reflect the original exception, even in PowerShell v1 and v2 - the $Errors collection contains no trace of [System.Management.Automation.ActionPreferenceStopException].
While the bug is fixed in v3+, v3+ still, but now also matches [System.Management.Automation.ActionPreferenceStopException] in typed catch handlers, presumably so as not to break backward compatibility.
There is little benefit in catching [System.Management.Automation.ActionPreferenceStopException], given its generic nature (it just tells you that a cmdlet experienced a non-terminating error).
The only conceivable reason for catching it is if you wanted to know whether the exception at hand was natively terminating or only treated as terminating due to -ErrorAction Stop or $ErrorActionPreference = 'Stop'.
Thus, for code that must also on run v2-, I'd solve the problem as follows:
try {
# Assumes no Q:\ drive connected.
$foo = Get-PSDrive -name 'Q' -ErrorAction Stop
}
catch { # Use a generic handler to work around the bug in v1 an v2.
# $_ is the [System.Management.Automation.ErrorRecord] instance representing
# the PowerShell error at hand (same as $Error[0]).
# $_.Exception contains the exception that triggered the error,
# and can be compared to specific exception types with -is.
if ($_.Exception -is [System.Management.Automation.DriveNotFoundException]) {
"Drive not found."
} else {
"Something else went wrong."
}
}
I realize that this is due to some strange behavior (according to the above comments about later versions of PowerShell), but this does manage to handle the specific error:
try {
$foo = Get-PSDrive -name 'Q' -ErrorAction Stop
}
catch {
if ($Error[0].Exception.GetType().Name -eq 'DriveNotFoundException') {
Write-Output 'No such drive.'
}
else {
Write-Output 'Something else went wrong.'
}
}
This is just a brief post of the final version of the code I used, based on the explanation provided by #HAL9256 in his answer:
try {
# Assumes no Q:\ drive connected.
$foo = Get-PSDrive -name 'Q' -ErrorAction Stop
}
# Catch exceptions thrown by both v2.0 and by later versions.
catch [System.Management.Automation.ActionPreferenceStopException], `
[System.Management.Automation.DriveNotFoundException] {
Write-Output "Drive not found."
}
catch {
Write-Output "Something else went wrong."
}
This has been tested on both PowerShell 2.0 & 4.0 and works on both. I suppose there's a minor risk that some other exception will occur in the Get-PSDrive statement in a PowerShell 2.0 environment, triggering the catch block, but in my use case, it's an acceptable risk and will trigger a different exception later in the script.

Catch "Cannot Find File"

I have been searching for a while, but I cannot find the exception in PowerShell that would catch a "Cannot find file" error.
I would also like to have this loop until the user types in the correct file name to get.
# Ask user for file to read from
Try {
$readFile = Read-Host "Name of file to read from: "
$ips = GC $env:USERPROFILE\Desktop\$readFile.txt
}
Catch {
}
The error you get is a non-terminating error, and thus not caught. Add -ErrorAction Stop to your Get-Content statement or set $ErrorActionPreference = 'Stop' and your code will work as you expect:
try {
$readFile = Read-Host "Name of file to read from: "
$ips = GC $env:USERPROFILE\Desktop\$readFile.txt -ErrorAction Stop
} catch {
}
Don't use try/catch blocks for flow control. That is a generally-frowned-on practice, especially in PowerShell, since PowerShell's cmdlets will write errors instead of throwing exceptions. Usually, only non-PowerShell .NET objects will throw exceptions.
Instead, test if the file exists. That gives you much greater error control:
do
{
$readFile = Read-Host "Name of file to read from: "
$path = '{0}\Desktop\{1}.txt' -f $env:USERPROFILE,$readFile
if( (Test-Path -Path $path -PathType Leaf) )
{
break
}
Write-Error -Message ('File ''{0}'' not found.' -f $path)
}
while( $true )

Start-DscConfiguration doesn't throw exceptions?

I noticed that if applying a configuration through Start-DscConfiguration fails, it writes to the error stream but doesn't
throw an Exception? That is, if I do the following:
try{
Start-DscConfiguration -Path ".\MyConfig" -Wait -Verbose
}catch{
#...
}
...it never ends up in the catch handler. I suspect this may have something to do with the fact that without the "-Wait",
Start-DscConfiguration starts an async job for this, and async commands probably don't throw exceptions, but in a synchronous
scenario, I would very much like to know if my configuration could be applied.
What is the proper way to determine if Start-DscConfiguration has completed succesfully?
The only way I know is to check the global "$error" variable and compare the number of error records before and after your call to Start-DscConfiguration. If there's more afterwards then something must have gone wrong during the call, so throw your own exception:
Configuration TestErrorHandling {
Node "localhost" {
Script ErroringResource {
GetScript = { return $null; }
TestScript = { return $false; }
SetScript = { throw new-object System.InvalidOperationException; }
}
}
}
$errorCount = $error.Count;
write-host "starting dsc configuration"
$mof = TestErrorHandling;
Start-DscConfiguration TestErrorHandling –Wait –Verbose;
write-host "dsc configuration finished"
if( $error.Count -gt $errorCount )
{
$dscErrors = $error[$errorCount..($error.Count - 1)];
write-host "the following errors occurred during dsc configuration";
write-host ($dscErrors | fl * | out-string);
throw $dscErrors[-1];
}
There's another way to make it cause an exception. Try saving it into the ErrorVariable like this :
try
{
Start-DscConfiguration -Path ".\MyConfig" -Wait -Verbose -ErrorVariable ev
}
catch
{
$myException = $_
}
Weirdly so, this throws the exception when there's an error (which is what you wanted). You can get the value of your exception in the $myexception variable, and also could get just a one liner description of your error using $ev
PS: Note that while mentioning ev in the errorVariable parameter, you do it without the '$' symbol - since you're only specifying the variable 'name'.
Start-DscConfiguration when used without -Wait will create a job object - with one child job for every computername. PowerShell job objects have an Error stream which contains all the errors. You can check this stream as well
$job = Start-DscConfiguration -Force -Verbose -Path C:\Temp\Demo\ -ComputerName localhost
Receive-Job $job -Wait
'Errors in job = ' + ($job.childjobs[0].Error.Count)