Scripted OpsAutomate the boring stuff.

PowerShell: Monitor a Windows service and restart if stopped

powershell

Monitor 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