Send an email if a PowerShell script gets any errors at all and terminate the script
For global error handling I prefer to use a trap statement.
function ErrorHandler($error)
{
... write out error info
... send email message with error info
... etc
}
trap { ErrorHandler $_; break }
... rest of script code
And if you want to bail on any error, then yes, set $ErrorActionPreference
to Stop
.
HungryHippos
Updated on June 22, 2022Comments
-
HungryHippos almost 2 years
Something that I've been thinking about recently, and I'd like some input from the good people of Stack Overflow on.
I run a fairly large number of PowerShell scripts on differing schedules (using Task Scheduler). These scripts generally gather data and store it in SQL tables for me. I would say I am running 40-50 scripts or so at the moment, maybe more.
One thing that I have seriously been considering recently is the best way of:
- Ensuring that if any errors occur the script exits without further processing.
- The script should also send me an email on failure with exception details, showing me how far it got.
- The transcript/progress is logged so I can review any errors and try better to handle them in the script.
This is going to be a fair bit of work to do, so I'd like to go in with the right approach from the start.
The only way I can see of achieving something like this would be to do the following:
Set $ErrorActionPreference to "Stop"
This should cause any exceptions to trigger a Try/Catch block.
Run the entire script within a Try/Catch/Finally block.
Finally block is optional of course. Something like this:
$ErrorActionPreference = "Stop" Try { $Content = gc "C:\Temp\NonExistentFile.txt" foreach ($Line in $Content) { Write-Host $Line } } Catch { Write-Host $_ -ForegroundColor "Red" -BackgroundColor "Black" break }
Log the "Transcript" Data into HTML and email the content on failure.
I've tried a few ways of logging, and one of the keys things for me is that I want something that is:
- Easy to log into by providing the text, and a log file location.
- Includes the Date/Time at the start of each line of the log file.
I started out writing text into .txt files, but it was a bit of work to do.
Start-Transcript was OK, but did not include the date/time on each line.
I had some success with a custom Function that would use Start-Transcript, and would write any text I sent into it into Write-Host, with the date/time at the start of each line.
But ultimately I think what I need to do is log into a .html document, and then email the contents of this if an error occurs. Something like this as an example:
Function Send-ErrorEmail { Write-Host "Would Send Email Here" -ForegroundColor "Magenta" } Function Write-Html { [CmdletBinding()] param ( [Parameter(ValueFromPipeline=$True)]$Text, $HTMLTag ) [string]$CurrentDateTime = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss - ") switch ($HTMLTag) { "h1" {$ReturnText = ("<h1>" + $Text + "</h1>")} "h2" {$ReturnText = ("<h2>" + $Text + "</h2>")} default {$ReturnText = ("<p>" + $CurrentDateTime + $Text + "</p>")} } Write-Host -ForegroundColor "Yellow" ($CurrentDateTime + $Text) return $ReturnText } $ErrorActionPreference = "Stop" Try { #Stores the HTML File $LogfilePath = "C:\Temp\LogFile.html" #Begin HTML $HTML = @() $HTML += "<html><head></head><body>" $HTML += "Begin Script" | Write-Html #Loop through Computers $Computers = "LocalHost", "DoesNotExist", "127.0.0.1" foreach ($Computer in $Computers) { #Get Services $HTML += "Computer: $Computer" | Write-Html -HTMLTag "h1" $Services = Get-Service -Computer $Computer $HTML += $Services | ConvertTo-Html -Fragment } #Complete HTML $HTML += "Script End" | Write-Html $HTML += "</body></html>" $HTML | Out-File $LogfilePath } Catch { #Add Exception to HTML $HTML += $_ | Out-String | Write-Html #Complete HTML $HTML += "</body></html>" $HTML | Out-File $LogfilePath #Send EMail Send-ErrorEmail #Exit Script exit }
Any thoughts? Improvements you think I could make? Obviously the HTML document is ugly without CSS formatting, and the Send Mail function doesn't do anything, but it seems to be the simplest way to do what I want.
I can of course dot source any functions amongst all of my scripts for ease of changing a notification email address or similar.
-
HungryHippos over 11 yearsInterestingly, I was reading into the trap statement some more and came across this, and then I realised it was you! :) rkeithhill.wordpress.com/2009/08/03/… - I've not spent a lot of time on it but it looks like the trap statement does not work properly when called interactively? I can't get your 2nd to last example to call the trap unless I put it into a .ps1 file and execute that.
-
Keith Hill over 11 yearsI normally would never use trap interactively but you can get it to work by putting in a scope other than the global scope e.g. execute this interactively:
& { trap { "error trapped !!"; continue }; throw "oops" }
-
HungryHippos over 11 yearsThanks Keith, well I just changed the way I tested the script a little so I could use the Trap {} instead. Seems to work just fine for me, and better to declare it once in the script as opposed to having to encase the whole thing into a Try/Catch I reckon. The main thing I miss about testing scripts interactively is the ability to inspect the variables for fields/data at intervals. I'll mark your response as the answer now.