I recently had a Windows Scheduled Task fail with this kind of event in the Task Scheduler Operational log:
Task Scheduler failed to start "\Folder\Example_Task" task for user "NT AUTHORITY\SYSTEM".
Additional Data: Error Value: 2147750687.The important parts are the event source, the event ID, and the error value:
| Field | Value |
|---|---|
| Log | Microsoft-Windows-TaskScheduler/Operational |
| Event ID | 101 |
| Level | Error |
| OpCode | Launch Failure |
| Error Value | 2147750687 |
| Hex value | 0x8004131F |
In plain English, this usually means Task Scheduler tried to start the task, but another instance of the same task was still running. The task did not fail because PowerShell could not start, and it did not necessarily fail because the account was wrong. It failed before the new run started because Task Scheduler blocked a second instance.
Quick answer
Task Scheduler error value 2147750687 is hexadecimal 0x8004131F, which means an instance of the task is already running. Fix it by finding and stopping the stuck task instance, checking why the previous run did not exit, and changing the task’s Settings tab under If the task is already running, then the following rule applies. For most admin scripts, choose Do not start a new instance or Stop the existing instance, then add logging, timeouts, and cleanup logic so the script does not hang forever.
What happened in this case
The confusing part is that the PowerShell script worked when it was run manually. It produced the expected result, so the script logic looked fine.
The failure happened only after testing through Task Scheduler:
- The script was run manually from PowerShell and completed successfully.
- In Task Scheduler, the task was selected and Run was clicked to test the action.
- The task status changed to
Running. - The task produced the expected result, but the Task Scheduler status never returned to
Ready. - At 8:00 AM, the real scheduled trigger fired.
- Task Scheduler tried to start the task again, saw the earlier test instance still running, and logged Event ID 101 with error value
2147750687.
That means the 8:00 AM failure was caused by the manual test run that never exited. The scheduled run was blocked by the stuck test instance.
The immediate fix is simple:
Stop-ScheduledTask -TaskPath '\Folder\' -TaskName 'Example_Task'Then confirm the task is back to Ready before the next scheduled time:
Get-ScheduledTask -TaskPath '\Folder\' -TaskName 'Example_Task' |
Select-Object TaskName, StateThe real fix is to find why Task Scheduler still thinks the action is running after the script has produced its result. That usually means the scheduled action process did not exit cleanly, a child process is still alive, or the task is starting something long-running that should be managed as a service instead of as a one-shot scheduled task.
Convert the error value to hex
Task Scheduler often logs decimal error values. Most references and many admin notes use the hexadecimal form.
Use PowerShell to convert the value:
'0x{0:X8}' -f 2147750687Output:
0x8004131FThat code maps to this condition:
SCHED_E_ALREADY_RUNNING
An instance of this task is already running.This is why the wording in the visible event can be confusing. Event ID 101 says “failed to start,” but the reason is not always a broken action. The new launch was refused because Task Scheduler believed the same task was already active.
Confirm the failure in Event Viewer
Open Event Viewer and go to:
Applications and Services Logs
Microsoft
Windows
TaskScheduler
OperationalFilter for these event IDs around the failed time:
| Event ID | Meaning |
|---|---|
| 100 | Task started |
| 101 | Task start failed |
| 102 | Task completed |
| 103 | Action started |
| 110 | Task triggered by scheduler |
| 111 | Task terminated |
| 129 | Task process created |
| 140 | Task updated |
| 200 | Action started |
| 201 | Action completed |
For this specific error, look for a warning shortly before the Event ID 101. It often says that Task Scheduler did not launch the task because an instance of the same task is already running.
You can query the operational log with PowerShell:
$taskName = '\Folder\Example_Task'
Get-WinEvent -LogName 'Microsoft-Windows-TaskScheduler/Operational' |
Where-Object {
$_.TimeCreated -gt (Get-Date).AddHours(-6) -and
$_.Message -like "*$taskName*"
} |
Select-Object TimeCreated, Id, LevelDisplayName, Message |
Format-ListUse your real task path when running the command locally. In public notes or tickets, sanitize internal computer names, domain names, task names, and service accounts unless they are meant to be shared.
Check whether the task is still running
Start with the ScheduledTasks cmdlets:
Get-ScheduledTask -TaskPath '\Folder\' -TaskName 'Example_Task' |
Get-ScheduledTaskInfo |
Select-Object LastRunTime, LastTaskResult, NextRunTime, NumberOfMissedRunsThen check the task state:
Get-ScheduledTask -TaskPath '\Folder\' -TaskName 'Example_Task' |
Select-Object TaskName, TaskPath, StateIf State is Running long after the job should have finished, Task Scheduler is not making up the error. The previous instance is still active.
You can stop it from PowerShell:
Stop-ScheduledTask -TaskPath '\Folder\' -TaskName 'Example_Task'Give it a few seconds, then check again:
Get-ScheduledTask -TaskPath '\Folder\' -TaskName 'Example_Task' |
Select-Object TaskName, StateIf the task refuses to stop, find the child process. For PowerShell-based tasks, this is often powershell.exe or pwsh.exe:
Get-CimInstance Win32_Process |
Where-Object {
$_.Name -in 'powershell.exe', 'pwsh.exe', 'cmd.exe', 'wscript.exe', 'cscript.exe'
} |
Select-Object ProcessId, Name, CommandLine |
Format-ListDo not kill processes just because the name matches. Check the command line first. If it is the stuck task process, stop it:
Stop-Process -Id 12345 -ForceReplace 12345 with the real process ID.
Fix the task instance rule
Open the task in Task Scheduler and go to the Settings tab. Look for:
If the task is already running, then the following rule applies:The common choices are:
| Setting | What happens | When to use it |
|---|---|---|
| Do not start a new instance | The next run is skipped if the old one is still running | Best default for most maintenance scripts |
| Run a new instance in parallel | A second copy starts anyway | Only safe for scripts designed for concurrency |
| Queue a new instance | The next run waits behind the current one | Useful when every run must happen in order |
| Stop the existing instance | The old run is stopped and the new run starts | Useful for polling jobs where newest run matters most |
For email jobs, report jobs, file copies, and most maintenance scripts, I usually avoid Run a new instance in parallel. Parallel instances can send duplicate emails, copy the same files twice, corrupt a working folder, or fight over the same log.
If the job runs every 5 minutes but sometimes takes 20 minutes, do not hide the problem by allowing parallel runs. Either make the script faster, schedule it less often, or make it intentionally single-instance.
Set a maximum runtime
The fastest practical fix is often a runtime limit. In the task’s Settings tab, enable:
Stop the task if it runs longer than:Pick a value that is longer than a normal run but shorter than “this has clearly hung.” For a job that normally runs in 2 minutes, a 30-minute limit may be reasonable. For a large copy job that normally runs in 45 minutes, a 2-hour limit may be safer.
You can configure this with PowerShell:
$task = Get-ScheduledTask -TaskPath '\Folder\' -TaskName 'Example_Task'
$task.Settings.ExecutionTimeLimit = 'PT30M'
$task | Set-ScheduledTaskPT30M means 30 minutes in ISO 8601 duration format.
If you use a runtime limit, make sure the script can tolerate being stopped. For example, write output to a temporary file first, then rename it when complete. That way a killed job does not leave a half-written report with the final filename.
Add logging to the script
Task Scheduler can tell you that the task started, stopped, or failed to launch. It cannot tell you whether your script got stuck waiting for a network share, SMTP server, prompt, password, database query, or file lock.
Add a simple log:
$ErrorActionPreference = 'Stop'
$logRoot = 'C:\Logs\ScheduledTasks'
$logPath = Join-Path $logRoot 'Example_Task.log'
New-Item -Path $logRoot -ItemType Directory -Force | Out-Null
function Write-TaskLog {
param(
[Parameter(Mandatory)]
[string] $Message
)
'{0} {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Message |
Add-Content -Path $logPath
}
Write-TaskLog 'Task started'
try {
Write-TaskLog 'Starting main work'
# Put the real work here.
Write-TaskLog 'Task finished successfully'
}
catch {
Write-TaskLog "Task failed: $($_.Exception.Message)"
throw
}If the log says “Starting main work” but never says “Task finished,” you know the hang is inside the script, not in Task Scheduler.
Add a single-instance lock
Task Scheduler can prevent overlapping starts, but I still like adding a script-level lock for jobs that touch shared files or send messages.
Here is a simple lock-file pattern:
$lockPath = 'C:\ProgramData\PwshTips\Locks\Example_Task.lock'
$lockFolder = Split-Path $lockPath
New-Item -Path $lockFolder -ItemType Directory -Force | Out-Null
if (Test-Path $lockPath) {
$lockAge = (Get-Date) - (Get-Item $lockPath).LastWriteTime
if ($lockAge.TotalHours -lt 2) {
Write-Output "Another run appears to be active. Lock age: $($lockAge.TotalMinutes) minutes."
exit 10
}
Write-Output 'Removing stale lock file'
Remove-Item -Path $lockPath -Force
}
New-Item -Path $lockPath -ItemType File -Force | Out-Null
try {
# Main work goes here.
}
finally {
Remove-Item -Path $lockPath -Force -ErrorAction SilentlyContinue
}This is not a replacement for writing safe code, but it prevents accidental overlap when someone manually starts a task while a scheduled run is still active.
For high-value jobs, use a stronger lock such as a named mutex or a database row. For a small admin script, a lock file is often enough.
Common root causes
When I see error 2147750687, I look for these causes first:
- The schedule interval is shorter than the task runtime.
- The script is waiting for user input because it was tested interactively.
- A network copy, API call, email send, or database query is hanging.
- The script started a child process and never waited for or closed it correctly.
- The task action calls a batch file that starts another program and leaves it open.
- The task is configured to run whether the user is logged on or not, but the script expects a desktop session.
- The task runs as
SYSTEMand waits on a resource that only a user account can access. - The script has no timeout around external commands.
- The previous run was manually started and forgotten.
The fix depends on the cause. The Task Scheduler setting controls overlap, but the script still needs to finish cleanly.
Check the task action
For PowerShell tasks, I prefer an action like this:
Program/script:
C:\Program Files\PowerShell\7\pwsh.exe
Add arguments:
-NoProfile -ExecutionPolicy Bypass -File "C:\Scripts\Example_Task.ps1"
Start in:
C:\ScriptsFor Windows PowerShell:
Program/script:
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
Add arguments:
-NoProfile -ExecutionPolicy Bypass -File "C:\Scripts\Example_Task.ps1"
Start in:
C:\ScriptsThe Start in field matters. Scripts that use relative paths can behave differently when launched by Task Scheduler. A missing working directory may not cause 2147750687 directly, but it can cause the script to hang or fail in a way that leaves the task running.
Fix a task that works manually but stays Running
If the script works when launched by hand but the task stays Running after clicking Run in Task Scheduler, focus on the scheduled action and anything the script starts.
This difference matters: when you run the .ps1 file manually, you usually judge success by the result. The file was copied, the email was sent, the report was created, or the service was restarted. Task Scheduler judges the run differently. It starts the configured action process and waits for that process to exit.
So the script can appear to work and still leave the scheduled task running.
Common reasons:
- The script finishes the visible work but then waits for input, a prompt, or a confirmation.
- PowerShell was started with
-NoExit, so the console stays open after the script ends. - The script starts another process in the foreground, and that process never exits.
- The script starts a helper program, batch file, or command window that stays open.
- A network command, email send, file copy, or API call is still waiting in the background.
- The task action launches a web server, watcher, or service-like process that is designed to keep running.
- The script behaves differently because Task Scheduler runs it as another account, such as
SYSTEM, with a different profile, working directory, mapped drives, and environment variables.
In other words, “the action worked” is not the same as “the scheduled task completed.” For Task Scheduler to move from Running back to Ready, the action process must exit.
First, make sure the task action launches PowerShell directly and does not leave an interactive shell open. Avoid -NoExit in scheduled tasks.
Good:
Program/script:
C:\Program Files\PowerShell\7\pwsh.exe
Add arguments:
-NoProfile -ExecutionPolicy Bypass -File "C:\Scripts\Example_Task.ps1"Bad for unattended scheduled tasks:
pwsh.exe -NoExit -File "C:\Scripts\Example_Task.ps1"Second, check whether the script starts another process and leaves it running. This can happen with Start-Process, batch files, report tools, browser automation, long-running web servers, or helper executables.
For a one-shot job, wait for the child process and return its exit code:
$process = Start-Process `
-FilePath 'C:\Tools\ExampleTool.exe' `
-ArgumentList '/run', '/quiet' `
-Wait `
-PassThru
exit $process.ExitCodeIf the scheduled task starts a long-running service, do not keep that process attached to the task forever. Install it as a Windows service, create a proper WSL systemd service, or make the scheduled task restart the service and then exit.
For example, restarting a service is a one-shot task:
Restart-Service -Name W3SVC -Force
exit 0Starting a web server directly inside the scheduled task is different. If the web server keeps running in the foreground, Task Scheduler will keep the task in the Running state. The next trigger can then fail with 2147750687.
Finally, add an explicit end-of-script log line:
Write-TaskLog 'Reached end of script'
exit 0If that line appears in the log but the task still shows Running, look for a child process that Task Scheduler is still tracking. If the line does not appear, the script is hanging before it reaches the end.
Use a practical fix sequence
Here is the sequence I use on a real server:
- Convert
2147750687to0x8004131F. - Confirm the task is still in the
Runningstate. - If the stuck run came from clicking Run during testing, stop that test instance before the real scheduled time.
- Review Task Scheduler Operational events around the failed time.
- Stop the stuck scheduled task.
- If needed, stop the stuck child process after confirming its command line.
- Check the task’s If the task is already running setting.
- Add or reduce the task’s maximum runtime.
- Add script logging with start, major step, success, and failure entries.
- Add an explicit end-of-script log line and
exit 0for successful completion. - Add timeouts around network, email, and external command calls.
- Schedule the task less frequently if normal runtime is longer than the trigger interval.
- Click Run in Task Scheduler and confirm the task returns from
RunningtoReady. - Wait for the next scheduled run and check Event ID 102 or action completion events.
The goal is not just to clear the immediate failure. The goal is to make the next failure obvious.
Bottom line
Event ID 101 with error value 2147750687 usually means Task Scheduler refused to start a new copy because the previous copy was still running. The visible failure is the skipped launch, but the real problem is the earlier run that did not finish before the next trigger.
Fix the overlap setting, stop or clean up the stale instance, and then find why the previous run stayed active. For PowerShell jobs, good logging and a reasonable execution time limit usually turn this from a mystery into a normal script bug you can fix.
💬 Comments