diff --git a/include/wil/registry.h b/include/wil/registry.h index 94728a22..746601cb 100644 --- a/include/wil/registry.h +++ b/include/wil/registry.h @@ -423,6 +423,48 @@ namespace reg return S_OK; } + /** + * @brief Queries whether the specified registry key is volatile (created with REG_OPTION_VOLATILE and therefore not + * persisted across a reboot) + * @param key The HKEY to query + * @param[out] isVolatile Receives true if the key is volatile, false otherwise. Only valid on success. + * @return HRESULT error code indicating success or failure (does not throw C++ exceptions) + * @remark There is no Win32 API that reports key volatility; this relies on NtQueryKey + KeyFlagsInformation, which + * are not declared by the public SDK. NtQueryKey is resolved dynamically from ntdll so that callers are not + * required to link against ntdll, and the minimal information class/struct are declared locally. + */ + inline HRESULT is_key_volatile_nothrow(HKEY key, _Out_ bool* isVolatile) WI_NOEXCEPT + { + *isVolatile = false; + + // KEY_INFORMATION_CLASS::KeyFlagsInformation and KEY_FLAGS_INFORMATION (from wdm.h) are not part of the public + // SDK, so the minimal definitions are reproduced here. + constexpr int c_KeyFlagsInformation = 5; + struct KEY_FLAGS_INFORMATION_t + { + ULONG Wow64Flags; + ULONG KeyFlags; + ULONG ControlFlags; + }; + using NtQueryKey_t = LONG(__stdcall*)(HANDLE, int, PVOID, ULONG, PULONG); + + const auto ntdll = ::GetModuleHandleW(L"ntdll.dll"); + RETURN_HR_IF_NULL(E_NOINTERFACE, ntdll); + const auto pfnNtQueryKey = ::wil::details::GetProcAddress(ntdll, "NtQueryKey"); + RETURN_HR_IF_NULL(E_NOINTERFACE, pfnNtQueryKey); + + KEY_FLAGS_INFORMATION_t info{}; + ULONG resultSize{}; + const LONG status = pfnNtQueryKey(key, c_KeyFlagsInformation, &info, sizeof(info), &resultSize); + if (status < 0) + { + RETURN_HR(HRESULT_FROM_NT(status)); + } + + *isVolatile = WI_IsFlagSet(info.KeyFlags, REG_OPTION_VOLATILE); + return S_OK; + } + #if defined(WIL_ENABLE_EXCEPTIONS) /** * @brief Queries for number of sub-keys @@ -462,6 +504,20 @@ namespace reg THROW_IF_FAILED(::wil::reg::get_last_write_filetime_nothrow(key, &lastModified)); return lastModified; } + + /** + * @brief Queries whether the specified registry key is volatile (created with REG_OPTION_VOLATILE and therefore not + * persisted across a reboot) + * @param key The HKEY to query + * @return true if the key is volatile, false otherwise + * @exception std::exception (including wil::ResultException) will be thrown on all failures + */ + inline bool is_key_volatile(HKEY key) + { + bool isVolatile{}; + THROW_IF_FAILED(::wil::reg::is_key_volatile_nothrow(key, &isVolatile)); + return isVolatile; + } #endif // #if defined(WIL_ENABLE_EXCEPTIONS) #if defined(WIL_ENABLE_EXCEPTIONS) diff --git a/tests/RegistryTests.cpp b/tests/RegistryTests.cpp index a075d2e0..d6e95f3c 100644 --- a/tests/RegistryTests.cpp +++ b/tests/RegistryTests.cpp @@ -6539,3 +6539,61 @@ TEST_CASE("BasicRegistryTests::key_heap_string_nothrow_iterator", "[registry]") REQUIRE(test_iterator == test_end_iterator); } } + +TEST_CASE("BasicRegistryTests::is_key_volatile", "[registry]") +{ + const auto deleteHr = HRESULT_FROM_WIN32(::RegDeleteTreeW(HKEY_CURRENT_USER, testSubkey)); + if (deleteHr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) + { + REQUIRE_SUCCEEDED(deleteHr); + } + + SECTION("is_key_volatile_nothrow: non-volatile key") + { + wil::unique_hkey hkey; + DWORD disposition{}; + REQUIRE_SUCCEEDED(HRESULT_FROM_WIN32( + ::RegCreateKeyExW( + HKEY_CURRENT_USER, testSubkey, 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_READ, nullptr, hkey.put(), &disposition))); + + bool isVolatile = true; + REQUIRE_SUCCEEDED(wil::reg::is_key_volatile_nothrow(hkey.get(), &isVolatile)); + REQUIRE(isVolatile == false); + } + + SECTION("is_key_volatile_nothrow: volatile key") + { + wil::unique_hkey hkey; + DWORD disposition{}; + REQUIRE_SUCCEEDED(HRESULT_FROM_WIN32( + ::RegCreateKeyExW(HKEY_CURRENT_USER, testSubkey, 0, nullptr, REG_OPTION_VOLATILE, KEY_READ, nullptr, hkey.put(), &disposition))); + REQUIRE(disposition == REG_CREATED_NEW_KEY); + + bool isVolatile = false; + REQUIRE_SUCCEEDED(wil::reg::is_key_volatile_nothrow(hkey.get(), &isVolatile)); + REQUIRE(isVolatile == true); + } + +#ifdef WIL_ENABLE_EXCEPTIONS + SECTION("is_key_volatile: throwing variant") + { + // non-volatile parent so that both a volatile and a non-volatile child can be created beneath it + wil::unique_hkey parent; + DWORD disposition{}; + REQUIRE_SUCCEEDED(HRESULT_FROM_WIN32( + ::RegCreateKeyExW( + HKEY_CURRENT_USER, testSubkey, 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_READ, nullptr, parent.put(), &disposition))); + + wil::unique_hkey volatileKey; + REQUIRE_SUCCEEDED(HRESULT_FROM_WIN32( + ::RegCreateKeyExW(parent.get(), L"vol", 0, nullptr, REG_OPTION_VOLATILE, KEY_READ, nullptr, volatileKey.put(), &disposition))); + REQUIRE(wil::reg::is_key_volatile(volatileKey.get()) == true); + + wil::unique_hkey persistentKey; + REQUIRE_SUCCEEDED(HRESULT_FROM_WIN32( + ::RegCreateKeyExW( + parent.get(), L"persist", 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_READ, nullptr, persistentKey.put(), &disposition))); + REQUIRE(wil::reg::is_key_volatile(persistentKey.get()) == false); + } +#endif +}