finally is skipped if the catch is triggered - powershell

this is PS 5.1
try {
Send-MailMessage -to $EmailTo -Body $Body -Subject "$TodayDate Report" -From 'r-admin#domain.com' -SmtpServer 'mail-relay' -port '25' -BodyAsHtml -ErrorAction Stop
}
Catch {
Write-Warning "Unable to send email"
& gam user $($EmailTo) sendemail html true to $($EmailTo) subject "$TodayDate Report"
}
finally {
#final cleanup
If (#($UsersResultsArray).count -gt 0) {
remove-Item $UsersResultFileName -Force -ErrorAction SilentlyContinue
}
IF ($ArchiveOverFlowCount -gt 0) {
remove-Item $ArchiveOverFilename -Force -ErrorAction SilentlyContinue
}
If ($ZeroCount -gt 0) {
remove-item $ZeroArrayFileName -Force -ErrorAction SilentlyContinue
}
remove-Item $NumbersTableFilename -Force -ErrorAction SilentlyContinue
}
I have tried many combinations but if the try gets an error the catch works but the finally does not.
I have tried no finally and nothing happens after the catch.
No errors.
I am not sure what is going on or why nothing happens after the catch?
If the Try does not get an error everything works fine.
I tested with a continue in the catch and whatever I do if the catch fires the finally does not.

The documentation says:
The finally keyword is followed by a statement list that runs every time the script is run, even if the try statement ran without error or an error was caught in a catch statement.
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_try_catch_finally?view=powershell-7.2
Now lets test:
try {
get-item C:\nothing.there -ErrorAction:Stop
}
catch {
write-error $_
}
finally {
write-host "finally"
}
#Output:
Write-Error: Cannot find path 'C:\nothing.there' because it does not exist.
finally
So the catch caught the error and finally got executed.
try {
get-item C:\Windows -ErrorAction:Stop
}
catch {
write-error $_
}
finally {
write-host "finally"
}
#Output:
Directory: C:\
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 12/10/2022 22:29 Windows
finally
Once again finally got executed.
So back to your example, I think that the variables used in the IF statements, e.g. $UsersResultsArray, are empty or the variables that should contain paths like $UsersResultFileName.
To verify if the finally block runs, simply add write-host "finally" to that block and re-run the code, you will see "finally" printed on the screen.
Btw. to verify if the variable holds elements you do not need to count them, this is enough: IF ($UsersResultsArray){}.

Related

Powershell terminate execution

I am using powershell to process csv files in a directory when no file found with current date stamp I want the process to raise an error notifying file not found and exit.
# Powershell raise error and exit
# File name: sale_2020_02_03.csv
$getLatestCSVFile = Get-ChildItem -Path $Folder -Filter "*.csv" | Sort-Object LastWriteTime -Descending | Select-Object -First 1
if ($getLatestCSVFile)
{
try
{
# Process .csv file
}
catch
{
# on error
$ErrorMessage = $_.Exception.Message
throw "$ErrorMessage"
}
}
else
{
# If current date file not found raise error and exit
Send-MailMessage
throw "File not found"
}
As for this...
I would like to have the powershell script to stop execution
... that is what the Exit keyword is for or the '-ErrorAction Stop' option is for.
As for this...
if the code gets into the else block in the else block I want to
notify file not found
... as per my above, same thing, and you have to set that.
Meaning stuff like ...
Clear-Host
$Error.Clear()
try
{
# Statement to try
New-Item -Path 'D:\Temp\DoesNOtExist' -Name 'Test.txt' -ItemType File -ErrorAction Stop
}
catch
{
# What to do with terminating errors
Write-Warning -Message $Error[0]
}
# Results
<#
WARNING: Could not find a part of the path 'D:\Temp\DoesNOtExist\Test.txt'.
#>
Or using multiple catch statements, like ...
Example: Force file warning
Clear-Host
$Error.Clear()
try
{
# Results in NoSupportException
# Statement to try
New-Item -Path 'D:\Temp\Temp' -Name my:Test.txt -ItemType File -ErrorAction Stop
# Results in DirectoryNotFoundException
# New-Item -Path 'D:\Temp\Temp' -Name 'Test.txt' -ItemType File -ErrorAction Stop
}
catch [System.NotSupportedException]
{
# What to do with terminating errors
Write-Warning -Message 'Illegal chracter or filename.'
}
catch [System.IO.DirectoryNotFoundException]
{
# What to do with terminating errors
Write-Warning -Message 'The path is not valid.'
}
catch
{
# What to do with terminating errors
Write-Warning -Message 'An unexpected error occurred.'
}
# Results
<#
WARNING: Illegal character or filename.
#>
Example: Force path warning
Clear-Host
$Error.Clear()
try
{
# Results in NoSupportException
# Statement to try
# New-Item -Path 'D:\Temp\Temp' -Name my:Test.txt -ItemType File -ErrorAction Stop
# Results in DirectoryNotFoundException
New-Item -Path 'D:\Temp\Temp' -Name 'Test.txt' -ItemType File -ErrorAction Stop
}
catch [System.NotSupportedException]
{
# What to do with terminating errors
Write-Warning -Message 'Illegal chracter or filename.'
}
catch [System.IO.DirectoryNotFoundException]
{
# What to do with terminating errors
Write-Warning -Message 'The path is not valid.'
}
catch
{
# What to do with terminating errors
Write-Warning -Message 'An unexpected error occurred.'
}
# Results
<#
WARNING: The path is not valid.
#>
See also this discussion, which is a similar use case. Pay particular attention the the 'Exit vs Return vs Break' answer.
Terminating a script in PowerShell

Error action not stopping script

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).

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.

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)

Capturing errors in this Powershell script

I have this test script to change the Administrator password on a list of servers.
I have set the script to log errors if the server can't be ping'd or account can't be found etc. However in addtion to this i'd like to capture any other errors that take place and also add those to the log file. I know you can use the "Try and Catch" for error handling but havn't had any luck so far.
Would someone be kind enough to show how to do it?
Here is the script
$date = Get-Date
$user = "Administrator"
$newpwd = "MyPassword"
$servers = gc C:\servers.txt
foreach ($server in $servers)
{
$ping = new-object System.Net.NetworkInformation.Ping
$Reply = $null
$Reply = $ping.send($server)
if($Reply.status -like 'Success')
{
$Admin=[adsi]("WinNT://" + $server + "/$user, user")
if($?)
{
$Admin.SetPassword($newpwd)
if($?)
{Add-Content -path C:\Audit\logs\servers-reset.csv -Value "$server, Succsess the $user password was changed. , $date"}
else
{Add-Content -path C:\Audit\logs\servers-reset.csv -Value "$server, Error: FAILED to change the password. , $date"}
}
else
{
Add-Content -path C:\Audit\logs\servers-reset.csv -Value "$server, Error: The $user user account was not found on the server. , $date"}
}
else
{
Add-Content -path C:\Audit\logs\servers-reset.csv -Value "$server, Error: Ping FAILED could not connect. , $date"
}
If you want to write exceptions to the log right after they were thrown, you could use a trap. Add something like this to you script:
trap [Exception] {
#Add message to log
Add-Content -Path test.csv -Value "$server, $($_.Exception.Message), $(Get-Date)"
#Continue script
continue;
}
That will log all exceptions (not all errors).
If you want all errors, you can access them using $Error. It's an arraylist containing every error during your sessions(script). The first item $Error[0] is the latest error. This however, is not something that fits directly into an csv file without formatting it.