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:

  1. The script was run manually from PowerShell and completed successfully.
  2. In Task Scheduler, the task was selected and Run was clicked to test the action.
  3. The task status changed to Running.
  4. The task produced the expected result, but the Task Scheduler status never returned to Ready.
  5. At 8:00 AM, the real scheduled trigger fired.
  6. 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, State

The 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 2147750687

Output:

0x8004131F

That 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
Operational

Filter 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-List

Use 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, NumberOfMissedRuns

Then check the task state:

Get-ScheduledTask -TaskPath '\Folder\' -TaskName 'Example_Task' |
    Select-Object TaskName, TaskPath, State

If 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, State

If 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-List

Do 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 -Force

Replace 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-ScheduledTask

PT30M 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 SYSTEM and 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:\Scripts

For 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:\Scripts

The 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.ExitCode

If 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 0

Starting 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 0

If 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:

  1. Convert 2147750687 to 0x8004131F.
  2. Confirm the task is still in the Running state.
  3. If the stuck run came from clicking Run during testing, stop that test instance before the real scheduled time.
  4. Review Task Scheduler Operational events around the failed time.
  5. Stop the stuck scheduled task.
  6. If needed, stop the stuck child process after confirming its command line.
  7. Check the task’s If the task is already running setting.
  8. Add or reduce the task’s maximum runtime.
  9. Add script logging with start, major step, success, and failure entries.
  10. Add an explicit end-of-script log line and exit 0 for successful completion.
  11. Add timeouts around network, email, and external command calls.
  12. Schedule the task less frequently if normal runtime is longer than the trigger interval.
  13. Click Run in Task Scheduler and confirm the task returns from Running to Ready.
  14. 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.