
Monitor a Windows Service and Auto-Restart It with PowerShell
Before you run this
This guide builds a small watchdog: a PowerShell script that checks one named Windows service, and if it is not running, tries to start it and writes the outcome to a log file and the Windows event log. You then schedule it with Task Scheduler so it runs every few minutes. The script starts a stopped service — that is its whole point — so run it only against a service you actually want kept running. It does not delete or reconfigure anything, and starting a service is reversible (you can stop it again), so there is no destructive, one-way step here.
Privileges: Starting a service and writing to the event log both require elevation. Run PowerShell as Administrator. The first execution also creates an event-log source, which needs admin rights the one time it happens. When you schedule the task, it will run as the SYSTEM account, which has these rights.
Test first: read the script before you run it, and try it against a harmless test service on a non-production box first — the Print Spooler (Spooler) is a good throwaway target you can stop by hand and watch the script bring back. Do not point it at a production database or mail service until you have seen it behave on the test service.
One thing worth knowing before you write any script at all: Windows already has a built-in recovery mechanism. In services.msc, each service has a Recovery tab (exposed on the command line by sc.exe failure) that can restart a service automatically when its process crashes. If your service dies by crashing, use that first — it is the native, supported way. The script below covers the cases the built-in recovery does not: a service that was stopped cleanly, stopped by a dependency, or never came back after a reboot. That is when a polling watchdog earns its place.
What I'm assuming
- Windows Server 2019 or 2022 (this also works on Windows 10/11), PowerShell 5.1, which ships in the box.
- You know the service's short name (the "Service name" on the General tab in
services.msc), not just its display name.Get-Service | Select-Object Name, DisplayName, Statuswill list both. - You have a folder to hold the script and its log, e.g.
C:\Scripts. Replace paths and the service name with your own throughout.
The script
Save this as C:\Scripts\Monitor-Service.ps1. Replace Spooler with your service's short name and adjust the log path.
# Monitor-Service.ps1
# Checks one service; starts it if it is not running. Logs to file and event log.
$ServiceName = 'Spooler' # <-- replace with your service SHORT name
$LogPath = 'C:\Scripts\service-monitor.log' # <-- replace with your log path
$Source = 'ServiceMonitor' # event log source name
# Create the event log source once. Needs admin the first time it runs.
if (-not [System.Diagnostics.EventLog]::SourceExists($Source)) {
New-EventLog -LogName Application -Source $Source
}
function Write-MonitorLog {
param([string]$Level, [string]$Message, [int]$EventId, [string]$EntryType)
$line = "{0} {1} {2}" -f (Get-Date -Format s), $Level, $Message
Add-Content -Path $LogPath -Value $line
Write-EventLog -LogName Application -Source $Source -EventId $EventId -EntryType $EntryType -Message $Message
}
$svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
if ($null -eq $svc) {
Write-MonitorLog 'ERROR' "Service '$ServiceName' not found." 1001 'Error'
exit 1
}
if ($svc.Status -eq 'Running') {
# Healthy: nothing to do. Comment out the next line if you don't want a heartbeat.
Add-Content -Path $LogPath -Value ("{0} INFO '{1}' is running." -f (Get-Date -Format s), $ServiceName)
exit 0
}
# Not running: attempt a start.
Write-MonitorLog 'WARN' "Service '$ServiceName' was $($svc.Status). Attempting to start." 1002 'Warning'
try {
Start-Service -Name $ServiceName -ErrorAction Stop
Start-Sleep -Seconds 5
$svc = Get-Service -Name $ServiceName # re-query for current state
if ($svc.Status -eq 'Running') {
Write-MonitorLog 'INFO' "Service '$ServiceName' started successfully." 1003 'Information'
} else {
Write-MonitorLog 'ERROR' "Service '$ServiceName' still $($svc.Status) after start attempt." 1004 'Error'
}
} catch {
Write-MonitorLog 'ERROR' "Failed to start '$ServiceName': $($_.Exception.Message)" 1005 'Error'
}
A few notes on the non-obvious lines. EventLog.SourceExists is a .NET call rather than a cmdlet because there is no clean PowerShell test for a source that also works before the source exists. I re-query with Get-Service after the start rather than trusting the old object, because the original $svc still holds the stale Stopped status. And Start-Service is the right verb here — I want to bring a stopped service up, not Restart-Service, which would first stop an already-running one.
Test it by hand
On your test box, stop the service and run the script once, elevated:
Stop-Service -Name 'Spooler'
Get-Service -Name 'Spooler' # confirm it shows Stopped
.\Monitor-Service.ps1
Get-Service -Name 'Spooler' # should now show Running
Get-Content 'C:\Scripts\service-monitor.log' -Tail 5
If the service comes back and the log shows the WARN/INFO pair, the logic works. Do this before you schedule anything.
Schedule it with Task Scheduler
Run these once, in an elevated PowerShell session, to register a task that runs the script every 5 minutes as SYSTEM. Adjust the interval and the script path.
$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
-Argument '-NoProfile -ExecutionPolicy Bypass -File "C:\Scripts\Monitor-Service.ps1"'
# Run every 5 minutes, starting now, for a long duration.
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) `
-RepetitionInterval (New-TimeSpan -Minutes 5) `
-RepetitionDuration (New-TimeSpan -Days 3650)
# Run as SYSTEM with full rights, whether or not anyone is logged in.
$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' `
-LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName 'Service Monitor - Spooler' `
-Action $action -Trigger $trigger -Principal $principal `
-Description 'Restarts the Spooler service if it stops.'
I set an explicit 3650-day (-RepetitionDuration) window rather than relying on an implicit "indefinite" default, because the default behaviour differs between Windows versions and I would rather be plain about it than have the repetition quietly expire. Ten years is effectively forever for this purpose. The -ExecutionPolicy Bypass on the command line affects only this one invocation and does not change the machine's policy.
If you prefer, you can build the same task in the Task Scheduler GUI (taskschd.msc) — set the trigger to repeat every 5 minutes indefinitely, the action to run powershell.exe with the -File argument above, and the "Run as" account to SYSTEM. For the exact parameter reference on any of these cmdlets, see Microsoft Learn for the ScheduledTasks module (Register-ScheduledTask, New-ScheduledTaskTrigger).
Verify it's working
Confirm the task exists and check when it last ran and its result:
Get-ScheduledTask -TaskName 'Service Monitor - Spooler'
Get-ScheduledTaskInfo -TaskName 'Service Monitor - Spooler' # LastRunTime, LastTaskResult
A LastTaskResult of 0 means the last run finished cleanly. For a live end-to-end test, stop the service and wait one interval:
Stop-Service -Name 'Spooler'
# wait up to 5 minutes, then:
Get-Service -Name 'Spooler' # expect Running
Get-Content 'C:\Scripts\service-monitor.log' -Tail 10
You can also read the event log entries the script wrote:
Get-EventLog -LogName Application -Source 'ServiceMonitor' -Newest 10
Undo it
To remove the watchdog completely, delete the scheduled task and, if you like, the event log source. Removing the source is optional and cosmetic:
Unregister-ScheduledTask -TaskName 'Service Monitor - Spooler' -Confirm:$false
Remove-EventLog -Source 'ServiceMonitor' # optional; removes the log source
Unregister-ScheduledTask stops the watchdog from running again but leaves the service itself untouched — whatever state it is in now, it stays. Deleting the script file and its log finishes the cleanup. Nothing here modifies the monitored service's own configuration, so there is no service-side change to reverse.
Runs enterprise networks and security for a living, and writes Shore Up to turn two decades of hands-on Linux, Windows and mail-server work into guides you can actually use.
More about the author →Was this article helpful?
Tap a star — no sign-in needed.
Be the first to rate this article.
