aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/burn/engine/precomp.h1
-rw-r--r--src/burn/engine/relatedbundle.cpp390
-rw-r--r--src/burn/test/BurnUnitTest/BurnTestException.h11
-rw-r--r--src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj1
-rw-r--r--src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj.filters3
-rw-r--r--src/burn/test/BurnUnitTest/RelatedBundleTest.cpp199
-rw-r--r--src/burn/test/BurnUnitTest/precomp.h1
-rw-r--r--src/internal/WixBuildTools.TestSupport/SucceededException.cs1
-rw-r--r--src/libs/dutil/WixToolset.DUtil/butil.cpp480
-rw-r--r--src/libs/dutil/WixToolset.DUtil/inc/butil.h49
10 files changed, 731 insertions, 405 deletions
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 @@
55#include <atomutil.h> 55#include <atomutil.h>
56#include <apuputil.h> 56#include <apuputil.h>
57#include <dpiutil.h> 57#include <dpiutil.h>
58#include <butil.h>
58 59
59#include "BootstrapperEngine.h" 60#include "BootstrapperEngine.h"
60#include "BootstrapperApplication.h" 61#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 @@
2 2
3#include "precomp.h" 3#include "precomp.h"
4 4
5typedef struct _BUNDLE_QUERY_CONTEXT
6{
7 BURN_REGISTRATION* pRegistration;
8 BURN_RELATED_BUNDLES* pRelatedBundles;
9} BUNDLE_QUERY_CONTEXT;
10
5// internal function declarations 11// internal function declarations
6 12
7static __callback int __cdecl CompareRelatedBundles( 13static __callback int __cdecl CompareRelatedBundles(
@@ -9,25 +15,15 @@ static __callback int __cdecl CompareRelatedBundles(
9 __in const void* pvLeft, 15 __in const void* pvLeft,
10 __in const void* pvRight 16 __in const void* pvRight
11); 17);
12static HRESULT InitializeForScopeAndBitness( 18static BUNDLE_QUERY_CALLBACK_RESULT CALLBACK QueryRelatedBundlesCallback(
13 __in BOOL fPerMachine, 19 __in const BUNDLE_QUERY_RELATED_BUNDLE_RESULT* pBundle,
14 __in REG_KEY_BITNESS regBitness, 20 __in_opt LPVOID pvContext
15 __in BURN_REGISTRATION* pRegistration,
16 __in BURN_RELATED_BUNDLES* pRelatedBundles
17 ); 21 );
18static HRESULT LoadIfRelatedBundle( 22static HRESULT LoadIfRelatedBundle(
19 __in BOOL fPerMachine, 23 __in const BUNDLE_QUERY_RELATED_BUNDLE_RESULT* pBundle,
20 __in REG_KEY_BITNESS regBitness,
21 __in HKEY hkUninstallKey,
22 __in_z LPCWSTR sczRelatedBundleId,
23 __in BURN_REGISTRATION* pRegistration, 24 __in BURN_REGISTRATION* pRegistration,
24 __in BURN_RELATED_BUNDLES* pRelatedBundles 25 __in BURN_RELATED_BUNDLES* pRelatedBundles
25 ); 26 );
26static HRESULT DetermineRelationType(
27 __in HKEY hkBundleId,
28 __in BURN_REGISTRATION* pRegistration,
29 __out BOOTSTRAPPER_RELATION_TYPE* pRelationType
30 );
31static HRESULT LoadRelatedBundleFromKey( 27static HRESULT LoadRelatedBundleFromKey(
32 __in_z LPCWSTR wzRelatedBundleId, 28 __in_z LPCWSTR wzRelatedBundleId,
33 __in HKEY hkBundleId, 29 __in HKEY hkBundleId,
@@ -46,12 +42,25 @@ extern "C" HRESULT RelatedBundlesInitializeForScope(
46 ) 42 )
47{ 43{
48 HRESULT hr = S_OK; 44 HRESULT hr = S_OK;
49 45 BUNDLE_INSTALL_CONTEXT installContext = fPerMachine ? BUNDLE_INSTALL_CONTEXT_MACHINE : BUNDLE_INSTALL_CONTEXT_USER;
50 hr = InitializeForScopeAndBitness(fPerMachine, REG_KEY_32BIT, pRegistration, pRelatedBundles); 46 BUNDLE_QUERY_CONTEXT queryContext = { };
51 ExitOnFailure(hr, "Failed to open 32-bit uninstall registry key."); 47
52 48 queryContext.pRegistration = pRegistration;
53 hr = InitializeForScopeAndBitness(fPerMachine, REG_KEY_64BIT, pRegistration, pRelatedBundles); 49 queryContext.pRelatedBundles = pRelatedBundles;
54 ExitOnFailure(hr, "Failed to open 64-bit uninstall registry key."); 50
51 hr = BundleQueryRelatedBundles(
52 installContext,
53 const_cast<LPCWSTR*>(pRegistration->rgsczDetectCodes),
54 pRegistration->cDetectCodes,
55 const_cast<LPCWSTR*>(pRegistration->rgsczUpgradeCodes),
56 pRegistration->cUpgradeCodes,
57 const_cast<LPCWSTR*>(pRegistration->rgsczAddonCodes),
58 pRegistration->cAddonCodes,
59 const_cast<LPCWSTR*>(pRegistration->rgsczPatchCodes),
60 pRegistration->cPatchCodes,
61 QueryRelatedBundlesCallback,
62 &queryContext);
63 ExitOnFailure(hr, "Failed to initialize related bundles for scope.");
55 64
56LExit: 65LExit:
57 return hr; 66 return hr;
@@ -166,346 +175,53 @@ static __callback int __cdecl CompareRelatedBundles(
166 return ret; 175 return ret;
167} 176}
168 177
169static HRESULT InitializeForScopeAndBitness( 178static BUNDLE_QUERY_CALLBACK_RESULT CALLBACK QueryRelatedBundlesCallback(
170 __in BOOL fPerMachine, 179 __in const BUNDLE_QUERY_RELATED_BUNDLE_RESULT* pBundle,
171 __in REG_KEY_BITNESS regBitness, 180 __in_opt LPVOID pvContext
172 __in BURN_REGISTRATION * pRegistration, 181 )
173 __in BURN_RELATED_BUNDLES * pRelatedBundles
174)
175{ 182{
176 HRESULT hr = S_OK; 183 HRESULT hr = S_OK;
177 HKEY hkRoot = fPerMachine ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; 184 BUNDLE_QUERY_CALLBACK_RESULT result = BUNDLE_QUERY_CALLBACK_RESULT_CONTINUE;
178 HKEY hkUninstallKey = NULL; 185 BUNDLE_QUERY_CONTEXT* pContext = reinterpret_cast<BUNDLE_QUERY_CONTEXT*>(pvContext);
179 LPWSTR sczRelatedBundleId = NULL;
180 186
181 hr = RegOpenEx(hkRoot, BURN_REGISTRATION_REGISTRY_UNINSTALL_KEY, KEY_READ, regBitness, &hkUninstallKey); 187 hr = LoadIfRelatedBundle(pBundle, pContext->pRegistration, pContext->pRelatedBundles);
182 if (HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr || HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr) 188 ExitOnFailure(hr, "Failed to load related bundle: %ls", pBundle->wzBundleId);
183 {
184 ExitFunction1(hr = S_OK);
185 }
186 ExitOnFailure(hr, "Failed to open uninstall registry key.");
187
188 for (DWORD dwIndex = 0; /* exit via break below */; ++dwIndex)
189 {
190 hr = RegKeyEnum(hkUninstallKey, dwIndex, &sczRelatedBundleId);
191 if (E_NOMOREITEMS == hr)
192 {
193 hr = S_OK;
194 break;
195 }
196 ExitOnFailure(hr, "Failed to enumerate uninstall key for related bundles.");
197
198 // If we did not find our bundle id, try to load the subkey as a related bundle.
199 if (CSTR_EQUAL != ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, sczRelatedBundleId, -1, pRegistration->sczId, -1))
200 {
201 // Ignore failures here since we'll often find products that aren't actually
202 // related bundles (or even bundles at all).
203 HRESULT hrRelatedBundle = LoadIfRelatedBundle(fPerMachine, regBitness, hkUninstallKey, sczRelatedBundleId, pRegistration, pRelatedBundles);
204 UNREFERENCED_PARAMETER(hrRelatedBundle);
205 }
206 }
207 189
208LExit: 190LExit:
209 ReleaseStr(sczRelatedBundleId); 191 return result;
210 ReleaseRegKey(hkUninstallKey);
211
212 return hr;
213} 192}
214 193
215static HRESULT LoadIfRelatedBundle( 194static HRESULT LoadIfRelatedBundle(
216 __in BOOL fPerMachine, 195 __in const BUNDLE_QUERY_RELATED_BUNDLE_RESULT* pBundle,
217 __in REG_KEY_BITNESS regBitness,
218 __in HKEY hkUninstallKey,
219 __in_z LPCWSTR sczRelatedBundleId,
220 __in BURN_REGISTRATION* pRegistration, 196 __in BURN_REGISTRATION* pRegistration,
221 __in BURN_RELATED_BUNDLES* pRelatedBundles 197 __in BURN_RELATED_BUNDLES* pRelatedBundles
222 ) 198 )
223{ 199{
224 HRESULT hr = S_OK; 200 HRESULT hr = S_OK;
225 HKEY hkBundleId = NULL; 201 BOOL fPerMachine = BUNDLE_INSTALL_CONTEXT_MACHINE == pBundle->installContext;
226 BOOTSTRAPPER_RELATION_TYPE relationType = BOOTSTRAPPER_RELATION_NONE; 202 BOOTSTRAPPER_RELATION_TYPE relationType = (BOOTSTRAPPER_RELATION_TYPE)pBundle->relationType;
227 203 BURN_RELATED_BUNDLE* pRelatedBundle = NULL;
228 hr = RegOpenEx(hkUninstallKey, sczRelatedBundleId, KEY_READ, regBitness, &hkBundleId);
229 ExitOnFailure(hr, "Failed to open uninstall key for potential related bundle: %ls", sczRelatedBundleId);
230
231 hr = DetermineRelationType(hkBundleId, pRegistration, &relationType);
232 if (FAILED(hr) || BOOTSTRAPPER_RELATION_NONE == relationType)
233 {
234 // Must not be a related bundle.
235 hr = E_NOTFOUND;
236 }
237 else // load the related bundle.
238 {
239 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&pRelatedBundles->rgRelatedBundles), pRelatedBundles->cRelatedBundles + 1, sizeof(BURN_RELATED_BUNDLE), 5);
240 ExitOnFailure(hr, "Failed to ensure there is space for related bundles.");
241
242 BURN_RELATED_BUNDLE* pRelatedBundle = pRelatedBundles->rgRelatedBundles + pRelatedBundles->cRelatedBundles;
243
244 hr = LoadRelatedBundleFromKey(sczRelatedBundleId, hkBundleId, fPerMachine, relationType, pRelatedBundle);
245 ExitOnFailure(hr, "Failed to initialize package from related bundle id: %ls", sczRelatedBundleId);
246
247 hr = DependencyDetectRelatedBundle(pRelatedBundle, pRegistration);
248 ExitOnFailure(hr, "Failed to detect dependencies for related bundle.");
249
250 ++pRelatedBundles->cRelatedBundles;
251 }
252
253LExit:
254 ReleaseRegKey(hkBundleId);
255
256 return hr;
257}
258
259static HRESULT DetermineRelationType(
260 __in HKEY hkBundleId,
261 __in BURN_REGISTRATION* pRegistration,
262 __out BOOTSTRAPPER_RELATION_TYPE* pRelationType
263 )
264{
265 HRESULT hr = S_OK;
266 LPWSTR* rgsczUpgradeCodes = NULL;
267 DWORD cUpgradeCodes = 0;
268 STRINGDICT_HANDLE sdUpgradeCodes = NULL;
269 LPWSTR* rgsczAddonCodes = NULL;
270 DWORD cAddonCodes = 0;
271 STRINGDICT_HANDLE sdAddonCodes = NULL;
272 LPWSTR* rgsczDetectCodes = NULL;
273 DWORD cDetectCodes = 0;
274 STRINGDICT_HANDLE sdDetectCodes = NULL;
275 LPWSTR* rgsczPatchCodes = NULL;
276 DWORD cPatchCodes = 0;
277 STRINGDICT_HANDLE sdPatchCodes = NULL;
278
279 *pRelationType = BOOTSTRAPPER_RELATION_NONE;
280
281 // All remaining operations should treat all related bundles as non-vital.
282 hr = RegReadStringArray(hkBundleId, BURN_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE, &rgsczUpgradeCodes, &cUpgradeCodes);
283 if (HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE) == hr)
284 {
285 TraceError(hr, "Failed to read upgrade codes as REG_MULTI_SZ. Trying again as REG_SZ in case of older bundles.");
286
287 rgsczUpgradeCodes = reinterpret_cast<LPWSTR*>(MemAlloc(sizeof(LPWSTR), TRUE));
288 ExitOnNull(rgsczUpgradeCodes, hr, E_OUTOFMEMORY, "Failed to allocate list for a single upgrade code from older bundle.");
289
290 hr = RegReadString(hkBundleId, BURN_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE, &rgsczUpgradeCodes[0]);
291 if (SUCCEEDED(hr))
292 {
293 cUpgradeCodes = 1;
294 }
295 }
296
297 // Compare upgrade codes.
298 if (SUCCEEDED(hr))
299 {
300 hr = DictCreateStringListFromArray(&sdUpgradeCodes, rgsczUpgradeCodes, cUpgradeCodes, DICT_FLAG_CASEINSENSITIVE);
301 ExitOnFailure(hr, "Failed to create string dictionary for %hs.", "upgrade codes");
302
303 // Upgrade relationship: when their upgrade codes match our upgrade codes.
304 hr = DictCompareStringListToArray(sdUpgradeCodes, const_cast<LPCWSTR*>(pRegistration->rgsczUpgradeCodes), pRegistration->cUpgradeCodes);
305 if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr)
306 {
307 hr = S_OK;
308 }
309 else
310 {
311 ExitOnFailure(hr, "Failed to do array search for upgrade code match.");
312
313 *pRelationType = BOOTSTRAPPER_RELATION_UPGRADE;
314 ExitFunction();
315 }
316
317 // Detect relationship: when their upgrade codes match our detect codes.
318 hr = DictCompareStringListToArray(sdUpgradeCodes, const_cast<LPCWSTR*>(pRegistration->rgsczDetectCodes), pRegistration->cDetectCodes);
319 if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr)
320 {
321 hr = S_OK;
322 }
323 else
324 {
325 ExitOnFailure(hr, "Failed to do array search for detect code match.");
326
327 *pRelationType = BOOTSTRAPPER_RELATION_DETECT;
328 ExitFunction();
329 }
330
331 // Dependent relationship: when their upgrade codes match our addon codes.
332 hr = DictCompareStringListToArray(sdUpgradeCodes, const_cast<LPCWSTR*>(pRegistration->rgsczAddonCodes), pRegistration->cAddonCodes);
333 if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr)
334 {
335 hr = S_OK;
336 }
337 else
338 {
339 ExitOnFailure(hr, "Failed to do array search for addon code match.");
340
341 *pRelationType = BOOTSTRAPPER_RELATION_DEPENDENT;
342 ExitFunction();
343 }
344
345 // Dependent relationship: when their upgrade codes match our patch codes.
346 hr = DictCompareStringListToArray(sdUpgradeCodes, const_cast<LPCWSTR*>(pRegistration->rgsczPatchCodes), pRegistration->cPatchCodes);
347 if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr)
348 {
349 hr = S_OK;
350 }
351 else
352 {
353 ExitOnFailure(hr, "Failed to do array search for addon code match.");
354
355 *pRelationType = BOOTSTRAPPER_RELATION_DEPENDENT;
356 ExitFunction();
357 }
358
359 ReleaseNullDict(sdUpgradeCodes);
360 ReleaseNullStrArray(rgsczUpgradeCodes, cUpgradeCodes);
361 }
362
363 // Compare addon codes.
364 hr = RegReadStringArray(hkBundleId, BURN_REGISTRATION_REGISTRY_BUNDLE_ADDON_CODE, &rgsczAddonCodes, &cAddonCodes);
365 if (SUCCEEDED(hr))
366 {
367 hr = DictCreateStringListFromArray(&sdAddonCodes, rgsczAddonCodes, cAddonCodes, DICT_FLAG_CASEINSENSITIVE);
368 ExitOnFailure(hr, "Failed to create string dictionary for %hs.", "addon codes");
369
370 // Addon relationship: when their addon codes match our detect codes.
371 hr = DictCompareStringListToArray(sdAddonCodes, const_cast<LPCWSTR*>(pRegistration->rgsczDetectCodes), pRegistration->cDetectCodes);
372 if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr)
373 {
374 hr = S_OK;
375 }
376 else
377 {
378 ExitOnFailure(hr, "Failed to do array search for addon code match.");
379
380 *pRelationType = BOOTSTRAPPER_RELATION_ADDON;
381 ExitFunction();
382 }
383
384 // Addon relationship: when their addon codes match our upgrade codes.
385 hr = DictCompareStringListToArray(sdAddonCodes, const_cast<LPCWSTR*>(pRegistration->rgsczUpgradeCodes), pRegistration->cUpgradeCodes);
386 if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr)
387 {
388 hr = S_OK;
389 }
390 else
391 {
392 ExitOnFailure(hr, "Failed to do array search for addon code match.");
393
394 *pRelationType = BOOTSTRAPPER_RELATION_ADDON;
395 ExitFunction();
396 }
397
398 ReleaseNullDict(sdAddonCodes);
399 ReleaseNullStrArray(rgsczAddonCodes, cAddonCodes);
400 }
401 204
402 // Compare patch codes. 205 // If we found our bundle id, it's not a related bundle.
403 hr = RegReadStringArray(hkBundleId, BURN_REGISTRATION_REGISTRY_BUNDLE_PATCH_CODE, &rgsczPatchCodes, &cPatchCodes); 206 if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, pBundle->wzBundleId, -1, pRegistration->sczId, -1))
404 if (SUCCEEDED(hr))
405 { 207 {
406 hr = DictCreateStringListFromArray(&sdPatchCodes, rgsczPatchCodes, cPatchCodes, DICT_FLAG_CASEINSENSITIVE); 208 ExitFunction1(hr = S_FALSE);
407 ExitOnFailure(hr, "Failed to create string dictionary for %hs.", "patch codes");
408
409 // Patch relationship: when their patch codes match our detect codes.
410 hr = DictCompareStringListToArray(sdPatchCodes, const_cast<LPCWSTR*>(pRegistration->rgsczDetectCodes), pRegistration->cDetectCodes);
411 if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr)
412 {
413 hr = S_OK;
414 }
415 else
416 {
417 ExitOnFailure(hr, "Failed to do array search for patch code match.");
418
419 *pRelationType = BOOTSTRAPPER_RELATION_PATCH;
420 ExitFunction();
421 }
422
423 // Patch relationship: when their patch codes match our upgrade codes.
424 hr = DictCompareStringListToArray(sdPatchCodes, const_cast<LPCWSTR*>(pRegistration->rgsczUpgradeCodes), pRegistration->cUpgradeCodes);
425 if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr)
426 {
427 hr = S_OK;
428 }
429 else
430 {
431 ExitOnFailure(hr, "Failed to do array search for patch code match.");
432
433 *pRelationType = BOOTSTRAPPER_RELATION_PATCH;
434 ExitFunction();
435 }
436
437 ReleaseNullDict(sdPatchCodes);
438 ReleaseNullStrArray(rgsczPatchCodes, cPatchCodes);
439 } 209 }
440 210
441 // Compare detect codes. 211 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&pRelatedBundles->rgRelatedBundles), pRelatedBundles->cRelatedBundles + 1, sizeof(BURN_RELATED_BUNDLE), 5);
442 hr = RegReadStringArray(hkBundleId, BURN_REGISTRATION_REGISTRY_BUNDLE_DETECT_CODE, &rgsczDetectCodes, &cDetectCodes); 212 ExitOnFailure(hr, "Failed to ensure there is space for related bundles.");
443 if (SUCCEEDED(hr))
444 {
445 hr = DictCreateStringListFromArray(&sdDetectCodes, rgsczDetectCodes, cDetectCodes, DICT_FLAG_CASEINSENSITIVE);
446 ExitOnFailure(hr, "Failed to create string dictionary for %hs.", "detect codes");
447 213
448 // Detect relationship: when their detect codes match our detect codes. 214 pRelatedBundle = pRelatedBundles->rgRelatedBundles + pRelatedBundles->cRelatedBundles;
449 hr = DictCompareStringListToArray(sdDetectCodes, const_cast<LPCWSTR*>(pRegistration->rgsczDetectCodes), pRegistration->cDetectCodes);
450 if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr)
451 {
452 hr = S_OK;
453 }
454 else
455 {
456 ExitOnFailure(hr, "Failed to do array search for detect code match.");
457 215
458 *pRelationType = BOOTSTRAPPER_RELATION_DETECT; 216 hr = LoadRelatedBundleFromKey(pBundle->wzBundleId, pBundle->hkBundle, fPerMachine, relationType, pRelatedBundle);
459 ExitFunction(); 217 ExitOnFailure(hr, "Failed to initialize package from related bundle id: %ls", pBundle->wzBundleId);
460 }
461 218
462 // Dependent relationship: when their detect codes match our addon codes. 219 hr = DependencyDetectRelatedBundle(pRelatedBundle, pRegistration);
463 hr = DictCompareStringListToArray(sdDetectCodes, const_cast<LPCWSTR*>(pRegistration->rgsczAddonCodes), pRegistration->cAddonCodes); 220 ExitOnFailure(hr, "Failed to detect dependencies for related bundle.");
464 if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr)
465 {
466 hr = S_OK;
467 }
468 else
469 {
470 ExitOnFailure(hr, "Failed to do array search for addon code match.");
471
472 *pRelationType = BOOTSTRAPPER_RELATION_DEPENDENT;
473 ExitFunction();
474 }
475
476 // Dependent relationship: when their detect codes match our patch codes.
477 hr = DictCompareStringListToArray(sdDetectCodes, const_cast<LPCWSTR*>(pRegistration->rgsczPatchCodes), pRegistration->cPatchCodes);
478 if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr)
479 {
480 hr = S_OK;
481 }
482 else
483 {
484 ExitOnFailure(hr, "Failed to do array search for addon code match.");
485 221
486 *pRelationType = BOOTSTRAPPER_RELATION_DEPENDENT; 222 ++pRelatedBundles->cRelatedBundles;
487 ExitFunction();
488 }
489
490 ReleaseNullDict(sdDetectCodes);
491 ReleaseNullStrArray(rgsczDetectCodes, cDetectCodes);
492 }
493 223
494LExit: 224LExit:
495 if (SUCCEEDED(hr) && BOOTSTRAPPER_RELATION_NONE == *pRelationType)
496 {
497 hr = E_NOTFOUND;
498 }
499
500 ReleaseDict(sdUpgradeCodes);
501 ReleaseStrArray(rgsczUpgradeCodes, cUpgradeCodes);
502 ReleaseDict(sdAddonCodes);
503 ReleaseStrArray(rgsczAddonCodes, cAddonCodes);
504 ReleaseDict(sdDetectCodes);
505 ReleaseStrArray(rgsczDetectCodes, cDetectCodes);
506 ReleaseDict(sdPatchCodes);
507 ReleaseStrArray(rgsczPatchCodes, cPatchCodes);
508
509 return hr; 225 return hr;
510} 226}
511 227
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
13namespace Bootstrapper 13namespace Bootstrapper
14{ 14{
15 using namespace System; 15 using namespace System;
16 using namespace WixBuildTools::TestSupport;
16 17
17 public ref struct BurnTestException : public System::Exception 18 public ref struct BurnTestException : public SucceededException
18 { 19 {
19 public: 20 public:
20 BurnTestException(HRESULT error)
21 {
22 this->HResult = error;
23 }
24
25 BurnTestException(HRESULT error, String^ message) 21 BurnTestException(HRESULT error, String^ message)
26 : Exception(message) 22 : SucceededException(error, message)
27 { 23 {
28 this->HResult = error;
29 } 24 }
30 25
31 property Int32 ErrorCode 26 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 @@
56 <DisableSpecificWarnings>4564;4691</DisableSpecificWarnings> 56 <DisableSpecificWarnings>4564;4691</DisableSpecificWarnings>
57 </ClCompile> 57 </ClCompile>
58 <ClCompile Include="RegistrationTest.cpp" /> 58 <ClCompile Include="RegistrationTest.cpp" />
59 <ClCompile Include="RelatedBundleTest.cpp" />
59 <ClCompile Include="SearchTest.cpp" /> 60 <ClCompile Include="SearchTest.cpp" />
60 <ClCompile Include="TestRegistryFixture.cpp" /> 61 <ClCompile Include="TestRegistryFixture.cpp" />
61 <ClCompile Include="VariableHelpers.cpp" /> 62 <ClCompile Include="VariableHelpers.cpp" />
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 @@
39 <ClCompile Include="RegistrationTest.cpp"> 39 <ClCompile Include="RegistrationTest.cpp">
40 <Filter>Source Files</Filter> 40 <Filter>Source Files</Filter>
41 </ClCompile> 41 </ClCompile>
42 <ClCompile Include="RelatedBundleTest.cpp">
43 <Filter>Source Files</Filter>
44 </ClCompile>
42 <ClCompile Include="SearchTest.cpp"> 45 <ClCompile Include="SearchTest.cpp">
43 <Filter>Source Files</Filter> 46 <Filter>Source Files</Filter>
44 </ClCompile> 47 </ClCompile>
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 @@
1// 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.
2
3#include "precomp.h"
4
5
6namespace Microsoft
7{
8namespace Tools
9{
10namespace WindowsInstallerXml
11{
12namespace Test
13{
14namespace Bootstrapper
15{
16 using namespace System;
17 using namespace System::IO;
18 using namespace Xunit;
19 using namespace WixBuildTools::TestSupport;
20
21 public ref class RelatedBundleTest : BurnUnitTest, IClassFixture<TestRegistryFixture^>
22 {
23 private:
24 TestRegistryFixture^ testRegistry;
25 public:
26 RelatedBundleTest(BurnTestFixture^ fixture, TestRegistryFixture^ registryFixture) : BurnUnitTest(fixture)
27 {
28 this->testRegistry = registryFixture;
29 }
30
31 [Fact]
32 void RelatedBundleDetectPerMachineTest()
33 {
34 HRESULT hr = S_OK;
35 IXMLDOMElement* pixeBundle = NULL;
36 BURN_REGISTRATION registration = { };
37 BURN_RELATED_BUNDLES relatedBundles = { };
38 BURN_CACHE cache = { };
39 BURN_ENGINE_COMMAND internalCommand = { };
40
41 try
42 {
43 this->testRegistry->SetUp();
44 this->RegisterFakeBundles();
45
46 LPCWSTR wzDocument =
47 L"<Bundle>"
48 L" <UX>"
49 L" <Payload Id='ux.dll' FilePath='ux.dll' Packaging='embedded' SourcePath='ux.dll' />"
50 L" </UX>"
51 L" <RelatedBundle Id='{89FDAE1F-8CC1-48B9-B930-3945E0D3E7F0}' Action='Upgrade' />"
52 L" <Registration Id='{D54F896D-1952-43E6-9C67-B5652240618C}' Tag='foo' ProviderKey='foo' Version='1.0.0.0' ExecutableName='setup.exe' PerMachine='yes'>"
53 L" <Arp Register='yes' Publisher='WiX Toolset' DisplayName='RegisterBasicTest' DisplayVersion='1.0.0.0' />"
54 L" </Registration>"
55 L"</Bundle>";
56
57 // load XML document
58 LoadBundleXmlHelper(wzDocument, &pixeBundle);
59
60 hr = CacheInitialize(&cache, &internalCommand);
61 TestThrowOnFailure(hr, L"Failed initialize cache.");
62
63 hr = RegistrationParseFromXml(&registration, &cache, pixeBundle);
64 TestThrowOnFailure(hr, L"Failed to parse registration from XML.");
65
66 RelatedBundlesInitializeForScope(registration.fPerMachine, &registration, &relatedBundles);
67
68 Assert::Equal(1lu, relatedBundles.cRelatedBundles);
69
70 BURN_RELATED_BUNDLE* pRelatedBundle = relatedBundles.rgRelatedBundles + 0;
71 NativeAssert::StringEqual(L"{AD75BE46-B5D7-4208-BC8B-918553C72D83}", pRelatedBundle->package.sczId);
72 //{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.
73 }
74 finally
75 {
76 ReleaseObject(pixeBundle);
77 RegistrationUninitialize(&registration);
78
79 this->testRegistry->TearDown();
80 }
81 }
82
83 [Fact]
84 void RelatedBundleDetectPerUserTest()
85 {
86 HRESULT hr = S_OK;
87 IXMLDOMElement* pixeBundle = NULL;
88 BURN_REGISTRATION registration = { };
89 BURN_RELATED_BUNDLES relatedBundles = { };
90 BURN_CACHE cache = { };
91 BURN_ENGINE_COMMAND internalCommand = { };
92
93 try
94 {
95 this->testRegistry->SetUp();
96 this->RegisterFakeBundles();
97
98 LPCWSTR wzDocument =
99 L"<Bundle>"
100 L" <UX>"
101 L" <Payload Id='ux.dll' FilePath='ux.dll' Packaging='embedded' SourcePath='ux.dll' />"
102 L" </UX>"
103 L" <RelatedBundle Id='{89FDAE1F-8CC1-48B9-B930-3945E0D3E7F0}' Action='Upgrade' />"
104 L" <Registration Id='{3DB49D3D-1FB8-4147-A465-BBE8BFD0DAD0}' Tag='foo' ProviderKey='foo' Version='4.0.0.0' ExecutableName='setup.exe' PerMachine='no'>"
105 L" <Arp Register='yes' Publisher='WiX Toolset' DisplayName='RegisterBasicTest' DisplayVersion='4.0.0.0' />"
106 L" </Registration>"
107 L"</Bundle>";
108
109 // load XML document
110 LoadBundleXmlHelper(wzDocument, &pixeBundle);
111
112 hr = CacheInitialize(&cache, &internalCommand);
113 TestThrowOnFailure(hr, L"Failed initialize cache.");
114
115 hr = RegistrationParseFromXml(&registration, &cache, pixeBundle);
116 TestThrowOnFailure(hr, L"Failed to parse registration from XML.");
117
118 RelatedBundlesInitializeForScope(registration.fPerMachine, &registration, &relatedBundles);
119
120 Assert::Equal(1lu, relatedBundles.cRelatedBundles);
121
122 BURN_RELATED_BUNDLE* pRelatedBundle = relatedBundles.rgRelatedBundles + 0;
123 NativeAssert::StringEqual(L"{6DB5D48C-CD7D-40D2-BCBC-AF630E136761}", pRelatedBundle->package.sczId);
124 //{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.
125 }
126 finally
127 {
128 ReleaseObject(pixeBundle);
129 RegistrationUninitialize(&registration);
130
131 this->testRegistry->TearDown();
132 }
133 }
134
135 void RegisterFakeBundles()
136 {
137 this->RegisterFakeBundle(L"{D54F896D-1952-43E6-9C67-B5652240618C}", L"{89FDAE1F-8CC1-48B9-B930-3945E0D3E7F0}", NULL, L"1.0.0.0", TRUE);
138 this->RegisterFakeBundle(L"{E2355133-384C-4332-9B62-1FA950D707B7}", L"{89FDAE1F-8CC1-48B9-B930-3945E0D3E7F0}", L"", L"1.1.0.0", TRUE);
139 this->RegisterFakeBundle(L"{AD75BE46-B5D7-4208-BC8B-918553C72D83}", L"{89FDAE1F-8CC1-48B9-B930-3945E0D3E7F0}", NULL, L"2.0.0.0", TRUE);
140 this->RegisterFakeBundle(L"{6DB5D48C-CD7D-40D2-BCBC-AF630E136761}", L"{89FDAE1F-8CC1-48B9-B930-3945E0D3E7F0}", NULL, L"3.0.0.0", FALSE);
141 this->RegisterFakeBundle(L"{42D16EBE-8B6B-4A9A-9AE9-5300F30011AA}", L"{89FDAE1F-8CC1-48B9-B930-3945E0D3E7F0}", L"", L"3.1.0.0", FALSE);
142 this->RegisterFakeBundle(L"{3DB49D3D-1FB8-4147-A465-BBE8BFD0DAD0}", L"{89FDAE1F-8CC1-48B9-B930-3945E0D3E7F0}", NULL, L"4.0.0.0", FALSE);
143 }
144
145 void RegisterFakeBundle(LPCWSTR wzBundleId, LPCWSTR wzUpgradeCodes, LPCWSTR wzCachePath, LPCWSTR wzVersion, BOOL fPerMachine)
146 {
147 HRESULT hr = S_OK;
148 LPWSTR* rgsczUpgradeCodes = NULL;
149 DWORD cUpgradeCodes = 0;
150 LPWSTR sczRegistrationKey = NULL;
151 LPWSTR sczCachePath = NULL;
152 HKEY hkRegistration = NULL;
153 HKEY hkRoot = fPerMachine ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
154
155 try
156 {
157 hr = StrSplitAllocArray(&rgsczUpgradeCodes, reinterpret_cast<UINT*>(&cUpgradeCodes), wzUpgradeCodes, L";");
158 NativeAssert::Succeeded(hr, "Failed to split upgrade codes.");
159
160 hr = StrAllocFormatted(&sczRegistrationKey, L"%s\\%s", BURN_REGISTRATION_REGISTRY_UNINSTALL_KEY, wzBundleId);
161 NativeAssert::Succeeded(hr, "Failed to build uninstall registry key path.");
162
163 if (!wzCachePath)
164 {
165 hr = StrAllocFormatted(&sczCachePath, L"%ls.exe", wzBundleId);
166 NativeAssert::Succeeded(hr, "Failed to build cache path.");
167
168 wzCachePath = sczCachePath;
169 }
170
171 hr = RegCreate(hkRoot, sczRegistrationKey, KEY_WRITE, &hkRegistration);
172 NativeAssert::Succeeded(hr, "Failed to create registration key.");
173
174 hr = RegWriteStringArray(hkRegistration, BURN_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE, rgsczUpgradeCodes, cUpgradeCodes);
175 NativeAssert::Succeeded(hr, "Failed to write %ls value.", BURN_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE);
176
177 if (wzCachePath && *wzCachePath)
178 {
179 hr = RegWriteString(hkRegistration, BURN_REGISTRATION_REGISTRY_BUNDLE_CACHE_PATH, wzCachePath);
180 NativeAssert::Succeeded(hr, "Failed to write %ls value.", BURN_REGISTRATION_REGISTRY_BUNDLE_CACHE_PATH);
181 }
182
183 hr = RegWriteString(hkRegistration, BURN_REGISTRATION_REGISTRY_BUNDLE_VERSION, wzVersion);
184 NativeAssert::Succeeded(hr, "Failed to write %ls value.", BURN_REGISTRATION_REGISTRY_BUNDLE_VERSION);
185 }
186 finally
187 {
188 ReleaseStrArray(rgsczUpgradeCodes, cUpgradeCodes);
189 ReleaseStr(sczRegistrationKey);
190 ReleaseStr(sczCachePath);
191 ReleaseRegKey(hkRegistration);
192 }
193 }
194 };
195}
196}
197}
198}
199}
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 @@
53#include "update.h" 53#include "update.h"
54#include "pseudobundle.h" 54#include "pseudobundle.h"
55#include "registration.h" 55#include "registration.h"
56#include "relatedbundle.h"
56#include "plan.h" 57#include "plan.h"
57#include "pipe.h" 58#include "pipe.h"
58#include "logging.h" 59#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
13 "Message: {1}", 13 "Message: {1}",
14 hr, userMessage)) 14 hr, userMessage))
15 { 15 {
16 this.HResult = hr;
16 } 17 }
17 } 18 }
18} 19}
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 @@
19// constants 19// constants
20// From engine/registration.h 20// From engine/registration.h
21const LPCWSTR BUNDLE_REGISTRATION_REGISTRY_UNINSTALL_KEY = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"; 21const LPCWSTR BUNDLE_REGISTRATION_REGISTRY_UNINSTALL_KEY = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall";
22const LPCWSTR BUNDLE_REGISTRATION_REGISTRY_BUNDLE_ADDON_CODE = L"BundleAddonCode";
23const LPCWSTR BUNDLE_REGISTRATION_REGISTRY_BUNDLE_DETECT_CODE = L"BundleDetectCode";
24const LPCWSTR BUNDLE_REGISTRATION_REGISTRY_BUNDLE_PATCH_CODE = L"BundlePatchCode";
22const LPCWSTR BUNDLE_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE = L"BundleUpgradeCode"; 25const LPCWSTR BUNDLE_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE = L"BundleUpgradeCode";
23const LPCWSTR BUNDLE_REGISTRATION_REGISTRY_BUNDLE_PROVIDER_KEY = L"BundleProviderKey"; 26const LPCWSTR BUNDLE_REGISTRATION_REGISTRY_BUNDLE_PROVIDER_KEY = L"BundleProviderKey";
24const LPCWSTR BUNDLE_REGISTRATION_REGISTRY_BUNDLE_VARIABLE_KEY = L"variables"; 27const LPCWSTR BUNDLE_REGISTRATION_REGISTRY_BUNDLE_VARIABLE_KEY = L"variables";
@@ -30,7 +33,41 @@ enum INTERNAL_BUNDLE_STATUS
30 INTERNAL_BUNDLE_STATUS_UNKNOWN_PROPERTY, 33 INTERNAL_BUNDLE_STATUS_UNKNOWN_PROPERTY,
31}; 34};
32 35
36typedef struct _BUNDLE_QUERY_CONTEXT
37{
38 BUNDLE_INSTALL_CONTEXT installContext;
39 REG_KEY_BITNESS regBitness;
40 PFNBUNDLE_QUERY_RELATED_BUNDLE_CALLBACK pfnCallback;
41 LPVOID pvContext;
42
43 LPCWSTR* rgwzDetectCodes;
44 DWORD cDetectCodes;
45
46 LPCWSTR* rgwzUpgradeCodes;
47 DWORD cUpgradeCodes;
48
49 LPCWSTR* rgwzAddonCodes;
50 DWORD cAddonCodes;
51
52 LPCWSTR* rgwzPatchCodes;
53 DWORD cPatchCodes;
54} BUNDLE_QUERY_CONTEXT;
55
33// Forward declarations. 56// Forward declarations.
57static HRESULT QueryRelatedBundlesForScopeAndBitness(
58 __in BUNDLE_QUERY_CONTEXT* pQueryContext
59 );
60static HRESULT QueryPotentialRelatedBundle(
61 __in BUNDLE_QUERY_CONTEXT* pQueryContext,
62 __in HKEY hkUninstallKey,
63 __in_z LPCWSTR wzRelatedBundleId,
64 __inout BUNDLE_QUERY_CALLBACK_RESULT* pResult
65 );
66static HRESULT DetermineRelationType(
67 __in BUNDLE_QUERY_CONTEXT* pQueryContext,
68 __in HKEY hkBundleId,
69 __out BUNDLE_RELATION_TYPE* pRelationType
70 );
34/******************************************************************** 71/********************************************************************
35LocateAndQueryBundleValue - Locates the requested key for the bundle, 72LocateAndQueryBundleValue - Locates the requested key for the bundle,
36 then queries the registry type for requested value. 73 then queries the registry type for requested value.
@@ -160,11 +197,13 @@ DAPI_(HRESULT) BundleEnumRelatedBundle(
160 HKEY hkBundle = NULL; 197 HKEY hkBundle = NULL;
161 LPWSTR sczUninstallSubKey = NULL; 198 LPWSTR sczUninstallSubKey = NULL;
162 LPWSTR sczUninstallSubKeyPath = NULL; 199 LPWSTR sczUninstallSubKeyPath = NULL;
163 LPWSTR sczValue = NULL;
164 DWORD dwType = 0;
165 LPWSTR* rgsczBundleUpgradeCodes = NULL;
166 DWORD cBundleUpgradeCodes = 0;
167 HKEY hkRoot = BUNDLE_INSTALL_CONTEXT_USER == context ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE; 200 HKEY hkRoot = BUNDLE_INSTALL_CONTEXT_USER == context ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE;
201 BUNDLE_QUERY_CONTEXT queryContext = { };
202 BUNDLE_RELATION_TYPE relationType = BUNDLE_RELATION_NONE;
203
204 queryContext.installContext = context;
205 queryContext.rgwzUpgradeCodes = &wzUpgradeCode;
206 queryContext.cUpgradeCodes = 1;
168 207
169 if (!wzUpgradeCode || !pdwStartIndex) 208 if (!wzUpgradeCode || !pdwStartIndex)
170 { 209 {
@@ -185,60 +224,12 @@ DAPI_(HRESULT) BundleEnumRelatedBundle(
185 hr = RegOpenEx(hkRoot, sczUninstallSubKeyPath, KEY_READ, kbKeyBitness, &hkBundle); 224 hr = RegOpenEx(hkRoot, sczUninstallSubKeyPath, KEY_READ, kbKeyBitness, &hkBundle);
186 ButilExitOnFailure(hr, "Failed to open uninstall key path."); 225 ButilExitOnFailure(hr, "Failed to open uninstall key path.");
187 226
188 // If it's a bundle, it should have a BundleUpgradeCode value of type REG_SZ (old) or REG_MULTI_SZ 227 hr = DetermineRelationType(&queryContext, hkBundle, &relationType);
189 hr = RegGetType(hkBundle, BUNDLE_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE, &dwType); 228 if (SUCCEEDED(hr) && BUNDLE_RELATION_UPGRADE == relationType)
190 if (FAILED(hr))
191 { 229 {
192 ReleaseRegKey(hkBundle); 230 fUpgradeCodeFound = TRUE;
193 ReleaseNullStr(sczUninstallSubKey); 231 *pdwStartIndex = dwIndex;
194 ReleaseNullStr(sczUninstallSubKeyPath);
195 // Not a bundle
196 continue;
197 }
198
199 switch (dwType)
200 {
201 case REG_SZ:
202 hr = RegReadString(hkBundle, BUNDLE_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE, &sczValue);
203 ButilExitOnFailure(hr, "Failed to read BundleUpgradeCode string property.");
204
205 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, sczValue, -1, wzUpgradeCode, -1))
206 {
207 *pdwStartIndex = dwIndex;
208 fUpgradeCodeFound = TRUE;
209 break;
210 }
211
212 ReleaseNullStr(sczValue);
213
214 break;
215 case REG_MULTI_SZ:
216 hr = RegReadStringArray(hkBundle, BUNDLE_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE, &rgsczBundleUpgradeCodes, &cBundleUpgradeCodes);
217 ButilExitOnFailure(hr, "Failed to read BundleUpgradeCode multi-string property.");
218 232
219 for (DWORD i = 0; i < cBundleUpgradeCodes; i++)
220 {
221 LPWSTR wzBundleUpgradeCode = rgsczBundleUpgradeCodes[i];
222 if (wzBundleUpgradeCode && *wzBundleUpgradeCode)
223 {
224 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, wzBundleUpgradeCode, -1, wzUpgradeCode, -1))
225 {
226 *pdwStartIndex = dwIndex;
227 fUpgradeCodeFound = TRUE;
228 break;
229 }
230 }
231 }
232 ReleaseNullStrArray(rgsczBundleUpgradeCodes, cBundleUpgradeCodes);
233
234 break;
235
236 default:
237 ButilExitWithRootFailure(hr, E_NOTIMPL, "BundleUpgradeCode of type 0x%x not implemented.", dwType);
238 }
239
240 if (fUpgradeCodeFound)
241 {
242 if (psczBundleId) 233 if (psczBundleId)
243 { 234 {
244 *psczBundleId = sczUninstallSubKey; 235 *psczBundleId = sczUninstallSubKey;
@@ -250,17 +241,13 @@ DAPI_(HRESULT) BundleEnumRelatedBundle(
250 241
251 // Cleanup before next iteration 242 // Cleanup before next iteration
252 ReleaseRegKey(hkBundle); 243 ReleaseRegKey(hkBundle);
253 ReleaseNullStr(sczUninstallSubKey);
254 ReleaseNullStr(sczUninstallSubKeyPath);
255 } 244 }
256 245
257LExit: 246LExit:
258 ReleaseStr(sczValue);
259 ReleaseStr(sczUninstallSubKey); 247 ReleaseStr(sczUninstallSubKey);
260 ReleaseStr(sczUninstallSubKeyPath); 248 ReleaseStr(sczUninstallSubKeyPath);
261 ReleaseRegKey(hkBundle); 249 ReleaseRegKey(hkBundle);
262 ReleaseRegKey(hkUninstall); 250 ReleaseRegKey(hkUninstall);
263 ReleaseStrArray(rgsczBundleUpgradeCodes, cBundleUpgradeCodes);
264 251
265 return FAILED(hr) ? hr : fUpgradeCodeFound ? S_OK : S_FALSE; 252 return FAILED(hr) ? hr : fUpgradeCodeFound ? S_OK : S_FALSE;
266} 253}
@@ -370,6 +357,379 @@ LExit:
370 return hr; 357 return hr;
371} 358}
372 359
360DAPI_(HRESULT) BundleQueryRelatedBundles(
361 __in BUNDLE_INSTALL_CONTEXT installContext,
362 __in_z_opt LPCWSTR* rgwzDetectCodes,
363 __in DWORD cDetectCodes,
364 __in_z_opt LPCWSTR* rgwzUpgradeCodes,
365 __in DWORD cUpgradeCodes,
366 __in_z_opt LPCWSTR* rgwzAddonCodes,
367 __in DWORD cAddonCodes,
368 __in_z_opt LPCWSTR* rgwzPatchCodes,
369 __in DWORD cPatchCodes,
370 __in PFNBUNDLE_QUERY_RELATED_BUNDLE_CALLBACK pfnCallback,
371 __in_opt LPVOID pvContext
372 )
373{
374 HRESULT hr = S_OK;
375 BUNDLE_QUERY_CONTEXT queryContext = { };
376
377 queryContext.installContext = installContext;
378 queryContext.rgwzDetectCodes = rgwzDetectCodes;
379 queryContext.cDetectCodes = cDetectCodes;
380 queryContext.rgwzUpgradeCodes = rgwzUpgradeCodes;
381 queryContext.cUpgradeCodes = cUpgradeCodes;
382 queryContext.rgwzAddonCodes = rgwzAddonCodes;
383 queryContext.cAddonCodes = cAddonCodes;
384 queryContext.rgwzPatchCodes = rgwzPatchCodes;
385 queryContext.cPatchCodes = cPatchCodes;
386 queryContext.pfnCallback = pfnCallback;
387 queryContext.pvContext = pvContext;
388
389 queryContext.regBitness = REG_KEY_32BIT;
390
391 hr = QueryRelatedBundlesForScopeAndBitness(&queryContext);
392 ExitOnFailure(hr, "Failed to query 32-bit related bundles.");
393
394 queryContext.regBitness = REG_KEY_64BIT;
395
396 hr = QueryRelatedBundlesForScopeAndBitness(&queryContext);
397 ExitOnFailure(hr, "Failed to query 64-bit related bundles.");
398
399LExit:
400 return hr;
401}
402
403static HRESULT QueryRelatedBundlesForScopeAndBitness(
404 __in BUNDLE_QUERY_CONTEXT* pQueryContext
405 )
406{
407 HRESULT hr = S_OK;
408 HKEY hkRoot = BUNDLE_INSTALL_CONTEXT_USER == pQueryContext->installContext ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE;
409 HKEY hkUninstallKey = NULL;
410 LPWSTR sczRelatedBundleId = NULL;
411 BUNDLE_QUERY_CALLBACK_RESULT result = BUNDLE_QUERY_CALLBACK_RESULT_CONTINUE;
412
413 hr = RegOpenEx(hkRoot, BUNDLE_REGISTRATION_REGISTRY_UNINSTALL_KEY, KEY_READ, pQueryContext->regBitness, &hkUninstallKey);
414 if (HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr || HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr)
415 {
416 ExitFunction1(hr = S_OK);
417 }
418 ExitOnFailure(hr, "Failed to open uninstall registry key.");
419
420 for (DWORD dwIndex = 0; /* exit via break below */; ++dwIndex)
421 {
422 hr = RegKeyEnum(hkUninstallKey, dwIndex, &sczRelatedBundleId);
423 if (E_NOMOREITEMS == hr)
424 {
425 hr = S_OK;
426 break;
427 }
428 ExitOnFailure(hr, "Failed to enumerate uninstall key for related bundles.");
429
430 // Ignore failures here since we'll often find products that aren't actually
431 // related bundles (or even bundles at all).
432 HRESULT hrRelatedBundle = QueryPotentialRelatedBundle(pQueryContext, hkUninstallKey, sczRelatedBundleId, &result);
433 if (SUCCEEDED(hrRelatedBundle) && BUNDLE_QUERY_CALLBACK_RESULT_CONTINUE != result)
434 {
435 ExitFunction1(hr = HRESULT_FROM_WIN32(ERROR_REQUEST_ABORTED));
436 }
437 }
438
439LExit:
440 ReleaseStr(sczRelatedBundleId);
441 ReleaseRegKey(hkUninstallKey);
442
443 return hr;
444}
445
446static HRESULT QueryPotentialRelatedBundle(
447 __in BUNDLE_QUERY_CONTEXT* pQueryContext,
448 __in HKEY hkUninstallKey,
449 __in_z LPCWSTR wzRelatedBundleId,
450 __inout BUNDLE_QUERY_CALLBACK_RESULT* pResult
451 )
452{
453 HRESULT hr = S_OK;
454 HKEY hkBundleId = NULL;
455 BUNDLE_RELATION_TYPE relationType = BUNDLE_RELATION_NONE;
456 BUNDLE_QUERY_RELATED_BUNDLE_RESULT bundle = { };
457
458 hr = RegOpenEx(hkUninstallKey, wzRelatedBundleId, KEY_READ, pQueryContext->regBitness, &hkBundleId);
459 ExitOnFailure(hr, "Failed to open uninstall key for potential related bundle: %ls", wzRelatedBundleId);
460
461 hr = DetermineRelationType(pQueryContext, hkBundleId, &relationType);
462 if (FAILED(hr))
463 {
464 ExitFunction();
465 }
466
467 bundle.installContext = pQueryContext->installContext;
468 bundle.regBitness = pQueryContext->regBitness;
469 bundle.wzBundleId = wzRelatedBundleId;
470 bundle.relationType = relationType;
471 bundle.hkBundle = hkBundleId;
472
473 *pResult = pQueryContext->pfnCallback(&bundle, pQueryContext->pvContext);
474
475LExit:
476 ReleaseRegKey(hkBundleId);
477
478 return hr;
479}
480
481static HRESULT DetermineRelationType(
482 __in BUNDLE_QUERY_CONTEXT* pQueryContext,
483 __in HKEY hkBundleId,
484 __out BUNDLE_RELATION_TYPE* pRelationType
485 )
486{
487 HRESULT hr = S_OK;
488 LPWSTR* rgsczUpgradeCodes = NULL;
489 DWORD cUpgradeCodes = 0;
490 STRINGDICT_HANDLE sdUpgradeCodes = NULL;
491 LPWSTR* rgsczAddonCodes = NULL;
492 DWORD cAddonCodes = 0;
493 STRINGDICT_HANDLE sdAddonCodes = NULL;
494 LPWSTR* rgsczDetectCodes = NULL;
495 DWORD cDetectCodes = 0;
496 STRINGDICT_HANDLE sdDetectCodes = NULL;
497 LPWSTR* rgsczPatchCodes = NULL;
498 DWORD cPatchCodes = 0;
499 STRINGDICT_HANDLE sdPatchCodes = NULL;
500
501 *pRelationType = BUNDLE_RELATION_NONE;
502
503 hr = RegReadStringArray(hkBundleId, BUNDLE_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE, &rgsczUpgradeCodes, &cUpgradeCodes);
504 if (HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE) == hr)
505 {
506 TraceError(hr, "Failed to read upgrade codes as REG_MULTI_SZ. Trying again as REG_SZ in case of older bundles.");
507
508 rgsczUpgradeCodes = reinterpret_cast<LPWSTR*>(MemAlloc(sizeof(LPWSTR), TRUE));
509 ExitOnNull(rgsczUpgradeCodes, hr, E_OUTOFMEMORY, "Failed to allocate list for a single upgrade code from older bundle.");
510
511 hr = RegReadString(hkBundleId, BUNDLE_REGISTRATION_REGISTRY_BUNDLE_UPGRADE_CODE, &rgsczUpgradeCodes[0]);
512 if (SUCCEEDED(hr))
513 {
514 cUpgradeCodes = 1;
515 }
516 }
517
518 // Compare upgrade codes.
519 if (SUCCEEDED(hr))
520 {
521 hr = DictCreateStringListFromArray(&sdUpgradeCodes, rgsczUpgradeCodes, cUpgradeCodes, DICT_FLAG_CASEINSENSITIVE);
522 ExitOnFailure(hr, "Failed to create string dictionary for %hs.", "upgrade codes");
523
524 // Upgrade relationship: when their upgrade codes match our upgrade codes.
525 hr = DictCompareStringListToArray(sdUpgradeCodes, const_cast<LPCWSTR*>(pQueryContext->rgwzUpgradeCodes), pQueryContext->cUpgradeCodes);
526 if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr)
527 {
528 hr = S_OK;
529 }
530 else
531 {
532 ExitOnFailure(hr, "Failed to do array search for upgrade code match.");
533
534 *pRelationType = BUNDLE_RELATION_UPGRADE;
535 ExitFunction();
536 }
537
538 // Detect relationship: when their upgrade codes match our detect codes.
539 hr = DictCompareStringListToArray(sdUpgradeCodes, const_cast<LPCWSTR*>(pQueryContext->rgwzDetectCodes), pQueryContext->cDetectCodes);
540 if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr)
541 {
542 hr = S_OK;
543 }
544 else
545 {
546 ExitOnFailure(hr, "Failed to do array search for detect code match.");
547
548 *pRelationType = BUNDLE_RELATION_DETECT;
549 ExitFunction();
550 }
551
552 // Dependent relationship: when their upgrade codes match our addon codes.
553 hr = DictCompareStringListToArray(sdUpgradeCodes, const_cast<LPCWSTR*>(pQueryContext->rgwzAddonCodes), pQueryContext->cAddonCodes);
554 if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr)
555 {
556 hr = S_OK;
557 }
558 else
559 {
560 ExitOnFailure(hr, "Failed to do array search for addon code match.");
561
562 *pRelationType = BUNDLE_RELATION_DEPENDENT;
563 ExitFunction();
564 }
565
566 // Dependent relationship: when their upgrade codes match our patch codes.
567 hr = DictCompareStringListToArray(sdUpgradeCodes, const_cast<LPCWSTR*>(pQueryContext->rgwzPatchCodes), pQueryContext->cPatchCodes);
568 if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr)
569 {
570 hr = S_OK;
571 }
572 else
573 {
574 ExitOnFailure(hr, "Failed to do array search for addon code match.");
575
576 *pRelationType = BUNDLE_RELATION_DEPENDENT;
577 ExitFunction();
578 }
579
580 ReleaseNullDict(sdUpgradeCodes);
581 ReleaseNullStrArray(rgsczUpgradeCodes, cUpgradeCodes);
582 }
583
584 // Compare addon codes.
585 hr = RegReadStringArray(hkBundleId, BUNDLE_REGISTRATION_REGISTRY_BUNDLE_ADDON_CODE, &rgsczAddonCodes, &cAddonCodes);
586 if (SUCCEEDED(hr))
587 {
588 hr = DictCreateStringListFromArray(&sdAddonCodes, rgsczAddonCodes, cAddonCodes, DICT_FLAG_CASEINSENSITIVE);
589 ExitOnFailure(hr, "Failed to create string dictionary for %hs.", "addon codes");
590
591 // Addon relationship: when their addon codes match our detect codes.
592 hr = DictCompareStringListToArray(sdAddonCodes, const_cast<LPCWSTR*>(pQueryContext->rgwzDetectCodes), pQueryContext->cDetectCodes);
593 if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr)
594 {
595 hr = S_OK;
596 }
597 else
598 {
599 ExitOnFailure(hr, "Failed to do array search for addon code match.");
600
601 *pRelationType = BUNDLE_RELATION_ADDON;
602 ExitFunction();
603 }
604
605 // Addon relationship: when their addon codes match our upgrade codes.
606 hr = DictCompareStringListToArray(sdAddonCodes, const_cast<LPCWSTR*>(pQueryContext->rgwzUpgradeCodes), pQueryContext->cUpgradeCodes);
607 if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr)
608 {
609 hr = S_OK;
610 }
611 else
612 {
613 ExitOnFailure(hr, "Failed to do array search for addon code match.");
614
615 *pRelationType = BUNDLE_RELATION_ADDON;
616 ExitFunction();
617 }
618
619 ReleaseNullDict(sdAddonCodes);
620 ReleaseNullStrArray(rgsczAddonCodes, cAddonCodes);
621 }
622
623 // Compare patch codes.
624 hr = RegReadStringArray(hkBundleId, BUNDLE_REGISTRATION_REGISTRY_BUNDLE_PATCH_CODE, &rgsczPatchCodes, &cPatchCodes);
625 if (SUCCEEDED(hr))
626 {
627 hr = DictCreateStringListFromArray(&sdPatchCodes, rgsczPatchCodes, cPatchCodes, DICT_FLAG_CASEINSENSITIVE);
628 ExitOnFailure(hr, "Failed to create string dictionary for %hs.", "patch codes");
629
630 // Patch relationship: when their patch codes match our detect codes.
631 hr = DictCompareStringListToArray(sdPatchCodes, const_cast<LPCWSTR*>(pQueryContext->rgwzDetectCodes), pQueryContext->cDetectCodes);
632 if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr)
633 {
634 hr = S_OK;
635 }
636 else
637 {
638 ExitOnFailure(hr, "Failed to do array search for patch code match.");
639
640 *pRelationType = BUNDLE_RELATION_PATCH;
641 ExitFunction();
642 }
643
644 // Patch relationship: when their patch codes match our upgrade codes.
645 hr = DictCompareStringListToArray(sdPatchCodes, const_cast<LPCWSTR*>(pQueryContext->rgwzUpgradeCodes), pQueryContext->cUpgradeCodes);
646 if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr)
647 {
648 hr = S_OK;
649 }
650 else
651 {
652 ExitOnFailure(hr, "Failed to do array search for patch code match.");
653
654 *pRelationType = BUNDLE_RELATION_PATCH;
655 ExitFunction();
656 }
657
658 ReleaseNullDict(sdPatchCodes);
659 ReleaseNullStrArray(rgsczPatchCodes, cPatchCodes);
660 }
661
662 // Compare detect codes.
663 hr = RegReadStringArray(hkBundleId, BUNDLE_REGISTRATION_REGISTRY_BUNDLE_DETECT_CODE, &rgsczDetectCodes, &cDetectCodes);
664 if (SUCCEEDED(hr))
665 {
666 hr = DictCreateStringListFromArray(&sdDetectCodes, rgsczDetectCodes, cDetectCodes, DICT_FLAG_CASEINSENSITIVE);
667 ExitOnFailure(hr, "Failed to create string dictionary for %hs.", "detect codes");
668
669 // Detect relationship: when their detect codes match our detect codes.
670 hr = DictCompareStringListToArray(sdDetectCodes, const_cast<LPCWSTR*>(pQueryContext->rgwzDetectCodes), pQueryContext->cDetectCodes);
671 if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr)
672 {
673 hr = S_OK;
674 }
675 else
676 {
677 ExitOnFailure(hr, "Failed to do array search for detect code match.");
678
679 *pRelationType = BUNDLE_RELATION_DETECT;
680 ExitFunction();
681 }
682
683 // Dependent relationship: when their detect codes match our addon codes.
684 hr = DictCompareStringListToArray(sdDetectCodes, const_cast<LPCWSTR*>(pQueryContext->rgwzAddonCodes), pQueryContext->cAddonCodes);
685 if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr)
686 {
687 hr = S_OK;
688 }
689 else
690 {
691 ExitOnFailure(hr, "Failed to do array search for addon code match.");
692
693 *pRelationType = BUNDLE_RELATION_DEPENDENT;
694 ExitFunction();
695 }
696
697 // Dependent relationship: when their detect codes match our patch codes.
698 hr = DictCompareStringListToArray(sdDetectCodes, const_cast<LPCWSTR*>(pQueryContext->rgwzPatchCodes), pQueryContext->cPatchCodes);
699 if (HRESULT_FROM_WIN32(ERROR_NO_MATCH) == hr)
700 {
701 hr = S_OK;
702 }
703 else
704 {
705 ExitOnFailure(hr, "Failed to do array search for addon code match.");
706
707 *pRelationType = BUNDLE_RELATION_DEPENDENT;
708 ExitFunction();
709 }
710
711 ReleaseNullDict(sdDetectCodes);
712 ReleaseNullStrArray(rgsczDetectCodes, cDetectCodes);
713 }
714
715LExit:
716 if (SUCCEEDED(hr) && BUNDLE_RELATION_NONE == *pRelationType)
717 {
718 hr = E_NOTFOUND;
719 }
720
721 ReleaseDict(sdUpgradeCodes);
722 ReleaseStrArray(rgsczUpgradeCodes, cUpgradeCodes);
723 ReleaseDict(sdAddonCodes);
724 ReleaseStrArray(rgsczAddonCodes, cAddonCodes);
725 ReleaseDict(sdDetectCodes);
726 ReleaseStrArray(rgsczDetectCodes, cDetectCodes);
727 ReleaseDict(sdPatchCodes);
728 ReleaseStrArray(rgsczPatchCodes, cPatchCodes);
729
730 return hr;
731}
732
373static HRESULT LocateAndQueryBundleValue( 733static HRESULT LocateAndQueryBundleValue(
374 __in_z LPCWSTR wzBundleId, 734 __in_z LPCWSTR wzBundleId,
375 __in_opt LPCWSTR wzSubKey, 735 __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
12 BUNDLE_INSTALL_CONTEXT_USER, 12 BUNDLE_INSTALL_CONTEXT_USER,
13} BUNDLE_INSTALL_CONTEXT; 13} BUNDLE_INSTALL_CONTEXT;
14 14
15typedef enum _BUNDLE_QUERY_CALLBACK_RESULT
16{
17 BUNDLE_QUERY_CALLBACK_RESULT_CONTINUE,
18 BUNDLE_QUERY_CALLBACK_RESULT_CANCEL,
19} BUNDLE_QUERY_CALLBACK_RESULT;
20
21typedef enum _BUNDLE_RELATION_TYPE
22{
23 BUNDLE_RELATION_NONE,
24 BUNDLE_RELATION_DETECT,
25 BUNDLE_RELATION_UPGRADE,
26 BUNDLE_RELATION_ADDON,
27 BUNDLE_RELATION_PATCH,
28 BUNDLE_RELATION_DEPENDENT,
29 BUNDLE_RELATION_UPDATE,
30} BUNDLE_RELATION_TYPE;
31
32typedef struct _BUNDLE_QUERY_RELATED_BUNDLE_RESULT
33{
34 LPCWSTR wzBundleId;
35 BUNDLE_INSTALL_CONTEXT installContext;
36 REG_KEY_BITNESS regBitness;
37 HKEY hkBundle;
38 BUNDLE_RELATION_TYPE relationType;
39} BUNDLE_QUERY_RELATED_BUNDLE_RESULT;
40
41typedef BUNDLE_QUERY_CALLBACK_RESULT(CALLBACK *PFNBUNDLE_QUERY_RELATED_BUNDLE_CALLBACK)(
42 __in const BUNDLE_QUERY_RELATED_BUNDLE_RESULT* pBundle,
43 __in_opt LPVOID pvContext
44 );
45
15 46
16/******************************************************************** 47/********************************************************************
17BundleGetBundleInfo - Queries the bundle installation metadata for a given property, 48BundleGetBundleInfo - Queries the bundle installation metadata for a given property,
@@ -155,6 +186,24 @@ HRESULT DAPI BundleGetBundleVariableFixed(
155 __inout SIZE_T* pcchValue 186 __inout SIZE_T* pcchValue
156 ); 187 );
157 188
189/********************************************************************
190BundleQueryRelatedBundles - Queries the bundle installation metadata for installs with the given detect, upgrade, addon, and patch codes.
191 Passes each related bundle to the callback function.
192********************************************************************/
193HRESULT BundleQueryRelatedBundles(
194 __in BUNDLE_INSTALL_CONTEXT installContext,
195 __in_z_opt LPCWSTR* rgwzDetectCodes,
196 __in DWORD cDetectCodes,
197 __in_z_opt LPCWSTR* rgwzUpgradeCodes,
198 __in DWORD cUpgradeCodes,
199 __in_z_opt LPCWSTR* rgwzAddonCodes,
200 __in DWORD cAddonCodes,
201 __in_z_opt LPCWSTR* rgwzPatchCodes,
202 __in DWORD cPatchCodes,
203 __in PFNBUNDLE_QUERY_RELATED_BUNDLE_CALLBACK pfnCallback,
204 __in_opt LPVOID pvContext
205 );
206
158 207
159#ifdef __cplusplus 208#ifdef __cplusplus
160} 209}