Skip to content

Application Experience & ProgramDataUpdater: how .bcf gets populated

Walking through the Windows 7 service and scheduled task that write RecentFileCache.bcf, and what that lifecycle means for triage windows.

Published on 4 min read

RecentFileCache.bcf does not appear by magic. A specific service writes it at runtime and a specific scheduled task drains it on a cadence. Knowing both is what lets you reason about how far back the artifact reaches, how often it changes, and where its blind spots live.

The Application Experience service

On Windows 7 the service is registered as AeLookupSvc ("Application Experience"). It is the runtime half of the Application Compatibility infrastructure, which exists to detect known-bad-on-newer-Windows binaries and silently apply compatibility shims so legacy software keeps working.

Two things happen on a typical executable launch:

  1. The image loader consults sysmain.sdb for shims that match the binary.
  2. The Application Experience service records that the binary was seen, so the offline maintenance task can later evaluate it for shim eligibility.

RecentFileCache.bcf is the staging buffer for step 2. It is not a long-term log. It exists to feed the next maintenance run, full stop.

ProgramDataUpdater

The scheduled task that consumes the buffer is:

\Microsoft\Windows\Application Experience\ProgramDataUpdater

It does three things, in order:

  1. Reads C:\Windows\AppCompat\Programs\RecentFileCache.bcf.
  2. Walks each path, fingerprints the executable, and updates the Amcache.hve record set.
  3. Rewrites RecentFileCache.bcf. Sometimes shorter, sometimes empty, sometimes replaced wholesale.

The task is registered to run on system idle or at logon, governed by the Automatic Maintenance framework. On a healthy desktop you see it fire roughly daily. On a kiosk that only runs during business hours, the window between writes can stretch for days.

What that means for triage

The artifact's freshness is a function of the last ProgramDataUpdater run, not of any single executable launch. Practical consequences:

  • The file's LastWriteTime is an upper bound on when ProgramDataUpdater last completed. Useful, but not per-entry. Do not put it on an execution timeline as if it were.
  • If a system was compromised, used briefly, and powered off before the next ProgramDataUpdater run, the malicious binary lives in RecentFileCache.bcf and has not yet been promoted to Amcache.hve. That makes the BCF the only artifact carrying the lead.
  • After a successful run, an entry can vanish from the BCF while persisting in Amcache.hve. Always read the two together. A path absent from the BCF but present in Amcache with a recent registry key LastWritten is the normal post-promotion state.

What does not land in .bcf

The Application Experience pipeline only catches binaries that go through the normal image loader with AppCompat hooks active. The following routinely bypass it:

  • Reflective DLL injection and in-memory PE loaders.
  • Drivers and kernel-mode payloads (different code path).
  • LOLBin chains where the launcher is a signed binary and the actual payload is a script or COM scriptlet (mshta.exe, regsvr32.exe /i).
  • Anti-forensics tooling that disables AeLookupSvc outright.

If you have reason to believe one of those was used, RecentFileCache absence is not an "it did not run" signal. It is silence, not evidence.

What an attacker's footprint looks like if they suppress it

Worth knowing the audit trail for the obvious moves. Stopping or disabling AeLookupSvc lights up Service Control Manager events 7036 and 7040 in the System log. Disabling or deleting the ProgramDataUpdater task lights up 141/142 in Microsoft-Windows-TaskScheduler%4Operational.evtx. Both are covered in detail in Detecting RecentFileCache tampering.

A BCF that has not been touched in months on a daily-used machine should raise an eyebrow even if you find no service-control or task events. Sometimes the attacker disabled the task by editing the Task Scheduler XML on disk, and the events fire on the wrong account or get rolled out. Cross-check against the System and Application logs in EVTX and against the Registry state for the service.

Cadence in numbers

Approximate values from healthy hosts, useful as a sanity baseline:

  • Workstation, daily user: ProgramDataUpdater fires every 18 to 30 hours.
  • Server (low idle): fires every 2 to 5 days.
  • Kiosk powered on only during business hours: fires whenever Automatic Maintenance can squeeze it in, often every 3 to 7 days.
  • Host with idle never reached (heavy workload): can go a week or more between runs.

A BCF stale by a month on a host you know is active daily is meaningful. A BCF stale by a week on a server is normal.

Further reading

Related articles

The BCF's lifecycle is one scheduled task. Understand when ProgramDataUpdater runs, when it clears the file, and how to spot a disabled appraiser.
The BCF format is a header, a list of length-prefixed UTF-16LE paths, and that's it. Here is what every byte means and why nothing has changed since 2009.
A SOC-flagged Win7 endpoint, 14 paths in the BCF, three worth a second look. A realistic corroboration chain and how much the BCF actually contributed.