Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SchtaskDoctor

CI PowerShell Gallery License: MIT PowerShell 5.1+

A Windows Task Scheduler health auditor. It finds scheduled tasks that are failing silently — the ones nobody notices until something downstream breaks — and explains exactly why, in plain English, with a suggested fix.

Read-only, always. SchtaskDoctor never enables, disables, deletes, or modifies a scheduled task. It only inspects and reports. See Read-only guarantee.

Why this exists

Scheduled tasks are one of the few pieces of infrastructure that can fail completely, repeatedly, and totally silently. A task with output redirected nowhere and history disabled can fail every single run for weeks and produce no signal at all — no email, no log, no alert. The task just quietly stops doing its job.

The failure mode that motivated this tool: a scheduled task was registered with its action pointed at a bare command name — say python — instead of a fully-qualified path. It ran fine when tested interactively, because the person testing it had python on their shell's PATH. But Task Scheduler doesn't run your interactive shell's environment — it runs in its own default, non-interactive context, and that bare command name didn't resolve. Every scheduled run failed instantly with 0x80070002 (file not found). Nobody was watching, because nothing was set up to watch. The task ran, "did its job" as far as Task Scheduler was concerned (a run happened, on schedule), and quietly produced nothing for weeks.

That is the shape of bug SchtaskDoctor is built to catch before it costs you anything: a task that looks configured, looks scheduled, and is completely dead.

Quickstart

Install from source

git clone https://github.com/AgentJDrew/schtask-doctor.git
cd schtask-doctor
Import-Module .\src\SchtaskDoctor\SchtaskDoctor.psd1

Or copy src\SchtaskDoctor into a module path (e.g. $env:UserProfile\Documents\WindowsPowerShell\Modules\SchtaskDoctor) and Import-Module SchtaskDoctor normally.

PowerShell Gallery

Coming soon — Install-Module SchtaskDoctor is not yet published.

Run an audit

# Console report, color-coded by severity, non-Microsoft tasks only
Invoke-TaskAudit

# Markdown report to a file
Invoke-TaskAudit -Format Markdown -OutFile .\audit.md

# Only tasks under a specific folder, as JSON
Invoke-TaskAudit -TaskPath '\MyCompany\*' -Format Json -OutFile .\audit.json

# Include Microsoft's own scheduled tasks too
Invoke-TaskAudit -IncludeMicrosoft

# Deep-dive one task
Get-TaskHealth -TaskName 'Nightly Sync' -TaskPath '\MyApp\'

Checks

Invoke-TaskAudit runs seven checks against every matched task. Each finding carries a severity, a plain-English explanation, and a suggested fix.

ID Severity What it catches
FAILING Critical LastTaskResult is a nonzero, non-benign result code. Common HRESULTs are decoded to friendly text: 0x80070002 (file not found), 0x80070005 (access denied), 0x8007010B (bad working directory), 0x800704DD (not logged on), 0x41301 (still running), 0x41303 (never ran), and more.
UNRESOLVABLE-ACTION Critical The action's executable is a bare name (e.g. python, no path) that would not resolve in Task Scheduler's default, non-interactive environment — or a qualified path that simply doesn't exist. This is the classic silent-0x80070002 shape described above.
SILENT Warning The action has no output redirection (>>, 2>&1, -LogFile, etc.) and task history is disabled — if it fails, nothing will ever record it.
NEVER-RAN / STALE Warning An enabled task with active triggers that has never run, or whose last run is far older than its trigger cadence implies (e.g. a daily task that hasn't run in 3+ days).
OVER-PRIVILEGED Warning RunLevel=Highest where the action doesn't obviously need elevation, or a task running as SYSTEM for what looks like a user-level job (touches a user-profile path).
MISSING-WORKDIR Info No WorkingDirectory is set while the action's arguments reference relative-looking paths — a common cause of "works when I run it, fails when scheduled."
ORPHAN-SCHEDULE Info A disabled task that still has a future-dated trigger — informational, but easy to mistake for "this is still active" at a glance.

Sample report

# SchtaskDoctor Audit Report

Generated: 2026-01-15 09:00:00

Tasks scanned: **12**  |  Findings: **4**

## Critical (2)

| Check | Task | Message | Suggested Fix |
|---|---|---|---|
| FAILING | `\MyApp\Nightly Sync` | Last run failed with 0x80070002: ERROR_FILE_NOT_FOUND ... | Open the task's History tab (enable it if off) or Event Viewer... |
| UNRESOLVABLE-ACTION | `\MyApp\Nightly Sync` | Action 'python' is a bare command name that does not resolve... | Replace with the fully-qualified path to the executable... |

## Warning (1)

| Check | Task | Message | Suggested Fix |
|---|---|---|---|
| SILENT | `\MyApp\Nightly Sync` | Action 'python' has no output redirection and task history is disabled... | Enable task history, or wrap the action in a script that redirects stdout/stderr... |

## Info (1)

| Check | Task | Message | Suggested Fix |
|---|---|---|---|
| ORPHAN-SCHEDULE | `\MyApp\Old Cleanup Job` | Task is disabled but still has trigger(s) scheduled in the future... | If permanently retired, delete it or remove its triggers... |

(Synthetic data — not a real audit result.)

Read-only guarantee

SchtaskDoctor never calls anything that changes Task Scheduler state. It only calls Get-ScheduledTask and Get-ScheduledTaskInfo (wrapped so they can be mocked in tests), plus read-only filesystem and environment-variable checks to see whether an action's executable would resolve. There is no Enable-, Disable-, Set-, Unregister-, or Register- anywhere in this module. Audit away — it cannot break anything.

Compatibility

  • Windows PowerShell 5.1 — primary target; all module code is written to run on 5.1 without changes.
  • PowerShell 7+ — works as well; the ScheduledTasks module ships with Windows and is available to both hosts.
  • Requires Windows (Task Scheduler is a Windows-only concept). Not tested on PowerShell for Linux/macOS.

Development

# Run tests (Pester 5)
Install-Module Pester -Scope CurrentUser -MinimumVersion 5.0 -Force
Import-Module Pester -MinimumVersion 5.0
Invoke-Pester -Path .\tests

# Lint
Install-Module PSScriptAnalyzer -Scope CurrentUser -Force
Invoke-ScriptAnalyzer -Path .\src\SchtaskDoctor -Recurse -Settings .\PSScriptAnalyzerSettings.psd1

Every check function is a pure function over a plain "task record" object (see src/SchtaskDoctor/Private/ConvertTo-TaskRecord.ps1) — none of them call Get-ScheduledTask/Get-ScheduledTaskInfo directly. Those two cmdlets are isolated behind wrapper functions (src/SchtaskDoctor/Private/Get-TaskScheduleData.ps1) specifically so Pester can mock them and the whole check suite can run against hand-built synthetic tasks with no live Task Scheduler dependency.

License

MIT — Copyright (c) 2026 Andrew Lazzeroni

About

Windows Task Scheduler health auditor — finds silently-failing scheduled tasks (bare-name actions, unresolved paths, no-output tasks) before they burn you. Read-only PowerShell module with HRESULT decoding.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages