aboutsummaryrefslogtreecommitdiff
path: root/src/burn
diff options
context:
space:
mode:
authorSean Hall <r.sean.hall@gmail.com>2022-02-28 18:42:51 -0600
committerSean Hall <r.sean.hall@gmail.com>2022-03-01 11:37:00 -0600
commitbefcd209d62a25020f46a688002b259c59e4dc3b (patch)
treecbf6c5a57276264599f95a712939d44645f5ca7a /src/burn
parent3f658fa2b4bd80619fcf89e1e87ae12b48effb7a (diff)
downloadwix-befcd209d62a25020f46a688002b259c59e4dc3b.tar.gz
wix-befcd209d62a25020f46a688002b259c59e4dc3b.tar.bz2
wix-befcd209d62a25020f46a688002b259c59e4dc3b.zip
Refactor related bundle enumeration into butil.
Related to #3693
Diffstat (limited to 'src/burn')
-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
7 files changed, 261 insertions, 345 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"