PowerShell: Monitor a Windows service and restart if stopped
powershellMonitor a Windows service and automatically restart it if it stops. This PowerShell script includes detailed logging, log rotation, retry logic, optional email notifications, and performance tracking—ideal for homelabs, Windows servers, and automation workflows.
powershellwindowswindows serviceservice monitoringauto restartmonitoringloggingemail alertsnotificationsuptimereliabilitysysadminautomationhomelabserver management
Script Code
PowerShellpowershell
<#
.SYNOPSIS
Windows Service Monitor with Auto-Restart and Logging
.DESCRIPTION
Monitors a specified Windows service and automatically restarts it if stopped.
Includes comprehensive logging, email notifications, and performance tracking.
.PARAMETER ServiceName
The name of the service to monitor (required)
.PARAMETER CheckInterval
How often to check the service status in seconds (default: 30)
.PARAMETER LogPath
Path where log files will be stored (default: current directory)
.PARAMETER MaxLogSize
Maximum log file size in MB before rotation (default: 10)
.PARAMETER MaxLogFiles
Maximum number of log files to keep (default: 5)
.PARAMETER EmailNotifications
Enable email notifications for service events
.PARAMETER SmtpServer
SMTP server for email notifications
.PARAMETER EmailFrom
From email address
.PARAMETER EmailTo
To email address(es) - comma separated
.EXAMPLE
.\ServiceMonitor.ps1 -ServiceName "MyService" -CheckInterval 60
.EXAMPLE
.\ServiceMonitor.ps1 -ServiceName "MyService" -EmailNotifications -SmtpServer "smtp.company.com" -EmailFrom "[email protected]" -EmailTo "[email protected]"
#>
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$ServiceName,
[Parameter(Mandatory=$false)]
[int]$CheckInterval = 30,
[Parameter(Mandatory=$false)]
[string]$LogPath = ".",
[Parameter(Mandatory=$false)]
[int]$MaxLogSize = 10,
[Parameter(Mandatory=$false)]
[int]$MaxLogFiles = 5,
[Parameter(Mandatory=$false)]
[switch]$EmailNotifications,
[Parameter(Mandatory=$false)]
[string]$SmtpServer,
[Parameter(Mandatory=$false)]
[string]$EmailFrom,
[Parameter(Mandatory=$false)]
[string]$EmailTo
)
# Global variables
$Script:LogFile = Join-Path $LogPath "ServiceMonitor_$($ServiceName)_$(Get-Date -Format 'yyyyMMdd').log"
$Script:StatsFile = Join-Path $LogPath "ServiceMonitor_$($ServiceName)_stats.json"
$Script:Running = $true
$Script:Stats = @{
StartTime = Get-Date
ChecksPerformed = 0
RestartsPerformed = 0
LastRestart = $null
ServiceUptime = @()
}
# Function to write log entries
function Write-Log {
param(
[Parameter(Mandatory=$true)]
[string]$Message,
[Parameter(Mandatory=$false)]
[ValidateSet("INFO", "WARN", "ERROR", "SUCCESS")]
[string]$Level = "INFO"
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logEntry = "[$timestamp] [$Level] $Message"
# Write to console with colors
switch ($Level) {
"INFO" { Write-Host $logEntry -ForegroundColor White }
"WARN" { Write-Host $logEntry -ForegroundColor Yellow }
"ERROR" { Write-Host $logEntry -ForegroundColor Red }
"SUCCESS" { Write-Host $logEntry -ForegroundColor Green }
}
# Write to log file
try {
Add-Content -Path $Script:LogFile -Value $logEntry -ErrorAction Stop
# Check for log rotation
if ((Get-Item $Script:LogFile -ErrorAction SilentlyContinue).Length -gt ($MaxLogSize * 1MB)) {
Rotate-LogFile
}
}
catch {
Write-Host "Failed to write to log file: $_" -ForegroundColor Red
}
}
# Function to rotate log files
function Rotate-LogFile {
try {
$logDir = Split-Path $Script:LogFile -Parent
$logBaseName = [System.IO.Path]::GetFileNameWithoutExtension($Script:LogFile)
$logExtension = [System.IO.Path]::GetExtension($Script:LogFile)
# Rename current log file with timestamp
$rotatedLog = Join-Path $logDir "$logBaseName`_$(Get-Date -Format 'yyyyMMddHHmmss')$logExtension"
Move-Item -Path $Script:LogFile -Destination $rotatedLog
# Clean up old log files
$oldLogs = Get-ChildItem -Path $logDir -Filter "$logBaseName`_*$logExtension" |
Sort-Object CreationTime -Descending |
Select-Object -Skip $MaxLogFiles
if ($oldLogs) {
$oldLogs | Remove-Item -Force
Write-Log "Cleaned up $($oldLogs.Count) old log files" -Level "INFO"
}
Write-Log "Log file rotated successfully" -Level "INFO"
}
catch {
Write-Log "Failed to rotate log file: $_" -Level "ERROR"
}
}
# Function to send email notifications
function Send-EmailNotification {
param(
[Parameter(Mandatory=$true)]
[string]$Subject,
[Parameter(Mandatory=$true)]
[string]$Body,
[Parameter(Mandatory=$false)]
[ValidateSet("Low", "Normal", "High")]
[string]$Priority = "Normal"
)
if (-not $EmailNotifications -or -not $SmtpServer -or -not $EmailFrom -or -not $EmailTo) {
return
}
try {
$emailParams = @{
SmtpServer = $SmtpServer
From = $EmailFrom
To = $EmailTo.Split(',').Trim()
Subject = "[$env:COMPUTERNAME] $Subject"
Body = $Body
Priority = $Priority
}
Send-MailMessage @emailParams
Write-Log "Email notification sent: $Subject" -Level "INFO"
}
catch {
Write-Log "Failed to send email notification: $_" -Level "ERROR"
}
}
# Function to get service status with detailed information
function Get-ServiceDetails {
param(
[Parameter(Mandatory=$true)]
[string]$Name
)
try {
$service = Get-Service -Name $Name -ErrorAction Stop
$wmiService = Get-WmiObject -Class Win32_Service -Filter "Name='$Name'" -ErrorAction Stop
return @{
Service = $service
Status = $service.Status
StartType = $service.StartType
ProcessId = $wmiService.ProcessId
StartName = $wmiService.StartName
Description = $wmiService.Description
Exists = $true
}
}
catch {
return @{
Service = $null
Status = "NotFound"
Exists = $false
Error = $_.Exception.Message
}
}
}
# Function to restart service with retry logic
function Restart-ServiceWithRetry {
param(
[Parameter(Mandatory=$true)]
[string]$Name,
[Parameter(Mandatory=$false)]
[int]$MaxRetries = 3,
[Parameter(Mandatory=$false)]
[int]$RetryDelay = 10
)
$attempt = 1
$success = $false
while ($attempt -le $MaxRetries -and -not $success) {
try {
Write-Log "Attempting to restart service '$Name' (attempt $attempt of $MaxRetries)" -Level "WARN"
# Stop the service first if it's running
$service = Get-Service -Name $Name -ErrorAction Stop
if ($service.Status -eq 'Running') {
Stop-Service -Name $Name -Force -ErrorAction Stop
Write-Log "Service '$Name' stopped successfully" -Level "INFO"
}
# Wait a moment before starting
Start-Sleep -Seconds 2
# Start the service
Start-Service -Name $Name -ErrorAction Stop
# Verify it's running
Start-Sleep -Seconds 5
$service = Get-Service -Name $Name -ErrorAction Stop
if ($service.Status -eq 'Running') {
$success = $true
$Script:Stats.RestartsPerformed++
$Script:Stats.LastRestart = Get-Date
Write-Log "Service '$Name' restarted successfully" -Level "SUCCESS"
Send-EmailNotification -Subject "Service Restarted: $Name" -Body "Service '$Name' was successfully restarted on $env:COMPUTERNAME at $(Get-Date)"
}
else {
throw "Service status is '$($service.Status)' after start attempt"
}
}
catch {
Write-Log "Failed to restart service '$Name' on attempt $attempt`: $_" -Level "ERROR"
if ($attempt -eq $MaxRetries) {
Send-EmailNotification -Subject "Service Restart Failed: $Name" -Body "Failed to restart service '$Name' on $env:COMPUTERNAME after $MaxRetries attempts. Last error: $_" -Priority "High"
}
else {
Start-Sleep -Seconds $RetryDelay
}
$attempt++
}
}
return $success
}
# Function to save statistics
function Save-Statistics {
try {
$Script:Stats | ConvertTo-Json -Depth 3 | Set-Content -Path $Script:StatsFile
}
catch {
Write-Log "Failed to save statistics: $_" -Level "ERROR"
}
}
# Function to load statistics
function Load-Statistics {
try {
if (Test-Path $Script:StatsFile) {
$stats = Get-Content -Path $Script:StatsFile | ConvertFrom-Json
# Convert datetime strings back to datetime objects
if ($stats.StartTime) { $stats.StartTime = [DateTime]$stats.StartTime }
if ($stats.LastRestart) { $stats.LastRestart = [DateTime]$stats.LastRestart }
$Script:Stats = $stats
Write-Log "Statistics loaded from previous session" -Level "INFO"
}
}
catch {
Write-Log "Failed to load statistics: $_" -Level "WARN"
}
}
# Function to display current statistics
function Show-Statistics {
$uptime = (Get-Date) - $Script:Stats.StartTime
Write-Log "=== SERVICE MONITOR STATISTICS ===" -Level "INFO"
Write-Log "Monitor started: $($Script:Stats.StartTime)" -Level "INFO"
Write-Log "Monitor uptime: $($uptime.Days) days, $($uptime.Hours) hours, $($uptime.Minutes) minutes" -Level "INFO"
Write-Log "Health checks performed: $($Script:Stats.ChecksPerformed)" -Level "INFO"
Write-Log "Service restarts performed: $($Script:Stats.RestartsPerformed)" -Level "INFO"
if ($Script:Stats.LastRestart) {
Write-Log "Last restart: $($Script:Stats.LastRestart)" -Level "INFO"
}
Write-Log "===================================" -Level "INFO"
}
# Function to handle Ctrl+C gracefully
function Stop-Monitor {
Write-Log "Received stop signal. Shutting down monitor..." -Level "WARN"
$Script:Running = $false
Save-Statistics
Show-Statistics
Write-Log "Service monitor stopped gracefully" -Level "INFO"
exit 0
}
# Main monitoring function
function Start-ServiceMonitor {
# Set up signal handling
Register-ObjectEvent -InputObject (New-Object System.Console) -EventName CancelKeyPress -Action {
Stop-Monitor
} | Out-Null
Write-Log "Starting Service Monitor for '$ServiceName'" -Level "SUCCESS"
Write-Log "Check interval: $CheckInterval seconds" -Level "INFO"
Write-Log "Log path: $LogPath" -Level "INFO"
if ($EmailNotifications) {
Write-Log "Email notifications enabled" -Level "INFO"
}
# Load previous statistics
Load-Statistics
# Initial service check
$serviceDetails = Get-ServiceDetails -Name $ServiceName
if (-not $serviceDetails.Exists) {
Write-Log "Service '$ServiceName' not found on this system: $($serviceDetails.Error)" -Level "ERROR"
Send-EmailNotification -Subject "Service Not Found: $ServiceName" -Body "Service '$ServiceName' was not found on $env:COMPUTERNAME. Please check the service name." -Priority "High"
return
}
Write-Log "Service found: $ServiceName" -Level "INFO"
Write-Log "Description: $($serviceDetails.Description)" -Level "INFO"
Write-Log "Start Type: $($serviceDetails.StartType)" -Level "INFO"
Write-Log "Current Status: $($serviceDetails.Status)" -Level "INFO"
# Send startup notification
Send-EmailNotification -Subject "Service Monitor Started: $ServiceName" -Body "Service monitor for '$ServiceName' started on $env:COMPUTERNAME at $(Get-Date)"
# Main monitoring loop
while ($Script:Running) {
try {
$Script:Stats.ChecksPerformed++
$serviceDetails = Get-ServiceDetails -Name $ServiceName
if (-not $serviceDetails.Exists) {
Write-Log "Service '$ServiceName' no longer exists!" -Level "ERROR"
break
}
$status = $serviceDetails.Status
Write-Log "Service status check #$($Script:Stats.ChecksPerformed): $status" -Level "INFO"
# Track service uptime
if ($status -eq 'Running') {
$Script:Stats.ServiceUptime += Get-Date
if ($Script:Stats.ServiceUptime.Count -gt 1000) {
# Keep only last 1000 entries to prevent memory issues
$Script:Stats.ServiceUptime = $Script:Stats.ServiceUptime[-500..-1]
}
}
# Check if service needs to be restarted
if ($status -ne 'Running') {
Write-Log "Service '$ServiceName' is $status - attempting restart" -Level "WARN"
$restartSuccess = Restart-ServiceWithRetry -Name $ServiceName
if (-not $restartSuccess) {
Write-Log "Failed to restart service '$ServiceName' after multiple attempts" -Level "ERROR"
# Continue monitoring in case manual intervention occurs
}
}
# Save statistics periodically
if ($Script:Stats.ChecksPerformed % 10 -eq 0) {
Save-Statistics
}
# Show statistics every hour
if ($Script:Stats.ChecksPerformed % (3600 / $CheckInterval) -eq 0) {
Show-Statistics
}
Start-Sleep -Seconds $CheckInterval
}
catch {
Write-Log "Error in monitoring loop: $_" -Level "ERROR"
Start-Sleep -Seconds $CheckInterval
}
}
Write-Log "Service monitor stopped" -Level "WARN"
}
# Validate email parameters if notifications are enabled
if ($EmailNotifications) {
if (-not $SmtpServer -or -not $EmailFrom -or -not $EmailTo) {
Write-Log "Email notifications enabled but missing required parameters (SmtpServer, EmailFrom, EmailTo)" -Level "ERROR"
exit 1
}
}
# Create log directory if it doesn't exist
if (-not (Test-Path $LogPath)) {
try {
New-Item -Path $LogPath -ItemType Directory -Force | Out-Null
Write-Log "Created log directory: $LogPath" -Level "INFO"
}
catch {
Write-Log "Failed to create log directory '$LogPath': $_" -Level "ERROR"
exit 1
}
}
# Start the monitor
try {
Start-ServiceMonitor
}
catch {
Write-Log "Fatal error in service monitor: $_" -Level "ERROR"
exit 1
}
finally {
Save-Statistics
}Get new scripts weekly.
Subscribe to receive fresh PowerShell scripts, automation tips, and IT workflow improvements.
Subscribe to Newsletter