From befcd209d62a25020f46a688002b259c59e4dc3b Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Mon, 28 Feb 2022 18:42:51 -0600 Subject: Refactor related bundle enumeration into butil. Related to #3693 --- src/burn/engine/precomp.h | 1 + src/burn/engine/relatedbundle.cpp | 390 +++-------------- src/burn/test/BurnUnitTest/BurnTestException.h | 11 +- src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj | 1 + .../test/BurnUnitTest/BurnUnitTest.vcxproj.filters | 3 + src/burn/test/BurnUnitTest/RelatedBundleTest.cpp | 199 +++++++++ src/burn/test/BurnUnitTest/precomp.h | 1 + .../SucceededException.cs | 1 + src/libs/dutil/WixToolset.DUtil/butil.cpp | 480 ++++++++++++++++++--- src/libs/dutil/WixToolset.DUtil/inc/butil.h | 49 +++ 10 files changed, 731 insertions(+), 405 deletions(-) create mode 100644 src/burn/test/BurnUnitTest/RelatedBundleTest.cpp diff --git a/src/burn/engine/precomp.h b/src/burn/engine/precomp.h index 26adf44c..c83c1e74 100644 --- a/src/burn/engine/precomp.h +++ b/src/burn/engine/precomp.h @@ -55,6 +55,7 @@ #include #include #include +#include #include "BootstrapperEngine.h" #include "BootstrapperApplication.h" diff --git a/src/burn/engine/relatedbundle.cpp b/src/burn/engine/relatedbundle.cpp index 3e0bc799..e6633131 100644 --- a/src/burn/engine/relatedbundle.cpp +++ b/src/burn/engine/relatedbundle.cpp @@ -2,6 +2,12 @@ #include "precomp.h" +typedef struct _BUNDLE_QUERY_CONTEXT +{ + BURN_REGISTRATION* pRegistration; + BURN_RELATED_BUNDLES* pRelatedBundles; +} BUNDLE_QUERY_CONTEXT; + // internal function declarations static __callback int __cdecl CompareRelatedBundles( @@ -9,25 +15,15 @@ static __callback int __cdecl CompareRelatedBundles( __in const void* pvLeft, __in const void* pvRight ); -static HRESULT InitializeForScopeAndBitness( - __in BOOL fPerMachine, - __in REG_KEY_BITNESS regBitness, - __in BURN_REGISTRATION* pRegistration, - __in BURN_RELATED_BUNDLES* pRelatedBundles +static BUNDLE_QUERY_CALLBACK_RESULT CALLBACK QueryRelatedBundlesCallback( + __in const BUNDLE_QUERY_RELATED_BUNDLE_RESULT* pBundle, + __in_opt LPVOID pvContext ); static HRESULT LoadIfRelatedBundle( - __in BOOL fPerMachine, - __in REG_KEY_BITNESS regBitness, - __in HKEY hkUninstallKey, - __in_z LPCWSTR sczRelatedBundleId, + __in const BUNDLE_QUERY_RELATED_BUNDLE_RESULT* pBundle, __in BURN_REGISTRATION* pRegistration, __in BURN_RELATED_BUNDLES* pRelatedBundles ); -static HRESULT DetermineRelationType( - __in HKEY hkBundleId, - __in BURN_REGISTRATION* pRegistration, - __out BOOTSTRAPPER_RELATION_TYPE* pRelationType - ); static HRESULT LoadRelatedBundleFromKey( __in_z LPCWSTR wzRelatedBundleId, __in HKEY hkBundleId, @@ -46,12 +42,25 @@ extern "C" HRESULT RelatedBundlesInitializeForScope( ) { HRESULT hr = S_OK; - - hr = InitializeForScopeAndBitness(fPerMachine, REG_KEY_32BIT, pRegistration, pRelatedBundles); - ExitOnFailure(hr, "Failed to open 32-bit uninstall registry key."); - - hr = InitializeForScopeAndBitness(fPerMachine, REG_KEY_64BIT, pRegistration, pRelatedBundles); - ExitOnFailure(hr, "Failed to open 64-bit uninstall registry key."); + BUNDLE_INSTALL_CONTEXT installContext = fPerMachine ? BUNDLE_INSTALL_CONTEXT_MACHINE : BUNDLE_INSTALL_CONTEXT_USER; + BUNDLE_QUERY_CONTEXT queryContext = { }; + + queryContext.pRegistration = pRegistration; + queryContext.pRelatedBundles = pRelatedBundles; + + hr = BundleQueryRelatedBundles( + installContext, + const_cast(pRegistration->rgsczDetectCodes), + pRegistration->cDetectCodes, + const_cast(pRegistration->rgsczUpgradeCodes), + pRegistration->cUpgradeCodes, + const_cast(pRegistration->rgsczAddonCodes), + pRegistration->cAddonCodes, + const_cast(pRegistration->rgsczPatchCodes), + pRegistration->cPatchCodes, + QueryRelatedBundlesCallback, + &queryContext); + ExitOnFailure(hr, "Failed to initialize related bundles for scope."); LExit: return hr; @@ -166,346 +175,53 @@ static __callback int __cdecl CompareRelatedBundles( return ret; } -static HRESULT InitializeForScopeAndBitness( - __in BOOL fPerMachine, - __in REG_KEY_BITNESS regBitness, - __in BURN_REGISTRATION * pRegistration, - __in BURN_RELATED_BUNDLES * pRelatedBundles -) +static BUNDLE_QUERY_CALLBACK_RESULT CALLBACK QueryRelatedBundlesCallback( + __in const BUNDLE_QUERY_RELATED_BUNDLE_RESULT* pBundle, + __in_opt LPVOID pvContext + ) { HRESULT hr = S_OK; - HKEY hkRoot = fPerMachine ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; - HKEY hkUninstallKey = NULL; - LPWSTR sczRelatedBundleId = NULL; + BUNDLE_QUERY_CALLBACK_RESULT result = BUNDLE_QUERY_CALLBACK_RESULT_CONTINUE; + BUNDLE_QUERY_CONTEXT* pContext = reinterpret_cast(pvContext); - hr = RegOpenEx(hkRoot, BURN_REGISTRATION_REGISTRY_UNINSTALL_KEY, KEY_READ, regBitness, &hkUninstallKey); - 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 uninstall registry key."); - - for (DWORD dwIndex = 0; /* exit via break below */; ++dwIndex) - { - hr = RegKeyEnum(hkUninstallKey, dwIndex, &sczRelatedBundleId); - if (E_NOMOREITEMS == hr) - { - hr = S_OK; - break; - } - ExitOnFailure(hr, "Failed to enumerate uninstall key for related bundles."); - - // If we did not find our bundle id, try to load the subkey as a related bundle. - if (CSTR_EQUAL != ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, sczRelatedBundleId, -1, pRegistration->sczId, -1)) - { - // Ignore failures here since we'll often find products that aren't actually - // related bundles (or even bundles at all). - HRESULT hrRelatedBundle = LoadIfRelatedBundle(fPerMachine, regBitness, hkUninstallKey, sczRelatedBundleId, pRegistration, pRelatedBundles); - UNREFERENCED_PARAMETER(hrRelatedBundle); - } - } + hr = LoadIfRelatedBundle(pBundle, pContext->pRegistration, pContext->pRelatedBundles); + ExitOnFailure(hr, "Failed to load related bundle: %ls", pBundle->wzBundleId); LExit: - ReleaseStr(sczRelatedBundleId); - ReleaseRegKey(hkUninstallKey); - - return hr; + return result; } static HRESULT LoadIfRelatedBundle( - __in BOOL fPerMachine, - __in REG_KEY_BITNESS regBitness, - __in HKEY hkUninstallKey, - __in_z LPCWSTR sczRelatedBundleId, + __in const BUNDLE_QUERY_RELATED_BUNDLE_RESULT* pBundle, __in BURN_REGISTRATION* pRegistration, __in BURN_RELATED_BUNDLES* pRelatedBundles ) { HRESULT hr = S_OK; - HKEY hkBundleId = NULL; - BOOTSTRAPPER_RELATION_TYPE relationType = BOOTSTRAPPER_RELATION_NONE; - - hr = RegOpenEx(hkUninstallKey, sczRelatedBundleId, KEY_READ, regBitness, &hkBundleId); - ExitOnFailure(hr, "Failed to open uninstall key for potential related bundle: %ls", sczRelatedBundleId); - - hr = DetermineRelationType(hkBundleId, pRegistration, &relationType); - if (FAILED(hr) || BOOTSTRAPPER_RELATION_NONE == relationType) - { - // Must not be a related bundle. - hr = E_NOTFOUND; - } - else // load the related bundle. - { - hr = MemEnsureArraySize(reinterpret_cast(&pRelatedBundles->rgRelatedBundles), pRelatedBundles->cRelatedBundles + 1, sizeof(BURN_RELATED_BUNDLE), 5); - ExitOnFailure(hr, "Failed to ensure there is space for related bundles."); - - BURN_RELATED_BUNDLE* pRelatedBundle = pRelatedBundles->rgRelatedBundles + pRelatedBundles->cRelatedBundles; - - hr = LoadRelatedBundleFromKey(sczRelatedBundleId, hkBundleId, fPerMachine, relationType, pRelatedBundle); - ExitOnFailure(hr, "Failed to initialize package from related bundle id: %ls", sczRelatedBundleId); - - hr = DependencyDetectRelatedBundle(pRelatedBundle, pRegistration); - ExitOnFailure(hr, "Failed to detect dependencies for related bundle."); - - ++pRelatedBundles->cRelatedBundles; - } - -LExit: - ReleaseRegKey(hkBundleId); - - return hr; -} - -static HRESULT DetermineRelationType( - __in HKEY hkBundleId, - __in BURN_REGISTRATION* pRegistration, - __out BOOTSTRAPPER_RELATION_TYPE* pRelationType - ) -{ - HRESULT hr = S_OK; - LPWSTR* rgsczUpgradeCodes = NULL; - DWORD cUpgradeCodes = 0; - STRINGDICT_HANDLE sdUpgradeCodes = NULL; - LPWSTR* rgsczAddonCodes = NULL; - DWORD cAddonCodes = 0; - STRINGDICT_HANDLE sdAddonCodes = NULL; - LPWSTR* rgsczDetectCodes = NULL; - DWORD cDetectCodes = 0; - STRINGDICT_HANDLE sdDetectCodes = NULL; - LPWSTR* rgsczPatchCodes = NULL; - DWORD cPatchCodes = 0; - STRINGDICT_HANDLE sdPatchCodes = NULL; - - *pRelationType = BOOTSTRAPPER_RELATION_NONE; - - // All remaining operations should treat all related bundles as non-vital. - hr = RegReadStringArray(hkBundleId, BURN_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE, &rgsczUpgradeCodes, &cUpgradeCodes); - if (HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE) == hr) - { - TraceError(hr, "Failed to read upgrade codes as REG_MULTI_SZ. Trying again as REG_SZ in case of older bundles."); - - rgsczUpgradeCodes = reinterpret_cast(MemAlloc(sizeof(LPWSTR), TRUE)); - ExitOnNull(rgsczUpgradeCodes, hr, E_OUTOFMEMORY, "Failed to allocate list for a single upgrade code from older bundle."); - - hr = RegReadString(hkBundleId, BURN_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE, &rgsczUpgradeCodes[0]); - if (SUCCEEDED(hr)) - { - cUpgradeCodes = 1; - } - } - - // Compare upgrade codes. - if (SUCCEEDED(hr)) - { - hr = DictCreateStringListFromArray(&sdUpgradeCodes, rgsczUpgradeCodes, cUpgradeCodes, DICT_FLAG_CASEINSENSITIVE); - ExitOnFailure(hr, "Failed to create string dictionary for %hs.", "upgrade codes"); - - // Upgrade relationship: when their upgrade codes match our upgrade codes. - hr = DictCompareStringListToArray(sdUpgradeCodes, const_cast(pRegistration->rgsczUpgradeCodes), pRegistration->cUpgradeCodes); - if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) - { - hr = S_OK; - } - else - { - ExitOnFailure(hr, "Failed to do array search for upgrade code match."); - - *pRelationType = BOOTSTRAPPER_RELATION_UPGRADE; - ExitFunction(); - } - - // Detect relationship: when their upgrade codes match our detect codes. - hr = DictCompareStringListToArray(sdUpgradeCodes, const_cast(pRegistration->rgsczDetectCodes), pRegistration->cDetectCodes); - if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) - { - hr = S_OK; - } - else - { - ExitOnFailure(hr, "Failed to do array search for detect code match."); - - *pRelationType = BOOTSTRAPPER_RELATION_DETECT; - ExitFunction(); - } - - // Dependent relationship: when their upgrade codes match our addon codes. - hr = DictCompareStringListToArray(sdUpgradeCodes, const_cast(pRegistration->rgsczAddonCodes), pRegistration->cAddonCodes); - if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) - { - hr = S_OK; - } - else - { - ExitOnFailure(hr, "Failed to do array search for addon code match."); - - *pRelationType = BOOTSTRAPPER_RELATION_DEPENDENT; - ExitFunction(); - } - - // Dependent relationship: when their upgrade codes match our patch codes. - hr = DictCompareStringListToArray(sdUpgradeCodes, const_cast(pRegistration->rgsczPatchCodes), pRegistration->cPatchCodes); - if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) - { - hr = S_OK; - } - else - { - ExitOnFailure(hr, "Failed to do array search for addon code match."); - - *pRelationType = BOOTSTRAPPER_RELATION_DEPENDENT; - ExitFunction(); - } - - ReleaseNullDict(sdUpgradeCodes); - ReleaseNullStrArray(rgsczUpgradeCodes, cUpgradeCodes); - } - - // Compare addon codes. - hr = RegReadStringArray(hkBundleId, BURN_REGISTRATION_REGISTRY_BUNDLE_ADDON_CODE, &rgsczAddonCodes, &cAddonCodes); - if (SUCCEEDED(hr)) - { - hr = DictCreateStringListFromArray(&sdAddonCodes, rgsczAddonCodes, cAddonCodes, DICT_FLAG_CASEINSENSITIVE); - ExitOnFailure(hr, "Failed to create string dictionary for %hs.", "addon codes"); - - // Addon relationship: when their addon codes match our detect codes. - hr = DictCompareStringListToArray(sdAddonCodes, const_cast(pRegistration->rgsczDetectCodes), pRegistration->cDetectCodes); - if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) - { - hr = S_OK; - } - else - { - ExitOnFailure(hr, "Failed to do array search for addon code match."); - - *pRelationType = BOOTSTRAPPER_RELATION_ADDON; - ExitFunction(); - } - - // Addon relationship: when their addon codes match our upgrade codes. - hr = DictCompareStringListToArray(sdAddonCodes, const_cast(pRegistration->rgsczUpgradeCodes), pRegistration->cUpgradeCodes); - if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) - { - hr = S_OK; - } - else - { - ExitOnFailure(hr, "Failed to do array search for addon code match."); - - *pRelationType = BOOTSTRAPPER_RELATION_ADDON; - ExitFunction(); - } - - ReleaseNullDict(sdAddonCodes); - ReleaseNullStrArray(rgsczAddonCodes, cAddonCodes); - } + BOOL fPerMachine = BUNDLE_INSTALL_CONTEXT_MACHINE == pBundle->installContext; + BOOTSTRAPPER_RELATION_TYPE relationType = (BOOTSTRAPPER_RELATION_TYPE)pBundle->relationType; + BURN_RELATED_BUNDLE* pRelatedBundle = NULL; - // Compare patch codes. - hr = RegReadStringArray(hkBundleId, BURN_REGISTRATION_REGISTRY_BUNDLE_PATCH_CODE, &rgsczPatchCodes, &cPatchCodes); - if (SUCCEEDED(hr)) + // If we found our bundle id, it's not a related bundle. + if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, pBundle->wzBundleId, -1, pRegistration->sczId, -1)) { - hr = DictCreateStringListFromArray(&sdPatchCodes, rgsczPatchCodes, cPatchCodes, DICT_FLAG_CASEINSENSITIVE); - ExitOnFailure(hr, "Failed to create string dictionary for %hs.", "patch codes"); - - // Patch relationship: when their patch codes match our detect codes. - hr = DictCompareStringListToArray(sdPatchCodes, const_cast(pRegistration->rgsczDetectCodes), pRegistration->cDetectCodes); - if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) - { - hr = S_OK; - } - else - { - ExitOnFailure(hr, "Failed to do array search for patch code match."); - - *pRelationType = BOOTSTRAPPER_RELATION_PATCH; - ExitFunction(); - } - - // Patch relationship: when their patch codes match our upgrade codes. - hr = DictCompareStringListToArray(sdPatchCodes, const_cast(pRegistration->rgsczUpgradeCodes), pRegistration->cUpgradeCodes); - if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) - { - hr = S_OK; - } - else - { - ExitOnFailure(hr, "Failed to do array search for patch code match."); - - *pRelationType = BOOTSTRAPPER_RELATION_PATCH; - ExitFunction(); - } - - ReleaseNullDict(sdPatchCodes); - ReleaseNullStrArray(rgsczPatchCodes, cPatchCodes); + ExitFunction1(hr = S_FALSE); } - // Compare detect codes. - hr = RegReadStringArray(hkBundleId, BURN_REGISTRATION_REGISTRY_BUNDLE_DETECT_CODE, &rgsczDetectCodes, &cDetectCodes); - if (SUCCEEDED(hr)) - { - hr = DictCreateStringListFromArray(&sdDetectCodes, rgsczDetectCodes, cDetectCodes, DICT_FLAG_CASEINSENSITIVE); - ExitOnFailure(hr, "Failed to create string dictionary for %hs.", "detect codes"); + hr = MemEnsureArraySize(reinterpret_cast(&pRelatedBundles->rgRelatedBundles), pRelatedBundles->cRelatedBundles + 1, sizeof(BURN_RELATED_BUNDLE), 5); + ExitOnFailure(hr, "Failed to ensure there is space for related bundles."); - // Detect relationship: when their detect codes match our detect codes. - hr = DictCompareStringListToArray(sdDetectCodes, const_cast(pRegistration->rgsczDetectCodes), pRegistration->cDetectCodes); - if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) - { - hr = S_OK; - } - else - { - ExitOnFailure(hr, "Failed to do array search for detect code match."); + pRelatedBundle = pRelatedBundles->rgRelatedBundles + pRelatedBundles->cRelatedBundles; - *pRelationType = BOOTSTRAPPER_RELATION_DETECT; - ExitFunction(); - } + hr = LoadRelatedBundleFromKey(pBundle->wzBundleId, pBundle->hkBundle, fPerMachine, relationType, pRelatedBundle); + ExitOnFailure(hr, "Failed to initialize package from related bundle id: %ls", pBundle->wzBundleId); - // Dependent relationship: when their detect codes match our addon codes. - hr = DictCompareStringListToArray(sdDetectCodes, const_cast(pRegistration->rgsczAddonCodes), pRegistration->cAddonCodes); - if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) - { - hr = S_OK; - } - else - { - ExitOnFailure(hr, "Failed to do array search for addon code match."); - - *pRelationType = BOOTSTRAPPER_RELATION_DEPENDENT; - ExitFunction(); - } - - // Dependent relationship: when their detect codes match our patch codes. - hr = DictCompareStringListToArray(sdDetectCodes, const_cast(pRegistration->rgsczPatchCodes), pRegistration->cPatchCodes); - if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) - { - hr = S_OK; - } - else - { - ExitOnFailure(hr, "Failed to do array search for addon code match."); + hr = DependencyDetectRelatedBundle(pRelatedBundle, pRegistration); + ExitOnFailure(hr, "Failed to detect dependencies for related bundle."); - *pRelationType = BOOTSTRAPPER_RELATION_DEPENDENT; - ExitFunction(); - } - - ReleaseNullDict(sdDetectCodes); - ReleaseNullStrArray(rgsczDetectCodes, cDetectCodes); - } + ++pRelatedBundles->cRelatedBundles; LExit: - if (SUCCEEDED(hr) && BOOTSTRAPPER_RELATION_NONE == *pRelationType) - { - hr = E_NOTFOUND; - } - - ReleaseDict(sdUpgradeCodes); - ReleaseStrArray(rgsczUpgradeCodes, cUpgradeCodes); - ReleaseDict(sdAddonCodes); - ReleaseStrArray(rgsczAddonCodes, cAddonCodes); - ReleaseDict(sdDetectCodes); - ReleaseStrArray(rgsczDetectCodes, cDetectCodes); - ReleaseDict(sdPatchCodes); - ReleaseStrArray(rgsczPatchCodes, cPatchCodes); - return hr; } diff --git a/src/burn/test/BurnUnitTest/BurnTestException.h b/src/burn/test/BurnUnitTest/BurnTestException.h index bd94b4fc..e813f95c 100644 --- a/src/burn/test/BurnUnitTest/BurnTestException.h +++ b/src/burn/test/BurnUnitTest/BurnTestException.h @@ -13,19 +13,14 @@ namespace Test namespace Bootstrapper { using namespace System; + using namespace WixBuildTools::TestSupport; - public ref struct BurnTestException : public System::Exception + public ref struct BurnTestException : public SucceededException { public: - BurnTestException(HRESULT error) - { - this->HResult = error; - } - BurnTestException(HRESULT error, String^ message) - : Exception(message) + : SucceededException(error, message) { - this->HResult = error; } property Int32 ErrorCode diff --git a/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj b/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj index 36903239..f4f95b7f 100644 --- a/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj +++ b/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj @@ -56,6 +56,7 @@ 4564;4691 + diff --git a/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj.filters b/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj.filters index 96563fc6..90290f52 100644 --- a/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj.filters +++ b/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj.filters @@ -39,6 +39,9 @@ Source Files + + Source Files + Source Files diff --git a/src/burn/test/BurnUnitTest/RelatedBundleTest.cpp b/src/burn/test/BurnUnitTest/RelatedBundleTest.cpp new file mode 100644 index 00000000..3d1964c3 --- /dev/null +++ b/src/burn/test/BurnUnitTest/RelatedBundleTest.cpp @@ -0,0 +1,199 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + + +namespace Microsoft +{ +namespace Tools +{ +namespace WindowsInstallerXml +{ +namespace Test +{ +namespace Bootstrapper +{ + using namespace System; + using namespace System::IO; + using namespace Xunit; + using namespace WixBuildTools::TestSupport; + + public ref class RelatedBundleTest : BurnUnitTest, IClassFixture + { + private: + TestRegistryFixture^ testRegistry; + public: + RelatedBundleTest(BurnTestFixture^ fixture, TestRegistryFixture^ registryFixture) : BurnUnitTest(fixture) + { + this->testRegistry = registryFixture; + } + + [Fact] + void RelatedBundleDetectPerMachineTest() + { + HRESULT hr = S_OK; + IXMLDOMElement* pixeBundle = NULL; + BURN_REGISTRATION registration = { }; + BURN_RELATED_BUNDLES relatedBundles = { }; + BURN_CACHE cache = { }; + BURN_ENGINE_COMMAND internalCommand = { }; + + try + { + this->testRegistry->SetUp(); + this->RegisterFakeBundles(); + + LPCWSTR wzDocument = + L"" + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L""; + + // load XML document + LoadBundleXmlHelper(wzDocument, &pixeBundle); + + hr = CacheInitialize(&cache, &internalCommand); + TestThrowOnFailure(hr, L"Failed initialize cache."); + + hr = RegistrationParseFromXml(®istration, &cache, pixeBundle); + TestThrowOnFailure(hr, L"Failed to parse registration from XML."); + + RelatedBundlesInitializeForScope(registration.fPerMachine, ®istration, &relatedBundles); + + Assert::Equal(1lu, relatedBundles.cRelatedBundles); + + BURN_RELATED_BUNDLE* pRelatedBundle = relatedBundles.rgRelatedBundles + 0; + NativeAssert::StringEqual(L"{AD75BE46-B5D7-4208-BC8B-918553C72D83}", pRelatedBundle->package.sczId); + //{E2355133-384C-4332-9B62-1FA950D707B7} should be missing because it causes an error while processing it. It's important that this doesn't cause initialization to fail. + } + finally + { + ReleaseObject(pixeBundle); + RegistrationUninitialize(®istration); + + this->testRegistry->TearDown(); + } + } + + [Fact] + void RelatedBundleDetectPerUserTest() + { + HRESULT hr = S_OK; + IXMLDOMElement* pixeBundle = NULL; + BURN_REGISTRATION registration = { }; + BURN_RELATED_BUNDLES relatedBundles = { }; + BURN_CACHE cache = { }; + BURN_ENGINE_COMMAND internalCommand = { }; + + try + { + this->testRegistry->SetUp(); + this->RegisterFakeBundles(); + + LPCWSTR wzDocument = + L"" + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L""; + + // load XML document + LoadBundleXmlHelper(wzDocument, &pixeBundle); + + hr = CacheInitialize(&cache, &internalCommand); + TestThrowOnFailure(hr, L"Failed initialize cache."); + + hr = RegistrationParseFromXml(®istration, &cache, pixeBundle); + TestThrowOnFailure(hr, L"Failed to parse registration from XML."); + + RelatedBundlesInitializeForScope(registration.fPerMachine, ®istration, &relatedBundles); + + Assert::Equal(1lu, relatedBundles.cRelatedBundles); + + BURN_RELATED_BUNDLE* pRelatedBundle = relatedBundles.rgRelatedBundles + 0; + NativeAssert::StringEqual(L"{6DB5D48C-CD7D-40D2-BCBC-AF630E136761}", pRelatedBundle->package.sczId); + //{42D16EBE-8B6B-4A9A-9AE9-5300F30011AA} should be missing because it causes an error while processing it. It's important that this doesn't cause initialization to fail. + } + finally + { + ReleaseObject(pixeBundle); + RegistrationUninitialize(®istration); + + this->testRegistry->TearDown(); + } + } + + void RegisterFakeBundles() + { + this->RegisterFakeBundle(L"{D54F896D-1952-43E6-9C67-B5652240618C}", L"{89FDAE1F-8CC1-48B9-B930-3945E0D3E7F0}", NULL, L"1.0.0.0", TRUE); + this->RegisterFakeBundle(L"{E2355133-384C-4332-9B62-1FA950D707B7}", L"{89FDAE1F-8CC1-48B9-B930-3945E0D3E7F0}", L"", L"1.1.0.0", TRUE); + this->RegisterFakeBundle(L"{AD75BE46-B5D7-4208-BC8B-918553C72D83}", L"{89FDAE1F-8CC1-48B9-B930-3945E0D3E7F0}", NULL, L"2.0.0.0", TRUE); + this->RegisterFakeBundle(L"{6DB5D48C-CD7D-40D2-BCBC-AF630E136761}", L"{89FDAE1F-8CC1-48B9-B930-3945E0D3E7F0}", NULL, L"3.0.0.0", FALSE); + this->RegisterFakeBundle(L"{42D16EBE-8B6B-4A9A-9AE9-5300F30011AA}", L"{89FDAE1F-8CC1-48B9-B930-3945E0D3E7F0}", L"", L"3.1.0.0", FALSE); + this->RegisterFakeBundle(L"{3DB49D3D-1FB8-4147-A465-BBE8BFD0DAD0}", L"{89FDAE1F-8CC1-48B9-B930-3945E0D3E7F0}", NULL, L"4.0.0.0", FALSE); + } + + void RegisterFakeBundle(LPCWSTR wzBundleId, LPCWSTR wzUpgradeCodes, LPCWSTR wzCachePath, LPCWSTR wzVersion, BOOL fPerMachine) + { + HRESULT hr = S_OK; + LPWSTR* rgsczUpgradeCodes = NULL; + DWORD cUpgradeCodes = 0; + LPWSTR sczRegistrationKey = NULL; + LPWSTR sczCachePath = NULL; + HKEY hkRegistration = NULL; + HKEY hkRoot = fPerMachine ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; + + try + { + hr = StrSplitAllocArray(&rgsczUpgradeCodes, reinterpret_cast(&cUpgradeCodes), wzUpgradeCodes, L";"); + NativeAssert::Succeeded(hr, "Failed to split upgrade codes."); + + hr = StrAllocFormatted(&sczRegistrationKey, L"%s\\%s", BURN_REGISTRATION_REGISTRY_UNINSTALL_KEY, wzBundleId); + NativeAssert::Succeeded(hr, "Failed to build uninstall registry key path."); + + if (!wzCachePath) + { + hr = StrAllocFormatted(&sczCachePath, L"%ls.exe", wzBundleId); + NativeAssert::Succeeded(hr, "Failed to build cache path."); + + wzCachePath = sczCachePath; + } + + hr = RegCreate(hkRoot, sczRegistrationKey, KEY_WRITE, &hkRegistration); + NativeAssert::Succeeded(hr, "Failed to create registration key."); + + hr = RegWriteStringArray(hkRegistration, BURN_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE, rgsczUpgradeCodes, cUpgradeCodes); + NativeAssert::Succeeded(hr, "Failed to write %ls value.", BURN_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE); + + if (wzCachePath && *wzCachePath) + { + hr = RegWriteString(hkRegistration, BURN_REGISTRATION_REGISTRY_BUNDLE_CACHE_PATH, wzCachePath); + NativeAssert::Succeeded(hr, "Failed to write %ls value.", BURN_REGISTRATION_REGISTRY_BUNDLE_CACHE_PATH); + } + + hr = RegWriteString(hkRegistration, BURN_REGISTRATION_REGISTRY_BUNDLE_VERSION, wzVersion); + NativeAssert::Succeeded(hr, "Failed to write %ls value.", BURN_REGISTRATION_REGISTRY_BUNDLE_VERSION); + } + finally + { + ReleaseStrArray(rgsczUpgradeCodes, cUpgradeCodes); + ReleaseStr(sczRegistrationKey); + ReleaseStr(sczCachePath); + ReleaseRegKey(hkRegistration); + } + } + }; +} +} +} +} +} diff --git a/src/burn/test/BurnUnitTest/precomp.h b/src/burn/test/BurnUnitTest/precomp.h index ecab3494..ded9fc2d 100644 --- a/src/burn/test/BurnUnitTest/precomp.h +++ b/src/burn/test/BurnUnitTest/precomp.h @@ -53,6 +53,7 @@ #include "update.h" #include "pseudobundle.h" #include "registration.h" +#include "relatedbundle.h" #include "plan.h" #include "pipe.h" #include "logging.h" diff --git a/src/internal/WixBuildTools.TestSupport/SucceededException.cs b/src/internal/WixBuildTools.TestSupport/SucceededException.cs index 00b31d68..704fba28 100644 --- a/src/internal/WixBuildTools.TestSupport/SucceededException.cs +++ b/src/internal/WixBuildTools.TestSupport/SucceededException.cs @@ -13,6 +13,7 @@ namespace WixBuildTools.TestSupport "Message: {1}", hr, userMessage)) { + this.HResult = hr; } } } diff --git a/src/libs/dutil/WixToolset.DUtil/butil.cpp b/src/libs/dutil/WixToolset.DUtil/butil.cpp index 2f45da56..ac322ae7 100644 --- a/src/libs/dutil/WixToolset.DUtil/butil.cpp +++ b/src/libs/dutil/WixToolset.DUtil/butil.cpp @@ -19,6 +19,9 @@ // constants // From engine/registration.h const LPCWSTR BUNDLE_REGISTRATION_REGISTRY_UNINSTALL_KEY = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"; +const LPCWSTR BUNDLE_REGISTRATION_REGISTRY_BUNDLE_ADDON_CODE = L"BundleAddonCode"; +const LPCWSTR BUNDLE_REGISTRATION_REGISTRY_BUNDLE_DETECT_CODE = L"BundleDetectCode"; +const LPCWSTR BUNDLE_REGISTRATION_REGISTRY_BUNDLE_PATCH_CODE = L"BundlePatchCode"; const LPCWSTR BUNDLE_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE = L"BundleUpgradeCode"; const LPCWSTR BUNDLE_REGISTRATION_REGISTRY_BUNDLE_PROVIDER_KEY = L"BundleProviderKey"; const LPCWSTR BUNDLE_REGISTRATION_REGISTRY_BUNDLE_VARIABLE_KEY = L"variables"; @@ -30,7 +33,41 @@ enum INTERNAL_BUNDLE_STATUS INTERNAL_BUNDLE_STATUS_UNKNOWN_PROPERTY, }; +typedef struct _BUNDLE_QUERY_CONTEXT +{ + BUNDLE_INSTALL_CONTEXT installContext; + REG_KEY_BITNESS regBitness; + PFNBUNDLE_QUERY_RELATED_BUNDLE_CALLBACK pfnCallback; + LPVOID pvContext; + + LPCWSTR* rgwzDetectCodes; + DWORD cDetectCodes; + + LPCWSTR* rgwzUpgradeCodes; + DWORD cUpgradeCodes; + + LPCWSTR* rgwzAddonCodes; + DWORD cAddonCodes; + + LPCWSTR* rgwzPatchCodes; + DWORD cPatchCodes; +} BUNDLE_QUERY_CONTEXT; + // Forward declarations. +static HRESULT QueryRelatedBundlesForScopeAndBitness( + __in BUNDLE_QUERY_CONTEXT* pQueryContext + ); +static HRESULT QueryPotentialRelatedBundle( + __in BUNDLE_QUERY_CONTEXT* pQueryContext, + __in HKEY hkUninstallKey, + __in_z LPCWSTR wzRelatedBundleId, + __inout BUNDLE_QUERY_CALLBACK_RESULT* pResult + ); +static HRESULT DetermineRelationType( + __in BUNDLE_QUERY_CONTEXT* pQueryContext, + __in HKEY hkBundleId, + __out BUNDLE_RELATION_TYPE* pRelationType + ); /******************************************************************** LocateAndQueryBundleValue - Locates the requested key for the bundle, then queries the registry type for requested value. @@ -160,11 +197,13 @@ DAPI_(HRESULT) BundleEnumRelatedBundle( HKEY hkBundle = NULL; LPWSTR sczUninstallSubKey = NULL; LPWSTR sczUninstallSubKeyPath = NULL; - LPWSTR sczValue = NULL; - DWORD dwType = 0; - LPWSTR* rgsczBundleUpgradeCodes = NULL; - DWORD cBundleUpgradeCodes = 0; HKEY hkRoot = BUNDLE_INSTALL_CONTEXT_USER == context ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE; + BUNDLE_QUERY_CONTEXT queryContext = { }; + BUNDLE_RELATION_TYPE relationType = BUNDLE_RELATION_NONE; + + queryContext.installContext = context; + queryContext.rgwzUpgradeCodes = &wzUpgradeCode; + queryContext.cUpgradeCodes = 1; if (!wzUpgradeCode || !pdwStartIndex) { @@ -185,60 +224,12 @@ DAPI_(HRESULT) BundleEnumRelatedBundle( hr = RegOpenEx(hkRoot, sczUninstallSubKeyPath, KEY_READ, kbKeyBitness, &hkBundle); ButilExitOnFailure(hr, "Failed to open uninstall key path."); - // If it's a bundle, it should have a BundleUpgradeCode value of type REG_SZ (old) or REG_MULTI_SZ - hr = RegGetType(hkBundle, BUNDLE_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE, &dwType); - if (FAILED(hr)) + hr = DetermineRelationType(&queryContext, hkBundle, &relationType); + if (SUCCEEDED(hr) && BUNDLE_RELATION_UPGRADE == relationType) { - ReleaseRegKey(hkBundle); - ReleaseNullStr(sczUninstallSubKey); - ReleaseNullStr(sczUninstallSubKeyPath); - // Not a bundle - continue; - } - - switch (dwType) - { - case REG_SZ: - hr = RegReadString(hkBundle, BUNDLE_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE, &sczValue); - ButilExitOnFailure(hr, "Failed to read BundleUpgradeCode string property."); - - if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, sczValue, -1, wzUpgradeCode, -1)) - { - *pdwStartIndex = dwIndex; - fUpgradeCodeFound = TRUE; - break; - } - - ReleaseNullStr(sczValue); - - break; - case REG_MULTI_SZ: - hr = RegReadStringArray(hkBundle, BUNDLE_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE, &rgsczBundleUpgradeCodes, &cBundleUpgradeCodes); - ButilExitOnFailure(hr, "Failed to read BundleUpgradeCode multi-string property."); + fUpgradeCodeFound = TRUE; + *pdwStartIndex = dwIndex; - for (DWORD i = 0; i < cBundleUpgradeCodes; i++) - { - LPWSTR wzBundleUpgradeCode = rgsczBundleUpgradeCodes[i]; - if (wzBundleUpgradeCode && *wzBundleUpgradeCode) - { - if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, wzBundleUpgradeCode, -1, wzUpgradeCode, -1)) - { - *pdwStartIndex = dwIndex; - fUpgradeCodeFound = TRUE; - break; - } - } - } - ReleaseNullStrArray(rgsczBundleUpgradeCodes, cBundleUpgradeCodes); - - break; - - default: - ButilExitWithRootFailure(hr, E_NOTIMPL, "BundleUpgradeCode of type 0x%x not implemented.", dwType); - } - - if (fUpgradeCodeFound) - { if (psczBundleId) { *psczBundleId = sczUninstallSubKey; @@ -250,17 +241,13 @@ DAPI_(HRESULT) BundleEnumRelatedBundle( // Cleanup before next iteration ReleaseRegKey(hkBundle); - ReleaseNullStr(sczUninstallSubKey); - ReleaseNullStr(sczUninstallSubKeyPath); } LExit: - ReleaseStr(sczValue); ReleaseStr(sczUninstallSubKey); ReleaseStr(sczUninstallSubKeyPath); ReleaseRegKey(hkBundle); ReleaseRegKey(hkUninstall); - ReleaseStrArray(rgsczBundleUpgradeCodes, cBundleUpgradeCodes); return FAILED(hr) ? hr : fUpgradeCodeFound ? S_OK : S_FALSE; } @@ -370,6 +357,379 @@ LExit: return hr; } +DAPI_(HRESULT) BundleQueryRelatedBundles( + __in BUNDLE_INSTALL_CONTEXT installContext, + __in_z_opt LPCWSTR* rgwzDetectCodes, + __in DWORD cDetectCodes, + __in_z_opt LPCWSTR* rgwzUpgradeCodes, + __in DWORD cUpgradeCodes, + __in_z_opt LPCWSTR* rgwzAddonCodes, + __in DWORD cAddonCodes, + __in_z_opt LPCWSTR* rgwzPatchCodes, + __in DWORD cPatchCodes, + __in PFNBUNDLE_QUERY_RELATED_BUNDLE_CALLBACK pfnCallback, + __in_opt LPVOID pvContext + ) +{ + HRESULT hr = S_OK; + BUNDLE_QUERY_CONTEXT queryContext = { }; + + queryContext.installContext = installContext; + queryContext.rgwzDetectCodes = rgwzDetectCodes; + queryContext.cDetectCodes = cDetectCodes; + queryContext.rgwzUpgradeCodes = rgwzUpgradeCodes; + queryContext.cUpgradeCodes = cUpgradeCodes; + queryContext.rgwzAddonCodes = rgwzAddonCodes; + queryContext.cAddonCodes = cAddonCodes; + queryContext.rgwzPatchCodes = rgwzPatchCodes; + queryContext.cPatchCodes = cPatchCodes; + queryContext.pfnCallback = pfnCallback; + queryContext.pvContext = pvContext; + + queryContext.regBitness = REG_KEY_32BIT; + + hr = QueryRelatedBundlesForScopeAndBitness(&queryContext); + ExitOnFailure(hr, "Failed to query 32-bit related bundles."); + + queryContext.regBitness = REG_KEY_64BIT; + + hr = QueryRelatedBundlesForScopeAndBitness(&queryContext); + ExitOnFailure(hr, "Failed to query 64-bit related bundles."); + +LExit: + return hr; +} + +static HRESULT QueryRelatedBundlesForScopeAndBitness( + __in BUNDLE_QUERY_CONTEXT* pQueryContext + ) +{ + HRESULT hr = S_OK; + HKEY hkRoot = BUNDLE_INSTALL_CONTEXT_USER == pQueryContext->installContext ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE; + HKEY hkUninstallKey = NULL; + LPWSTR sczRelatedBundleId = NULL; + BUNDLE_QUERY_CALLBACK_RESULT result = BUNDLE_QUERY_CALLBACK_RESULT_CONTINUE; + + hr = RegOpenEx(hkRoot, BUNDLE_REGISTRATION_REGISTRY_UNINSTALL_KEY, KEY_READ, pQueryContext->regBitness, &hkUninstallKey); + 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 uninstall registry key."); + + for (DWORD dwIndex = 0; /* exit via break below */; ++dwIndex) + { + hr = RegKeyEnum(hkUninstallKey, dwIndex, &sczRelatedBundleId); + if (E_NOMOREITEMS == hr) + { + hr = S_OK; + break; + } + ExitOnFailure(hr, "Failed to enumerate uninstall key for related bundles."); + + // Ignore failures here since we'll often find products that aren't actually + // related bundles (or even bundles at all). + HRESULT hrRelatedBundle = QueryPotentialRelatedBundle(pQueryContext, hkUninstallKey, sczRelatedBundleId, &result); + if (SUCCEEDED(hrRelatedBundle) && BUNDLE_QUERY_CALLBACK_RESULT_CONTINUE != result) + { + ExitFunction1(hr = HRESULT_FROM_WIN32(ERROR_REQUEST_ABORTED)); + } + } + +LExit: + ReleaseStr(sczRelatedBundleId); + ReleaseRegKey(hkUninstallKey); + + return hr; +} + +static HRESULT QueryPotentialRelatedBundle( + __in BUNDLE_QUERY_CONTEXT* pQueryContext, + __in HKEY hkUninstallKey, + __in_z LPCWSTR wzRelatedBundleId, + __inout BUNDLE_QUERY_CALLBACK_RESULT* pResult + ) +{ + HRESULT hr = S_OK; + HKEY hkBundleId = NULL; + BUNDLE_RELATION_TYPE relationType = BUNDLE_RELATION_NONE; + BUNDLE_QUERY_RELATED_BUNDLE_RESULT bundle = { }; + + hr = RegOpenEx(hkUninstallKey, wzRelatedBundleId, KEY_READ, pQueryContext->regBitness, &hkBundleId); + ExitOnFailure(hr, "Failed to open uninstall key for potential related bundle: %ls", wzRelatedBundleId); + + hr = DetermineRelationType(pQueryContext, hkBundleId, &relationType); + if (FAILED(hr)) + { + ExitFunction(); + } + + bundle.installContext = pQueryContext->installContext; + bundle.regBitness = pQueryContext->regBitness; + bundle.wzBundleId = wzRelatedBundleId; + bundle.relationType = relationType; + bundle.hkBundle = hkBundleId; + + *pResult = pQueryContext->pfnCallback(&bundle, pQueryContext->pvContext); + +LExit: + ReleaseRegKey(hkBundleId); + + return hr; +} + +static HRESULT DetermineRelationType( + __in BUNDLE_QUERY_CONTEXT* pQueryContext, + __in HKEY hkBundleId, + __out BUNDLE_RELATION_TYPE* pRelationType + ) +{ + HRESULT hr = S_OK; + LPWSTR* rgsczUpgradeCodes = NULL; + DWORD cUpgradeCodes = 0; + STRINGDICT_HANDLE sdUpgradeCodes = NULL; + LPWSTR* rgsczAddonCodes = NULL; + DWORD cAddonCodes = 0; + STRINGDICT_HANDLE sdAddonCodes = NULL; + LPWSTR* rgsczDetectCodes = NULL; + DWORD cDetectCodes = 0; + STRINGDICT_HANDLE sdDetectCodes = NULL; + LPWSTR* rgsczPatchCodes = NULL; + DWORD cPatchCodes = 0; + STRINGDICT_HANDLE sdPatchCodes = NULL; + + *pRelationType = BUNDLE_RELATION_NONE; + + hr = RegReadStringArray(hkBundleId, BUNDLE_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE, &rgsczUpgradeCodes, &cUpgradeCodes); + if (HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE) == hr) + { + TraceError(hr, "Failed to read upgrade codes as REG_MULTI_SZ. Trying again as REG_SZ in case of older bundles."); + + rgsczUpgradeCodes = reinterpret_cast(MemAlloc(sizeof(LPWSTR), TRUE)); + ExitOnNull(rgsczUpgradeCodes, hr, E_OUTOFMEMORY, "Failed to allocate list for a single upgrade code from older bundle."); + + hr = RegReadString(hkBundleId, BUNDLE_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE, &rgsczUpgradeCodes[0]); + if (SUCCEEDED(hr)) + { + cUpgradeCodes = 1; + } + } + + // Compare upgrade codes. + if (SUCCEEDED(hr)) + { + hr = DictCreateStringListFromArray(&sdUpgradeCodes, rgsczUpgradeCodes, cUpgradeCodes, DICT_FLAG_CASEINSENSITIVE); + ExitOnFailure(hr, "Failed to create string dictionary for %hs.", "upgrade codes"); + + // Upgrade relationship: when their upgrade codes match our upgrade codes. + hr = DictCompareStringListToArray(sdUpgradeCodes, const_cast(pQueryContext->rgwzUpgradeCodes), pQueryContext->cUpgradeCodes); + if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) + { + hr = S_OK; + } + else + { + ExitOnFailure(hr, "Failed to do array search for upgrade code match."); + + *pRelationType = BUNDLE_RELATION_UPGRADE; + ExitFunction(); + } + + // Detect relationship: when their upgrade codes match our detect codes. + hr = DictCompareStringListToArray(sdUpgradeCodes, const_cast(pQueryContext->rgwzDetectCodes), pQueryContext->cDetectCodes); + if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) + { + hr = S_OK; + } + else + { + ExitOnFailure(hr, "Failed to do array search for detect code match."); + + *pRelationType = BUNDLE_RELATION_DETECT; + ExitFunction(); + } + + // Dependent relationship: when their upgrade codes match our addon codes. + hr = DictCompareStringListToArray(sdUpgradeCodes, const_cast(pQueryContext->rgwzAddonCodes), pQueryContext->cAddonCodes); + if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) + { + hr = S_OK; + } + else + { + ExitOnFailure(hr, "Failed to do array search for addon code match."); + + *pRelationType = BUNDLE_RELATION_DEPENDENT; + ExitFunction(); + } + + // Dependent relationship: when their upgrade codes match our patch codes. + hr = DictCompareStringListToArray(sdUpgradeCodes, const_cast(pQueryContext->rgwzPatchCodes), pQueryContext->cPatchCodes); + if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) + { + hr = S_OK; + } + else + { + ExitOnFailure(hr, "Failed to do array search for addon code match."); + + *pRelationType = BUNDLE_RELATION_DEPENDENT; + ExitFunction(); + } + + ReleaseNullDict(sdUpgradeCodes); + ReleaseNullStrArray(rgsczUpgradeCodes, cUpgradeCodes); + } + + // Compare addon codes. + hr = RegReadStringArray(hkBundleId, BUNDLE_REGISTRATION_REGISTRY_BUNDLE_ADDON_CODE, &rgsczAddonCodes, &cAddonCodes); + if (SUCCEEDED(hr)) + { + hr = DictCreateStringListFromArray(&sdAddonCodes, rgsczAddonCodes, cAddonCodes, DICT_FLAG_CASEINSENSITIVE); + ExitOnFailure(hr, "Failed to create string dictionary for %hs.", "addon codes"); + + // Addon relationship: when their addon codes match our detect codes. + hr = DictCompareStringListToArray(sdAddonCodes, const_cast(pQueryContext->rgwzDetectCodes), pQueryContext->cDetectCodes); + if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) + { + hr = S_OK; + } + else + { + ExitOnFailure(hr, "Failed to do array search for addon code match."); + + *pRelationType = BUNDLE_RELATION_ADDON; + ExitFunction(); + } + + // Addon relationship: when their addon codes match our upgrade codes. + hr = DictCompareStringListToArray(sdAddonCodes, const_cast(pQueryContext->rgwzUpgradeCodes), pQueryContext->cUpgradeCodes); + if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) + { + hr = S_OK; + } + else + { + ExitOnFailure(hr, "Failed to do array search for addon code match."); + + *pRelationType = BUNDLE_RELATION_ADDON; + ExitFunction(); + } + + ReleaseNullDict(sdAddonCodes); + ReleaseNullStrArray(rgsczAddonCodes, cAddonCodes); + } + + // Compare patch codes. + hr = RegReadStringArray(hkBundleId, BUNDLE_REGISTRATION_REGISTRY_BUNDLE_PATCH_CODE, &rgsczPatchCodes, &cPatchCodes); + if (SUCCEEDED(hr)) + { + hr = DictCreateStringListFromArray(&sdPatchCodes, rgsczPatchCodes, cPatchCodes, DICT_FLAG_CASEINSENSITIVE); + ExitOnFailure(hr, "Failed to create string dictionary for %hs.", "patch codes"); + + // Patch relationship: when their patch codes match our detect codes. + hr = DictCompareStringListToArray(sdPatchCodes, const_cast(pQueryContext->rgwzDetectCodes), pQueryContext->cDetectCodes); + if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) + { + hr = S_OK; + } + else + { + ExitOnFailure(hr, "Failed to do array search for patch code match."); + + *pRelationType = BUNDLE_RELATION_PATCH; + ExitFunction(); + } + + // Patch relationship: when their patch codes match our upgrade codes. + hr = DictCompareStringListToArray(sdPatchCodes, const_cast(pQueryContext->rgwzUpgradeCodes), pQueryContext->cUpgradeCodes); + if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) + { + hr = S_OK; + } + else + { + ExitOnFailure(hr, "Failed to do array search for patch code match."); + + *pRelationType = BUNDLE_RELATION_PATCH; + ExitFunction(); + } + + ReleaseNullDict(sdPatchCodes); + ReleaseNullStrArray(rgsczPatchCodes, cPatchCodes); + } + + // Compare detect codes. + hr = RegReadStringArray(hkBundleId, BUNDLE_REGISTRATION_REGISTRY_BUNDLE_DETECT_CODE, &rgsczDetectCodes, &cDetectCodes); + if (SUCCEEDED(hr)) + { + hr = DictCreateStringListFromArray(&sdDetectCodes, rgsczDetectCodes, cDetectCodes, DICT_FLAG_CASEINSENSITIVE); + ExitOnFailure(hr, "Failed to create string dictionary for %hs.", "detect codes"); + + // Detect relationship: when their detect codes match our detect codes. + hr = DictCompareStringListToArray(sdDetectCodes, const_cast(pQueryContext->rgwzDetectCodes), pQueryContext->cDetectCodes); + if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) + { + hr = S_OK; + } + else + { + ExitOnFailure(hr, "Failed to do array search for detect code match."); + + *pRelationType = BUNDLE_RELATION_DETECT; + ExitFunction(); + } + + // Dependent relationship: when their detect codes match our addon codes. + hr = DictCompareStringListToArray(sdDetectCodes, const_cast(pQueryContext->rgwzAddonCodes), pQueryContext->cAddonCodes); + if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) + { + hr = S_OK; + } + else + { + ExitOnFailure(hr, "Failed to do array search for addon code match."); + + *pRelationType = BUNDLE_RELATION_DEPENDENT; + ExitFunction(); + } + + // Dependent relationship: when their detect codes match our patch codes. + hr = DictCompareStringListToArray(sdDetectCodes, const_cast(pQueryContext->rgwzPatchCodes), pQueryContext->cPatchCodes); + if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr) + { + hr = S_OK; + } + else + { + ExitOnFailure(hr, "Failed to do array search for addon code match."); + + *pRelationType = BUNDLE_RELATION_DEPENDENT; + ExitFunction(); + } + + ReleaseNullDict(sdDetectCodes); + ReleaseNullStrArray(rgsczDetectCodes, cDetectCodes); + } + +LExit: + if (SUCCEEDED(hr) && BUNDLE_RELATION_NONE == *pRelationType) + { + hr = E_NOTFOUND; + } + + ReleaseDict(sdUpgradeCodes); + ReleaseStrArray(rgsczUpgradeCodes, cUpgradeCodes); + ReleaseDict(sdAddonCodes); + ReleaseStrArray(rgsczAddonCodes, cAddonCodes); + ReleaseDict(sdDetectCodes); + ReleaseStrArray(rgsczDetectCodes, cDetectCodes); + ReleaseDict(sdPatchCodes); + ReleaseStrArray(rgsczPatchCodes, cPatchCodes); + + return hr; +} + static HRESULT LocateAndQueryBundleValue( __in_z LPCWSTR wzBundleId, __in_opt LPCWSTR wzSubKey, diff --git a/src/libs/dutil/WixToolset.DUtil/inc/butil.h b/src/libs/dutil/WixToolset.DUtil/inc/butil.h index 9c2010ee..0d3eefe3 100644 --- a/src/libs/dutil/WixToolset.DUtil/inc/butil.h +++ b/src/libs/dutil/WixToolset.DUtil/inc/butil.h @@ -12,6 +12,37 @@ typedef enum _BUNDLE_INSTALL_CONTEXT BUNDLE_INSTALL_CONTEXT_USER, } BUNDLE_INSTALL_CONTEXT; +typedef enum _BUNDLE_QUERY_CALLBACK_RESULT +{ + BUNDLE_QUERY_CALLBACK_RESULT_CONTINUE, + BUNDLE_QUERY_CALLBACK_RESULT_CANCEL, +} BUNDLE_QUERY_CALLBACK_RESULT; + +typedef enum _BUNDLE_RELATION_TYPE +{ + BUNDLE_RELATION_NONE, + BUNDLE_RELATION_DETECT, + BUNDLE_RELATION_UPGRADE, + BUNDLE_RELATION_ADDON, + BUNDLE_RELATION_PATCH, + BUNDLE_RELATION_DEPENDENT, + BUNDLE_RELATION_UPDATE, +} BUNDLE_RELATION_TYPE; + +typedef struct _BUNDLE_QUERY_RELATED_BUNDLE_RESULT +{ + LPCWSTR wzBundleId; + BUNDLE_INSTALL_CONTEXT installContext; + REG_KEY_BITNESS regBitness; + HKEY hkBundle; + BUNDLE_RELATION_TYPE relationType; +} BUNDLE_QUERY_RELATED_BUNDLE_RESULT; + +typedef BUNDLE_QUERY_CALLBACK_RESULT(CALLBACK *PFNBUNDLE_QUERY_RELATED_BUNDLE_CALLBACK)( + __in const BUNDLE_QUERY_RELATED_BUNDLE_RESULT* pBundle, + __in_opt LPVOID pvContext + ); + /******************************************************************** BundleGetBundleInfo - Queries the bundle installation metadata for a given property, @@ -155,6 +186,24 @@ HRESULT DAPI BundleGetBundleVariableFixed( __inout SIZE_T* pcchValue ); +/******************************************************************** +BundleQueryRelatedBundles - Queries the bundle installation metadata for installs with the given detect, upgrade, addon, and patch codes. + Passes each related bundle to the callback function. +********************************************************************/ +HRESULT BundleQueryRelatedBundles( + __in BUNDLE_INSTALL_CONTEXT installContext, + __in_z_opt LPCWSTR* rgwzDetectCodes, + __in DWORD cDetectCodes, + __in_z_opt LPCWSTR* rgwzUpgradeCodes, + __in DWORD cUpgradeCodes, + __in_z_opt LPCWSTR* rgwzAddonCodes, + __in DWORD cAddonCodes, + __in_z_opt LPCWSTR* rgwzPatchCodes, + __in DWORD cPatchCodes, + __in PFNBUNDLE_QUERY_RELATED_BUNDLE_CALLBACK pfnCallback, + __in_opt LPVOID pvContext + ); + #ifdef __cplusplus } -- cgit v1.2.3-55-g6feb