Registry Parser
All articles

Explorer search history: the RegRipper wordwheelquery plugin

8 min read

WordWheelQuery is the artifact that tells you what a user typed into the Windows Explorer search box, and it is one of the few places in the registry where you recover not just what the user did but what the user was looking for. The Explorer search history it holds is a list of literal search terms — a filename fragment, a keyword, a person's name — each stored as a UTF-16 value, ordered most-recent-first by an MRUListEx blob. The RegRipper wordwheelquery plugin decodes that list. What it cannot do is tell you when each individual term was typed: like most MRU artifacts, it gives you ordering for everything and a real timestamp for exactly one entry. Read it for intent, respect the single-timestamp limit, and you have a sharp tool.

The key

The data lives under HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\WordWheelQuery in the user's NTUSER.DAT. Because it is in the per-user hive, attribution is unambiguous: the user who owns the profile is the user who typed the search. There is no service or scheduled task writing here — this is a human typing into the search box.

The key holds a flat set of values:

  • Numbered values — 0, 1, 2, and so on — each containing one search term.
  • An MRUListEx value, the binary blob that records recency order across those numbered slots.

The number on a value is an opaque slot ID, not a rank and not a sequence counter. A search typed today might sit in slot 7 while slot 0 holds an older one. The ranking comes entirely from MRUListEx. The wordwheelquery plugin in this parser keys on that: it filters to values whose names match /^\d+$/ (pure integers), decodes the term from each, and resolves the rank by looking the slot ID up in the decoded MRU order.

What is in the value data

Each numbered value is the search term itself, stored as a null-terminated UTF-16LE (little-endian) string. There is no surrounding binary structure to parse — no PIDL, no fixed-size record, just the text the user typed. Decode it as UTF-16LE and strip the trailing null:

new TextDecoder("utf-16le").decode(raw).replace(/\0+$/, "")

That yields the literal term: quarterly report, passwords.xlsx, *.pst, a colleague's surname, a project codename. Whatever the user typed into the Explorer or Start search box is what you get back, verbatim. This is the part that makes the artifact valuable — it is the user's own words, not a derived path or a system-generated identifier.

A small note on scope, and a place to hedge: the exact provenance of these terms has shifted across Windows versions. On Windows 7 the key cleanly captured Explorer search-box queries. On Windows 8, 8.1, 10, and 11 the search experience changed repeatedly — the Start search box, the search-charm, Cortana, and Windows Search have all owned the search UI at various points — and what lands in WordWheelQuery versus what goes to other search-history stores has moved with it. Treat a term here as "something this user searched for through an Explorer-family search box," and confirm the precise UI on the specific Windows build before asserting more.

MRUListEx: the recency order

MRUListEx is the same structure you decode for RecentDocs and the comdlg32 MRUs. It is an array of 4-byte little-endian integers, each one a slot ID, ordered most-recent-first, terminated by the sentinel 0xFFFFFFFF. Walk the buffer four bytes at a time, read each as a uint32, and stop at the sentinel:

for (let i = 0; i + 4 <= raw.length; i += 4) {
  const idx = dv.getUint32(i, true);
  if (idx === 0xffffffff) break;
  order.push(idx);
}

This parser uses one shared mruListEx helper for every MRU artifact, so the wordwheelquery plugin and the recentdocs plugin decode the order identically. The resulting array is the recency ranking: the first element is the slot that was searched most recently, the last is the oldest term still tracked. To find a term's rank, look its slot ID up in that array — rank 0 is the most recent search. A numbered value whose ID is not present in the decoded order has fallen out of the MRU and should be treated as stale, not current.

A decoded set of rows looks like this:

Entry  Term               MRU rank   Last searched (UTC)
4      acme merger        0          2026-06-15T11:02:47.000Z
1      passwords.xlsx     1
7      *.pst              2
0      jsmith             3
2      vpn config         4

Note which row carries a timestamp and which do not.

What it proves, and the one timestamp

When a term appears under WordWheelQuery, it proves the profile owner typed that exact string into an Explorer-family search box on this machine. That is a statement about intent. Execution artifacts like UserAssist tell you a program ran; file-access artifacts like RecentDocs tell you a file was opened. WordWheelQuery sits a step earlier in the user's mental process — it captures the user actively hunting for something, by name or by keyword, before they found it (or failed to). A search for passwords.xlsx, crypto wallet seed, or a departing employee's name is a different kind of evidence than a file-open record: it shows what the user was after, including searches that returned nothing.

Recency is where you have to be disciplined. There is no per-term timestamp anywhere in WordWheelQuery. The only time you get is the LastWrite of the key, and that LastWrite was set by the most recent write — the operation that placed a term at MRU rank 0. So the key's LastWrite dates the rank-0 term and nothing else.

This parser encodes that limit directly. It emits the Last searched (UTC) value only for the row at MRU rank 0 and leaves the column blank for every other rank, with the plugin note stating it plainly: the key write time dates only the most-recently-searched term (MRU rank 0). That is the honest representation. A tool that stamps every WordWheelQuery row with the same key LastWrite is asserting a timestamp it does not have, and you should distrust output that does it. You know the order of the searches and you know precisely when the most recent one happened; you do not know when the others were typed.

Limits and pivots

The structural limit is the timestamp, as above: ordering for all terms, a real time for one. If you need to know when the rank-3 search happened, WordWheelQuery cannot tell you. You infer it from order and corroborate from other artifacts.

The behavioural limits are worth stating plainly:

  • Privacy tools clear it. CCleaner-class utilities and the built-in "clear search history" controls wipe these values. A WordWheelQuery key present but empty, with a recent LastWrite, is itself a tell that history was cleared. Recovered unallocated cells in the hive may still hold the deleted terms; yarp's recovery modes and RegRipper's del plugin can surface them.
  • Behaviour varies by Windows version. As noted, what reaches this key depends on the build and on which search UI the user used. Do not assume a clean Windows 7 mapping on a Windows 11 host.
  • Empty or absent key. If the user never searched through Explorer, or the build routes search history elsewhere, the key may not exist at all. Absence of a term is not evidence the user never looked for it.

The pivots that turn a bare search term into a timed, corroborated event:

  • Match the term against file-access artifacts. If a user searched passwords.xlsx and that filename later appears in RecentDocs or in the LNK files in Recent\, you have connected intent (the search) to action (the open), and the file-access side carries real timestamps.
  • Match against navigation artifacts. A search for a folder name followed by a manually entered path shows up in TypedPaths; a hostname or URL search may correlate with browser history or TypedURLs. See the typedpaths and typedurls plugin notes for those MRUs and their decode.
  • Use the rank-0 timestamp as an anchor. The one real time you have — the most recent search — anchors the top of the list. Everything below it is older, in known order. Combine that with timestamped neighbours to bracket when the earlier searches plausibly occurred.

Tools

  • RegRipper's wordwheelquery plugin. Reads the WordWheelQuery key, decodes the numbered UTF-16 values into terms, and orders them by MRUListEx. The classic output lists the terms in MRU order with the key LastWrite. Read the plugin source to confirm exactly which fields it surfaces on your RegRipper version.
  • Eric Zimmerman's RECmd with a WordWheelQuery batch file produces the same Explorer search history in CSV.
  • The parser on this site decodes WordWheelQuery in the tree view: it filters to the numbered values, pulls each UTF-16 term, resolves the MRU rank from the shared MRUListEx decoder, and — correctly — dates only the rank-0 term with the key's LastWrite. You can analyze NTUSER.DAT in your browser without uploading the hive anywhere.

For the broader set of NTUSER plugins this one sits alongside, see the RegRipper plugins reference.

The bottom line

WordWheelQuery answers a question almost nothing else on the system does cleanly: what was this user searching for, in their own words, and in what order? It is the Explorer search history, decoded from numbered UTF-16 values and ordered by MRUListEx, and its forensic strength is showing intent — the term the user typed before they acted, including searches that found nothing. It answers "when" for exactly one term: the most recent, dated by the key LastWrite. Read the terms for what the user wanted, use the MRU order for sequence, lean on the single rank-0 timestamp as your anchor, and pivot to RecentDocs, the Recent\ LNK files, and the navigation MRUs for the timing this artifact does not keep. Used that way, WordWheelQuery puts a user's own search words into your timeline. Used as if every term carried the key's LastWrite, it puts a wrong time in your report.