Windows Server 2019 cumulative updates fail every month with 0x800f0986 DISM & SFC find no corruption - how do I find and fix the actual corrupt file?
Windows Server 2019 cumulative updates fail every month with 0x800f0986 (PSFX_E_APPLY_FORWARD_DELTA_FAILED) but DISM and SFC find no corruption - how do I find and fix the actual corrupt file?
Windows Server 2019 (1809, build 17763) VM running SQL Server 2019. Every monthly cumulative update for several months has failed and rolled back with error 0x800f0986. DISM /Online /Cleanup-Image /RestoreHealth and sfc /scannow complete with no integrity violations, resetting SoftwareDistribution/catroot2 changes nothing, and manual installs from the Update Catalog (wusa and DISM /Add-Package) fail identically.
CBS.log shows the real failure:
Error CSI ... delta_library.cpp(287): Error NTSTATUS_FROM_WIN32(ERROR_INVALID_DATA)
originated in function Windows::Rtl::DeltaDecompressBuffer ...
Error CSI ... Hydration failed for component Microsoft-Windows-CodeIntegrity-DriverPolicy,
version 10.0.17763.9020, arch amd64, nonSxS ... on file driversipolicy.p7b ...
CBS Failed to stage execution package: ... [HRESULT = 0x800f0986 - PSFX_E_APPLY_FORWARD_DELTA_FAILED]
How do I identify which stored file is actually corrupt, and repair it without an in-place upgrade or rebuild?
Top Answer/Comment:
I solved this on the affected server and generalized the method into an open-source toolkit: https://github.com/icarus202/winsxs-hydration-repair
Why DISM/SFC find nothing: 1809-era CUs are delta-based. Per component, WinSxS stores an RTM BASE file, the current FULL file, and forward/reverse PA30 deltas. A new CU hydrates its file by applying its forward delta against the BASE. DISM/SFC validate metadata and live files - they never deep-verify the delta chain, so a truncated BASE or corrupt delta passes every scan while killing every CU at the same component.
The fix - replay the delta math with msdelta.dll (the same engine CBS uses, callable from PowerShell via P/Invoke), read-only:
- TEST A: Apply(BASE, forward delta) - must reproduce FULL
- TEST B: Apply(FULL, reverse delta) - must reproduce BASE
- TEST C: Apply(BASE, the failing CU's own forward delta) - dry run of the exact failing operation
PA30 deltas embed a hash of their intended output, so a delta that applies cleanly certifies its own result. The pass/fail pattern names the corrupt piece, and a successful test has already reconstructed the true bytes of its target - often giving you a verified drop-in replacement with no donor machine and no ISO.
In my case: TEST A failed; TEST B applied cleanly but produced a 6,630-byte base whereas the stored BASE was 1,395 bytes. Since the reverse delta ships in Microsoft's LCU (built against the true RTM file), TEST B's output was the true base: the stored BASE had been silently truncated, and every monthly CU since had died against it. Swapped in the reconstruction (snapshot first; note you must take ownership of the parent folder, not just the file, and restoring TrustedInstaller ownership afterward requires SeRestorePrivilege via the security API - icacls /setowner cannot do it). The CU installed on the next attempt. Total active time with the toolkit: ~30 minutes.
The repo contains the evidence collector (parses CBS.log incl. nonSxS components, which evade the usual folder-name regexes - WinSxS also contracts long component names with .., so never grep for full literal names), the generalized chain-replay script, the verified swap script, the ownership restore, and a full write-up with the verdict matrix for every pass/fail combination. One warning that bears repeating: never run DISM /StartComponentCleanup /ResetBase while broken - it deletes the reverse deltas this recovery depends on.
상단 광고의 [X] 버튼을 누르면 내용이 보입니다