From 6f6e4ced9f398ff37a44b91fdba62479cde29d06 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Thu, 9 Jun 2022 15:30:48 -0500 Subject: Implement ArpEntry flavored ExePackage. 6772 --- src/burn/engine/approvedexe.cpp | 8 +- src/burn/engine/approvedexe.h | 2 +- src/burn/engine/bundlepackageengine.cpp | 6 +- src/burn/engine/elevation.cpp | 2 +- src/burn/engine/engine.mc | 7 + src/burn/engine/exeengine.cpp | 295 ++++++++++++++++++--- src/burn/engine/package.h | 13 + src/burn/engine/plan.cpp | 3 +- src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj | 1 + src/burn/test/BurnUnitTest/PlanTest.cpp | 261 ++++++++++++++++++ .../PlanTest/BundlePackage_Multiple_manifest.xml | 2 +- .../ExePackage_PerUserArpEntry_manifest.xml | 1 + .../TestData/PlanTest/Failure_BundleD_manifest.xml | 2 +- .../PlanTest/Slipstream_BundleA_manifest.xml | 2 +- .../Slipstream_BundleA_modified_manifest.xml | 2 +- 15 files changed, 562 insertions(+), 45 deletions(-) create mode 100644 src/burn/test/BurnUnitTest/TestData/PlanTest/ExePackage_PerUserArpEntry_manifest.xml (limited to 'src/burn') diff --git a/src/burn/engine/approvedexe.cpp b/src/burn/engine/approvedexe.cpp index b9efd624..2a96868e 100644 --- a/src/burn/engine/approvedexe.cpp +++ b/src/burn/engine/approvedexe.cpp @@ -213,7 +213,7 @@ LExit: extern "C" HRESULT ApprovedExesVerifySecureLocation( __in BURN_CACHE* pCache, __in BURN_VARIABLES* pVariables, - __in BURN_LAUNCH_APPROVED_EXE* pLaunchApprovedExe + __in LPCWSTR wzExecutablePath ) { HRESULT hr = S_OK; @@ -232,7 +232,7 @@ extern "C" HRESULT ApprovedExesVerifySecureLocation( hr = VariableGetString(pVariables, wzSecureFolderVariable, &scz); if (SUCCEEDED(hr)) { - hr = PathDirectoryContainsPath(scz, pLaunchApprovedExe->sczExecutablePath); + hr = PathDirectoryContainsPath(scz, wzExecutablePath); if (S_OK == hr) { ExitFunction(); @@ -252,14 +252,14 @@ extern "C" HRESULT ApprovedExesVerifySecureLocation( // If the package cache is redirected, hr is S_FALSE. if (S_FALSE == hr) { - hr = PathDirectoryContainsPath(sczSecondary, pLaunchApprovedExe->sczExecutablePath); + hr = PathDirectoryContainsPath(sczSecondary, wzExecutablePath); if (S_OK == hr) { ExitFunction(); } } - hr = PathDirectoryContainsPath(scz, pLaunchApprovedExe->sczExecutablePath); + hr = PathDirectoryContainsPath(scz, wzExecutablePath); if (S_OK == hr) { ExitFunction(); diff --git a/src/burn/engine/approvedexe.h b/src/burn/engine/approvedexe.h index 8a3f6779..cbfda601 100644 --- a/src/burn/engine/approvedexe.h +++ b/src/burn/engine/approvedexe.h @@ -67,7 +67,7 @@ HRESULT ApprovedExesLaunch( HRESULT ApprovedExesVerifySecureLocation( __in BURN_CACHE* pCache, __in BURN_VARIABLES* pVariables, - __in BURN_LAUNCH_APPROVED_EXE* pLaunchApprovedExe + __in LPCWSTR wzExecutablePath ); diff --git a/src/burn/engine/bundlepackageengine.cpp b/src/burn/engine/bundlepackageengine.cpp index 6a0343bd..81279cf6 100644 --- a/src/burn/engine/bundlepackageengine.cpp +++ b/src/burn/engine/bundlepackageengine.cpp @@ -365,8 +365,7 @@ extern "C" HRESULT BundlePackageEnginePlanCalculatePackage( break; default: - hr = E_INVALIDARG; - ExitOnRootFailure(hr, "Invalid package current state: %d.", pPackage->currentState); + ExitWithRootFailure(hr, E_INVALIDARG, "Invalid package current state: %d.", pPackage->currentState); } // Calculate the rollback action if there is an execute action. @@ -413,8 +412,7 @@ extern "C" HRESULT BundlePackageEnginePlanCalculatePackage( break; default: - hr = E_INVALIDARG; - ExitOnRootFailure(hr, "Invalid package expected state."); + ExitWithRootFailure(hr, E_INVALIDARG, "Invalid package expected state."); } } diff --git a/src/burn/engine/elevation.cpp b/src/burn/engine/elevation.cpp index 75b24ec3..9c7cf89f 100644 --- a/src/burn/engine/elevation.cpp +++ b/src/burn/engine/elevation.cpp @@ -3858,7 +3858,7 @@ static HRESULT OnLaunchApprovedExe( hr = RegReadString(hKey, pApprovedExe->sczValueName, &pLaunchApprovedExe->sczExecutablePath); ExitOnFailure(hr, "Failed to read the value for the approved exe path."); - hr = ApprovedExesVerifySecureLocation(pCache, pVariables, pLaunchApprovedExe); + hr = ApprovedExesVerifySecureLocation(pCache, pVariables, pLaunchApprovedExe->sczExecutablePath); ExitOnFailure(hr, "Failed to verify the executable path is in a secure location: %ls", pLaunchApprovedExe->sczExecutablePath); if (S_FALSE == hr) { diff --git a/src/burn/engine/engine.mc b/src/burn/engine/engine.mc index 1c03145b..52524edb 100644 --- a/src/burn/engine/engine.mc +++ b/src/burn/engine/engine.mc @@ -331,6 +331,13 @@ Language=English Detected msi package with invalid version, product code: '%1!ls!', version: '%2!ls!' . +MessageId=124 +Severity=Warning +SymbolicName=MSG_DETECTED_EXE_PACKAGE_INVALID_VERSION +Language=English +Detected exe package with invalid version, arp id: '%1!ls!', version: '%2!ls!' +. + MessageId=151 Severity=Error SymbolicName=MSG_FAILED_DETECT_PACKAGE diff --git a/src/burn/engine/exeengine.cpp b/src/burn/engine/exeengine.cpp index 808577bc..3a64ecd8 100644 --- a/src/burn/engine/exeengine.cpp +++ b/src/burn/engine/exeengine.cpp @@ -2,6 +2,16 @@ #include "precomp.h" +static HRESULT DetectArpEntry( + __in const BURN_PACKAGE* pPackage, + __out BOOTSTRAPPER_PACKAGE_STATE* pPackageState, + __out_opt LPWSTR* psczQuietUninstallString + ); +static HRESULT ParseArpUninstallString( + __in_z LPCWSTR wzArpUninstallString, + __inout LPWSTR* psczExecutablePath, + __inout LPWSTR* psczArguments + ); // function definitions @@ -16,18 +26,73 @@ extern "C" HRESULT ExeEngineParsePackageFromXml( IXMLDOMNode* pixnNode = NULL; LPWSTR scz = NULL; - // @DetectCondition - hr = XmlGetAttributeEx(pixnExePackage, L"DetectCondition", &pPackage->Exe.sczDetectCondition); - ExitOnOptionalXmlQueryFailure(hr, fFoundXml, "Failed to get @DetectCondition."); + // @DetectionType + hr = XmlGetAttributeEx(pixnExePackage, L"DetectionType", &scz); + ExitOnRequiredXmlQueryFailure(hr, "Failed to get @DetectionType."); + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"condition", -1)) + { + pPackage->Exe.detectionType = BURN_EXE_DETECTION_TYPE_CONDITION; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"arp", -1)) + { + pPackage->Exe.detectionType = BURN_EXE_DETECTION_TYPE_ARP; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"none", -1)) + { + pPackage->Exe.detectionType = BURN_EXE_DETECTION_TYPE_NONE; + } + else + { + ExitWithRootFailure(hr, E_UNEXPECTED, "Invalid detection type: %ls", scz); + } + + if (BURN_EXE_DETECTION_TYPE_CONDITION == pPackage->Exe.detectionType) + { + // @DetectCondition + hr = XmlGetAttributeEx(pixnExePackage, L"DetectCondition", &pPackage->Exe.sczDetectCondition); + ExitOnRequiredXmlQueryFailure(hr, "Failed to get @DetectCondition."); + + // @UninstallArguments + hr = XmlGetAttributeEx(pixnExePackage, L"UninstallArguments", &pPackage->Exe.sczUninstallArguments); + ExitOnOptionalXmlQueryFailure(hr, fFoundXml, "Failed to get @UninstallArguments."); + + // @Uninstallable + hr = XmlGetYesNoAttribute(pixnExePackage, L"Uninstallable", &pPackage->Exe.fUninstallable); + ExitOnOptionalXmlQueryFailure(hr, fFoundXml, "Failed to get @Uninstallable."); + } + else if (BURN_EXE_DETECTION_TYPE_ARP == pPackage->Exe.detectionType) + { + // @ArpId + hr = XmlGetAttributeEx(pixnExePackage, L"ArpId", &scz); + ExitOnRequiredXmlQueryFailure(hr, "Failed to get @ArpId."); + + hr = PathConcatRelativeToBase(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\", scz, &pPackage->Exe.sczArpKeyPath); + ExitOnFailure(hr, "Failed to build full key path."); + + // @ArpDisplayVersion + hr = XmlGetAttributeEx(pixnExePackage, L"ArpDisplayVersion", &scz); + ExitOnRequiredXmlQueryFailure(hr, "Failed to get @ArpDisplayVersion."); + + hr = VerParseVersion(scz, 0, FALSE, &pPackage->Exe.pArpDisplayVersion); + ExitOnFailure(hr, "Failed to parse @ArpDisplayVersion: %ls", scz); + + if (pPackage->Exe.pArpDisplayVersion->fInvalid) + { + LogId(REPORT_WARNING, MSG_MANIFEST_INVALID_VERSION, scz); + } + + // @ArpWin64 + hr = XmlGetYesNoAttribute(pixnExePackage, L"ArpWin64", &pPackage->Exe.fArpWin64); + ExitOnOptionalXmlQueryFailure(hr, fFoundXml, "Failed to get @ArpWin64."); + + pPackage->Exe.fUninstallable = TRUE; + } // @InstallArguments hr = XmlGetAttributeEx(pixnExePackage, L"InstallArguments", &pPackage->Exe.sczInstallArguments); ExitOnOptionalXmlQueryFailure(hr, fFoundXml, "Failed to get @InstallArguments."); - // @UninstallArguments - hr = XmlGetAttributeEx(pixnExePackage, L"UninstallArguments", &pPackage->Exe.sczUninstallArguments); - ExitOnOptionalXmlQueryFailure(hr, fFoundXml, "Failed to get @UninstallArguments."); - // @RepairArguments hr = XmlGetAttributeEx(pixnExePackage, L"RepairArguments", &pPackage->Exe.sczRepairArguments); ExitOnOptionalXmlQueryFailure(hr, fFoundXml, "Failed to get @RepairArguments."); @@ -36,10 +101,6 @@ extern "C" HRESULT ExeEngineParsePackageFromXml( hr = XmlGetYesNoAttribute(pixnExePackage, L"Repairable", &pPackage->Exe.fRepairable); ExitOnOptionalXmlQueryFailure(hr, fFoundXml, "Failed to get @Repairable."); - // @Uninstallable - hr = XmlGetYesNoAttribute(pixnExePackage, L"Uninstallable", &pPackage->Exe.fUninstallable); - ExitOnOptionalXmlQueryFailure(hr, fFoundXml, "Failed to get @Uninstallable."); - // @Bundle hr = XmlGetYesNoAttribute(pixnExePackage, L"Bundle", &pPackage->Exe.fBundle); ExitOnOptionalXmlQueryFailure(hr, fFoundXml, "Failed to get @Bundle."); @@ -64,8 +125,7 @@ extern "C" HRESULT ExeEngineParsePackageFromXml( } else { - hr = E_UNEXPECTED; - ExitOnFailure(hr, "Invalid protocol type: %ls", scz); + ExitWithRootFailure(hr, E_UNEXPECTED, "Invalid protocol type: %ls", scz); } } @@ -91,6 +151,8 @@ extern "C" void ExeEnginePackageUninitialize( ReleaseStr(pPackage->Exe.sczInstallArguments); ReleaseStr(pPackage->Exe.sczRepairArguments); ReleaseStr(pPackage->Exe.sczUninstallArguments); + ReleaseStr(pPackage->Exe.sczArpKeyPath); + ReleaseVerutilVersion(pPackage->Exe.pArpDisplayVersion); ReleaseMem(pPackage->Exe.rgExitCodes); // free command-line arguments @@ -126,15 +188,31 @@ extern "C" HRESULT ExeEngineDetectPackage( HRESULT hr = S_OK; BOOL fDetected = FALSE; - // evaluate detect condition - if (pPackage->Exe.sczDetectCondition && *pPackage->Exe.sczDetectCondition) + switch (pPackage->Exe.detectionType) { - hr = ConditionEvaluate(pVariables, pPackage->Exe.sczDetectCondition, &fDetected); - ExitOnFailure(hr, "Failed to evaluate executable package detect condition."); - } + case BURN_EXE_DETECTION_TYPE_NONE: + pPackage->currentState = BOOTSTRAPPER_PACKAGE_STATE_ABSENT; + break; + case BURN_EXE_DETECTION_TYPE_CONDITION: + // evaluate detect condition + if (pPackage->Exe.sczDetectCondition && *pPackage->Exe.sczDetectCondition) + { + hr = ConditionEvaluate(pVariables, pPackage->Exe.sczDetectCondition, &fDetected); + ExitOnFailure(hr, "Failed to evaluate EXE package detect condition."); + } + + // update detect state + pPackage->currentState = fDetected ? BOOTSTRAPPER_PACKAGE_STATE_PRESENT : BOOTSTRAPPER_PACKAGE_STATE_ABSENT; - // update detect state - pPackage->currentState = fDetected ? BOOTSTRAPPER_PACKAGE_STATE_PRESENT : BOOTSTRAPPER_PACKAGE_STATE_ABSENT; + break; + case BURN_EXE_DETECTION_TYPE_ARP: + hr = DetectArpEntry(pPackage, &pPackage->currentState, NULL); + ExitOnFailure(hr, "Failed to detect EXE package by ArpEntry."); + + break; + default: + ExitWithRootFailure(hr, E_INVALIDARG, "Unknown EXE package detection type: %d.", pPackage->Exe.detectionType); + } if (pPackage->fCanAffectRegistration) { @@ -187,6 +265,7 @@ extern "C" HRESULT ExeEnginePlanCalculatePackage( } break; + case BOOTSTRAPPER_PACKAGE_STATE_OBSOLETE: __fallthrough; case BOOTSTRAPPER_PACKAGE_STATE_ABSENT: switch (pPackage->requested) { @@ -205,8 +284,7 @@ extern "C" HRESULT ExeEnginePlanCalculatePackage( break; default: - hr = E_INVALIDARG; - ExitOnRootFailure(hr, "Invalid package current state: %d.", pPackage->currentState); + ExitWithRootFailure(hr, E_INVALIDARG, "Invalid package current state: %d.", pPackage->currentState); } // Calculate the rollback action if there is an execute action. @@ -232,6 +310,7 @@ extern "C" HRESULT ExeEnginePlanCalculatePackage( } break; + case BOOTSTRAPPER_PACKAGE_STATE_OBSOLETE: __fallthrough; case BOOTSTRAPPER_PACKAGE_STATE_ABSENT: switch (pPackage->requested) { @@ -251,8 +330,7 @@ extern "C" HRESULT ExeEnginePlanCalculatePackage( break; default: - hr = E_INVALIDARG; - ExitOnRootFailure(hr, "Invalid package expected state."); + ExitWithRootFailure(hr, E_INVALIDARG, "Invalid package expected state."); } } @@ -356,10 +434,41 @@ extern "C" HRESULT ExeEngineExecutePackage( LPWSTR sczUserArgs = NULL; LPWSTR sczUserArgsObfuscated = NULL; LPWSTR sczCommandObfuscated = NULL; + LPWSTR sczArpUninstallString = NULL; + LPWSTR sczArpArguments = NULL; + BOOTSTRAPPER_PACKAGE_STATE applyState = BOOTSTRAPPER_PACKAGE_STATE_UNKNOWN; HANDLE hExecutableFile = INVALID_HANDLE_VALUE; DWORD dwExitCode = 0; BURN_PACKAGE* pPackage = pExecuteAction->exePackage.pPackage; BURN_PAYLOAD* pPackagePayload = pPackage->payloads.rgItems[0].pPayload; + LPCWSTR wzUninstallArguments = pPackage->Exe.sczUninstallArguments; + + if (BURN_EXE_DETECTION_TYPE_ARP == pPackage->Exe.detectionType && + (BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pExecuteAction->exePackage.action || + BOOTSTRAPPER_ACTION_STATE_INSTALL == pExecuteAction->exePackage.action && fRollback)) + { + hr = DetectArpEntry(pPackage, &applyState, &sczArpUninstallString); + ExitOnFailure(hr, "Failed to query ArpEntry for uninstall."); + + if (BOOTSTRAPPER_PACKAGE_STATE_ABSENT == applyState && BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pExecuteAction->exePackage.action) + { + if (fRollback) + { + LogId(REPORT_STANDARD, MSG_ROLLBACK_PACKAGE_SKIPPED, pPackage->sczId, LoggingActionStateToString(pExecuteAction->exePackage.action), LoggingPackageStateToString(applyState)); + } + else + { + LogId(REPORT_STANDARD, MSG_ATTEMPTED_UNINSTALL_ABSENT_PACKAGE, pPackage->sczId); + } + + ExitFunction(); + } + else if (BOOTSTRAPPER_PACKAGE_STATE_ABSENT != applyState && BOOTSTRAPPER_ACTION_STATE_INSTALL == pExecuteAction->exePackage.action) + { + LogId(REPORT_STANDARD, MSG_ROLLBACK_PACKAGE_SKIPPED, pPackage->sczId, LoggingActionStateToString(pExecuteAction->exePackage.action), LoggingPackageStateToString(applyState)); + ExitFunction(); + } + } if (pPackage->Exe.fPseudoPackage && BURN_PAYLOAD_VERIFICATION_UPDATE_BUNDLE != pPackagePayload->verification) { @@ -372,7 +481,30 @@ extern "C" HRESULT ExeEngineExecutePackage( ExitOnFailure(hr, "Failed to build executable path."); hr = PathGetDirectory(sczExecutablePath, &sczCachedDirectory); - ExitOnFailure(hr, "Failed to get cached path for pseudo-package: %ls", pPackage->sczId); + ExitOnFailure(hr, "Failed to get parent directory for pseudo-package: %ls", pPackage->sczId); + } + else if (BURN_EXE_DETECTION_TYPE_ARP == pPackage->Exe.detectionType && BOOTSTRAPPER_ACTION_STATE_UNINSTALL == pExecuteAction->exePackage.action) + { + ExitOnNull(sczArpUninstallString, hr, E_INVALIDARG, "QuietUninstallString is null."); + + hr = ParseArpUninstallString(sczArpUninstallString, &sczExecutablePath, &sczArpArguments); + ExitOnFailure(hr, "Failed to parse QuietUninstallString: %ls.", sczArpUninstallString); + + if (pPackage->fPerMachine) + { + hr = ApprovedExesVerifySecureLocation(pCache, pVariables, sczExecutablePath); + ExitOnFailure(hr, "Failed to verify the QuietUninstallString executable path is in a secure location: %ls", sczExecutablePath); + if (S_FALSE == hr) + { + LogStringLine(REPORT_STANDARD, "The QuietUninstallString executable path is not in a secure location: %ls", sczExecutablePath); + ExitFunction1(hr = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED)); + } + } + + hr = PathGetDirectory(sczExecutablePath, &sczCachedDirectory); + ExitOnFailure(hr, "Failed to get parent directory for QuietUninstallString executable path: %ls", sczExecutablePath); + + wzUninstallArguments = sczArpArguments; } else { @@ -396,7 +528,7 @@ extern "C" HRESULT ExeEngineExecutePackage( break; case BOOTSTRAPPER_ACTION_STATE_UNINSTALL: - wzArguments = pPackage->Exe.sczUninstallArguments; + wzArguments = wzUninstallArguments; break; case BOOTSTRAPPER_ACTION_STATE_REPAIR: @@ -404,8 +536,7 @@ extern "C" HRESULT ExeEngineExecutePackage( break; default: - hr = E_INVALIDARG; - ExitOnFailure(hr, "Invalid Exe package action: %d.", pExecuteAction->exePackage.action); + ExitWithRootFailure(hr, E_INVALIDARG, "Invalid Exe package action: %d.", pExecuteAction->exePackage.action); } // now add optional arguments @@ -443,8 +574,7 @@ extern "C" HRESULT ExeEngineExecutePackage( break; default: - hr = E_INVALIDARG; - ExitOnFailure(hr, "Invalid Exe package action: %d.", pExecuteAction->exePackage.action); + ExitWithRootFailure(hr, E_INVALIDARG, "Invalid Exe package action: %d.", pExecuteAction->exePackage.action); } } } @@ -524,6 +654,8 @@ LExit: StrSecureZeroFreeString(sczUserArgs); ReleaseStr(sczUserArgsObfuscated); ReleaseStr(sczCommandObfuscated); + ReleaseStr(sczArpUninstallString); + ReleaseStr(sczArpArguments); ReleaseFileHandle(hExecutableFile); @@ -894,3 +1026,106 @@ extern "C" HRESULT ExeEngineHandleExitCode( //LExit: return hr; } + +static HRESULT DetectArpEntry( + __in const BURN_PACKAGE* pPackage, + __out BOOTSTRAPPER_PACKAGE_STATE* pPackageState, + __out_opt LPWSTR* psczQuietUninstallString + ) +{ + HRESULT hr = S_OK; + HKEY hKey = NULL; + VERUTIL_VERSION* pVersion = NULL; + int nCompareResult = 0; + HKEY hkRoot = pPackage->fPerMachine ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; + REG_KEY_BITNESS keyBitness = pPackage->Exe.fArpWin64 ? REG_KEY_64BIT : REG_KEY_32BIT; + + *pPackageState = BOOTSTRAPPER_PACKAGE_STATE_ABSENT; + if (psczQuietUninstallString) + { + ReleaseNullStr(*psczQuietUninstallString); + } + + hr = RegOpenEx(hkRoot, pPackage->Exe.sczArpKeyPath, KEY_READ, keyBitness, &hKey); + if (HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr || HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr) + { + ExitFunction1(hr = S_OK); + } + ExitOnFailure(hr, "Failed to open registry key: %ls.", pPackage->Exe.sczArpKeyPath); + + hr = RegReadWixVersion(hKey, L"DisplayVersion", &pVersion); + if (HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr) + { + ExitFunction1(hr = S_OK); + } + ExitOnFailure(hr, "Failed to read DisplayVersion."); + + if (pVersion->fInvalid) + { + LogId(REPORT_WARNING, MSG_DETECTED_EXE_PACKAGE_INVALID_VERSION, pPackage->Exe.sczArpKeyPath, pVersion->sczVersion); + } + + hr = VerCompareParsedVersions(pPackage->Exe.pArpDisplayVersion, pVersion, &nCompareResult); + ExitOnFailure(hr, "Failed to compare versions."); + + if (nCompareResult < 0) + { + *pPackageState = BOOTSTRAPPER_PACKAGE_STATE_OBSOLETE; + } + else if (nCompareResult > 0) + { + *pPackageState = BOOTSTRAPPER_PACKAGE_STATE_ABSENT; + } + else + { + *pPackageState = BOOTSTRAPPER_PACKAGE_STATE_PRESENT; + } + + if (psczQuietUninstallString) + { + hr = RegReadString(hKey, L"QuietUninstallString", psczQuietUninstallString); + if (HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr) + { + hr = S_OK; + } + ExitOnFailure(hr, "Failed to read QuietUninstallString."); + } + +LExit: + ReleaseRegKey(hKey); + ReleaseVerutilVersion(pVersion); + + return hr; +} + +static HRESULT ParseArpUninstallString( + __in_z LPCWSTR wzArpUninstallString, + __inout LPWSTR* psczExecutablePath, + __inout LPWSTR* psczArguments + ) +{ + HRESULT hr = S_OK; + int argc = 0; + LPWSTR* argv = NULL; + + hr = AppParseCommandLine(wzArpUninstallString, &argc, &argv); + ExitOnFailure(hr, "Failed to parse uninstall string as command line: %ls.", wzArpUninstallString); + ExitOnNull(argc, hr, E_INVALIDARG, "Uninstall string must contain an executable path."); + + hr = StrAllocString(psczExecutablePath, argv[0], 0); + ExitOnFailure(hr, "Failed to copy executable path for ArpCommand."); + + for (int i = 1; i < argc; ++i) + { + hr = AppAppendCommandLineArgument(psczArguments, argv[i]); + ExitOnFailure(hr, "Failed to append argument for ArpCommand."); + } + +LExit: + if (argv) + { + AppFreeCommandLineArgs(argv); + } + + return hr; +} diff --git a/src/burn/engine/package.h b/src/burn/engine/package.h index 3ec77baf..85f34de5 100644 --- a/src/burn/engine/package.h +++ b/src/burn/engine/package.h @@ -16,6 +16,13 @@ typedef _BURN_PACKAGE BURN_PACKAGE; const DWORD BURN_PACKAGE_INVALID_PATCH_INDEX = 0x80000000; +enum BURN_EXE_DETECTION_TYPE +{ + BURN_EXE_DETECTION_TYPE_NONE, + BURN_EXE_DETECTION_TYPE_CONDITION, + BURN_EXE_DETECTION_TYPE_ARP, +}; + enum BURN_EXE_EXIT_CODE_TYPE { BURN_EXE_EXIT_CODE_TYPE_NONE, @@ -338,6 +345,12 @@ typedef struct _BURN_PACKAGE } Bundle; struct { + BURN_EXE_DETECTION_TYPE detectionType; + + BOOL fArpWin64; + LPWSTR sczArpKeyPath; + VERUTIL_VERSION* pArpDisplayVersion; + LPWSTR sczDetectCondition; LPWSTR sczInstallArguments; LPWSTR sczRepairArguments; diff --git a/src/burn/engine/plan.cpp b/src/burn/engine/plan.cpp index 52bf6298..419d3272 100644 --- a/src/burn/engine/plan.cpp +++ b/src/burn/engine/plan.cpp @@ -2798,7 +2798,8 @@ static BOOL NeedsCache( { BOOTSTRAPPER_ACTION_STATE action = fExecute ? pPackage->execute : pPackage->rollback; // TODO: bundles could theoretically use package cache - if (BURN_PACKAGE_TYPE_BUNDLE == pPackage->type || BURN_PACKAGE_TYPE_EXE == pPackage->type) // Bundle and Exe packages require the package for all operations (even uninstall). + if (BURN_PACKAGE_TYPE_BUNDLE == pPackage->type || // Bundle and Exe packages require the package for all operations (even uninstall). + BURN_PACKAGE_TYPE_EXE == pPackage->type && BURN_EXE_DETECTION_TYPE_ARP != pPackage->Exe.detectionType) { return BOOTSTRAPPER_ACTION_STATE_NONE != action; } diff --git a/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj b/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj index e1a28712..f07418a7 100644 --- a/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj +++ b/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj @@ -81,6 +81,7 @@ + diff --git a/src/burn/test/BurnUnitTest/PlanTest.cpp b/src/burn/test/BurnUnitTest/PlanTest.cpp index be078c5c..2135d9f5 100644 --- a/src/burn/test/BurnUnitTest/PlanTest.cpp +++ b/src/burn/test/BurnUnitTest/PlanTest.cpp @@ -9,6 +9,7 @@ static HRESULT WINAPI PlanTestBAProc( __in_opt LPVOID pvContext ); +static LPCWSTR wzArpEntryExeManifestFileName = L"ExePackage_PerUserArpEntry_manifest.xml"; static LPCWSTR wzMsiTransactionManifestFileName = L"MsiTransaction_BundleAv1_manifest.xml"; static LPCWSTR wzMultipleBundlePackageManifestFileName = L"BundlePackage_Multiple_manifest.xml"; static LPCWSTR wzSingleExeManifestFileName = L"Failure_BundleD_manifest.xml"; @@ -47,6 +48,266 @@ namespace Bootstrapper { } + [Fact] + void ArpEntryExeInstallTest() + { + HRESULT hr = S_OK; + BURN_ENGINE_STATE engineState = { }; + BURN_ENGINE_STATE* pEngineState = &engineState; + BURN_PLAN* pPlan = &engineState.plan; + + InitializeEngineStateForCorePlan(wzArpEntryExeManifestFileName, pEngineState); + DetectAttachedContainerAsAttached(pEngineState); + DetectPermanentPackagesAsPresentAndCached(pEngineState); + + hr = CorePlan(pEngineState, BOOTSTRAPPER_ACTION_INSTALL); + NativeAssert::Succeeded(hr, "CorePlan failed"); + + Assert::Equal(BOOTSTRAPPER_ACTION_INSTALL, pPlan->action); + NativeAssert::StringEqual(L"{9C459DAD-0E64-40C8-8C9F-4F68E46AB223}", pPlan->wzBundleId); + NativeAssert::StringEqual(L"{9C459DAD-0E64-40C8-8C9F-4F68E46AB223}", pPlan->wzBundleProviderKey); + Assert::Equal(FALSE, pPlan->fEnabledForwardCompatibleBundle); + Assert::Equal(FALSE, pPlan->fPerMachine); + Assert::Equal(TRUE, pPlan->fCanAffectMachineState); + Assert::Equal(FALSE, pPlan->fDisableRollback); + Assert::Equal(FALSE, pPlan->fDisallowRemoval); + Assert::Equal(FALSE, pPlan->fDowngrade); + Assert::Equal(BURN_REGISTRATION_ACTION_OPERATIONS_CACHE_BUNDLE | BURN_REGISTRATION_ACTION_OPERATIONS_WRITE_PROVIDER_KEY, pPlan->dwRegistrationOperations); + + BOOL fRollback = FALSE; + DWORD dwIndex = 0; + ValidateDependentRegistrationAction(pPlan, fRollback, dwIndex++, TRUE, L"{9C459DAD-0E64-40C8-8C9F-4F68E46AB223}", L"{9C459DAD-0E64-40C8-8C9F-4F68E46AB223}"); + Assert::Equal(dwIndex, pPlan->cRegistrationActions); + + fRollback = TRUE; + dwIndex = 0; + ValidateDependentRegistrationAction(pPlan, fRollback, dwIndex++, FALSE, L"{9C459DAD-0E64-40C8-8C9F-4F68E46AB223}", L"{9C459DAD-0E64-40C8-8C9F-4F68E46AB223}"); + Assert::Equal(dwIndex, pPlan->cRollbackRegistrationActions); + + fRollback = FALSE; + dwIndex = 0; + ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 1); + ValidateCachePackage(pPlan, fRollback, dwIndex++, L"TestExe"); + ValidateCacheSignalSyncpoint(pPlan, fRollback, dwIndex++); + Assert::Equal(dwIndex, pPlan->cCacheActions); + + fRollback = TRUE; + dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cRollbackCacheActions); + + Assert::Equal(119695ull, pPlan->qwCacheSizeTotal); + + fRollback = FALSE; + dwIndex = 0; + DWORD dwExecuteCheckpointId = 2; + ValidateExecuteRollbackBoundaryStart(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteWaitCachePackage(pPlan, fRollback, dwIndex++, L"TestExe"); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteExePackage(pPlan, fRollback, dwIndex++, L"TestExe", BOOTSTRAPPER_ACTION_STATE_INSTALL); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteRollbackBoundaryEnd(pPlan, fRollback, dwIndex++); + Assert::Equal(dwIndex, pPlan->cExecuteActions); + + fRollback = TRUE; + dwIndex = 0; + dwExecuteCheckpointId = 2; + ValidateExecuteRollbackBoundaryStart(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteExePackage(pPlan, fRollback, dwIndex++, L"TestExe", BOOTSTRAPPER_ACTION_STATE_UNINSTALL); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteRollbackBoundaryEnd(pPlan, fRollback, dwIndex++); + Assert::Equal(dwIndex, pPlan->cRollbackActions); + + Assert::Equal(1ul, pPlan->cExecutePackagesTotal); + Assert::Equal(2ul, pPlan->cOverallProgressTicksTotal); + + dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cRestoreRelatedBundleActions); + + dwIndex = 0; + ValidateCleanAction(pPlan, dwIndex++, L"NetFx48Web"); + Assert::Equal(dwIndex, pPlan->cCleanActions); + + UINT uIndex = 0; + ValidatePlannedProvider(pPlan, uIndex++, L"{9C459DAD-0E64-40C8-8C9F-4F68E46AB223}", NULL); + Assert::Equal(uIndex, pPlan->cPlannedProviders); + + Assert::Equal(2ul, pEngineState->packages.cPackages); + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[1], L"TestExe", BURN_PACKAGE_REGISTRATION_STATE_PRESENT, BURN_PACKAGE_REGISTRATION_STATE_PRESENT); + } + + [Fact] + void ArpEntryExeInstallObsoleteTest() + { + HRESULT hr = S_OK; + BURN_ENGINE_STATE engineState = { }; + BURN_ENGINE_STATE* pEngineState = &engineState; + BURN_PLAN* pPlan = &engineState.plan; + + InitializeEngineStateForCorePlan(wzArpEntryExeManifestFileName, pEngineState); + DetectAttachedContainerAsAttached(pEngineState); + DetectPermanentPackagesAsPresentAndCached(pEngineState); + + pEngineState->packages.rgPackages[1].currentState = BOOTSTRAPPER_PACKAGE_STATE_OBSOLETE; + + hr = CorePlan(pEngineState, BOOTSTRAPPER_ACTION_INSTALL); + NativeAssert::Succeeded(hr, "CorePlan failed"); + + Assert::Equal(BOOTSTRAPPER_ACTION_INSTALL, pPlan->action); + NativeAssert::StringEqual(L"{9C459DAD-0E64-40C8-8C9F-4F68E46AB223}", pPlan->wzBundleId); + NativeAssert::StringEqual(L"{9C459DAD-0E64-40C8-8C9F-4F68E46AB223}", pPlan->wzBundleProviderKey); + Assert::Equal(FALSE, pPlan->fEnabledForwardCompatibleBundle); + Assert::Equal(FALSE, pPlan->fPerMachine); + Assert::Equal(TRUE, pPlan->fCanAffectMachineState); + Assert::Equal(FALSE, pPlan->fDisableRollback); + Assert::Equal(FALSE, pPlan->fDisallowRemoval); + Assert::Equal(FALSE, pPlan->fDowngrade); + Assert::Equal(BURN_REGISTRATION_ACTION_OPERATIONS_CACHE_BUNDLE | BURN_REGISTRATION_ACTION_OPERATIONS_WRITE_PROVIDER_KEY, pPlan->dwRegistrationOperations); + + BOOL fRollback = FALSE; + DWORD dwIndex = 0; + ValidateDependentRegistrationAction(pPlan, fRollback, dwIndex++, TRUE, L"{9C459DAD-0E64-40C8-8C9F-4F68E46AB223}", L"{9C459DAD-0E64-40C8-8C9F-4F68E46AB223}"); + Assert::Equal(dwIndex, pPlan->cRegistrationActions); + + fRollback = TRUE; + dwIndex = 0; + ValidateDependentRegistrationAction(pPlan, fRollback, dwIndex++, FALSE, L"{9C459DAD-0E64-40C8-8C9F-4F68E46AB223}", L"{9C459DAD-0E64-40C8-8C9F-4F68E46AB223}"); + Assert::Equal(dwIndex, pPlan->cRollbackRegistrationActions); + + fRollback = FALSE; + dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cCacheActions); + + fRollback = TRUE; + dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cRollbackCacheActions); + + Assert::Equal(0ull, pPlan->qwCacheSizeTotal); + + fRollback = FALSE; + dwIndex = 0; + DWORD dwExecuteCheckpointId = 1; + ValidateExecuteRollbackBoundaryStart(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteRollbackBoundaryEnd(pPlan, fRollback, dwIndex++); + Assert::Equal(dwIndex, pPlan->cExecuteActions); + + fRollback = TRUE; + dwIndex = 0; + dwExecuteCheckpointId = 1; + ValidateExecuteRollbackBoundaryStart(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteRollbackBoundaryEnd(pPlan, fRollback, dwIndex++); + Assert::Equal(dwIndex, pPlan->cRollbackActions); + + Assert::Equal(0ul, pPlan->cExecutePackagesTotal); + Assert::Equal(0ul, pPlan->cOverallProgressTicksTotal); + + dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cRestoreRelatedBundleActions); + + dwIndex = 0; + ValidateCleanAction(pPlan, dwIndex++, L"NetFx48Web"); + Assert::Equal(dwIndex, pPlan->cCleanActions); + + UINT uIndex = 0; + ValidatePlannedProvider(pPlan, uIndex++, L"{9C459DAD-0E64-40C8-8C9F-4F68E46AB223}", NULL); + Assert::Equal(uIndex, pPlan->cPlannedProviders); + + Assert::Equal(2ul, pEngineState->packages.cPackages); + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[1], L"TestExe", BURN_PACKAGE_REGISTRATION_STATE_ABSENT, BURN_PACKAGE_REGISTRATION_STATE_ABSENT); + } + + [Fact] + void ArpEntryExeUninstallTest() + { + HRESULT hr = S_OK; + BURN_ENGINE_STATE engineState = { }; + BURN_ENGINE_STATE* pEngineState = &engineState; + BURN_PLAN* pPlan = &engineState.plan; + + InitializeEngineStateForCorePlan(wzArpEntryExeManifestFileName, pEngineState); + DetectPackagesAsPresentAndCached(pEngineState); + + hr = CorePlan(pEngineState, BOOTSTRAPPER_ACTION_UNINSTALL); + NativeAssert::Succeeded(hr, "CorePlan failed"); + + Assert::Equal(BOOTSTRAPPER_ACTION_UNINSTALL, pPlan->action); + NativeAssert::StringEqual(L"{9C459DAD-0E64-40C8-8C9F-4F68E46AB223}", pPlan->wzBundleId); + NativeAssert::StringEqual(L"{9C459DAD-0E64-40C8-8C9F-4F68E46AB223}", pPlan->wzBundleProviderKey); + Assert::Equal(FALSE, pPlan->fEnabledForwardCompatibleBundle); + Assert::Equal(FALSE, pPlan->fPerMachine); + Assert::Equal(TRUE, pPlan->fCanAffectMachineState); + Assert::Equal(FALSE, pPlan->fDisableRollback); + Assert::Equal(FALSE, pPlan->fDisallowRemoval); + Assert::Equal(FALSE, pPlan->fDowngrade); + Assert::Equal(BURN_REGISTRATION_ACTION_OPERATIONS_CACHE_BUNDLE | BURN_REGISTRATION_ACTION_OPERATIONS_WRITE_PROVIDER_KEY, pPlan->dwRegistrationOperations); + + BOOL fRollback = FALSE; + DWORD dwIndex = 0; + ValidateDependentRegistrationAction(pPlan, fRollback, dwIndex++, FALSE, L"{9C459DAD-0E64-40C8-8C9F-4F68E46AB223}", L"{9C459DAD-0E64-40C8-8C9F-4F68E46AB223}"); + Assert::Equal(dwIndex, pPlan->cRegistrationActions); + + fRollback = TRUE; + dwIndex = 0; + ValidateDependentRegistrationAction(pPlan, fRollback, dwIndex++, TRUE, L"{9C459DAD-0E64-40C8-8C9F-4F68E46AB223}", L"{9C459DAD-0E64-40C8-8C9F-4F68E46AB223}"); + Assert::Equal(dwIndex, pPlan->cRollbackRegistrationActions); + + fRollback = FALSE; + dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cCacheActions); + + fRollback = TRUE; + dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cRollbackCacheActions); + + Assert::Equal(0ull, pPlan->qwCacheSizeTotal); + + fRollback = FALSE; + dwIndex = 0; + DWORD dwExecuteCheckpointId = 1; + ValidateExecuteRollbackBoundaryStart(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteExePackage(pPlan, fRollback, dwIndex++, L"TestExe", BOOTSTRAPPER_ACTION_STATE_UNINSTALL); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteRollbackBoundaryEnd(pPlan, fRollback, dwIndex++); + Assert::Equal(dwIndex, pPlan->cExecuteActions); + + fRollback = TRUE; + dwIndex = 0; + dwExecuteCheckpointId = 1; + ValidateExecuteRollbackBoundaryStart(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE); + ValidateExecuteExePackage(pPlan, fRollback, dwIndex++, L"TestExe", BOOTSTRAPPER_ACTION_STATE_INSTALL); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++); + ValidateExecuteRollbackBoundaryEnd(pPlan, fRollback, dwIndex++); + Assert::Equal(dwIndex, pPlan->cRollbackActions); + + Assert::Equal(1ul, pPlan->cExecutePackagesTotal); + Assert::Equal(1ul, pPlan->cOverallProgressTicksTotal); + + dwIndex = 0; + Assert::Equal(dwIndex, pPlan->cRestoreRelatedBundleActions); + + dwIndex = 0; + ValidateCleanAction(pPlan, dwIndex++, L"TestExe"); + ValidateCleanAction(pPlan, dwIndex++, L"NetFx48Web"); + Assert::Equal(dwIndex, pPlan->cCleanActions); + + UINT uIndex = 0; + ValidatePlannedProvider(pPlan, uIndex++, L"{9C459DAD-0E64-40C8-8C9F-4F68E46AB223}", NULL); + Assert::Equal(uIndex, pPlan->cPlannedProviders); + + Assert::Equal(2ul, pEngineState->packages.cPackages); + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[1], L"TestExe", BURN_PACKAGE_REGISTRATION_STATE_ABSENT, BURN_PACKAGE_REGISTRATION_STATE_ABSENT); + } + [Fact] void MsiTransactionInstallTest() { diff --git a/src/burn/test/BurnUnitTest/TestData/PlanTest/BundlePackage_Multiple_manifest.xml b/src/burn/test/BurnUnitTest/TestData/PlanTest/BundlePackage_Multiple_manifest.xml index b02c2056..862156e8 100644 --- a/src/burn/test/BurnUnitTest/TestData/PlanTest/BundlePackage_Multiple_manifest.xml +++ b/src/burn/test/BurnUnitTest/TestData/PlanTest/BundlePackage_Multiple_manifest.xml @@ -1 +1 @@ - + diff --git a/src/burn/test/BurnUnitTest/TestData/PlanTest/ExePackage_PerUserArpEntry_manifest.xml b/src/burn/test/BurnUnitTest/TestData/PlanTest/ExePackage_PerUserArpEntry_manifest.xml new file mode 100644 index 00000000..59d6d50f --- /dev/null +++ b/src/burn/test/BurnUnitTest/TestData/PlanTest/ExePackage_PerUserArpEntry_manifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/burn/test/BurnUnitTest/TestData/PlanTest/Failure_BundleD_manifest.xml b/src/burn/test/BurnUnitTest/TestData/PlanTest/Failure_BundleD_manifest.xml index 6afb0108..13223db8 100644 --- a/src/burn/test/BurnUnitTest/TestData/PlanTest/Failure_BundleD_manifest.xml +++ b/src/burn/test/BurnUnitTest/TestData/PlanTest/Failure_BundleD_manifest.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/burn/test/BurnUnitTest/TestData/PlanTest/Slipstream_BundleA_manifest.xml b/src/burn/test/BurnUnitTest/TestData/PlanTest/Slipstream_BundleA_manifest.xml index 184bba38..5cfd8c98 100644 --- a/src/burn/test/BurnUnitTest/TestData/PlanTest/Slipstream_BundleA_manifest.xml +++ b/src/burn/test/BurnUnitTest/TestData/PlanTest/Slipstream_BundleA_manifest.xml @@ -1 +1 @@ - + diff --git a/src/burn/test/BurnUnitTest/TestData/PlanTest/Slipstream_BundleA_modified_manifest.xml b/src/burn/test/BurnUnitTest/TestData/PlanTest/Slipstream_BundleA_modified_manifest.xml index 0b35860b..175bfba1 100644 --- a/src/burn/test/BurnUnitTest/TestData/PlanTest/Slipstream_BundleA_modified_manifest.xml +++ b/src/burn/test/BurnUnitTest/TestData/PlanTest/Slipstream_BundleA_modified_manifest.xml @@ -1 +1 @@ - + -- cgit v1.2.3-55-g6feb