.job File Forensics: Legacy Tasks in C:\Windows\Tasks
How to read legacy .job files: the MS-TSCH fixed and variable sections, triggers, the local-time last run SYSTEMTIME, and when .job files still appear on modern Windows.
TL;DR. .job files are the Task Scheduler 1.0 format, in C:\Windows\Tasks. Microsoft specifies them in MS-TSCH: a 68-byte fixed section (versions, job UUID, priority, status, flags, last run time as a SYSTEMTIME) and a variable section (command, parameters, working directory, author, comment, triggers). They are rare on modern Windows, which is exactly why one showing up there deserves attention.
This article belongs to the fundamentals series that starts with the scheduled task forensics guide.
Where .job files come from
| Era | Primary task store | .job files |
|---|---|---|
| Windows 2000, XP, Server 2003 | C:\Windows\Tasks\*.job | Every task |
| Vista, 7, Server 2008 (R2) | XML in System32\Tasks + TaskCache | Tasks created through the 1.0 API or at.exe can also get a .job for compatibility |
| Windows 8 and later | XML + TaskCache | Uncommon; at.exe is deprecated in favour of schtasks |
The Cyber Triage overview describes the same progression. On a Windows 10 or 11 machine, a .job file therefore means one of: a very old application still using the 1.0 interface, a leftover from an in-place upgrade, or deliberate use of legacy tooling. None of those is malicious by itself; all of them are worth explaining.
The KapeFiles ScheduledTasks target collects C:\Windows\Tasks\*.job and the Windows.old equivalent, together with SchedLgU.txt, the 1.0 scheduler's text log.
The fixed-length section
MS-TSCH names it FIXDLEN_DATA. libyal's Job file format documentation lays it out as 68 bytes:
| Offset | Size | Field | Forensic use |
|---|---|---|---|
| 0 | 2 | Product version | OS that wrote the file (e.g. 0x0501 XP, 0x0600 Vista, 0x0a00 Windows 10) |
| 2 | 2 | File format version | |
| 4 | 16 | Job UUID | Unique per job |
| 20 | 2 | Application name offset | Where the variable section starts |
| 22 | 2 | Triggers offset | |
| 24 | 4 | Error retry count and interval | |
| 28 | 4 | Idle deadline and wait | |
| 32 | 4 | Priority class | Normal, high, idle, realtime |
| 36 | 4 | Maximum run time (ms) | |
| 40 | 4 | Exit code | Last exit code of the program |
| 44 | 4 | Status | Ready, running, disabled, has not run... |
| 48 | 4 | Flags | Includes disabled and hidden bits |
| 52 | 16 | Last run time | SYSTEMTIME |
The product version is a quick provenance check: a .job file on a Windows 10 machine whose product version says XP was probably copied from elsewhere rather than created locally.
The last run time
The last run time is a SYSTEMTIME (year, month, day of week, day, hour, minute, second, millisecond), not a FILETIME. It carries no time zone. It is generally treated as local time, and libyal's documentation explicitly marks that as still to be confirmed. Record it as stored, state the assumption, and corroborate with another source before building a timeline on it. A zero value means the job has never run.
The variable-length section
After the fixed section come, in order:
- Running instance count.
- Application name (the command), length-prefixed UTF-16.
- Parameters.
- Working directory.
- Author.
- Comment.
- User data and reserved data (opaque blobs).
- Triggers: a count, then fixed-size trigger records with start date, end date, start time, duration, interval, flags and a type (once, daily, weekly, monthly, on idle, at startup, at logon).
- Optionally a job signature.
Triggers store dates as separate year, month and day words, without time zone. Same caveat as above: local time by convention.
Reading a .job file in practice
What the Scheduled Tasks Parser extracts from each .job file: the command, parameters, working directory, author, comment, flags (disabled, hidden), status, exit code, the triggers with their start and end dates, and the last run time as stored, labelled as local. The .job rows appear alongside XML and TaskCache tasks, and are flagged as legacy actions so they stand out on a modern system.
Questions to ask of each file:
- Is the command in a user-writable path? Same rule as for XML tasks, see detecting malicious scheduled tasks.
- Does the author match a real account? Like the XML
Author, it is free text. - Is there a matching XML task? On Vista and 7, a job registered through the compatibility layer should have one. A
.jobwith no XML or TaskCache entry was not registered by the current service, or was cleaned up partially. - Do the file system timestamps agree? The
$MFTtimes of the.jobfile, and ofC:\Windows\Tasksitself, bracket its creation and last modification. The scheduler updates the file when the job runs, so the modification time often follows the last run time. A USN journal can show when the file was created or deleted.
Legacy tooling and modern detections
at.exe still exists on current Windows for backward compatibility but is deprecated; Microsoft points to schtasks instead. Seeing at.exe in process execution evidence (Prefetch, Amcache) on a modern workstation is uncommon, and should lead you to check C:\Windows\Tasks and the TaskCache for At1-style task names.
FAQ
What is a .job file?
A binary scheduled task file used by Task Scheduler 1.0, stored in C:\Windows\Tasks. Microsoft documents the format in MS-TSCH as a fixed-length section followed by a variable-length section with the command, arguments, author, comment and triggers.
Do Windows 10 and 11 still use .job files?
Rarely. Modern tasks are XML plus TaskCache. A .job file can still appear for tasks registered through the legacy Task Scheduler 1.0 interfaces or the deprecated at command, and on systems upgraded from old versions. Collect C:\Windows\Tasks anyway.
Is the last run time in a .job file UTC?
It is stored as a SYSTEMTIME structure with no time zone information, generally understood to be local time. libyal's documentation still marks this as to be confirmed, so corroborate before relying on it.