Send an email if a PowerShell script gets any errors at all and terminate the script

14,925

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.

Share:
14,925
HungryHippos
Author by

HungryHippos

Updated on June 22, 2022

Comments

  • HungryHippos
    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:

    1. Ensuring that if any errors occur the script exits without further processing.
    2. The script should also send me an email on failure with exception details, showing me how far it got.
    3. 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:

    1. Easy to log into by providing the text, and a log file location.
    2. 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
    HungryHippos over 11 years
    Interestingly, 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
    Keith Hill over 11 years
    I 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
    HungryHippos over 11 years
    Thanks 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.