Using PowerShell to send a Windows Service Recovery Email Alert


recovery dialogue boxEach Service that runs in Windows features a Recovery tab in the Services.msc management console. Normally I only ever set this up to restart the service after 5 minutes in case something had conflicted with it’s initial start-up attempt. However, we recently had a problem with an IBM service that caused our Windows 2003 R2 x64 server to reboot if it crashed. I thought it would be very handy if we could get an email sent to the IT department if the service was failing. I had dabbled with using BLAT in the past but seeing as all of our servers already have PowerShell installed I thought that would be a more efficient option.

It turns out this is a relatively simple task. I don’t have much PowerShell knowledge but luckily I found the answer on the SQLteam site. All you need to do is create the following PowerShell file.

<# Power shell Script to send email
    by Thom McKiernan - thommck.wordpress.com - @thommck
    Created 28/03/2011
    #>

# Get the state of any services starting IBM* from computer Server1
$service = Get-WmiObject win32_service -computername Server1 | select name,state | where { $_.name -like "ibm*"} | out-string
# Specify a sender email address
$emailFrom = "alert@domain.lan"
# Specify a recipient email address
$emailTo = "it@domain.co.uk"
# Put in a subject line
$subject = "IBM Service Alert"
# Add the Service state from line 6 to some body text
$body = $service + "If the service, IBM Automatic Server Restart, fails to start it could prevent AAC1 shutting down properly.`nPlease restart it`n`nRegards,`nIT Dept`nAAC Services"
# Put the DNS name or IP address of your SMTP Server
$smtpServer = "192.168.0.25"
$smtp = new-object Net.Mail.SmtpClient($smtpServer)
# This line pieces together all the info into an email and sends it
$smtp.Send($emailFrom, $emailTo, $subject, $body)

For those new to PowerShell, a line starting with a # is a comment. Feel free to remove them to slim down your script. I use the Windows PowerShell Integrated Scripting Environment (ISE) to create *.Ps1 files. You can think of these as batch scripts that run in PowerShell rather than the command line.

If you’ve never run a PowerShell script before you have to submit a command to lower the security level so it can run unsigned script. If you want to keep secure then you will need to sign your script as instructed in the PowerShell help files.

To run unsigned scripts that you write on your local computer and signed    scripts from other users, use the following command to change the execution    policy on the computer to RemoteSigned:
set-executionpolicy remotesigned

Once this has been saved, you need to add it to the Recovery options Run a program section. Put the path to powershell.exe in the first box. The default location is (even for version 2!)

c:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe

In the second “parameters” box tell it to run the script above e.g.

-command C:\script\email.ps1

Be careful where you save the script as PowerShell is very picky about interpreting quotes, ampersands and other file name gotchas and this can cause your script to fail. In fact you may want to run the whole command in PowerShell first to check for any errors e.g.

PS c:> c:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -command C:\script\email.ps1 

These recovery options only work if a service closes unexpectedly. Sometimes you can force this by ending the process in Task Manager. Check the System Event Log to see if the recovery was initiated and then check your inbox for the email.

This script is only intended to send an email for a specified service on a specified server. You can of course modify the script to check a whole range of servers and/or services. If the Service Recovery option doesn’t seem to be working, you could also run this script as a Scheduled Task that will only send an email if the specified service is stopped.

Please let me know if I have made any PowerShell newbie errors or if you use something similar that you would like to share.

Update: Check the comment below from Jeffery Hicks for an even neater way of doing it.

P.S I used the techtopia article Windows PowerShell 1.0 String Quoting and Escape Sequences to help with formatting of the $body text

Advertisements

9 thoughts on “Using PowerShell to send a Windows Service Recovery Email Alert

  1. Fundamentally there’s not too much difference since the cmdlet is using the same underlying .NET class. The advantage is that with a cmdlet you don’t have to write what amounts to systems level programming. You simply invoke a command

    Send-MailMessage -To $emailTo -From $emailFrom -subject $subject -body $body

    And the parameter values can be in any order as long as you use the full parameter name. The cmdlet has other parameters as well which you may want to take advantage of. As a best practice I always recommend trying to use an existing cmdlet and avoid using “raw” .NET classes unless there is no alternative. If you approach PowerShell from a developer perspective then your approach is fine. But coming from the admin side (which is my audience and community) I look for ease of use.

  2. In looking at your script it really looks like it is designed to be run as a standalone script since it uses WMI to query services on a given computer. The recovery action should be something specific to the service. I took your code and turned it into a PowerShell script.

    #requires -version 2.0

    #Revised by Jeffery Hicks
    #http://jdhitsolutions.com/blog

    Param (
    [Parameter(Position=0,Mandatory=$True,HelpMessage=”Enter a service name”)]
    [ValidateNotNullorEmpty()]
    [string]$Service,
    [string]$emailFrom = “alert@domain.lan”,
    [string]$emailTo = “it@domain.co.uk”,
    [string]$subject = “IBM Service Alert”,
    [string]$smtpServer = “192.168.0.25”,
    )

    # Add the Service state to some body text
    $body = “If the service, $Service, fails to start it could prevent AAC1 shutting down properly.`nPlease restart it`n`nRegards,`nIT Dept`nAAC Services”

    # This line pieces together all the info into an email and sends it

    Send-MailMessage -To $emailTo -From $emailFrom -Subject $subject -Body $body -SmtpServer $smtpServer
    #end of script

    Then in the recovery tab I use the PowerShell.exe as the command and this as the arguments:

    -noprofile -command “”

    The script uses parameters so you can test things out by perhaps sending only to a test address.

    -noprofile -command “”

    There’s much more you can do with parameters to make this robust and re-usable without having to write different scripts for different services.

    Good luck.

  3. Extended your script with an loop, so that the script will run every 5 seconds during business hours:

    $LogPath = “C:\Log\”
    $LogName = “ServiceMonitoring_” + $((Get-Date).ToString(‘yyyy-MM-dd’)) + “.log”
    $Logfile = $LogPath + “\” + $LogName
    $TimeEnd = [datetime]::ParseExact(“$((Get-Date).ToString(‘ddMMyyyy’)) 23:59” ,”ddMMyyyy HH:mm”,$null)
    $TimeStart = Get-Date

    $FileExists = Test-Path $Logfile
    If ($FileExists -eq $False)
    {
    New-Item -Path $LogPath -Value $LogName -ItemType File
    }

    Add-Content -Path $Logfile -Value “***************************************************************************************************”
    Add-Content -Path $Logfile -Value “Started monitoring at $TimeStart”
    Add-Content -Path $Logfile -Value “Monitoring: $RootFolder ”
    Add-Content -Path $Logfile -Value “End Time: $TimeEnd ”
    Add-Content -Path $Logfile -Value “***************************************************************************************************”

    #setup loop

    Write-Host “Start Time: $TimeStart”
    Write-Host “Monitoring: $RootFolder ”
    write-host “End Time: $TimeEnd”

    Do {
    $TimeNow = Get-Date
    if ($TimeNow -ge $TimeEnd) {
    Write-host “It’s time to finish.”
    } else {
    Write-Host “Checking $TimeNow”

    #Filters the types of services you want to check.
    Get-WmiObject Win32_Service | Where-Object {$_.Name -like ‘TotalAgility Streaming Service’ -and $_.StartMode -eq ‘Auto’ -and $_.State -ne ‘Running’} | Select-Object Name | export-csv C:\Scripts\services.csv -NoTypeInformation
    #Get-WmiObject Win32_Service | Where-Object {$_.Name -like ‘*SomethingElse*’ -and $_.StartMode -eq ‘Auto’ -and $_.State -ne ‘Running’} | Select-Object Name | export-csv C:\temp\services.csv -NoTypeInformation -Append

    #Logic. If the file is not empty then there are failures.
    $NewBody = “The following list are services found that are listed as to be Automatically running.`f”
    $NewBody = $NewBody + “and are currently stopped / not running.`f`f”
    $NewBody = $NewBody + “Failed Services:`f”
    $test = Import-CSV C:\Scripts\services.csv
    if ($test -ne $NULL)
    {
    Foreach ($line in $test)
    {
    $NewLine = $line.Name
    $NewLine = $NewLine + “`f”
    $NewBody = $NewBody + $NewLine

    }
    RunSendEmail
    start-sleep -seconds 300
    }

    start-sleep -seconds 5

    }}
    Until ($TimeNow -ge $TimeEnd)

    Add-Content -Path $Logfile -Value “***************************************************************************************************”
    Add-Content -Path $Logfile -Value “Processing ended at [$([DateTime]::Now)].”
    Add-Content -Path $Logfile -Value “***************************************************************************************************”

    write-host “stop”

What do you think?

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s