Home / Guides / Restart a program when it crashes
Restart a program automatically when it crashes on Windows
Windows has a built-in restart for services, but not for ordinary programs: a desktop app, a tray tool or a console program that dies stays dead. This guide covers what Task Scheduler can do, a small PowerShell watchdog, where to run that watchdog so the program's window is visible, and what to do about programs that hang instead of exiting.
Short answer.
Run a watchdog in your own logon session that checks every few seconds whether the program is running and starts it again if it is not. Start that watchdog with a Task Scheduler task that runs at logon, with the logon type Run only when user is logged on. Do not start a program with a window from a service: services run in session 0, and anything they start cannot be seen on your desktop.
Task Scheduler's own "restart on failure" setting is a limited alternative: it retries a failed task a fixed number of times, and does nothing for a program that exits normally or hangs.
First: is it a service?
If the thing you want to keep running is a Windows service, use the service's Recovery settings or a service watchdog instead; see Windows service recovery vs a watchdog. This guide is about programs you start yourself.
Turning a program into a service with a "service wrapper" is a different trade. The program then starts with nobody logged on and gets the Recovery tab, but it runs in session 0 as the service account, so any window it opens is invisible. That suits a console program or background worker on a server. It does not suit anything a person needs to see or click.
Option 1: Task Scheduler with restart on failure
A task can start the program when you log on and retry it if the task fails. In the Task Scheduler window these are the task's Settings tab options If the task fails, restart every and Attempt to restart up to. From PowerShell, for your own account (a task that runs as you with the interactive logon type needs no stored password):
$me = "$env:USERDOMAIN\$env:USERNAME"
$action = New-ScheduledTaskAction -Execute 'C:\Program Files\MyApp\MyApp.exe'
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $me
$principal = New-ScheduledTaskPrincipal -UserId $me -LogonType Interactive
$settings = New-ScheduledTaskSettingsSet -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) `
-ExecutionTimeLimit ([TimeSpan]::Zero) -MultipleInstances IgnoreNew `
-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
Register-ScheduledTask -TaskName 'Start MyApp' -Action $action -Trigger $trigger -Principal $principal -Settings $settings
What each setting does:
-RestartCountand-RestartInterval: how many restart attempts and how far apart. The interval must be between 1 minute and 31 days.-ExecutionTimeLimit ([TimeSpan]::Zero): no time limit. By default Task Scheduler stops a task 72 hours after it starts, which would close your program after three days.-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries: without these,New-ScheduledTaskSettingsSetcreates a task that does not start on battery power and is stopped when a laptop is unplugged.-MultipleInstances IgnoreNew: a second trigger while the program is running does not start a second copy.-LogonType Interactive: the task runs only when you are logged on, inside your existing session, so the program's window is on your desktop.
The limits matter more than the settings:
- Only a task failure counts. Microsoft documents the setting as restarting the task "if the task fails for any reason", but does not define which program exits count as a failure. A program that closes normally completes the task, so nothing restarts. Kill your program once and check the TaskScheduler Operational event log before you rely on it.
- A fixed number of retries. After the last attempt the program stays down until the next trigger, here your next logon.
- Hangs are invisible. A frozen program is still running, so the task is still running.
- No alert. Nothing tells you it happened.
Option 2: a PowerShell watchdog
A watchdog does not care how the program ended. It looks for the process every few seconds and starts it when it is missing. This one matches on the full path (so a copy of the same program in another folder does not count), logs every start, and backs off when the program keeps dying right after it starts, so a broken program is not relaunched in a tight loop:
param(
[Parameter(Mandatory)][string]$Path,
[string[]]$ArgumentList,
[int]$CheckSeconds = 15,
[int]$StableSeconds = 60,
[int]$MaxWaitSeconds = 600,
[System.Diagnostics.ProcessWindowStyle]$WindowStyle = 'Normal',
[string]$LogFile = "$env:LOCALAPPDATA\Keep-ProgramRunning.log"
)
$Path = [System.IO.Path]::GetFullPath($Path)
$name = [System.IO.Path]::GetFileNameWithoutExtension($Path)
function Write-Log([string]$Message) {
Add-Content -Path $LogFile -Value ('{0:s} {1}' -f (Get-Date), $Message)
}
function Test-Running {
# Match on the full path, not only the name, so another copy elsewhere does not count.
@(Get-Process -Name $name -ErrorAction SilentlyContinue |
Where-Object { $_.Path -eq $Path }).Count -gt 0
}
$start = @{ FilePath = $Path; WindowStyle = $WindowStyle; PassThru = $true; ErrorAction = 'Stop' }
if ($ArgumentList) { $start.ArgumentList = $ArgumentList }
$lastStart = [datetime]::MinValue
$quickFails = 0
Write-Log "Watching $Path"
while ($true) {
if (-not (Test-Running)) {
$upFor = (Get-Date) - $lastStart
if ($upFor.TotalSeconds -lt $StableSeconds) { $quickFails++ } else { $quickFails = 0 }
if ($quickFails -gt 0) {
# Not running soon after the last start: wait longer each time instead of looping.
$wait = [int][Math]::Min($CheckSeconds * [Math]::Pow(2, $quickFails), $MaxWaitSeconds)
Write-Log "$name not running $([int]$upFor.TotalSeconds)s after the last start attempt; waiting ${wait}s"
Start-Sleep -Seconds $wait
}
try {
$p = Start-Process @start
Write-Log "Started $name (PID $($p.Id))"
}
catch {
Write-Log "FAILED to start ${name}: $($_.Exception.Message)"
}
$lastStart = Get-Date
}
Start-Sleep -Seconds $CheckSeconds
}
Save it as C:\Scripts\Keep-ProgramRunning.ps1 and try it in a console first:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Scripts\Keep-ProgramRunning.ps1 -Path "C:\Program Files\MyApp\MyApp.exe" -CheckSeconds 10
-CheckSeconds: how often it looks (default 15). This is the longest the program stays down after a crash.-StableSeconds: if the program is gone within this many seconds of being started, the next start waits twice as long as the one before, up to-MaxWaitSeconds. Once it has run longer than that, the wait resets.-ArgumentListand-WindowStyle: passed toStart-Process(for example-WindowStyle Minimized).- The log goes to
%LOCALAPPDATA%\Keep-ProgramRunning.logunless you give-LogFile.
Tested on Windows PowerShell 5.1 with a test program that has no window: killed, it was started again within the check interval; a copy of the same program in another folder was ignored; a program that exited two seconds after every start was retried after 4, 8 and then 16 seconds (the cap set for the test); a path that does not exist was logged as FAILED and retried.
The watchdog restarts the program whenever it is missing, including when you closed it on purpose. Stop the watchdog first when you want the program closed.
Where to run the watchdog: at logon, not as a service
Where the watchdog runs decides where the program appears. Since Windows Vista, services run in session 0, which is reserved for services and has no interactive desktop. A service cannot show a window to the user, and Microsoft documents that processes a LocalSystem service creates cannot display a user interface either. So a watchdog that runs as a service restarts your program where nobody can see it.
Run it in your own session instead: a task with an at-logon trigger and the interactive logon type. Microsoft documents that logon type as running the task "only in an existing interactive session". A task with the S4U logon type runs on a non-interactive desktop and has the same problem as a service.
$me = "$env:USERDOMAIN\$env:USERNAME"
$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
-Argument '-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "C:\Scripts\Keep-ProgramRunning.ps1" -Path "C:\Program Files\MyApp\MyApp.exe"'
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $me
$principal = New-ScheduledTaskPrincipal -UserId $me -LogonType Interactive
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit ([TimeSpan]::Zero) -MultipleInstances IgnoreNew `
-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
Register-ScheduledTask -TaskName 'Keep MyApp running' -Action $action -Trigger $trigger -Principal $principal -Settings $settings
The task has no time limit because the watchdog never ends; with the default 72-hour limit it would be stopped after three days.
To check that the task is set up as intended, run Get-ScheduledTask -TaskName 'Keep MyApp running' | Select-Object -ExpandProperty Settings.
The catch: the program only runs while you are logged on. On a server where nobody logs on, a program with a window is the wrong tool. Use a program that can run as a service and the Recovery tab, and accept that it has no visible window.
Developers who write their own watchdog service can still reach the user's desktop. Microsoft's guidance is a separate helper program
that runs in the user's session (started with CreateProcessAsUser, or from the Run registry key on multi-user
systems) and talks to the service over interprocess communication. That is code, not configuration.
Crashed or hung?
Everything above detects a program that is gone. A program that is frozen still has a process, so every watchdog that checks "is it running" sees it as fine. Two ways to catch a hang:
- Window responsiveness. PowerShell's process objects have a
Respondingproperty, which checks whether the program's window responds to user input. A program without a main window always reportsTrue, and a window that does not answer immediately reportsFalseeven if the program is only busy, so only act after several failed checks in a row.Get-Process -Name MyApp | Select-Object Name, Id, Responding - A health signal from the program itself. If the program writes a log or heartbeat file, or answers on a port, check that
instead: if the file has not changed for a few minutes, stop the process with
Stop-Process -Forceand let the watchdog start it again. This works for programs without a window too.
Questions
Can Task Scheduler restart a program that crashes?
Partly. A task can retry when it fails, a set number of times and at least one minute apart. Microsoft does not define which program exits count as a failure, a program that closes normally ends the task without a retry, and a hung program is never noticed. Test it with your program before relying on it.
Why does a program started by a service not appear on my screen?
Services run in session 0, which has no interactive desktop, so programs they start cannot show a window to the user. Run the watchdog in your own session instead: a task with an at-logon trigger and the logon type "Run only when user is logged on".
How do I restart a program that is frozen, not closed?
A frozen program still has a process, so a running check does not see a problem. Check the Responding property of its process several times in a row (it only works for programs with a main window), or check a health signal the program gives, such as a log file that keeps changing, then stop the process and let the watchdog start it again.
How fast is a crashed program started again?
The script checks every 15 seconds by default (-CheckSeconds), so that is the longest the program stays down after a crash. SvcSentinel checks every 30 seconds. Task Scheduler's restart interval is at least one minute.
Will the watchdog restart a program I closed on purpose?
Yes. A watchdog cannot tell a crash from a deliberate close. Stop the watchdog first, or in SvcSentinel pause that application from the tray menu, and start it again when you are done.
Related
- SvcSentinel: automatic restarts for Windows services and applications
- Restart a Windows service automatically: the Recovery tab vs a watchdog
- All guides
Sources (Microsoft Learn): New-ScheduledTaskSettingsSet, New-ScheduledTaskTrigger, RestartOnFailure, Interval (restartType), TaskSettings.ExecutionTimeLimit, TASK_LOGON_TYPE, Security contexts for tasks, Troubleshoot scheduled tasks not running, Service changes for Windows Vista (session 0), Interactive services, Window station and desktop creation, Process.Responding, Start-Process, about_PowerShell_exe.