TaskCache Registry Forensics: Tree, Tasks and DynamicInfo
What the TaskCache key in the SOFTWARE hive stores for each scheduled task: Tree Id/Index/SD, Tasks values, DynamicInfo run times, Actions and Triggers blobs, and the Hash.
TL;DR. TaskCache has two halves. Tree\<path> maps a task name to its GUID (Id), a category (Index) and a security descriptor (SD). Tasks\{GUID} stores Path, URI, Author, Date, Hash, Actions, Triggers and DynamicInfo, whose FILETIMEs give creation, last run and last successful run in UTC. The binary layouts are community-reverse-engineered: trust the fields that are well corroborated and cross-check the rest.
This is the registry deep dive of the series that starts with the scheduled task forensics guide. The main sources are the cyber.wtf analysis (Windows 10 1909, checked against Windows 7) and libyal's winreg-kb. Microsoft does not document these values.
Layout of the key
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache
├── Tree\<folder>\<task name> Id, Index, SD
├── Tasks\{GUID} Path, URI, Author, Date, Hash, Actions, Triggers, DynamicInfo, ...
├── Boot\{GUID}
├── Logon\{GUID}
├── Plain\{GUID}
└── Maintenance\{GUID}
Tree mirrors the folder structure of C:\Windows\System32\Tasks. Tasks is flat, one key per task GUID. The four category keys hold a subkey per task GUID in that category.
Tree: names, categories, permissions
| Value | Type | Meaning |
|---|---|---|
Id | String | The task GUID, e.g. {8A1B...}, pointing to Tasks\{GUID} |
Index | DWORD | Category. cyber.wtf maps 1 Boot, 2 Logon, 3 Plain, 4 Maintenance; folders have no Index |
SD | Binary | Self-relative security descriptor controlling access to the task |
Three things to check on every Tree entry:
- Is
SDpresent? A task key withoutSDis invisible toschtasksand the console. That is the Tarrask hiding technique (Microsoft, 2022). - Does the decoded SD deny read access? Binary Defense showed that a valid descriptor denying read to everyone hides a task just as well while looking normal (Binary Defense). Read the SDDL, not just its presence.
- Is
Indexplausible? MITRE lists altering theIndexvalue as another hiding method (T1053.005). A value of 0 on a task, or anIndexthat disagrees with the category key the GUID appears under, is worth a note.
The last-write time of the Tree key is a UTC FILETIME updated when any value in that key changes. For a normal task it is close to registration. If it is much later than DynamicInfo's creation time, something rewrote the key; on a Tarrask-style task, that can be the moment the SD was deleted. It is an inference, not a direct record, so phrase it that way in a report.
Tasks{GUID}: the task data
| Value | Meaning |
|---|---|
Path | Task path, e.g. \Microsoft\Windows\Defrag\ScheduledDefrag |
URI | Same as the XML URI, when present |
Author, Description, Source, Version, Documentation | Copies of RegistrationInfo fields |
Date | Registration date string, same zoneless format as the XML |
Hash | Integrity hash of the XML file, see below |
Schema | Schema version as a number |
SecurityDescriptor | Optional SDDL from the XML |
Actions | Binary serialisation of the actions |
Triggers | Binary serialisation of triggers, principal and settings |
DynamicInfo | Binary run-state record |
Not every value exists for every task. cyber.wtf notes that only Actions and Triggers appear to be required for the task to show up in the console.
DynamicInfo
DynamicInfo is the reason to always parse TaskCache. Layout as described by cyber.wtf, consistent with winreg-kb's size notes (28 bytes on Vista/7, 36 bytes on Windows 8 and later):
| Offset | Size | Field |
|---|---|---|
| 0 | 4 | Version / magic, typically 3 |
| 4 | 8 | FILETIME: created (registration or last update) |
| 12 | 8 | FILETIME: last run |
| 20 | 4 | Task state |
| 24 | 4 | Last result code (HRESULT, e.g. 0x80070002 file not found) |
| 28 | 8 | FILETIME: last successful run (Windows 8+ only) |
winreg-kb labels the first two timestamps "unknown" and suggests "last registered or update time?" and "launch time?"; cyber.wtf names them as above. In practice the "created" value moves when a task is re-registered, so read it as last registration rather than first creation. The last result code is often the most useful field: a persistence task that has run successfully shows 0x0; one whose binary was removed shows a file-not-found HRESULT.
All three FILETIMEs are UTC. That makes them the anchor for the zoneless XML Date, as explained in task XML anatomy.
Actions blob
The Actions value starts with a version word, then the principal context string, then one record per action identified by a magic value (cyber.wtf):
| Magic | Action |
|---|---|
0x6666 | Exec: command, arguments, working directory |
0x7777 | ComHandler: CLSID and data |
0x8888 | SendEmail |
0x9999 | ShowMessage |
Strings are length-prefixed UTF-16. When the XML file is gone, this blob is how you still know what the task ran. Compare it with the XML when both exist: an attacker who edits the registry directly may change the action without touching the file, or the reverse.
Triggers blob
Triggers starts with a header (version, then a "job bucket" with flags, a CRC32, the principal id, user information and optional settings), followed by trigger records of various types, each with its own magic and 8-byte alignment. The header layout is reasonably well understood; individual trigger records vary by Windows version and are less settled. A parser can reliably extract the header and the principal (the account the task runs as); treat decoded trigger details from the blob as best-effort and prefer the XML when you have it.
Hash
The Hash value lets the service detect an XML file changed outside its control. winreg-kb records it as SHA-256, or CRC32 on systems before the KB2305420 update. On current Windows 10 and 11 systems it is observed to be the SHA-256 of the XML file content without its two-byte UTF-16 BOM.
Recomputing it gives a cheap integrity check:
- Match: the XML on disk is what the service registered.
- Mismatch: the file was edited after registration, or the registry entry was, or the file was restored from elsewhere. Either way, the two copies disagree: read both.
- No XML: the file was deleted; the registry is the only definition left.
Correlating TaskCache with the XML files
Pair each Tree key with the XML file at the same relative path under System32\Tasks. Then list:
| Situation | Typical explanation | What to do |
|---|---|---|
| XML and TaskCache, hash matches | Normal | Triage on content |
| TaskCache only (no XML) | XML deleted by hand or by a cleanup tool; task may still run | High priority: decode Actions |
| XML only (no TaskCache) | File dropped but never registered, or registry cleaned, or dirty hive | Check hive state and event logs |
| Tree without SD | Hidden task (Tarrask-style) | High priority |
| Hash mismatch | File or registry changed after registration | Diff the two definitions |
A caveat before calling anything "missing": if the SOFTWARE hive was copied live without replaying its transaction logs, recent registry changes may be absent. See scheduled task locations and acquisition.
The Scheduled Tasks Parser performs this correlation, recomputes the hash, decodes the SD to SDDL, DynamicInfo, Actions and the Triggers header, and flags registry-only, disk-only and SD-less tasks. For heavier registry work, the Registry parser covers the rest of the hive.
FAQ
What is the TaskCache registry key?
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache is where the Task Scheduler service keeps its copy of every registered task: names and security descriptors under Tree, task data under Tasks\{GUID}, and category lists under Boot, Logon, Plain and Maintenance.
What timestamps does DynamicInfo contain?
On Windows 8 and later it is usually 36 bytes: a version DWORD, a FILETIME commonly interpreted as the task creation or registration time, a last run FILETIME, a task state DWORD, a last result code and a last successful run FILETIME. The layout comes from community research, not from Microsoft documentation.
What is the Hash value in TaskCache\Tasks?
An integrity hash of the task XML. On current systems it is observed to be SHA-256 of the XML file without its two-byte byte order mark; older systems used CRC32. A mismatch with the XML on disk means one of them changed after registration.