The CustomDestinations-ms File Format Explained
automaticDestinations-ms is what Windows wrote about the user. customDestinations-ms is what the application wrote about itself. Same folder, same AppID-named files, completely different containers. The Automatic side is MS-CFB with numbered LNK streams. The Custom side is a flat byte stream with categories and back-to-back shell links and no compound file in sight. If you fed one to an OLE reader expecting the other, you would already know this.
Why CustomDestinations exists at all
The Automatic file answers "what did the user touch recently?". The Custom file answers "what does the application want on its Jump List?". The split is by author, not by content. A developer who wants Chrome's "New Incognito Window" entry, or Word's "New Document" task, calls into ICustomDestinationList: BeginList, append IObjectCollection instances via AppendCategory, AppendKnownCategory or AddUserTasks, then CommitList. The shell serializes that to %AppData%\Microsoft\Windows\Recent\CustomDestinations\<AppID>.customDestinations-ms.
Typical content:
- A Tasks category — verbs the developer wired in. "New Incognito Window", "New Private Window", "Compose Email", "Quit". These point at the application's own executable with specific command-line arguments.
- A Known category —
FrequentorRecent, delegated to the shell. The app says "show the standard list here"; the shell fills in. - One or more Custom categories — arbitrary UTF-16LE names. Browsers, IDEs, media players use this for things like "Most visited", "Recent projects", "Recently played".
On-disk structure
The file opens with a fixed header carrying a magic value, a format version, and a count of category entries. Each category entry encodes:
- A type field:
0for a custom (app-named) category,1for a known category like Frequent or Recent,2for the Tasks category. - For type
0, a UTF-16LE name prefixed by its length in characters (not bytes). - An entry count.
- That many serialized LNK structures, written back-to-back.
[ header: magic + version + category_count ]
repeat category_count times:
[ type (0=custom | 1=known | 2=tasks) ]
[ if type==0: name_len_chars + UTF-16LE name ]
[ entry_count ]
[ LNK #1 ][ LNK #2 ] ... [ LNK #entry_count ]
[ trailing footer 0xBABFFBAB / padding ]
There is no per-entry length prefix in front of each LNK. You parse the shell link in place and let the LNK structure tell you where it ends. That is why a working LNK parser is non-negotiable for this format.
The terminator 0xBABFFBAB is a sniff test as good as the MS-CFB magic on the Automatic side. If you see those four bytes at end-of-file and no compound-file header at the start, you are looking at a CustomDestinations file.
The LNK payload, byte for byte
Each destination is a standard MS-SHLLINK shell link, identical in shape to the numbered streams inside an Automatic file. Same parser, no special-casing. You get ShellLinkHeader, optional LinkTargetIDList, LinkInfo with the VolumeID and LocalBasePath, the StringData strings (NAME_STRING, RELATIVE_PATH, WORKING_DIR, COMMAND_LINE_ARGUMENTS, ICON_LOCATION), and any ExtraData blocks. The TrackerDataBlock survives here too with its volume and machine IDs and droid GUIDs. Target path, working directory, command-line arguments, icon location and the three FILETIME timestamps all come from inside the LNK.
That last point matters: the CustomDestinations container does not duplicate timestamps or per-entry access counts. There is no DestList. Ordering is by file position, and the only timestamps are those inside the embedded LNKs.
Pinned items and the Tasks category
The Tasks category (type = 2) holds developer-defined verbs, not user documents. "Open new window", "Compose mail", "Start a private session". These LNKs almost always point at the application binary with arguments — chrome.exe --incognito, winword.exe /n, outlook.exe /c ipm.note. Useful for understanding what an application offered; not useful as a record of user activity.
Pinned entries are different. They are stored alongside other categories but they survive Jump List resets and explicit clearing of recent items. In triage, a pinned entry is intent. The user actively clicked "Pin to this list". Treat that as a stronger signal than a transient MRU hit.
Practical parsing notes
- Read strictly sequentially. There is no central directory; offsets are implicit in the cursor position. A failed parse at offset N invalidates everything after N.
- No OLE layer. Do not try to open the file with a Compound File reader. Treat it as a raw byte stream.
- UTF-16LE strings are length-prefixed in characters. Multiply by two before advancing the cursor.
- Trailing footer or padding is normal. Tolerate a short tail rather than treating it as corruption. The
0xBABFFBABmarker, when present, is the end. - Reuse your LNK parser. The shape is documented in [MS-SHLLINK] and has been reverse-engineered repeatedly: Volatility, libforensics, Eric Zimmerman's JLECmd, Kacos2000's Jumplist-Browser, fox-it's research releases. Do not write your own from scratch unless you are doing it for the fun of it.
Why this matters for triage
A customDestinations-ms file with a stale Tasks list is unremarkable — it is what the application shipped. A customDestinations-ms file whose pinned LNKs point at network shares, USB drives or unexpected executables is interesting. The pin survived because the user put it there. Cross-reference the AppID against the application catalog (the LNK files in Recent\ and the matching automaticDestinations-ms will corroborate), then look at the LinkInfo and TrackerDataBlock fields in each pinned LNK for path, hostname and droid GUID.
To see all of this on a real sample without writing code, drop one into the parser on the homepage — it decodes both file types side by side.
Further reading
- Microsoft's ICustomDestinationList documentation — the developer side of the format.
- Microsoft's [MS-SHLLINK] specification — the LNK payload inside each category.
- Eric Zimmerman's JLECmd — canonical offline parser.
- Kacos2000's Jumplist-Browser — PowerShell viewer that exposes the raw bytes.
- For corroboration, the LNK parser, Prefetch parser, and Registry parser.