Open/Save dialog history: the RegRipper comdlg32 plugin
8 min read
The comdlg32 artifact answers a question that RecentDocs cannot: which files did a user reach through the Windows common Open/Save dialog, including the ones they saved rather than opened. When an attachment is saved to disk, when "Save As" writes a copy to a USB stick, when a browser prompts for a download location, the dialog that handles it is the shared comdlg32 common dialog, and it keeps an MRU. The RegRipper comdlg32 plugin parses that MRU, and the headline value OpenSavePidlMRU is the part that most analysts underuse. This is file-access history that routes around the artifacts people check first.
Where it lives
Everything is under one parent key in the user's NTUSER.DAT:
HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32
Three subkeys matter:
OpenSavePidlMRU: files opened or saved through the common dialog, organized into one subkey per file extension.LastVisitedPidlMRU: for each application that invoked the dialog, the executable name plus the last folder it browsed to.CIDSizeMRU: a legacy/auxiliary MRU that records dialog window sizing and the program names that opened dialogs. Less forensically interesting and, depending on your tooling, may not be decoded at all. Our own parser focuses on the first two; treatCIDSizeMRUas a supplementary list of program names, not a file-access source.
This is a per-user artifact in the most literal sense: it lives in the profile's NTUSER.DAT, so attribution is the user who owns the hive. There is no system-wide ambiguity here, the same property that makes UserAssist and RecentDocs so useful.
OpenSavePidlMRU: per-extension file history
Open OpenSavePidlMRU and you find subkeys named for file extensions: pdf, docx, jpg, exe, and a catch-all * for files with no extension or for the "All Files" filter. Inside each extension subkey are numbered values (0, 1, 2, ...) plus a binary MRUListEx value that holds the ordering.
Each numbered value is not a path string. It is a shell item ID list, a PIDL. That is the single most important thing to understand about this artifact, and the part that trips up anyone expecting to reg query their way to a filename.
In the parser on this site, the comdlg32 plugin walks each extension subkey, skips the MRUListEx value (it matches numbered names with ^\d+$ and ignores everything else), and runs the raw bytes of each value through a PIDL decoder. The output columns are Source, Extension / app, Path / file, and Key written (UTC). So a row looks like:
OpenSavePidlMRU pdf C:\Users\jdoe\Documents\contract-final.pdf 2026-06-14T09:41:02Z
The extension subkey tells you the file type the user was working with; the decoded PIDL tells you the specific file; the key's LastWrite gives you a coarse timestamp. Note that the timestamp is the extension subkey's last-write, not a per-file time. The MRU position 0 within that subkey was written most recently, and that write is what moved the subkey's LastWrite. Older entries in the same subkey were touched at earlier, unrecorded times. Do not present entry 3 as if it were timestamped to the subkey's LastWrite; only position 0 can be tied to it with confidence.
LastVisitedPidlMRU: app plus last folder
LastVisitedPidlMRU answers a different question: for each program that opened a common dialog, where did that dialog last point? Each numbered value packs two things back to back:
- The executable name as a null-terminated UTF-16LE string (for example
notepad.exe,WINWORD.EXE). - The folder PIDL the dialog last browsed to when that program invoked it.
The parser reads the UTF-16 executable name from the start of the value, finds the null terminator, then decodes the PIDL that begins immediately after it (two bytes past the name for the terminating NUL). A row comes out as:
LastVisitedPidlMRU WINWORD.EXE C:\Users\jdoe\Documents\proposals 2026-06-15T16:08:55Z
This is the missing context for OpenSavePidlMRU. The OpenSave list tells you a docx was touched; the LastVisited list tells you Word was the program holding the dialog and that it was sitting in \Documents\proposals. Together they reconstruct not just what file but through which application and in which directory the user was operating.
PIDLs, briefly
A PIDL (pointer to an ITEMIDLIST) is the shell's internal way of naming an object, and it is the same structure that drives Shellbags. It is a chain of SHITEMID entries: each entry is a 2-byte little-endian size header followed by that many bytes of opaque, type-dependent data. The decoder walks the chain, reading the size, slicing out the entry, extracting a name from it, advancing, until it hits a zero terminator or runs out of bytes.
Pulling a name from each entry is the hard part, because the byte layout depends on the item type. The volume/drive entries (type 0x2F) carry an ASCII drive string like C:\. File and folder entries (types 0x30-0x3F) carry a short ANSI name at a fixed offset and, on modern Windows, a longer UTF-16 name in an extension block; a good decoder prefers the longer of the two. Root and GUID-folder entries fall back to whatever printable run can be found. Our decoder does exactly this and joins the per-entry names with backslashes.
Because this is heuristic name extraction rather than a full shell-namespace resolution, treat the decoded path as best-effort. GUID-only folders may surface as a GUID or a partial name; deeply nested or unusual items may decode imperfectly. The plugin's own note says as much: "Paths are heuristic PIDL decodes." When the path matters to a finding, corroborate it. The PIDL still proves the item was selected through the dialog even when the rendered string is imperfect.
MRUListEx ordering
Both keys use MRUListEx rather than the old MRUList. It is a binary blob of 4-byte little-endian integers: each integer is the name of a numbered value, listed from most-recently-used to least, terminated by 0xFFFFFFFF. The first DWORD is the value most recently added or re-selected, which makes it the last file opened or saved for that extension, or the last folder visited for that app.
If you only need to know "what was the last PDF this user opened or saved," read the first entry of the pdf subkey's MRUListEx, then decode the value it names. If you want the full chronology-of-recency (not wall-clock time, recency), walk the whole MRUListEx in order. The decoder on this site emits all numbered entries; you can reconstruct the recency order from MRUListEx yourself if your tool does not sort by it.
Forensic value: the access RecentDocs misses
This is why comdlg32 earns a place in the standard NTUSER triage. RecentDocs tracks files opened via the shell. It does not reliably capture files written through a Save dialog, and it does not capture the per-application, per-extension structure that ComDlg32 does. Concretely, OpenSavePidlMRU is where you find:
- "Save As" targets. A user exfiltrating a document by saving a copy to a removable drive leaves the destination in the relevant extension subkey, often with the drive letter or volume name in the decoded PIDL.
- Attachment saves. When a user saves an email attachment to disk, the Save dialog records it here, even if the file is later deleted.
- Upload/download picks. Browser and application file pickers go through the common dialog. The file a user selected to upload, or the location they chose for a download, lands in this MRU.
Pair it with RecentDocs for the open side, and with Shellbags for folder-browsing context, which share the same PIDL machinery. Where RecentDocs says "this file was opened," comdlg32 frequently says "and here is the directory it came from, the program that touched it, and the copy that was saved out." Cross-reference the LastVisited folder for an app against the Shellbags entry for the same path: if both exist, you have independent confirmation the user navigated there.
Tools
- RegRipper's
comdlg32plugin. The reference implementation. It parses bothOpenSavePidlMRUandLastVisitedPidlMRU, decodes the PIDLs, and honorsMRUListExordering. Read the source to see exactly which shell-item types it handles. - Eric Zimmerman's RECmd with a ComDlg32 batch file, for CSV output across many hives.
- The parser on this site decodes ComDlg32 as part of its standard NTUSER tree view, with the heuristic PIDL decoder described above. You can analyze NTUSER.DAT in your browser without uploading the hive anywhere.
For the full set of NTUSER artifacts and how they fit together, see the RegRipper plugins reference.
Edge cases worth knowing
Extension subkeys are sticky. An extension subkey persists even after the files it referenced are gone. Finding a dll or exe subkey under OpenSavePidlMRU means the user, at some point, opened or saved a file of that type through a common dialog, which is itself a behavioral signal regardless of whether the file still exists.
The * subkey is the catch-all. Files selected with no specific extension filter, or with "All Files" chosen, land under *. Do not skip it; it often holds the entries an attacker did not expect to be recorded.
The decoded path can be imperfect. Because PIDL name extraction is heuristic, corroborate any path that anchors a conclusion against LNK files, Shellbags, $MFT, or the file itself. The presence of the entry is solid; the exact rendered string is best-effort.
comdlg32 will not carry a case by itself, but it routinely supplies the one fact the other artifacts miss: the file a user saved, the directory it came from, and the program that held the dialog open. Decode the PIDLs, respect MRUListEx, and treat the timestamps as coarse, and it becomes one of the more honest windows into deliberate file handling in the whole hive.