Each 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
If you are running PowerShell 2.0 on the box then you can use the new Send-MailMessage cmdlet which is even easier.
LikeLike
Would you mind explaining what advantage this would have and give an example that would match mine?
LikeLike
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.
LikeLike
That’s the side I’m coming from to, the simpler the better! Thanks. I’ll be giving it a go soon.
LikeLike
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.
LikeLike
I’ve just given that a go and it works brilliantly, thanks for the pointers 🙂
LikeLike
just what i needed. thanks
LikeLike
Reblogged this on SimpleSQLDBA | Shadab Mohammad.
LikeLike
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”
LikeLike