@joebigelow / wix / commits / 584213c5

Add RegReadUnexpandedString to get an unexpanded REG_EXPAND_SZ value.

Sean Hall committed Jun 3, 2022 at 17:49 UTC 584213c5ffeca09b3fe24bd5e92f73fd057ac642
8 files changed +1007 -196
src/burn/engine/search.cpp
+12 -38
@@ -955,15 +955,15 @@ static HRESULT RegistrySearchValue(
955 )
956 {
957 HRESULT hr = S_OK;
958 - DWORD er = ERROR_SUCCESS;
958 LPWSTR sczKey = NULL;
959 LPWSTR sczValue = NULL;
960 HKEY hKey = NULL;
961 DWORD dwType = 0;
963 - DWORD cbData = 0;
962 + SIZE_T cbData = 0;
963 LPBYTE pData = NULL;
965 - DWORD cch = 0;
964 BURN_VARIANT value = { };
965 + DWORD dwValue = 0;
966 + LONGLONG llValue = 0;
967
968 // format key string
969 hr = VariableFormatString(pVariables, pSearch->RegistrySearch.sczKey, &sczKey, NULL);
@@ -988,64 +988,38 @@ static HRESULT RegistrySearchValue(
988 ExitOnFailure(hr, "Failed to open registry key.");
989
990 // get value
991 - er = ::RegQueryValueExW(hKey, sczValue, NULL, &dwType, NULL, &cbData);
992 - if (ERROR_FILE_NOT_FOUND == er)
991 + hr = RegReadValue(hKey, sczValue, pSearch->RegistrySearch.fExpandEnvironment, &pData, &cbData, &dwType);
992 + if (E_FILENOTFOUND == hr)
993 {
994 // What if there is a hidden variable in sczKey or sczValue?
995 LogStringLine(REPORT_STANDARD, "Registry value not found. Key = '%ls', Value = '%ls'", sczKey, sczValue);
996
997 ExitFunction1(hr = S_OK);
998 }
999 - ExitOnWin32Error(er, hr, "Failed to query registry key value size.");
1000 -
1001 - pData = (LPBYTE)MemAlloc(cbData + sizeof(WCHAR), TRUE); // + sizeof(WCHAR) here to ensure that we always have a null terminator for REG_SZ
1002 - ExitOnNull(pData, hr, E_OUTOFMEMORY, "Failed to allocate memory registry value.");
1003 -
1004 - er = ::RegQueryValueExW(hKey, sczValue, NULL, &dwType, pData, &cbData);
1005 - ExitOnWin32Error(er, hr, "Failed to query registry key value.");
999 + ExitOnFailure(hr, "Failed to query registry key value.");
1000
1001 switch (dwType)
1002 {
1003 case REG_DWORD:
1010 - if (sizeof(LONG) != cbData)
1004 + if (memcpy_s(&dwValue, sizeof(DWORD), pData, cbData))
1005 {
1006 ExitFunction1(hr = E_UNEXPECTED);
1007 }
1014 - hr = BVariantSetNumeric(&value, *((LONG*)pData));
1008 + hr = BVariantSetNumeric(&value, dwValue);
1009 break;
1010 case REG_QWORD:
1017 - if (sizeof(LONGLONG) != cbData)
1011 + if (memcpy_s(&llValue, sizeof(LONGLONG), pData, cbData))
1012 {
1013 ExitFunction1(hr = E_UNEXPECTED);
1014 }
1021 - hr = BVariantSetNumeric(&value, *((LONGLONG*)pData));
1015 + hr = BVariantSetNumeric(&value, llValue);
1016 break;
1023 - case REG_EXPAND_SZ:
1024 - if (pSearch->RegistrySearch.fExpandEnvironment)
1025 - {
1026 - hr = StrAlloc(&value.sczValue, cbData);
1027 - ExitOnFailure(hr, "Failed to allocate string buffer.");
1028 - value.Type = BURN_VARIANT_TYPE_STRING;
1029 -
1030 - cch = ::ExpandEnvironmentStringsW((LPCWSTR)pData, value.sczValue, cbData);
1031 - if (cch > cbData)
1032 - {
1033 - hr = StrAlloc(&value.sczValue, cch);
1034 - ExitOnFailure(hr, "Failed to allocate string buffer.");
1035 -
1036 - if (cch != ::ExpandEnvironmentStringsW((LPCWSTR)pData, value.sczValue, cch))
1037 - {
1038 - ExitWithLastError(hr, "Failed to get expand environment string.");
1039 - }
1040 - }
1041 - break;
1042 - }
1043 - __fallthrough;
1017 + case REG_EXPAND_SZ: __fallthrough;
1018 case REG_SZ:
1019 hr = BVariantSetString(&value, (LPCWSTR)pData, 0, FALSE);
1020 break;
1021 default:
1048 - ExitOnFailure(hr = E_NOTIMPL, "Unsupported registry key value type. Type = '%u'", dwType);
1022 + ExitWithRootFailure(hr, E_NOTIMPL, "Unsupported registry key value type. Type = '%u'", dwType);
1023 }
1024 ExitOnFailure(hr, "Failed to read registry value.");
1025
src/burn/test/BurnUnitTest/TestRegistryFixture.cpp
+2 -2
@@ -186,7 +186,7 @@ namespace WixBuildTools
186 void TestRegistryFixture::SetUp()
187 {
188 // set mock API's
189 - RegFunctionOverride(TestRegistryFixture_RegCreateKeyExW, TestRegistryFixture_RegOpenKeyExW, TestRegistryFixture_RegDeleteKeyExW, NULL, NULL, NULL, NULL, NULL, NULL);
189 + RegFunctionOverride(TestRegistryFixture_RegCreateKeyExW, TestRegistryFixture_RegOpenKeyExW, TestRegistryFixture_RegDeleteKeyExW, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
190
191 Registry::CurrentUser->CreateSubKey(TEST_REGISTRY_FIXTURE_HKCU_PATH);
192 Registry::CurrentUser->CreateSubKey(TEST_REGISTRY_FIXTURE_HKCU32_PATH);
@@ -198,7 +198,7 @@ namespace WixBuildTools
198 {
199 Registry::CurrentUser->DeleteSubKeyTree(this->rootPath, false);
200
201 - RegFunctionOverride(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
201 + RegFunctionOverride(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
202 }
203
204 String^ TestRegistryFixture::GetDirectHkcuPath(REG_KEY_BITNESS bitness, ... array<String^>^ paths)
src/libs/dutil/WixToolset.DUtil/inc/regutil.h
+43 -1
@@ -99,6 +99,15 @@ typedef LSTATUS (APIENTRY *PFN_REGDELETEVALUEW)(
99 __in HKEY hKey,
100 __in_opt LPCWSTR lpValueName
101 );
102 +typedef LSTATUS(APIENTRY *PFN_REGGETVALUEW)(
103 + __in HKEY hkey,
104 + __in_opt LPCWSTR lpSubKey,
105 + __in_opt LPCWSTR lpValue,
106 + __in DWORD dwFlags,
107 + __out_opt LPDWORD pdwType,
108 + __out_bcount_part_opt(*pcbData, *pcbData) __out_data_source(REGISTRY) PVOID pvData,
109 + __inout_opt LPDWORD pcbData
110 + );
111
112 /********************************************************************
113 RegInitialize - initializes regutil
@@ -126,9 +135,17 @@ void DAPI RegFunctionOverride(
135 __in_opt PFN_REGQUERYINFOKEYW pfnRegQueryInfoKeyW,
136 __in_opt PFN_REGQUERYVALUEEXW pfnRegQueryValueExW,
137 __in_opt PFN_REGSETVALUEEXW pfnRegSetValueExW,
129 - __in_opt PFN_REGDELETEVALUEW pfnRegDeleteValueW
138 + __in_opt PFN_REGDELETEVALUEW pfnRegDeleteValueW,
139 + __in_opt PFN_REGGETVALUEW pfnRegGetValueW
140 );
141
142 +/********************************************************************
143 + RegFunctionForceFallback - ignore functions only available in newer versions of Windows.
144 + Typically used for unit testing.
145 +
146 +*********************************************************************/
147 +void DAPI RegFunctionForceFallback();
148 +
149 /********************************************************************
150 RegCreate - creates a registry key.
151
@@ -219,6 +236,20 @@ HRESULT DAPI RegGetType(
236 __out DWORD *pdwType
237 );
238
239 +/********************************************************************
240 + RegReadBinary - reads a registry key value.
241 + If fExpand is TRUE and the value is REG_EXPAND_SZ then it will be expanded.
242 + NOTE: caller is responsible for freeing *ppbBuffer
243 +*********************************************************************/
244 +HRESULT DAPI RegReadValue(
245 + __in HKEY hk,
246 + __in_z_opt LPCWSTR wzName,
247 + __in BOOL fExpand,
248 + __deref_out_bcount_opt(*pcbBuffer) BYTE** ppbBuffer,
249 + __inout SIZE_T* pcbBuffer,
250 + __out DWORD* pdwType
251 + );
252 +
253 /********************************************************************
254 RegReadBinary - reads a registry key binary value.
255 NOTE: caller is responsible for freeing *ppbBuffer
@@ -240,6 +271,17 @@ HRESULT DAPI RegReadString(
271 __deref_out_z LPWSTR* psczValue
272 );
273
274 +/********************************************************************
275 + RegReadUnexpandedString - reads a registry key value as a string without expanding it.
276 +
277 +*********************************************************************/
278 +HRESULT DAPI RegReadUnexpandedString(
279 + __in HKEY hk,
280 + __in_z_opt LPCWSTR wzName,
281 + __inout BOOL* pfNeedsExpansion,
282 + __deref_out_z LPWSTR* psczValue
283 + );
284 +
285 /********************************************************************
286 RegReadStringArray - reads a registry key value REG_MULTI_SZ value as a string array.
287
src/libs/dutil/WixToolset.DUtil/regutil.cpp
+265 -155
@@ -9,6 +9,7 @@
9 #define RegExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_REGUTIL, x, s, __VA_ARGS__)
10 #define RegExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_REGUTIL, x, s, __VA_ARGS__)
11 #define RegExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_REGUTIL, x, s, __VA_ARGS__)
12 +#define RegExitWithRootFailure(x, e, s, ...) ExitWithRootFailureSource(DUTIL_SOURCE_REGUTIL, x, e, s, __VA_ARGS__)
13 #define RegExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_REGUTIL, x, s, __VA_ARGS__)
14 #define RegExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_REGUTIL, p, x, e, s, __VA_ARGS__)
15 #define RegExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_REGUTIL, p, x, s, __VA_ARGS__)
@@ -28,10 +29,19 @@ static PFN_REGQUERYINFOKEYW vpfnRegQueryInfoKeyW = ::RegQueryInfoKeyW;
29 static PFN_REGQUERYVALUEEXW vpfnRegQueryValueExW = ::RegQueryValueExW;
30 static PFN_REGSETVALUEEXW vpfnRegSetValueExW = ::RegSetValueExW;
31 static PFN_REGDELETEVALUEW vpfnRegDeleteValueW = ::RegDeleteValueW;
32 +static PFN_REGGETVALUEW vpfnRegGetValueW = NULL;
33 +static PFN_REGGETVALUEW vpfnRegGetValueWFromLibrary = NULL;
34
35 static HMODULE vhAdvApi32Dll = NULL;
36 static BOOL vfRegInitialized = FALSE;
37
38 +static HRESULT GetRegValue(
39 + __in HKEY hk,
40 + __in_z_opt LPCWSTR wzName,
41 + __deref_out_bcount_opt(*pcbBuffer) BYTE* pbBuffer,
42 + __inout SIZE_T* pcbBuffer,
43 + __out DWORD* pdwType
44 + );
45 static HRESULT WriteStringToRegistry(
46 __in HKEY hk,
47 __in_z_opt LPCWSTR wzName,
@@ -46,14 +56,22 @@ DAPI_(HRESULT) RegInitialize()
56 hr = LoadSystemLibrary(L"AdvApi32.dll", &vhAdvApi32Dll);
57 RegExitOnFailure(hr, "Failed to load AdvApi32.dll");
58
49 - // ignore failures - if this doesn't exist, we'll fall back to RegDeleteKeyW
59 + // Ignore failures - if this doesn't exist, we'll fall back to RegDeleteKeyW.
60 vpfnRegDeleteKeyExWFromLibrary = reinterpret_cast<PFN_REGDELETEKEYEXW>(::GetProcAddress(vhAdvApi32Dll, "RegDeleteKeyExW"));
61
52 - if (NULL == vpfnRegDeleteKeyExW)
62 + // Ignore failures - if this doesn't exist, we'll fall back to RegQueryValueExW.
63 + vpfnRegGetValueWFromLibrary = reinterpret_cast<PFN_REGGETVALUEW>(::GetProcAddress(vhAdvApi32Dll, "RegGetValueW"));
64 +
65 + if (!vpfnRegDeleteKeyExW)
66 {
67 vpfnRegDeleteKeyExW = vpfnRegDeleteKeyExWFromLibrary;
68 }
69
70 + if (!vpfnRegGetValueW)
71 + {
72 + vpfnRegGetValueW = vpfnRegGetValueWFromLibrary;
73 + }
74 +
75 vfRegInitialized = TRUE;
76
77 LExit:
@@ -68,7 +86,9 @@ DAPI_(void) RegUninitialize()
86 ::FreeLibrary(vhAdvApi32Dll);
87 vhAdvApi32Dll = NULL;
88 vpfnRegDeleteKeyExWFromLibrary = NULL;
89 + vpfnRegGetValueWFromLibrary = NULL;
90 vpfnRegDeleteKeyExW = NULL;
91 + vpfnRegGetValueW = NULL;
92 }
93
94 vfRegInitialized = FALSE;
@@ -84,7 +104,8 @@ DAPI_(void) RegFunctionOverride(
104 __in_opt PFN_REGQUERYINFOKEYW pfnRegQueryInfoKeyW,
105 __in_opt PFN_REGQUERYVALUEEXW pfnRegQueryValueExW,
106 __in_opt PFN_REGSETVALUEEXW pfnRegSetValueExW,
87 - __in_opt PFN_REGDELETEVALUEW pfnRegDeleteValueW
107 + __in_opt PFN_REGDELETEVALUEW pfnRegDeleteValueW,
108 + __in_opt PFN_REGGETVALUEW pfnRegGetValueW
109 )
110 {
111 vpfnRegCreateKeyExW = pfnRegCreateKeyExW ? pfnRegCreateKeyExW : ::RegCreateKeyExW;
@@ -96,6 +117,14 @@ DAPI_(void) RegFunctionOverride(
117 vpfnRegQueryValueExW = pfnRegQueryValueExW ? pfnRegQueryValueExW : ::RegQueryValueExW;
118 vpfnRegSetValueExW = pfnRegSetValueExW ? pfnRegSetValueExW : ::RegSetValueExW;
119 vpfnRegDeleteValueW = pfnRegDeleteValueW ? pfnRegDeleteValueW : ::RegDeleteValueW;
120 + vpfnRegGetValueW = pfnRegGetValueW ? pfnRegGetValueW : vpfnRegGetValueWFromLibrary;
121 +}
122 +
123 +
124 +DAPI_(void) RegFunctionForceFallback()
125 +{
126 + vpfnRegDeleteKeyExW = NULL;
127 + vpfnRegGetValueW = NULL;
128 }
129
130
@@ -358,54 +387,104 @@ LExit:
387 return hr;
388 }
389
361 -DAPI_(HRESULT) RegReadBinary(
390 +DAPI_(HRESULT) RegReadValue(
391 __in HKEY hk,
392 __in_z_opt LPCWSTR wzName,
393 + __in BOOL fExpand,
394 __deref_out_bcount_opt(*pcbBuffer) BYTE** ppbBuffer,
365 - __out SIZE_T *pcbBuffer
366 - )
395 + __inout SIZE_T* pcbBuffer,
396 + __out DWORD* pdwType
397 + )
398 {
399 HRESULT hr = S_OK;
369 - LPBYTE pbBuffer = NULL;
370 - DWORD er = ERROR_SUCCESS;
371 - DWORD cb = 0;
372 - DWORD dwType = 0;
373 -
374 - er = vpfnRegQueryValueExW(hk, wzName, NULL, &dwType, NULL, &cb);
375 - RegExitOnWin32Error(er, hr, "Failed to get size of registry value.");
400 + DWORD dwAttempts = 0;
401 + LPWSTR sczExpand = NULL;
402
377 - // Zero-length binary values can exist
378 - if (0 < cb)
403 + hr = GetRegValue(hk, wzName, *ppbBuffer, pcbBuffer, pdwType);
404 + if (E_FILENOTFOUND == hr)
405 {
380 - pbBuffer = static_cast<LPBYTE>(MemAlloc(cb, FALSE));
381 - RegExitOnNull(pbBuffer, hr, E_OUTOFMEMORY, "Failed to allocate buffer for binary registry value.");
406 + ExitFunction();
407 + }
408 + else if (E_MOREDATA != hr)
409 + {
410 + RegExitOnFailure(hr, "Failed to get size of raw registry value.");
411
383 - er = vpfnRegQueryValueExW(hk, wzName, NULL, &dwType, pbBuffer, &cb);
384 - if (E_FILENOTFOUND == HRESULT_FROM_WIN32(er))
412 + // Zero-length raw values can exist
413 + if (!*ppbBuffer && 0 < *pcbBuffer)
414 {
386 - ExitFunction1(hr = E_FILENOTFOUND);
415 + hr = E_MOREDATA;
416 }
388 - RegExitOnWin32Error(er, hr, "Failed to read registry value.");
417 }
418
391 - if (REG_BINARY == dwType)
419 + while (E_MOREDATA == hr && dwAttempts < 10)
420 {
393 - *ppbBuffer = pbBuffer;
394 - pbBuffer = NULL;
395 - *pcbBuffer = cb;
421 + ++dwAttempts;
422 +
423 + if (*ppbBuffer)
424 + {
425 + *ppbBuffer = static_cast<LPBYTE>(MemReAlloc(*ppbBuffer, *pcbBuffer, FALSE));
426 + }
427 + else
428 + {
429 + *ppbBuffer = static_cast<LPBYTE>(MemAlloc(*pcbBuffer, FALSE));
430 + }
431 + RegExitOnNull(*ppbBuffer, hr, E_OUTOFMEMORY, "Failed to allocate buffer for raw registry value.");
432 +
433 + hr = GetRegValue(hk, wzName, *ppbBuffer, pcbBuffer, pdwType);
434 + if (E_FILENOTFOUND == hr)
435 + {
436 + ExitFunction();
437 + }
438 + else if (E_MOREDATA != hr)
439 + {
440 + RegExitOnFailure(hr, "Failed to read raw registry value.");
441 + }
442 }
397 - else
443 +
444 + if (fExpand && SUCCEEDED(hr) && REG_EXPAND_SZ == *pdwType)
445 {
399 - hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE);
400 - RegExitOnRootFailure(hr, "Error reading binary registry value due to unexpected data type: %u", dwType);
446 + LPWSTR sczValue = reinterpret_cast<LPWSTR>(*ppbBuffer);
447 + hr = PathExpand(&sczExpand, sczValue, PATH_EXPAND_ENVIRONMENT);
448 + RegExitOnFailure(hr, "Failed to expand registry value: %ls", sczValue);
449 +
450 + *ppbBuffer = reinterpret_cast<LPBYTE>(sczExpand);
451 + *pcbBuffer = (lstrlenW(sczExpand) + 1) * sizeof(WCHAR);
452 + sczExpand = NULL;
453 + ReleaseMem(sczValue);
454 }
455
456 LExit:
404 - ReleaseMem(pbBuffer);
457 + ReleaseStr(sczExpand);
458
459 return hr;
460 }
461
462 +DAPI_(HRESULT) RegReadBinary(
463 + __in HKEY hk,
464 + __in_z_opt LPCWSTR wzName,
465 + __deref_out_bcount_opt(*pcbBuffer) BYTE** ppbBuffer,
466 + __out SIZE_T* pcbBuffer
467 + )
468 +{
469 + HRESULT hr = S_OK;
470 + DWORD dwType = 0;
471 +
472 + hr = RegReadValue(hk, wzName, FALSE, ppbBuffer, pcbBuffer, &dwType);
473 + if (E_FILENOTFOUND == hr)
474 + {
475 + ExitFunction();
476 + }
477 + RegExitOnFailure(hr, "Failed to read binary registry value.");
478 +
479 + if (REG_BINARY != dwType)
480 + {
481 + RegExitWithRootFailure(hr, HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE), "Error reading binary registry value due to unexpected data type: %u", dwType);
482 + }
483 +
484 +LExit:
485 + return hr;
486 +}
487 +
488
489 DAPI_(HRESULT) RegReadString(
490 __in HKEY hk,
@@ -414,65 +493,64 @@ DAPI_(HRESULT) RegReadString(
493 )
494 {
495 HRESULT hr = S_OK;
417 - DWORD er = ERROR_SUCCESS;
496 SIZE_T cbValue = 0;
419 - DWORD cch = 0;
420 - DWORD cb = 0;
497 DWORD dwType = 0;
422 - LPWSTR sczExpand = NULL;
498
499 if (psczValue && *psczValue)
500 {
426 - hr = StrMaxLength(*psczValue, &cbValue);
427 - RegExitOnFailure(hr, "Failed to determine length of string.");
428 -
429 - cch = (DWORD)min(DWORD_MAX, cbValue);
501 + hr = MemSizeChecked(*psczValue, &cbValue);
502 + RegExitOnFailure(hr, "Failed to get size of input buffer.");
503 }
504
432 - if (2 > cch)
505 + hr = RegReadValue(hk, wzName, TRUE, reinterpret_cast<LPBYTE*>(psczValue), &cbValue, &dwType);
506 + if (E_FILENOTFOUND == hr)
507 {
434 - cch = 2;
435 -
436 - hr = StrAlloc(psczValue, cch);
437 - RegExitOnFailure(hr, "Failed to allocate string to minimum size.");
508 + ExitFunction();
509 }
510 + RegExitOnFailure(hr, "Failed to read string registry value.");
511
440 - cb = sizeof(WCHAR) * (cch - 1); // subtract one to ensure there will be a space at the end of the string for the null terminator.
441 - er = vpfnRegQueryValueExW(hk, wzName, NULL, &dwType, reinterpret_cast<LPBYTE>(*psczValue), &cb);
442 - if (ERROR_MORE_DATA == er)
512 + if (REG_EXPAND_SZ != dwType && REG_SZ != dwType)
513 {
444 - cch = cb / sizeof(WCHAR) + 1; // add one to ensure there will be space at the end for the null terminator
445 - hr = StrAlloc(psczValue, cch);
446 - RegExitOnFailure(hr, "Failed to allocate string bigger for registry value.");
447 -
448 - er = vpfnRegQueryValueExW(hk, wzName, NULL, &dwType, reinterpret_cast<LPBYTE>(*psczValue), &cb);
514 + RegExitWithRootFailure(hr, HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE), "Error reading string registry value due to unexpected data type: %u", dwType);
515 }
450 - if (E_FILENOTFOUND == HRESULT_FROM_WIN32(er))
516 +
517 +LExit:
518 + return hr;
519 +}
520 +
521 +
522 +DAPI_(HRESULT) RegReadUnexpandedString(
523 + __in HKEY hk,
524 + __in_z_opt LPCWSTR wzName,
525 + __inout BOOL* pfNeedsExpansion,
526 + __deref_out_z LPWSTR* psczValue
527 + )
528 +{
529 + HRESULT hr = S_OK;
530 + SIZE_T cbValue = 0;
531 + DWORD dwType = 0;
532 + LPWSTR sczExpand = NULL;
533 +
534 + if (psczValue && *psczValue)
535 {
452 - ExitFunction1(hr = E_FILENOTFOUND);
536 + hr = MemSizeChecked(*psczValue, &cbValue);
537 + RegExitOnFailure(hr, "Failed to get size of input buffer.");
538 }
454 - RegExitOnWin32Error(er, hr, "Failed to read registry key.");
539
456 - if (REG_SZ == dwType || REG_EXPAND_SZ == dwType)
540 + hr = RegReadValue(hk, wzName, FALSE, reinterpret_cast<LPBYTE*>(psczValue), &cbValue, &dwType);
541 + if (E_FILENOTFOUND == hr)
542 {
458 - // Always ensure the registry value is null terminated.
459 - (*psczValue)[cch - 1] = L'\0';
460 -
461 - if (REG_EXPAND_SZ == dwType)
462 - {
463 - hr = StrAllocString(&sczExpand, *psczValue, 0);
464 - RegExitOnFailure(hr, "Failed to copy registry value to expand.");
465 -
466 - hr = PathExpand(psczValue, sczExpand, PATH_EXPAND_ENVIRONMENT);
467 - RegExitOnFailure(hr, "Failed to expand registry value: %ls", *psczValue);
468 - }
543 + ExitFunction();
544 }
470 - else
545 + RegExitOnFailure(hr, "Failed to read expand string registry value.");
546 +
547 + if (REG_EXPAND_SZ != dwType && REG_SZ != dwType)
548 {
472 - hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE);
473 - RegExitOnRootFailure(hr, "Error reading string registry value due to unexpected data type: %u", dwType);
549 + RegExitWithRootFailure(hr, HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE), "Error reading expand string registry value due to unexpected data type: %u", dwType);
550 }
551
552 + *pfNeedsExpansion = REG_EXPAND_SZ == dwType;
553 +
554 LExit:
555 ReleaseStr(sczExpand);
556
@@ -488,67 +566,57 @@ DAPI_(HRESULT) RegReadStringArray(
566 )
567 {
568 HRESULT hr = S_OK;
491 - DWORD er = ERROR_SUCCESS;
569 DWORD dwNullCharacters = 0;
570 DWORD dwType = 0;
494 - DWORD cb = 0;
495 - DWORD cch = 0;
571 + SIZE_T cb = 0;
572 + SIZE_T cch = 0;
573 LPCWSTR wzSource = NULL;
574 LPWSTR sczValue = NULL;
575
499 - er = vpfnRegQueryValueExW(hk, wzName, NULL, &dwType, reinterpret_cast<LPBYTE>(sczValue), &cb);
500 - if (0 < cb)
501 - {
502 - cch = cb / sizeof(WCHAR);
503 - hr = StrAlloc(&sczValue, cch);
504 - RegExitOnFailure(hr, "Failed to allocate string for registry value.");
505 -
506 - er = vpfnRegQueryValueExW(hk, wzName, NULL, &dwType, reinterpret_cast<LPBYTE>(sczValue), &cb);
507 - }
508 - if (E_FILENOTFOUND == HRESULT_FROM_WIN32(er))
576 + hr = RegReadValue(hk, wzName, FALSE, reinterpret_cast<LPBYTE*>(&sczValue), &cb, &dwType);
577 + if (E_FILENOTFOUND == hr)
578 {
510 - ExitFunction1(hr = E_FILENOTFOUND);
579 + ExitFunction();
580 }
512 - RegExitOnWin32Error(er, hr, "Failed to read registry key.");
581 + RegExitOnFailure(hr, "Failed to read string array registry value.");
582
514 - if (cb / sizeof(WCHAR) != cch)
583 + if (REG_MULTI_SZ != dwType)
584 {
516 - hr = E_UNEXPECTED;
517 - RegExitOnFailure(hr, "The size of registry value %ls unexpected changed between 2 reads", wzName);
585 + RegExitWithRootFailure(hr, HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE), "Tried to read string array, but registry value %ls is of an incorrect type", wzName);
586 }
587
520 - if (REG_MULTI_SZ != dwType)
588 + cch = cb / sizeof(WCHAR);
589 +
590 + for (DWORD i = 0; i < cch; ++i)
591 {
522 - hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE);
523 - RegExitOnRootFailure(hr, "Tried to read string array, but registry value %ls is of an incorrect type", wzName);
592 + if (L'\0' == sczValue[i])
593 + {
594 + ++dwNullCharacters;
595 + }
596 }
597
598 // Value exists, but is empty, so no strings to return.
527 - if (2 > cch)
599 + if (!cb || 1 == dwNullCharacters && 1 == cch || 2 == dwNullCharacters && 2 == cch)
600 {
601 *prgsczStrings = NULL;
602 *pcStrings = 0;
603 ExitFunction1(hr = S_OK);
604 }
605
534 - // The docs specifically say if the value was written without double-null-termination, it'll get read back without it too.
535 - if (L'\0' != sczValue[cch-1] || L'\0' != sczValue[cch-2])
606 + if (sczValue[cch - 1] != L'\0')
607 {
537 - hr = E_INVALIDARG;
538 - RegExitOnFailure(hr, "Tried to read string array, but registry value %ls is invalid (isn't double-null-terminated)", wzName);
608 + // Count the terminating null character that RegReadValue added past the end.
609 + ++dwNullCharacters;
610 + Assert(!sczValue[cch]);
611 }
540 -
541 - cch = cb / sizeof(WCHAR);
542 - for (DWORD i = 0; i < cch; ++i)
612 + else if (cch > 1 && sczValue[cch - 2] == L'\0')
613 {
544 - if (L'\0' == sczValue[i])
545 - {
546 - ++dwNullCharacters;
547 - }
614 + // Don't count the extra 1 at the end of the properly double-null terminated string.
615 + --dwNullCharacters;
616 }
617
550 - // There's one string for every null character encountered (except the extra 1 at the end of the string)
551 - *pcStrings = dwNullCharacters - 1;
618 + // There's one string for every null character encountered.
619 + *pcStrings = dwNullCharacters;
620 hr = MemEnsureArraySize(reinterpret_cast<LPVOID *>(prgsczStrings), *pcStrings, sizeof(LPWSTR), 0);
621 RegExitOnFailure(hr, "Failed to resize array while reading REG_MULTI_SZ value");
622
@@ -557,11 +625,13 @@ DAPI_(HRESULT) RegReadStringArray(
625 wzSource = sczValue;
626 for (DWORD i = 0; i < *pcStrings; ++i)
627 {
560 - hr = StrAllocString(&(*prgsczStrings)[i], wzSource, 0);
628 + cch = lstrlenW(wzSource);
629 +
630 + hr = StrAllocString(&(*prgsczStrings)[i], wzSource, cch);
631 RegExitOnFailure(hr, "Failed to allocate copy of string");
632
633 // Skip past this string
564 - wzSource += lstrlenW(wzSource) + 1;
634 + wzSource += cch + 1;
635 }
636 #pragma prefast(pop)
637
@@ -579,38 +649,36 @@ DAPI_(HRESULT) RegReadVersion(
649 )
650 {
651 HRESULT hr = S_OK;
582 - DWORD er = ERROR_SUCCESS;
652 DWORD dwType = 0;
584 - DWORD cb = 0;
585 - LPWSTR sczVersion = NULL;
653 + SIZE_T cb = 0;
654 + LPWSTR sczValue = NULL;
655
587 - cb = sizeof(DWORD64);
588 - er = vpfnRegQueryValueExW(hk, wzName, NULL, &dwType, reinterpret_cast<LPBYTE>(*pdw64Version), &cb);
589 - if (E_FILENOTFOUND == HRESULT_FROM_WIN32(er))
656 + hr = RegReadValue(hk, wzName, TRUE, reinterpret_cast<LPBYTE*>(&sczValue), &cb, &dwType);
657 + if (E_FILENOTFOUND == hr)
658 {
591 - ExitFunction1(hr = E_FILENOTFOUND);
659 + ExitFunction();
660 }
661 + RegExitOnFailure(hr, "Failed to read version registry value.");
662
663 if (REG_SZ == dwType || REG_EXPAND_SZ == dwType)
664 {
596 - hr = RegReadString(hk, wzName, &sczVersion);
597 - RegExitOnFailure(hr, "Failed to read registry version as string.");
598 -
599 - hr = FileVersionFromStringEx(sczVersion, 0, pdw64Version);
665 + hr = FileVersionFromStringEx(sczValue, 0, pdw64Version);
666 RegExitOnFailure(hr, "Failed to convert registry string to version.");
667 }
668 else if (REG_QWORD == dwType)
669 {
604 - RegExitOnWin32Error(er, hr, "Failed to read registry key.");
670 + if (memcpy_s(pdw64Version, sizeof(DWORD64), sczValue, cb))
671 + {
672 + RegExitWithRootFailure(hr, E_INVALIDARG, "Failed to copy QWORD version value.");
673 + }
674 }
675 else // unexpected data type
676 {
608 - hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE);
609 - RegExitOnRootFailure(hr, "Error reading version registry value due to unexpected data type: %u", dwType);
677 + RegExitWithRootFailure(hr, HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE), "Error reading version registry value due to unexpected data type: %u", dwType);
678 }
679
680 LExit:
613 - ReleaseStr(sczVersion);
681 + ReleaseStr(sczValue);
682
683 return hr;
684 }
@@ -623,37 +691,38 @@ DAPI_(HRESULT) RegReadWixVersion(
691 )
692 {
693 HRESULT hr = S_OK;
626 - DWORD er = ERROR_SUCCESS;
694 DWORD dwType = 0;
628 - DWORD cb = 0;
695 + SIZE_T cb = 0;
696 DWORD64 dw64Version = 0;
630 - LPWSTR sczVersion = NULL;
697 + LPWSTR sczValue = NULL;
698 VERUTIL_VERSION* pVersion = NULL;
699
633 - cb = sizeof(DWORD64);
634 - er = vpfnRegQueryValueExW(hk, wzName, NULL, &dwType, reinterpret_cast<LPBYTE>(&dw64Version), &cb);
635 - if (E_FILENOTFOUND == HRESULT_FROM_WIN32(er))
700 + hr = RegReadValue(hk, wzName, TRUE, reinterpret_cast<LPBYTE*>(&sczValue), &cb, &dwType);
701 + if (E_FILENOTFOUND == hr)
702 {
637 - ExitFunction1(hr = E_FILENOTFOUND);
703 + ExitFunction();
704 }
705 + RegExitOnFailure(hr, "Failed to read wix version registry value.");
706
707 if (REG_SZ == dwType || REG_EXPAND_SZ == dwType)
708 {
642 - hr = RegReadString(hk, wzName, &sczVersion);
643 - RegExitOnFailure(hr, "Failed to read registry version as string.");
644 -
645 - hr = VerParseVersion(sczVersion, 0, FALSE, &pVersion);
646 - RegExitOnFailure(hr, "Failed to convert registry string to version.");
709 + hr = VerParseVersion(sczValue, 0, FALSE, &pVersion);
710 + RegExitOnFailure(hr, "Failed to convert registry string to wix version.");
711 }
712 else if (REG_QWORD == dwType)
713 {
714 + if (memcpy_s(&dw64Version, sizeof(DWORD64), sczValue, cb))
715 + {
716 + RegExitWithRootFailure(hr, E_INVALIDARG, "Failed to copy QWORD wix version value.");
717 + }
718 +
719 hr = VerVersionFromQword(dw64Version, &pVersion);
651 - RegExitOnFailure(hr, "Failed to convert registry string to version.");
720 + RegExitOnFailure(hr, "Failed to convert registry string to wix version.");
721 }
722 else // unexpected data type
723 {
724 hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE);
656 - RegExitOnRootFailure(hr, "Error reading version registry value due to unexpected data type: %u", dwType);
725 + RegExitOnRootFailure(hr, "Error reading wix version registry value due to unexpected data type: %u", dwType);
726 }
727
728 *ppVersion = pVersion;
@@ -661,7 +730,7 @@ DAPI_(HRESULT) RegReadWixVersion(
730
731 LExit:
732 ReleaseVerutilVersion(pVersion);
664 - ReleaseStr(sczVersion);
733 + ReleaseStr(sczValue);
734
735 return hr;
736 }
@@ -673,20 +742,18 @@ DAPI_(HRESULT) RegReadNone(
742 )
743 {
744 HRESULT hr = S_OK;
676 - DWORD er = ERROR_SUCCESS;
745 DWORD dwType = 0;
746
679 - er = vpfnRegQueryValueExW(hk, wzName, NULL, &dwType, NULL, NULL);
680 - if (E_FILENOTFOUND == HRESULT_FROM_WIN32(er))
747 + hr = RegGetType(hk, wzName, &dwType);
748 + if (E_FILENOTFOUND == hr)
749 {
682 - ExitFunction1(hr = E_FILENOTFOUND);
750 + ExitFunction();
751 }
684 - RegExitOnWin32Error(er, hr, "Failed to query registry key value.");
752 + RegExitOnFailure(hr, "Error reading none registry value type.");
753
754 if (REG_NONE != dwType)
755 {
688 - hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE);
689 - RegExitOnRootFailure(hr, "Error reading none registry value due to unexpected data type: %u", dwType);
756 + RegExitWithRootFailure(hr, HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE), "Error reading none registry value due to unexpected data type: %u", dwType);
757 }
758
759 LExit:
@@ -829,12 +896,9 @@ DAPI_(HRESULT) RegWriteStringArray(
896 DWORD dwTotalStringSize = 0;
897 DWORD cbTotalStringSize = 0;
898 DWORD dwTemp = 0;
899 + DWORD dwRemainingStringSize = 0;
900
833 - if (0 == cValues)
834 - {
835 - wzWriteValue = L"\0";
836 - }
837 - else
901 + if (cValues)
902 {
903 // Add space for the null terminator
904 dwTotalStringSize = 1;
@@ -850,21 +914,22 @@ DAPI_(HRESULT) RegWriteStringArray(
914 RegExitOnFailure(hr, "Failed to allocate space for string while writing REG_MULTI_SZ");
915
916 wzCopyDestination = sczWriteValue;
853 - dwTemp = dwTotalStringSize;
917 + dwRemainingStringSize = dwTotalStringSize;
918 for (DWORD i = 0; i < cValues; ++i)
919 {
856 - hr = ::StringCchCopyW(wzCopyDestination, dwTotalStringSize, rgwzValues[i]);
920 + hr = ::StringCchCopyW(wzCopyDestination, dwRemainingStringSize, rgwzValues[i]);
921 RegExitOnFailure(hr, "failed to copy string: %ls", rgwzValues[i]);
922
859 - dwTemp -= lstrlenW(rgwzValues[i]) + 1;
860 - wzCopyDestination += lstrlenW(rgwzValues[i]) + 1;
923 + dwTemp = lstrlenW(rgwzValues[i]) + 1;
924 + dwRemainingStringSize -= dwTemp;
925 + wzCopyDestination += dwTemp;
926 }
927
928 wzWriteValue = sczWriteValue;
864 - }
929
866 - hr = ::DWordMult(dwTotalStringSize, sizeof(WCHAR), &cbTotalStringSize);
867 - RegExitOnFailure(hr, "Failed to get total string size in bytes");
930 + hr = ::DWordMult(dwTotalStringSize, sizeof(WCHAR), &cbTotalStringSize);
931 + RegExitOnFailure(hr, "Failed to get total string size in bytes");
932 + }
933
934 er = vpfnRegSetValueExW(hk, wzName, 0, REG_MULTI_SZ, reinterpret_cast<const BYTE *>(wzWriteValue), cbTotalStringSize);
935 RegExitOnWin32Error(er, hr, "Failed to set registry value to array of strings (first string of which is): %ls", wzWriteValue);
@@ -1000,6 +1065,48 @@ DAPI_(REGSAM) RegTranslateKeyBitness(
1065 }
1066 }
1067
1068 +static HRESULT GetRegValue(
1069 + __in HKEY hk,
1070 + __in_z_opt LPCWSTR wzName,
1071 + __deref_out_bcount_opt(*pcbBuffer) BYTE* pbBuffer,
1072 + __inout SIZE_T* pcbBuffer,
1073 + __out DWORD* pdwType
1074 + )
1075 +{
1076 + HRESULT hr = S_OK;
1077 + DWORD cb = (DWORD)min(DWORD_MAX, *pcbBuffer);
1078 +
1079 + if (vpfnRegGetValueW)
1080 + {
1081 + hr = HRESULT_FROM_WIN32(vpfnRegGetValueW(hk, NULL, wzName, RRF_RT_ANY | RRF_NOEXPAND, pdwType, pbBuffer, &cb));
1082 + }
1083 + else
1084 + {
1085 + hr = HRESULT_FROM_WIN32(vpfnRegQueryValueExW(hk, wzName, NULL, pdwType, pbBuffer, &cb));
1086 +
1087 + if (REG_SZ == *pdwType || REG_EXPAND_SZ == *pdwType || REG_MULTI_SZ == *pdwType)
1088 + {
1089 + if (E_MOREDATA == hr || S_OK == hr && (cb + sizeof(WCHAR)) > *pcbBuffer)
1090 + {
1091 + // Make sure there's room for a null terminator at the end.
1092 + HRESULT hrAdd = ::DWordAdd(cb, sizeof(WCHAR), &cb);
1093 +
1094 + hr = FAILED(hrAdd) ? hrAdd : (pbBuffer ? E_MOREDATA : S_OK);
1095 + }
1096 + else if (S_OK == hr && pbBuffer)
1097 + {
1098 + // Always ensure the registry value is null terminated.
1099 + WCHAR* pch = reinterpret_cast<WCHAR*>(pbBuffer + cb);
1100 + *pch = L'\0';
1101 + }
1102 + }
1103 + }
1104 +
1105 + *pcbBuffer = cb;
1106 +
1107 + return hr;
1108 +}
1109 +
1110 static HRESULT WriteStringToRegistry(
1111 __in HKEY hk,
1112 __in_z_opt LPCWSTR wzName,
@@ -1013,9 +1120,12 @@ static HRESULT WriteStringToRegistry(
1120
1121 if (wzValue)
1122 {
1016 - hr = ::StringCbLengthW(wzValue, STRSAFE_MAX_CCH * sizeof(TCHAR), &cbValue);
1123 + hr = ::StringCbLengthW(wzValue, DWORD_MAX, &cbValue);
1124 RegExitOnFailure(hr, "Failed to determine length of registry value: %ls", wzName);
1125
1126 + // Need to include the null terminator.
1127 + cbValue += sizeof(WCHAR);
1128 +
1129 er = vpfnRegSetValueExW(hk, wzName, 0, dwType, reinterpret_cast<const BYTE *>(wzValue), static_cast<DWORD>(cbValue));
1130 RegExitOnWin32Error(er, hr, "Failed to set registry value: %ls", wzName);
1131 }
src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj
+1
@@ -61,6 +61,7 @@
61 <!-- Warnings from referencing netstandard dlls -->
62 <DisableSpecificWarnings>4564;4691</DisableSpecificWarnings>
63 </ClCompile>
64 + <ClCompile Include="RegUtilTest.cpp" />
65 <ClCompile Include="SceUtilTest.cpp" Condition=" Exists('$(SqlCESdkIncludePath)') " />
66 <ClCompile Include="StrUtilTest.cpp" />
67 <ClCompile Include="UriUtilTest.cpp" />
src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj.filters
+3
@@ -54,6 +54,9 @@
54 <ClCompile Include="precomp.cpp">
55 <Filter>Source Files</Filter>
56 </ClCompile>
57 + <ClCompile Include="RegUtilTest.cpp">
58 + <Filter>Source Files</Filter>
59 + </ClCompile>
60 <ClCompile Include="StrUtilTest.cpp">
61 <Filter>Source Files</Filter>
62 </ClCompile>
src/libs/dutil/test/DUtilUnitTest/MonUtilTest.cpp
+2
@@ -481,6 +481,8 @@ namespace DutilTests
481 ReleaseMem(pResults->rgDirectories);
482 ReleaseMem(pResults->rgRegKeys);
483 ReleaseMem(pResults);
484 +
485 + RegUninitialize();
486 }
487 }
488 };
src/libs/dutil/test/DUtilUnitTest/RegUtilTest.cpp new
+679
@@ -0,0 +1,679 @@
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 +using namespace System;
6 +using namespace System::Collections::Generic;
7 +using namespace System::Runtime::InteropServices;
8 +using namespace Xunit;
9 +using namespace WixBuildTools::TestSupport;
10 +
11 +LPCWSTR wzBaseRegKey = L"Software\\RegUtilTest\\";
12 +LPWSTR rgwzMultiValue[2] = { L"First", L"Second" };
13 +LPWSTR rgwzEmptyMultiValue[2] = { L"", L"" };
14 +
15 +HKEY hkBase;
16 +
17 +namespace DutilTests
18 +{
19 + public ref class RegUtil : IDisposable
20 + {
21 + private:
22 +
23 + void CreateBaseKey()
24 + {
25 + HRESULT hr = RegCreate(HKEY_CURRENT_USER, wzBaseRegKey, KEY_ALL_ACCESS, &hkBase);
26 + NativeAssert::Succeeded(hr, "Failed to create base key.");
27 + }
28 +
29 + public:
30 + RegUtil()
31 + {
32 + HRESULT hr = RegInitialize();
33 + NativeAssert::Succeeded(hr, "RegInitialize failed.");
34 + }
35 +
36 + ~RegUtil()
37 + {
38 + RegFunctionOverride(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
39 +
40 + if (hkBase)
41 + {
42 + RegDelete(hkBase, NULL, REG_KEY_DEFAULT, TRUE);
43 + }
44 +
45 + ReleaseRegKey(hkBase);
46 +
47 + RegUninitialize();
48 + }
49 +
50 + [Fact]
51 + void RegUtilStringValueTest()
52 + {
53 + this->StringValueTest();
54 + }
55 +
56 + [Fact]
57 + void RegUtilStringValueFallbackTest()
58 + {
59 + RegFunctionForceFallback();
60 + this->StringValueTest();
61 + }
62 +
63 + void StringValueTest()
64 + {
65 + HRESULT hr = S_OK;
66 + LPWSTR sczValue = NULL;
67 + LPCWSTR wzValue = L"Value";
68 +
69 + try
70 + {
71 + this->CreateBaseKey();
72 +
73 + hr = RegWriteString(hkBase, L"String", wzValue);
74 + NativeAssert::Succeeded(hr, "Failed to write string value.");
75 +
76 + hr = RegReadString(hkBase, L"String", &sczValue);
77 + NativeAssert::Succeeded(hr, "Failed to read string value.");
78 + NativeAssert::StringEqual(wzValue, sczValue);
79 +
80 + ReleaseNullStr(sczValue);
81 + hr = StrAllocString(&sczValue, L"e", 0);
82 + NativeAssert::Succeeded(hr, "Failed to reallocate string value.");
83 +
84 + hr = RegReadString(hkBase, L"String", &sczValue);
85 + NativeAssert::Succeeded(hr, "Failed to read string value.");
86 + NativeAssert::StringEqual(wzValue, sczValue);
87 + }
88 + finally
89 + {
90 + ReleaseStr(sczValue);
91 + }
92 + }
93 +
94 + [Fact]
95 + void RegUtilPartialStringValueTest()
96 + {
97 + this->PartialStringValueTest();
98 + }
99 +
100 + [Fact]
101 + void RegUtilPartialStringValueFallbackTest()
102 + {
103 + RegFunctionForceFallback();
104 + this->PartialStringValueTest();
105 + }
106 +
107 + void PartialStringValueTest()
108 + {
109 + HRESULT hr = S_OK;
110 + LPWSTR sczValue = NULL;
111 + LPCWSTR wzValue = L"Value";
112 + BOOL fNeedsExpansion = FALSE;
113 +
114 + try
115 + {
116 + this->CreateBaseKey();
117 +
118 + // Use API directly to write non-null terminated string.
119 + hr = HRESULT_FROM_WIN32(::RegSetValueExW(hkBase, L"PartialString", 0, REG_SZ, reinterpret_cast<const BYTE*>(wzValue), 4 * sizeof(WCHAR)));
120 + NativeAssert::Succeeded(hr, "Failed to write partial string value.");
121 +
122 + hr = RegReadString(hkBase, L"PartialString", &sczValue);
123 + NativeAssert::Succeeded(hr, "Failed to read partial string value.");
124 + NativeAssert::StringEqual(L"Valu", sczValue);
125 +
126 + hr = RegReadUnexpandedString(hkBase, L"PartialString", &fNeedsExpansion, &sczValue);
127 + NativeAssert::Succeeded(hr, "Failed to read partial unexpanded string value.");
128 + NativeAssert::StringEqual(L"Valu", sczValue);
129 + Assert::False(fNeedsExpansion);
130 + }
131 + finally
132 + {
133 + ReleaseStr(sczValue);
134 + }
135 + }
136 +
137 + [Fact]
138 + void RegUtilEmptyStringValueTest()
139 + {
140 + this->EmptyStringValueTest();
141 + }
142 +
143 + [Fact]
144 + void RegUtilEmptyStringValueFallbackTest()
145 + {
146 + RegFunctionForceFallback();
147 + this->EmptyStringValueTest();
148 + }
149 +
150 + void EmptyStringValueTest()
151 + {
152 + HRESULT hr = S_OK;
153 + LPWSTR sczValue = NULL;
154 +
155 + try
156 + {
157 + this->CreateBaseKey();
158 +
159 + // Use API directly to write non-null terminated string.
160 + hr = HRESULT_FROM_WIN32(::RegSetValueExW(hkBase, L"EmptyString", 0, REG_SZ, reinterpret_cast<const BYTE*>(L""), 0));
161 + NativeAssert::Succeeded(hr, "Failed to write partial string value.");
162 +
163 + hr = RegReadString(hkBase, L"EmptyString", &sczValue);
164 + NativeAssert::Succeeded(hr, "Failed to read partial string value.");
165 + NativeAssert::StringEqual(L"", sczValue);
166 + }
167 + finally
168 + {
169 + ReleaseStr(sczValue);
170 + }
171 + }
172 +
173 + [Fact]
174 + void RegUtilExpandStringValueTest()
175 + {
176 + this->ExpandStringValueTest();
177 + }
178 +
179 + [Fact]
180 + void RegUtilExpandStringValueFallbackTest()
181 + {
182 + RegFunctionForceFallback();
183 + this->ExpandStringValueTest();
184 + }
185 +
186 + void ExpandStringValueTest()
187 + {
188 + HRESULT hr = S_OK;
189 + LPWSTR sczValue = NULL;
190 + LPCWSTR wzValue = L"Value_%USERNAME%";
191 + String^ expandedValue = Environment::ExpandEnvironmentVariables(gcnew String(wzValue));
192 +
193 + try
194 + {
195 + this->CreateBaseKey();
196 +
197 + hr = RegWriteExpandString(hkBase, L"ExpandString", wzValue);
198 + NativeAssert::Succeeded(hr, "Failed to write expand string value.");
199 +
200 + hr = RegReadString(hkBase, L"ExpandString", &sczValue);
201 + NativeAssert::Succeeded(hr, "Failed to read expand string value.");
202 + WixAssert::StringEqual(expandedValue, gcnew String(sczValue), false);
203 + }
204 + finally
205 + {
206 + ReleaseStr(sczValue);
207 + }
208 + }
209 +
210 + [Fact]
211 + void RegUtilNotExpandStringValueTest()
212 + {
213 + this->NotExpandStringValueTest();
214 + }
215 +
216 + [Fact]
217 + void RegUtilNotExpandStringValueFallbackTest()
218 + {
219 + RegFunctionForceFallback();
220 + this->NotExpandStringValueTest();
221 + }
222 +
223 + void NotExpandStringValueTest()
224 + {
225 + HRESULT hr = S_OK;
226 + LPWSTR sczValue = NULL;
227 + BOOL fNeedsExpansion = FALSE;
228 + LPCWSTR wzValue = L"Value_%USERNAME%";
229 +
230 + try
231 + {
232 + this->CreateBaseKey();
233 +
234 + hr = RegWriteExpandString(hkBase, L"NotExpandString", wzValue);
235 + NativeAssert::Succeeded(hr, "Failed to write expand string value.");
236 +
237 + hr = RegReadUnexpandedString(hkBase, L"NotExpandString", &fNeedsExpansion, &sczValue);
238 + NativeAssert::Succeeded(hr, "Failed to read expand string value.");
239 + NativeAssert::StringEqual(wzValue, sczValue);
240 + Assert::True(fNeedsExpansion);
241 + }
242 + finally
243 + {
244 + ReleaseStr(sczValue);
245 + }
246 + }
247 +
248 + [Fact]
249 + void RegUtilMultiStringValueTest()
250 + {
251 + this->MultiStringValueTest();
252 + }
253 +
254 + [Fact]
255 + void RegUtilMultiStringValueFallbackTest()
256 + {
257 + RegFunctionForceFallback();
258 + this->MultiStringValueTest();
259 + }
260 +
261 + void MultiStringValueTest()
262 + {
263 + HRESULT hr = S_OK;
264 + LPWSTR* rgsczStrings = NULL;
265 + DWORD cStrings = 0;
266 +
267 + try
268 + {
269 + this->CreateBaseKey();
270 +
271 + hr = RegWriteStringArray(hkBase, L"MultiString", rgwzMultiValue, 2);
272 + NativeAssert::Succeeded(hr, "Failed to write multi string value.");
273 +
274 + hr = RegReadStringArray(hkBase, L"MultiString", &rgsczStrings, &cStrings);
275 + NativeAssert::Succeeded(hr, "Failed to read multi string value.");
276 + Assert::Equal<DWORD>(2, cStrings);
277 + NativeAssert::StringEqual(L"First", rgsczStrings[0]);
278 + NativeAssert::StringEqual(L"Second", rgsczStrings[1]);
279 + }
280 + finally
281 + {
282 + ReleaseStrArray(rgsczStrings, cStrings);
283 + }
284 + }
285 +
286 + [Fact]
287 + void RegUtilPartialMultiStringValueTest()
288 + {
289 + this->PartialMultiStringValueTest();
290 + }
291 +
292 + [Fact]
293 + void RegUtilPartialMultiStringValueFallbackTest()
294 + {
295 + RegFunctionForceFallback();
296 + this->PartialMultiStringValueTest();
297 + }
298 +
299 + void PartialMultiStringValueTest()
300 + {
301 + HRESULT hr = S_OK;
302 + LPWSTR* rgsczStrings = NULL;
303 + DWORD cStrings = 0;
304 +
305 + try
306 + {
307 + this->CreateBaseKey();
308 +
309 + // Use API directly to write non-double-null terminated string.
310 + hr = HRESULT_FROM_WIN32(::RegSetValueExW(hkBase, L"PartialMultiString", 0, REG_MULTI_SZ, reinterpret_cast<const BYTE*>(L"First\0Second"), 13 * sizeof(WCHAR)));
311 + NativeAssert::Succeeded(hr, "Failed to write partial multi string value.");
312 +
313 + hr = RegReadStringArray(hkBase, L"PartialMultiString", &rgsczStrings, &cStrings);
314 + NativeAssert::Succeeded(hr, "Failed to read partial multi string value.");
315 + Assert::Equal<DWORD>(2, cStrings);
316 + NativeAssert::StringEqual(L"First", rgsczStrings[0]);
317 + NativeAssert::StringEqual(L"Second", rgsczStrings[1]);
318 + }
319 + finally
320 + {
321 + ReleaseStrArray(rgsczStrings, cStrings);
322 + }
323 + }
324 +
325 + [Fact]
326 + void RegUtilEmptyMultiStringValueTest()
327 + {
328 + this->EmptyMultiStringValueTest();
329 + }
330 +
331 + [Fact]
332 + void RegUtilEmptyMultiStringValueFallbackTest()
333 + {
334 + RegFunctionForceFallback();
335 + this->EmptyMultiStringValueTest();
336 + }
337 +
338 + void EmptyMultiStringValueTest()
339 + {
340 + HRESULT hr = S_OK;
341 + LPWSTR* rgsczStrings = NULL;
342 + DWORD cStrings = 0;
343 +
344 + try
345 + {
346 + this->CreateBaseKey();
347 +
348 + hr = RegWriteStringArray(hkBase, L"EmptyMultiString", rgwzMultiValue, 0);
349 + NativeAssert::Succeeded(hr, "Failed to write empty multi string value.");
350 +
351 + hr = RegReadStringArray(hkBase, L"EmptyMultiString", &rgsczStrings, &cStrings);
352 + NativeAssert::Succeeded(hr, "Failed to read empty multi string value.");
353 + Assert::Equal<DWORD>(0, cStrings);
354 + }
355 + finally
356 + {
357 + ReleaseStrArray(rgsczStrings, cStrings);
358 + }
359 + }
360 +
361 + [Fact]
362 + void RegUtilOneEmptyMultiStringValueTest()
363 + {
364 + this->OneEmptyMultiStringValueTest();
365 + }
366 +
367 + [Fact]
368 + void RegUtilOneEmptyMultiStringValueFallbackTest()
369 + {
370 + RegFunctionForceFallback();
371 + this->OneEmptyMultiStringValueTest();
372 + }
373 +
374 + void OneEmptyMultiStringValueTest()
375 + {
376 + HRESULT hr = S_OK;
377 + LPWSTR* rgsczStrings = NULL;
378 + DWORD cStrings = 0;
379 +
380 + try
381 + {
382 + this->CreateBaseKey();
383 +
384 + hr = RegWriteStringArray(hkBase, L"OneEmptyMultiString", rgwzEmptyMultiValue, 1);
385 + NativeAssert::Succeeded(hr, "Failed to write one empty multi string value.");
386 +
387 + hr = RegReadStringArray(hkBase, L"OneEmptyMultiString", &rgsczStrings, &cStrings);
388 + NativeAssert::Succeeded(hr, "Failed to read one empty multi string value.");
389 + Assert::Equal<DWORD>(0, cStrings);
390 + }
391 + finally
392 + {
393 + ReleaseStrArray(rgsczStrings, cStrings);
394 + }
395 + }
396 +
397 + [Fact]
398 + void RegUtilTwoEmptyMultiStringValueTest()
399 + {
400 + this->TwoEmptyMultiStringValueTest();
401 + }
402 +
403 + [Fact]
404 + void RegUtilTwoEmptyMultiStringValueFallbackTest()
405 + {
406 + RegFunctionForceFallback();
407 + this->TwoEmptyMultiStringValueTest();
408 + }
409 +
410 + void TwoEmptyMultiStringValueTest()
411 + {
412 + HRESULT hr = S_OK;
413 + LPWSTR* rgsczStrings = NULL;
414 + DWORD cStrings = 0;
415 +
416 + try
417 + {
418 + this->CreateBaseKey();
419 +
420 + hr = RegWriteStringArray(hkBase, L"OneEmptyMultiString", rgwzEmptyMultiValue, 2);
421 + NativeAssert::Succeeded(hr, "Failed to write one empty multi string value.");
422 +
423 + hr = RegReadStringArray(hkBase, L"OneEmptyMultiString", &rgsczStrings, &cStrings);
424 + NativeAssert::Succeeded(hr, "Failed to read one empty multi string value.");
425 + Assert::Equal<DWORD>(2, cStrings);
426 + NativeAssert::StringEqual(L"", rgsczStrings[0]);
427 + NativeAssert::StringEqual(L"", rgsczStrings[1]);
428 + }
429 + finally
430 + {
431 + ReleaseStrArray(rgsczStrings, cStrings);
432 + }
433 + }
434 +
435 + [Fact]
436 + void RegUtilOnePartialEmptyMultiStringValueTest()
437 + {
438 + this->OnePartialEmptyMultiStringValueTest();
439 + }
440 +
441 + [Fact]
442 + void RegUtilOnePartialEmptyMultiStringValueFallbackTest()
443 + {
444 + RegFunctionForceFallback();
445 + this->OnePartialEmptyMultiStringValueTest();
446 + }
447 +
448 + void OnePartialEmptyMultiStringValueTest()
449 + {
450 + HRESULT hr = S_OK;
451 + LPWSTR* rgsczStrings = NULL;
452 + DWORD cStrings = 0;
453 +
454 + try
455 + {
456 + this->CreateBaseKey();
457 +
458 + // Use API directly to write non-double-null terminated string.
459 + hr = HRESULT_FROM_WIN32(::RegSetValueExW(hkBase, L"OnePartialEmptyMultiString", 0, REG_MULTI_SZ, reinterpret_cast<const BYTE*>(L""), 1 * sizeof(WCHAR)));
460 + NativeAssert::Succeeded(hr, "Failed to write partial empty multi string value.");
461 +
462 + hr = RegReadStringArray(hkBase, L"OnePartialEmptyMultiString", &rgsczStrings, &cStrings);
463 + NativeAssert::Succeeded(hr, "Failed to read partial empty multi string value.");
464 + Assert::Equal<DWORD>(0, cStrings);
465 + }
466 + finally
467 + {
468 + ReleaseStrArray(rgsczStrings, cStrings);
469 + }
470 + }
471 +
472 + [Fact]
473 + void RegUtilBinaryValueTest()
474 + {
475 + this->BinaryValueTest();
476 + }
477 +
478 + [Fact]
479 + void RegUtilBinaryValueFallbackTest()
480 + {
481 + RegFunctionForceFallback();
482 + this->BinaryValueTest();
483 + }
484 +
485 + void BinaryValueTest()
486 + {
487 + HRESULT hr = S_OK;
488 + BYTE pbSource[4] = { 1, 2, 3, 4 };
489 + BYTE* pbBuffer = NULL;
490 + SIZE_T cbBuffer = 0;
491 +
492 + try
493 + {
494 + this->CreateBaseKey();
495 +
496 + hr = RegWriteBinary(hkBase, L"Binary", pbSource, 4);
497 + NativeAssert::Succeeded(hr, "Failed to write binary value.");
498 +
499 + hr = RegReadBinary(hkBase, L"Binary", &pbBuffer, &cbBuffer);
500 + NativeAssert::Succeeded(hr, "Failed to read binary value.");
501 + Assert::Equal<DWORD>(4, cbBuffer);
502 + Assert::Equal<BYTE>(1, pbBuffer[0]);
503 + Assert::Equal<BYTE>(2, pbBuffer[1]);
504 + Assert::Equal<BYTE>(3, pbBuffer[2]);
505 + Assert::Equal<BYTE>(4, pbBuffer[3]);
506 + }
507 + finally
508 + {
509 + ReleaseMem(pbBuffer);
510 + }
511 + }
512 +
513 + [Fact]
514 + void RegUtilEmptyBinaryValueTest()
515 + {
516 + this->EmptyBinaryValueTest();
517 + }
518 +
519 + [Fact]
520 + void RegUtilEmptyBinaryValueFallbackTest()
521 + {
522 + RegFunctionForceFallback();
523 + this->EmptyBinaryValueTest();
524 + }
525 +
526 + void EmptyBinaryValueTest()
527 + {
528 + HRESULT hr = S_OK;
529 + BYTE* pbBuffer = NULL;
530 + SIZE_T cbBuffer = 0;
531 +
532 + try
533 + {
534 + this->CreateBaseKey();
535 +
536 + hr = RegWriteBinary(hkBase, L"Binary", NULL, 0);
537 + NativeAssert::Succeeded(hr, "Failed to write binary value.");
538 +
539 + hr = RegReadBinary(hkBase, L"Binary", &pbBuffer, &cbBuffer);
540 + NativeAssert::Succeeded(hr, "Failed to read binary value.");
541 + Assert::Equal<DWORD>(0, cbBuffer);
542 + }
543 + finally
544 + {
545 + ReleaseMem(pbBuffer);
546 + }
547 + }
548 +
549 + [Fact]
550 + void RegUtilQwordVersionValueTest()
551 + {
552 + this->QwordVersionValueTest();
553 + }
554 +
555 + [Fact]
556 + void RegUtilQwordVersionValueFallbackTest()
557 + {
558 + RegFunctionForceFallback();
559 + this->QwordVersionValueTest();
560 + }
561 +
562 + void QwordVersionValueTest()
563 + {
564 + HRESULT hr = S_OK;
565 + DWORD64 qwVersion = FILEMAKEVERSION(1, 2, 3, 4);
566 + DWORD64 qwValue = 0;
567 +
568 + this->CreateBaseKey();
569 +
570 + hr = RegWriteQword(hkBase, L"QwordVersion", qwVersion);
571 + NativeAssert::Succeeded(hr, "Failed to write qword version value.");
572 +
573 + hr = RegReadVersion(hkBase, L"QwordVersion", &qwValue);
574 + NativeAssert::Succeeded(hr, "Failed to read qword version value.");
575 + Assert::Equal<DWORD64>(qwVersion, qwValue);
576 + }
577 +
578 + [Fact]
579 + void RegUtilStringVersionValueTest()
580 + {
581 + this->StringVersionValueTest();
582 + }
583 +
584 + [Fact]
585 + void RegUtilStringVersionValueFallbackTest()
586 + {
587 + RegFunctionForceFallback();
588 + this->StringVersionValueTest();
589 + }
590 +
591 + void StringVersionValueTest()
592 + {
593 + HRESULT hr = S_OK;
594 + LPCWSTR wzVersion = L"65535.65535.65535.65535";
595 + DWORD64 qwValue = 0;
596 +
597 + this->CreateBaseKey();
598 +
599 + hr = RegWriteString(hkBase, L"StringVersion", wzVersion);
600 + NativeAssert::Succeeded(hr, "Failed to write string version value.");
601 +
602 + hr = RegReadVersion(hkBase, L"StringVersion", &qwValue);
603 + NativeAssert::Succeeded(hr, "Failed to read string version value.");
604 + Assert::Equal<DWORD64>(MAXDWORD64, qwValue);
605 + }
606 +
607 + [Fact]
608 + void RegUtilQwordWixVersionValueTest()
609 + {
610 + this->QwordWixVersionValueTest();
611 + }
612 +
613 + [Fact]
614 + void RegUtilQwordWixVersionValueFallbackTest()
615 + {
616 + RegFunctionForceFallback();
617 + this->QwordWixVersionValueTest();
618 + }
619 +
620 + void QwordWixVersionValueTest()
621 + {
622 + HRESULT hr = S_OK;
623 + DWORD64 qwVersion = FILEMAKEVERSION(1, 2, 3, 4);
624 + VERUTIL_VERSION* pVersion = NULL;
625 +
626 + try
627 + {
628 + this->CreateBaseKey();
629 +
630 + hr = RegWriteQword(hkBase, L"QwordWixVersion", qwVersion);
631 + NativeAssert::Succeeded(hr, "Failed to write qword wix version value.");
632 +
633 + hr = RegReadWixVersion(hkBase, L"QwordWixVersion", &pVersion);
634 + NativeAssert::Succeeded(hr, "Failed to read qword wix version value.");
635 + NativeAssert::StringEqual(L"1.2.3.4", pVersion->sczVersion);
636 + }
637 + finally
638 + {
639 + ReleaseVerutilVersion(pVersion);
640 + }
641 + }
642 +
643 + [Fact]
644 + void RegUtilStringWixVersionValueTest()
645 + {
646 + this->StringWixVersionValueTest();
647 + }
648 +
649 + [Fact]
650 + void RegUtilStringWixVersionValueFallbackTest()
651 + {
652 + RegFunctionForceFallback();
653 + this->StringWixVersionValueTest();
654 + }
655 +
656 + void StringWixVersionValueTest()
657 + {
658 + HRESULT hr = S_OK;
659 + LPCWSTR wzVersion = L"65535.65535.65535.65535-abc+def";
660 + VERUTIL_VERSION* pVersion = NULL;
661 +
662 + try
663 + {
664 + this->CreateBaseKey();
665 +
666 + hr = RegWriteString(hkBase, L"StringWixVersion", wzVersion);
667 + NativeAssert::Succeeded(hr, "Failed to write string wix version value.");
668 +
669 + hr = RegReadWixVersion(hkBase, L"StringWixVersion", &pVersion);
670 + NativeAssert::Succeeded(hr, "Failed to read string wix version value.");
671 + NativeAssert::StringEqual(wzVersion, pVersion->sczVersion);
672 + }
673 + finally
674 + {
675 + ReleaseVerutilVersion(pVersion);
676 + }
677 + }
678 + };
679 +}