Catching errors in Powershell - 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.

Related

finally is skipped if the catch is triggered

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

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

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)

Foreach error handling in Powershell

Is there a way to catch and save bad names in a foreach loop? I have the following:
$CollectionName = "Import Test"
$PCName = Import-Csv "C:\Powershell\import_test.csv"
foreach($computer in $PCName) {
Add-CMDeviceCollectionDirectMembershipRule -CollectionName $CollectionName -ResourceID $(Get- CMDevice -Name $computer.computername).ResourceID
}
What I would like to do is that if there is a bad name in the csv then instead of displaying the "Cannot validate argument" error I currently get, just output the failed name to a text file.
Thanks
Yes. Put the statement(s) inside the loop in a try..catch block:
foreach($computer in $PCName) {
try {
Add-CMDeviceCollectionDirectMembershipRule ...
} catch {
"Bad name: $name" | Out-File 'C:\bad_names.txt' -Append
}
}
If the error is a non-terminating error (i.e. displays an error message, but isn't caught by try..catch), you can turn it into a terminating error by adding -ErrorAction Stop to the command or by setting $ErrorActionPreference = "Stop".

Catching errors in the Exchange Management Shell

I'm trying to write a powershell script that creates an Exchange Mailbox. This works fine as long as the Mailbox doesn't already exist, but when I try to catch any error and report it back the script just runs through as if everythign were fine.
I ran the script on an already existing user and it shows the error, but it returns normally as if the mailbox was created.
I found this question, which solved the "why", I guess the Enable-Mailbox command only throws non-terminating errors.
Anyway, all the suggested solutions to catch these errors fail. The cmdlet seems to ignore the $ErrorActionPreference variable, $? is always $true, regardless if an error occured or not. $error always contains something, so here as well nothing to check against.
This is the script code I'm using, very basic.
param( [string]$uid, [string]$email )
trap [Exception] {
"ERROR: " + $_.Exception.Message
exit
}
Enable-Mailbox -Identity $uid -Database HaiTest-MBDataBase-01 -PrimarySmtpAddress $email
"SUCCESS: mailbox created successfully"
It works with everything else, it's just the Exchange Management Shell that causes trouble. The Exchange environment is an Exchange 2010 server.
Is there any way to check the cmdlets for errors?
Trapping errors works for terminating errors only, looks like the error you get from Enable-Mailbox is not a terminating error. You can force the error to be a terminating error by passing the ErrorAction variable a value of 'Stop'. You can also use try/catch (in PowerShell 2.0) instead of trap:
param( [string]$uid, [string]$email )
trap {
"ERROR: " + $_.Exception.Message
exit
}
Enable-Mailbox -Identity $uid -Database HaiTest-MBDataBase-01 -ErrorAction Stop -PrimarySmtpAddress $email
"SUCCESS: mailbox created successfully"
So far, the only reliable solution I have found to the strange exception handling behavior of the Exchange 2010 Cmdlets is adding "-ErrorAction SilentlyContinue", to suppress the exceptions, then looking for the expected results.
For instance, when enabling a mailbox, I look for the existence of the mailbox, like this...
function Enable-MailboxSafely {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
$Identity,
$Database,
[switch]$WhatIf
)
$output = [pscustomobject][ordered]#{
Identity = $Identity.ToString()
Mailbox = $null
Message = 'Unknown Error'
Success = $false
}
$output.Mailbox = Get-Mailbox -Identity $Identity -ErrorAction SilentlyContinue
if ($output.Mailbox -ne $null) {
$output.Message = 'Mailbox already exists.'
$output.success = $true
}
Else {
$null = Enable-Mailbox -Identity $Identity -Database:$Database -ErrorAction SilentlyContinue
# Have to look for a mailbox, as a workaround to the Exchange 2010 Cmdlets not implementing exceptions correctly.
$output.Mailbox = Get-Mailbox -Identity $Identity -DomainController:$DomainController -ErrorAction SilentlyContinue
if ($output.Mailbox -ne $null) {
$output.Message = "Mailbox created for [$Identity] on database [$Database]."
$output.success = $true
}
else {
$output.Message = "Failed to create mailbox for [$Identity] on database [$Database]."
$output.Success = $false
}
}
Write-Output $output
}
Enable-MailboxSafely -Identity Somebody