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.
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:
- The image loader consults
sysmain.sdbfor shims that match the binary. - 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:
- Reads
C:\Windows\AppCompat\Programs\RecentFileCache.bcf. - Walks each path, fingerprints the executable, and updates the Amcache.hve record set.
- 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
LastWriteTimeis 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.bcfand has not yet been promoted toAmcache.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 keyLastWrittenis 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
AeLookupSvcoutright.
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.