From 3e19f9be90bec04c63c35b39bb8e538fb7114a7c Mon Sep 17 00:00:00 2001 From: John Lambert Date: Fri, 18 Sep 2026 22:04:28 -0400 Subject: [PATCH] LT-22801: Keep directory resolution out of patch transactions LT-20524 scheduled the directory-resolution custom actions in the InstallExecuteSequence under "UILevel < 4", so that silent and basic-UI installs resolve APPFOLDER, DATAFOLDER and HARVESTDATAFOLDER the way a full-UI install does. A patch is applied with /qb, which is UILevel 3, so that condition caught every patch as well. Those actions are sequenced after AppSearch and FindRelatedProducts, which puts them before CostFinalize. Reassigning the directory properties there leaves the installer unable to confirm that the product's existing components are still installed locally, so every feature costs out Advertised and every component Action: Null. The patch then installs only components that are absent and replaces no file that is already there, while msiexec returns 0 and the registered ProductVersion advances -- so it looks like it worked. Add NOT PATCH to the nine conditions, and to the same nine actions in the InstallUISequence, which a patch reaches when it is applied with UI rather than through ProcRunner. A patch shows no directory-selection dialogs and resolves its directories from the installed product, so it has nothing to gain from these actions on either path. Found by bisecting five base builds. Base 1448, cut two days before LT-20524 landed, applies two patches in sequence correctly and its patch log never mentions these actions; base 1452, cut thirteen days after it, fails and its patch log mentions them fifteen times. Bases 1379, 1416 and 1439 all predate the change and all work. This needs a new base build to take effect. The sequence table lives in the base MSI, so an already installed base keeps the old conditions. Co-Authored-By: Claude Opus 5 --- BaseInstallerBuild/Framework.wxs | 56 ++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/BaseInstallerBuild/Framework.wxs b/BaseInstallerBuild/Framework.wxs index 8bae5ca..30d65c7 100644 --- a/BaseInstallerBuild/Framework.wxs +++ b/BaseInstallerBuild/Framework.wxs @@ -129,31 +129,45 @@ ICU_DATA points at) install to the wrong locations. Reduced (/passive, UILevel 4) and full UI installs continue to resolve these in the InstallUISequence, so the UILevel guard keeps this from overriding a user-chosen path. VerifyDataPath populates REGDATAFOLDER (used by the - conditions below) and always returns Success, so it is safe in a silent install. --> - UILevel < 4 - UILevel < 4 - (UILevel < 4) and (NOT REGAPPFOLDER) and (NOT OVRAPPFOLDER) - (UILevel < 4) and (NOT REGAPPFOLDER) and (OVRAPPFOLDER) - (UILevel < 4) and (REGAPPFOLDER) - (UILevel < 4) and (NOT REGDATAFOLDER) and (NOT OVRDATAFOLDER) - (UILevel < 4) and (NOT REGDATAFOLDER) and (OVRDATAFOLDER) - (UILevel < 4) and (REGDATAFOLDER) - (UILevel < 4) and (NOT OVRHARVESTDATAFOLDER) - (UILevel < 4) and (OVRHARVESTDATAFOLDER) + conditions below) and always returns Success, so it is safe in a silent install. + + LT-22801: NOT PATCH keeps them out of patch transactions. A patch is applied with /qb, which + is UILevel 3, so UILevel < 4 alone also caught every patch. These actions are sequenced + after AppSearch and FindRelatedProducts, which puts them before CostFinalize; reassigning + the directory properties there leaves the installer unable to confirm the product's existing + components are still local, so every feature costs out Advertised and every component + Action: Null. The patch then reports success while installing only components that are + absent, and no already-installed file is replaced. The directories a patch needs are + already resolved from the installed product, so a patch has nothing to gain here anyway. --> + (UILevel < 4) and (NOT PATCH) + (UILevel < 4) and (NOT PATCH) + (UILevel < 4) and (NOT PATCH) and (NOT REGAPPFOLDER) and (NOT OVRAPPFOLDER) + (UILevel < 4) and (NOT PATCH) and (NOT REGAPPFOLDER) and (OVRAPPFOLDER) + (UILevel < 4) and (NOT PATCH) and (REGAPPFOLDER) + (UILevel < 4) and (NOT PATCH) and (NOT REGDATAFOLDER) and (NOT OVRDATAFOLDER) + (UILevel < 4) and (NOT PATCH) and (NOT REGDATAFOLDER) and (OVRDATAFOLDER) + (UILevel < 4) and (NOT PATCH) and (REGDATAFOLDER) + (UILevel < 4) and (NOT PATCH) and (NOT OVRHARVESTDATAFOLDER) + (UILevel < 4) and (NOT PATCH) and (OVRHARVESTDATAFOLDER) - - - (NOT REGAPPFOLDER) and (NOT OVRAPPFOLDER) - (NOT REGAPPFOLDER) and (OVRAPPFOLDER) - REGAPPFOLDER - (NOT REGDATAFOLDER) and (NOT OVRDATAFOLDER) - (NOT REGDATAFOLDER) and (OVRDATAFOLDER) - REGDATAFOLDER - NOT OVRHARVESTDATAFOLDER - OVRHARVESTDATAFOLDER + + NOT PATCH + NOT PATCH + (NOT PATCH) and (NOT REGAPPFOLDER) and (NOT OVRAPPFOLDER) + (NOT PATCH) and (NOT REGAPPFOLDER) and (OVRAPPFOLDER) + (NOT PATCH) and (REGAPPFOLDER) + (NOT PATCH) and (NOT REGDATAFOLDER) and (NOT OVRDATAFOLDER) + (NOT PATCH) and (NOT REGDATAFOLDER) and (OVRDATAFOLDER) + (NOT PATCH) and (REGDATAFOLDER) + (NOT PATCH) and (NOT OVRHARVESTDATAFOLDER) + (NOT PATCH) and (OVRHARVESTDATAFOLDER)