How to Automatically Monitor and Restart a Failed Windows Service Using PowerShell and SC.exe


2 views

As a developer managing critical background processes, few things are more frustrating than discovering your Windows service has crashed overnight. Unlike desktop applications, services run unattended and need robust recovery mechanisms.

Windows actually includes built-in service recovery features that many developers overlook. Right-click your service in services.msc, go to Properties → Recovery tab. Here you can configure:

First failure: Restart the Service
Second failure: Restart the Service
Subsequent failures: Take No Action
Reset fail count after: 1 day

For more control, here's a PowerShell monitoring script that checks service status every 60 seconds:

# ServiceMonitor.ps1
$serviceName = "YourServiceName"
$checkInterval = 60 # seconds

while ($true) {
    $service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue
    
    if ($service -eq $null) {
        Write-Host "Service $serviceName not found!" -ForegroundColor Red
        break
    }
    
    if ($service.Status -ne "Running") {
        Write-Host "$(Get-Date) - Service not running. Restarting..." -ForegroundColor Yellow
        Start-Service -Name $serviceName
        # Optional: Send email notification
        # Send-MailMessage -To admin@yourdomain.com -From monitor@yourdomain.com -Subject "Service Restarted" -Body "Restarted $serviceName"
    }
    
    Start-Sleep -Seconds $checkInterval
}

To ensure your monitoring script runs continuously:

# Create scheduled task to run at startup
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-File C:\Path\To\ServiceMonitor.ps1"
$trigger = New-ScheduledTaskTrigger -AtStartup
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -DontStopOnIdleEnd -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1)
Register-ScheduledTask -TaskName "ServiceMonitor" -Action $action -Trigger $trigger -Settings $settings -RunLevel Highest

The Windows SC (Service Control) utility offers another approach. Create a recovery.cmd file:

@echo off
:check
sc query "YourServiceName" | find "RUNNING"
if %ERRORLEVEL% == 1 (
    sc start "YourServiceName"
    timeout /t 10
)
timeout /t 60
goto check

For mission-critical services, consider creating a watchdog service:

// WatchdogService.cs
protected override void OnStart(string[] args)
{
    ThreadPool.QueueUserWorkItem(state => 
    {
        while (true)
        {
            ServiceController sc = new ServiceController("TargetService");
            if (sc.Status != ServiceControllerStatus.Running)
            {
                sc.Start();
                sc.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(30));
            }
            Thread.Sleep(60000);
        }
    });
}

Implement proper logging in your monitoring solution:

# Add to PowerShell script
$logPath = "C:\Logs\ServiceMonitor.log"
"[$(Get-Date)] Service status: $($service.Status)" | Out-File -FilePath $logPath -Append

Be mindful of monitoring frequency - every 60 seconds is usually sufficient. For services with strict uptime requirements, consider:

  • Event log triggers instead of polling
  • Windows Performance Monitor alerts
  • Third-party monitoring tools like Nagios or Zabbix

Windows includes built-in service recovery options that can automatically restart failed services. Configure these settings through the Services management console:


sc failure "YourServiceName" reset= 60 actions= restart/60000

This configures the service to:

  • Attempt a restart after 60 seconds (60000 milliseconds)
  • Reset the failure counter after 60 seconds

For more control, create a PowerShell monitoring script:


# ServiceMonitor.ps1
$serviceName = "YourServiceName"
$sleepInterval = 30 # seconds

while ($true) {
    $service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue
    if ($service.Status -ne "Running") {
        Start-Service -Name $serviceName
        # Optional logging
        Add-Content -Path "C:\logs\service_monitor.log" -Value "$(Get-Date) - Restarted $serviceName"
    }
    Start-Sleep -Seconds $sleepInterval
}

Schedule the PowerShell script to run continuously:


$action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-File C:\scripts\ServiceMonitor.ps1"
$trigger = New-ScheduledTaskTrigger -AtStartup
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -DontStopOnIdleEnd -RestartInterval (New-TimeSpan -Minutes 1) -RestartCount 3
Register-ScheduledTask -TaskName "ServiceMonitor" -Action $action -Trigger $trigger -Settings $settings -RunLevel Highest

For mission-critical services, create a watchdog service in C#:


using System;
using System.ServiceProcess;
using System.Timers;

public class ServiceMonitor : ServiceBase
{
    private Timer timer;
    private const string TargetService = "YourServiceName";
    
    protected override void OnStart(string[] args)
    {
        timer = new Timer(30000); // Check every 30 seconds
        timer.Elapsed += CheckService;
        timer.Start();
    }
    
    private void CheckService(object sender, ElapsedEventArgs e)
    {
        using (var sc = new ServiceController(TargetService))
        {
            if (sc.Status != ServiceControllerStatus.Running)
            {
                sc.Start();
                sc.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(30));
            }
        }
    }
    
    protected override void OnStop()
    {
        timer?.Stop();
        timer?.Dispose();
    }
}

Enhance your solution with proper logging and notifications:


# Enhanced PowerShell monitoring with email alerts
$serviceName = "YourServiceName"
$logPath = "C:\logs\service_monitor.log"
$smtpServer = "smtp.yourdomain.com"
$toEmail = "admin@yourdomain.com"

function Send-Alert {
    param($message)
    Send-MailMessage -From "monitor@yourdomain.com" -To $toEmail -Subject "Service Alert" -Body $message -SmtpServer $smtpServer
}

try {
    $service = Get-Service -Name $serviceName
    if ($service.Status -ne "Running") {
        Start-Service -Name $serviceName
        $logEntry = "$(Get-Date) - Restarted $serviceName"
        Add-Content -Path $logPath -Value $logEntry
        Send-Alert $logEntry
    }
} catch {
    $errorMsg = "$(Get-Date) - ERROR: $_"
    Add-Content -Path $logPath -Value $errorMsg
    Send-Alert $errorMsg
}