From f3c96bcab560cb09355e9366eac3f4195479d95d Mon Sep 17 00:00:00 2001 From: Jacob Hoover Date: Fri, 11 Jun 2021 17:05:06 -0500 Subject: Allow access to persisted variables from related bundles. Implements #3704 --- src/burn/engine/registration.cpp | 92 ++++++++++++ src/burn/test/BurnUnitTest/RegistrationTest.cpp | 178 ++++++++++++++++++++++-- src/burn/test/BurnUnitTest/precomp.h | 1 + src/libs/dutil/WixToolset.DUtil/butil.cpp | 105 ++++++++++++-- src/libs/dutil/WixToolset.DUtil/inc/butil.h | 11 +- src/libs/dutil/WixToolset.DUtil/inc/regutil.h | 8 ++ src/libs/dutil/WixToolset.DUtil/regutil.cpp | 47 +++++++ 7 files changed, 420 insertions(+), 22 deletions(-) diff --git a/src/burn/engine/registration.cpp b/src/burn/engine/registration.cpp index eed1fee2..4088004d 100644 --- a/src/burn/engine/registration.cpp +++ b/src/burn/engine/registration.cpp @@ -32,6 +32,7 @@ const LPCWSTR REGISTRY_BUNDLE_RESUME_COMMAND_LINE = L"BundleResumeCommandLine"; const LPCWSTR REGISTRY_BUNDLE_VERSION_MAJOR = L"VersionMajor"; const LPCWSTR REGISTRY_BUNDLE_VERSION_MINOR = L"VersionMinor"; const LPCWSTR SWIDTAG_FOLDER = L"swidtag"; +const LPCWSTR REGISTRY_BUNDLE_VARIABLE_KEY = L"variables"; // internal function declarations @@ -909,6 +910,7 @@ extern "C" HRESULT RegistrationSessionEnd( { HRESULT hr = S_OK; LPWSTR sczRebootRequiredKey = NULL; + LPWSTR sczVariableKey = NULL; HKEY hkRebootRequired = NULL; HKEY hkRegistration = NULL; @@ -956,6 +958,17 @@ extern "C" HRESULT RegistrationSessionEnd( RemoveSoftwareTags(pVariables, &pRegistration->softwareTags); + // build variable registry key path + hr = StrAllocFormatted(&sczVariableKey, L"%s\\%s", pRegistration->sczRegistrationKey, REGISTRY_BUNDLE_VARIABLE_KEY); + ExitOnFailure(hr, "Failed to build variable registry key path."); + + // Delete registration variable key. + hr = RegDelete(pRegistration->hkRoot, sczVariableKey, REG_KEY_DEFAULT, FALSE); + if (E_FILENOTFOUND != hr) + { + ExitOnFailure(hr, "Failed to delete registration variable key: %ls", sczVariableKey); + } + // Delete registration key. hr = RegDelete(pRegistration->hkRoot, pRegistration->sczRegistrationKey, REG_KEY_DEFAULT, FALSE); if (E_FILENOTFOUND != hr) @@ -985,6 +998,7 @@ extern "C" HRESULT RegistrationSessionEnd( LExit: ReleaseRegKey(hkRegistration); ReleaseRegKey(hkRebootRequired); + ReleaseStr(sczVariableKey); ReleaseStr(sczRebootRequiredKey); return hr; @@ -1001,6 +1015,15 @@ extern "C" HRESULT RegistrationSaveState( ) { HRESULT hr = S_OK; + BURN_VARIABLES variables = { }; + SIZE_T iBuffer_Unused = 0; + HKEY hkRegistration = NULL; + LPWSTR sczVariableKey = NULL; + LPWSTR sczVariableValue = NULL; + LPWSTR sczValueName = NULL; + DWORD dwType = 0; + DWORD dwNumberOfExistingValues = 0; + // write data to file hr = FileWrite(pRegistration->sczStateFile, FILE_ATTRIBUTE_NORMAL, pbBuffer, cbBuffer, NULL); @@ -1011,7 +1034,76 @@ extern "C" HRESULT RegistrationSaveState( } ExitOnFailure(hr, "Failed to write state to file: %ls", pRegistration->sczStateFile); + ::InitializeCriticalSection(&variables.csAccess); + + hr = VariableDeserialize(&variables, TRUE, pbBuffer, cbBuffer, &iBuffer_Unused); + ExitOnFailure(hr, "Failed to read variables."); + + // build variable registry key path + hr = StrAllocFormatted(&sczVariableKey, L"%s\\%s", pRegistration->sczRegistrationKey, REGISTRY_BUNDLE_VARIABLE_KEY); + ExitOnFailure(hr, "Failed to build variable registry key path."); + + // open registration variable key + hr = RegCreate(pRegistration->hkRoot, sczVariableKey, KEY_WRITE | KEY_QUERY_VALUE, &hkRegistration); + ExitOnFailure(hr, "Failed to create registration variable key."); + + hr = RegQueryInfoKey(hkRegistration, 0, 0, 0, 0, 0, 0, &dwNumberOfExistingValues, 0, 0, 0, 0); + ExitOnFailure(hr, "Failed to query registration variable count."); + + for (DWORD i = dwNumberOfExistingValues; i >= 0; --i) + { + hr = RegValueEnum(hkRegistration, i, &sczValueName, &dwType); + + if (E_NOMOREITEMS == hr) + { + hr = S_OK; + break; + } + + ExitOnFailure(hr, "Failed to enumerate value %u", i); + + hr = RegDeleteValue(hkRegistration, sczValueName); + ExitOnFailure(hr, "Failed to delete registration variable value."); + } + + // Write variables. + for (DWORD i = 0; i < variables.cVariables; ++i) + { + BURN_VARIABLE* pVariable = &variables.rgVariables[i]; + + // Write variable value. + switch (pVariable->Value.Type) + { + case BURN_VARIANT_TYPE_NONE: + hr = RegWriteNone(hkRegistration, pVariable->sczName); + ExitOnFailure(hr, "Failed to set variable value."); + break; + case BURN_VARIANT_TYPE_NUMERIC: __fallthrough; + case BURN_VARIANT_TYPE_VERSION: __fallthrough; + case BURN_VARIANT_TYPE_FORMATTED: __fallthrough; + case BURN_VARIANT_TYPE_STRING: + hr = BVariantGetString(&pVariable->Value, &sczVariableValue); + ExitOnFailure(hr, "Failed to get variable value."); + + hr = RegWriteString(hkRegistration, pVariable->sczName, sczVariableValue); + ExitOnFailure(hr, "Failed to set variable value."); + + ReleaseNullStrSecure(sczVariableValue); + + break; + default: + hr = E_INVALIDARG; + ExitOnFailure(hr, "Unsupported variable type."); + } + + } LExit: + VariablesUninitialize(&variables); + ReleaseStr(sczValueName); + ReleaseStr(sczVariableValue); + ReleaseStr(sczVariableKey); + ReleaseRegKey(hkRegistration); + return hr; } diff --git a/src/burn/test/BurnUnitTest/RegistrationTest.cpp b/src/burn/test/BurnUnitTest/RegistrationTest.cpp index 96bdb2bf..298d4631 100644 --- a/src/burn/test/BurnUnitTest/RegistrationTest.cpp +++ b/src/burn/test/BurnUnitTest/RegistrationTest.cpp @@ -8,9 +8,12 @@ #define HKCU_PATH L"SOFTWARE\\WiX_Burn_UnitTest\\HKCU" #define REGISTRY_UNINSTALL_KEY L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall" #define REGISTRY_RUN_KEY L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce" +#define TEST_BUNDLE_ID L"{D54F896D-1952-43e6-9C67-B5652240618C}" +#define TEST_BUNDLE_UPGRADE_CODE L"{89FDAE1F-8CC1-48B9-B930-3945E0D3E7F0}" #define TEST_UNINSTALL_KEY L"HKEY_CURRENT_USER\\" HKCU_PATH L"\\" REGISTRY_UNINSTALL_KEY L"\\{D54F896D-1952-43e6-9C67-B5652240618C}" #define TEST_RUN_KEY L"HKEY_CURRENT_USER\\" HKCU_PATH L"\\" REGISTRY_RUN_KEY +#define TEST_VARIABLE_KEY L"HKEY_CURRENT_USER\\" HKCU_PATH L"\\" REGISTRY_UNINSTALL_KEY L"\\{D54F896D-1952-43e6-9C67-B5652240618C}\\variables" static LSTATUS APIENTRY RegistrationTest_RegCreateKeyExW( @@ -496,30 +499,153 @@ namespace Bootstrapper } } - [Fact(Skip = "Currently fails")] - void ResumeTest() + [Fact] + void DUtilButilTest() { HRESULT hr = S_OK; IXMLDOMElement* pixeBundle = NULL; LPWSTR sczCurrentProcess = NULL; + LPWSTR sczValue = NULL; + LPWSTR sczRelatedBundleId = NULL; + DWORD dwRelatedBundleIndex = 0; BURN_VARIABLES variables = { }; BURN_USER_EXPERIENCE userExperience = { }; BOOTSTRAPPER_COMMAND command = { }; BURN_REGISTRATION registration = { }; BURN_LOGGING logging = { }; BURN_PACKAGES packages = { }; - BYTE rgbData[256] = { }; - BOOTSTRAPPER_RESUME_TYPE resumeType = BOOTSTRAPPER_RESUME_TYPE_NONE; BYTE* pbBuffer = NULL; SIZE_T cbBuffer = 0; + String^ cacheDirectory = Path::Combine(Path::Combine(Environment::GetFolderPath(Environment::SpecialFolder::LocalApplicationData), gcnew String(L"Package Cache")), gcnew String(L"{D54F896D-1952-43e6-9C67-B5652240618C}")); try { - for (DWORD i = 0; i < 256; ++i) + // set mock API's + RegFunctionOverride(RegistrationTest_RegCreateKeyExW, RegistrationTest_RegOpenKeyExW, RegistrationTest_RegDeleteKeyExW, NULL, NULL, NULL, NULL, NULL, NULL); + + Registry::CurrentUser->CreateSubKey(gcnew String(HKCU_PATH)); + + logging.sczPath = L"BurnUnitTest.txt"; + + LPCWSTR wzDocument = + L"" + L" " + L" " + L" " + L" " + L" " + L" " + L" " + L" "; + + // load XML document + LoadBundleXmlHelper(wzDocument, &pixeBundle); + + hr = VariableInitialize(&variables); + TestThrowOnFailure(hr, L"Failed to initialize variables."); + + hr = VariablesParseFromXml(&variables, pixeBundle); + TestThrowOnFailure(hr, L"Failed to parse variables from XML."); + + hr = UserExperienceParseFromXml(&userExperience, pixeBundle); + TestThrowOnFailure(hr, L"Failed to parse UX from XML."); + + hr = RegistrationParseFromXml(®istration, pixeBundle); + TestThrowOnFailure(hr, L"Failed to parse registration from XML."); + + hr = PlanSetResumeCommand(®istration, BOOTSTRAPPER_ACTION_INSTALL, &command, &logging); + TestThrowOnFailure(hr, L"Failed to set registration resume command."); + + hr = PathForCurrentProcess(&sczCurrentProcess, NULL); + TestThrowOnFailure(hr, L"Failed to get current process path."); + + // begin session + hr = RegistrationSessionBegin(sczCurrentProcess, ®istration, &variables, BURN_REGISTRATION_ACTION_OPERATIONS_WRITE_REGISTRATION, BURN_DEPENDENCY_REGISTRATION_ACTION_REGISTER, 0, BOOTSTRAPPER_REGISTRATION_TYPE_INPROGRESS); + TestThrowOnFailure(hr, L"Failed to register bundle."); + + VariableSetNumericHelper(&variables, L"MyBurnVariable1", 42); + VariableSetStringHelper(&variables, L"MyBurnVariable2", L"bar", FALSE); + VariableSetVersionHelper(&variables, L"MyBurnVariable3", L"v1.0-beta"); + + hr = VariableSerialize(&variables, TRUE, &pbBuffer, &cbBuffer); + TestThrowOnFailure(hr, "Failed to serialize variables."); + + if (!Directory::Exists(cacheDirectory)) { - rgbData[i] = (BYTE)i; + Directory::CreateDirectory(cacheDirectory); } + hr = RegistrationSaveState(®istration, pbBuffer, cbBuffer); + TestThrowOnFailure(hr, L"Failed to save state."); + + ReleaseNullBuffer(pbBuffer); + cbBuffer = 0; + // Verify the variables exist + Assert::Equal(gcnew String(L"42"), (String^)Registry::GetValue(gcnew String(TEST_VARIABLE_KEY), gcnew String(L"MyBurnVariable1"), nullptr)); + Assert::Equal(gcnew String(L"bar"), (String^)Registry::GetValue(gcnew String(TEST_VARIABLE_KEY), gcnew String(L"MyBurnVariable2"), nullptr)); + Assert::Equal(gcnew String(L"1.0-beta"), (String^)Registry::GetValue(gcnew String(TEST_VARIABLE_KEY), gcnew String(L"MyBurnVariable3"), nullptr)); + Assert::Empty((System::Collections::IEnumerable ^)Registry::GetValue(gcnew String(TEST_VARIABLE_KEY), gcnew String(L"WixBundleForcedRestartPackage"), nullptr)); + + hr = StrAlloc(&sczRelatedBundleId, MAX_GUID_CHARS + 1); + + // Verify we can find ourself via the UpgradeCode + hr = BundleEnumRelatedBundle(TEST_BUNDLE_UPGRADE_CODE, BUNDLE_INSTALL_CONTEXT_USER, &dwRelatedBundleIndex, sczRelatedBundleId); + TestThrowOnFailure(hr, L"Failed to enumerate related bundle."); + Assert::Equal(gcnew String(TEST_BUNDLE_ID), gcnew String(sczRelatedBundleId)); + + // Verify we can read the bundle variables via the API + hr = BundleGetBundleVariable(TEST_BUNDLE_ID, L"MyBurnVariable1", &sczValue); + TestThrowOnFailure(hr, L"Failed to read MyBurnVariable1."); + Assert::Equal(gcnew String(L"42"), gcnew String(sczValue)); + + // end session + hr = RegistrationSessionEnd(®istration, &variables, &packages, BURN_RESUME_MODE_NONE, BOOTSTRAPPER_APPLY_RESTART_NONE, BURN_DEPENDENCY_REGISTRATION_ACTION_UNREGISTER, BOOTSTRAPPER_REGISTRATION_TYPE_NONE); + TestThrowOnFailure(hr, L"Failed to unregister bundle."); + } + finally + { + ReleaseStr(sczRelatedBundleId); + ReleaseStr(sczCurrentProcess); + ReleaseObject(pixeBundle); + UserExperienceUninitialize(&userExperience); + RegistrationUninitialize(®istration); + VariablesUninitialize(&variables); + + Registry::CurrentUser->DeleteSubKeyTree(gcnew String(ROOT_PATH)); + if (Directory::Exists(cacheDirectory)) + { + Directory::Delete(cacheDirectory, true); + } + + RegFunctionOverride(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + } + } + + [Fact]//(Skip = "Currently fails")] + void ResumeTest() + { + HRESULT hr = S_OK; + IXMLDOMElement* pixeBundle = NULL; + LPWSTR sczCurrentProcess = NULL; + LPWSTR sczValue = NULL; + BURN_VARIABLES variables = { }; + BURN_USER_EXPERIENCE userExperience = { }; + BOOTSTRAPPER_COMMAND command = { }; + BURN_REGISTRATION registration = { }; + BURN_LOGGING logging = { }; + BURN_PACKAGES packages = { }; + BOOTSTRAPPER_RESUME_TYPE resumeType = BOOTSTRAPPER_RESUME_TYPE_NONE; + BYTE* pbBuffer = NULL; + SIZE_T cbBuffer = 0; + SIZE_T piBuffer = 0; + String^ cacheDirectory = Path::Combine(Path::Combine(Environment::GetFolderPath(Environment::SpecialFolder::LocalApplicationData), gcnew String(L"Package Cache")), gcnew String(L"{D54F896D-1952-43e6-9C67-B5652240618C}")); + try + { // set mock API's RegFunctionOverride(RegistrationTest_RegCreateKeyExW, RegistrationTest_RegOpenKeyExW, RegistrationTest_RegDeleteKeyExW, NULL, NULL, NULL, NULL, NULL, NULL); @@ -535,6 +661,10 @@ namespace Bootstrapper L" " L" " L" " + L"