//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the MIT License.
//    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
//    ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
//    TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
//    PARTICULAR PURPOSE AND NONINFRINGEMENT.
//
//*********************************************************
//! @file
//! Helpers for reading and writing values to/from the registry.
#ifndef __WIL_REGISTRY_INCLUDED
#define __WIL_REGISTRY_INCLUDED

#ifdef _KERNEL_MODE
#error This header is not supported in kernel-mode.
#endif

#include <winreg.h>
#include <new.h> // new(std::nothrow)
#include "registry_helpers.h"
#include "resource.h"

// wil registry does not require the use of the STL or C++ exceptions (see _nothrow functions)
// wil registry natively supports std::vector and std::wstring when preferring those types
// wil registry uses the __WIL_WINREG_STL define to enable support for wil::shared_* types (defined in resource.h)

namespace wil
{
//! Functions and classes that support reading and writing values to/from the registry.
namespace reg
{
#if defined(WIL_ENABLE_EXCEPTIONS)
    /**
     * @brief Opens a new HKEY to the specified path - see RegOpenKeyExW
     * @param key An open or well-known registry key
     * @param subKey The name of the registry subkey to be opened.
     *        If `nullptr`, then `key` is used without modification.
     * @param access The requested access desired for the opened key
     * @return A wil::unique_hkey containing the resulting opened HKEY
     * @exception std::exception (including wil::ResultException) will be thrown on all failures
     */
    inline ::wil::unique_hkey open_unique_key(HKEY key, _In_opt_ PCWSTR subKey, ::wil::reg::key_access access = ::wil::reg::key_access::read)
    {
        const reg_view_details::reg_view regview{key};
        ::wil::unique_hkey return_value;
        regview.open_key(subKey, &return_value, access);
        return return_value;
    }

    /**
     * @brief Creates a new HKEY to the specified path - see RegCreateKeyExW
     * @param key An open or well-known registry key
     * @param subKey The name of a subkey that this function opens or creates.
     *        Note: this cannot be null (see the above referenced API documentation)
     * @param access The requested access desired for the opened key
     * @return A wil::unique_hkey or wil::shared_hkey containing the resulting opened HKEY
     * @exception std::exception (including wil::ResultException) will be thrown on all failures
     */
    inline ::wil::unique_hkey create_unique_key(HKEY key, PCWSTR subKey, ::wil::reg::key_access access = ::wil::reg::key_access::read)
    {
        const reg_view_details::reg_view regview{key};
        ::wil::unique_hkey return_value;
        regview.create_key(subKey, &return_value, access);
        return return_value;
    }

#if defined(__WIL_WINREG_STL) || defined(WIL_DOXYGEN)
    /**
     * @brief Opens a new HKEY to the specified path - see RegOpenKeyExW
     * @param key An open or well-known registry key
     * @param subKey The name of the registry subkey to be opened.
     *        If `nullptr`, then `key` is used without modification.
     * @param access The requested access desired for the opened key
     * @return A wil::shared_hkey containing the resulting opened HKEY
     * @exception std::exception (including wil::ResultException) will be thrown on all failures
     */
    inline ::wil::shared_hkey open_shared_key(HKEY key, _In_opt_ PCWSTR subKey, ::wil::reg::key_access access = ::wil::reg::key_access::read)
    {
        const reg_view_details::reg_view regview{key};
        ::wil::shared_hkey return_value;
        regview.open_key(subKey, &return_value, access);
        return return_value;
    }

    /**
     * @brief Creates a new HKEY to the specified path - see RegCreateKeyExW
     * @param key An open or well-known registry key
     * @param subKey The name of a subkey that this function opens or creates.
     *        Note: this cannot be null (see the above referenced API documentation)
     * @param access The requested access desired for the opened key
     * @return A wil::shared_hkey or wil::shared_hkey containing the resulting opened HKEY
     * @exception std::exception (including wil::ResultException) will be thrown on all failures
     */
    inline ::wil::shared_hkey create_shared_key(HKEY key, PCWSTR subKey, ::wil::reg::key_access access = ::wil::reg::key_access::read)
    {
        const reg_view_details::reg_view regview{key};
        ::wil::shared_hkey return_value;
        regview.create_key(subKey, &return_value, access);
        return return_value;
    }
#endif // #if defined(__WIL_WINREG_STL)
#endif // #if defined(WIL_ENABLE_EXCEPTIONS)

    /**
     * @brief Opens a new HKEY to the specified path - see RegOpenKeyExW
     * @param key An open or well-known registry key
     * @param subKey The name of the registry subkey to be opened.
     *        If `nullptr`, then `key` is used without modification.
     * @param[out] hkey A reference to a wil::unique_hkey to receive the opened HKEY
     * @param access The requested access desired for the opened key
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT open_unique_key_nothrow(
        HKEY key, _In_opt_ PCWSTR subKey, ::wil::unique_hkey& hkey, ::wil::reg::key_access access = ::wil::reg::key_access::read) WI_NOEXCEPT
    {
        const reg_view_details::reg_view_nothrow regview{key};
        return regview.open_key(subKey, hkey.put(), access);
    }

    /**
     * @brief Creates a new HKEY to the specified path - see RegCreateKeyExW
     * @param key An open or well-known registry key
     * @param subKey The name of a subkey that this function opens or creates.
     *        Note: this cannot be null (see the above referenced API documentation)
     * @param[out] hkey A reference to a wil::unique_hkey to receive the opened HKEY
     * @param access The requested access desired for the opened key
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT create_unique_key_nothrow(
        HKEY key, PCWSTR subKey, ::wil::unique_hkey& hkey, ::wil::reg::key_access access = ::wil::reg::key_access::read) WI_NOEXCEPT
    {
        const reg_view_details::reg_view_nothrow regview{key};
        return regview.create_key(subKey, hkey.put(), access);
    }

#if defined(__WIL_WINREG_STL) || defined(WIL_DOXYGEN)
    /**
     * @brief Opens a new HKEY to the specified path - see RegOpenKeyExW
     * @param key An open or well-known registry key
     * @param subKey The name of the registry subkey to be opened.
     *        If `nullptr`, then `key` is used without modification.
     * @param[out] hkey A reference to a wil::shared_hkey to receive the opened HKEY
     * @param access The requested access desired for the opened key
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT open_shared_key_nothrow(
        HKEY key, _In_opt_ PCWSTR subKey, ::wil::shared_hkey& hkey, ::wil::reg::key_access access = ::wil::reg::key_access::read) WI_NOEXCEPT
    {
        const reg_view_details::reg_view_nothrow regview{key};
        return regview.open_key(subKey, hkey.put(), access);
    }

    /**
     * @brief Creates a new HKEY to the specified path - see RegCreateKeyExW
     * @param key An open or well-known registry key
     * @param subKey The name of a subkey that this function opens or creates.
     *        Note: this cannot be null (see the above referenced API documentation)
     * @param[out] hkey A reference to a wil::shared_hkey to receive the opened HKEY
     * @param access The requested access desired for the opened key
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT create_shared_key_nothrow(
        HKEY key, PCWSTR subKey, ::wil::shared_hkey& hkey, ::wil::reg::key_access access = ::wil::reg::key_access::read) WI_NOEXCEPT
    {
        const reg_view_details::reg_view_nothrow regview{key};
        return regview.create_key(subKey, hkey.put(), access);
    }
#endif // #define __WIL_WINREG_STL

    //
    //  wil::key_iterator and wil::value_iterator objects enable enumerating registry keys and values.
    //
    // Examples of usage when std::wstring is included:
    //
    //     for (const auto& key_data : wil::make_range(wil::reg::key_iterator{hkey}, wil::reg::key_iterator{}))
    //     {
    //         key_data.name; // the std::wstring of the enumerated key
    //     }
    //
    //     for (const auto& value_data : wil::make_range(wil::reg::value_iterator{hkey}, wil::reg::value_iterator{}))
    //     {
    //         value_data.name; // the std::wstring of the enumerated value
    //         value_data.type; // the REG_ type of the enumerated value
    //     }
    //
    // When std::wstring is not included, wil::unique_process_heap_string can be used instead:
    //
    //     for (const auto& key_data : wil::make_range(wil::reg::key_heap_string_iterator{hkey}, wil::reg::key_heap_string_iterator{}))
    //     {
    //         key_data.name.get(); // the PCWSTR of the enumerated key
    //     }
    //
    //     for (const auto& value_data : wil::make_range(wil::reg::value_heap_string_iterator{hkey}, wil::reg::value_heap_string_iterator{}))
    //     {
    //         value_data.name.get(); // the PCWSTR of the enumerated value
    //         value_data.type; // the REG_ type of the enumerated value
    //     }
    //
    // When not using exceptions, can manually walk the iterator using wil::unique_process_heap_string:
    //
    //     auto iterate_keys = wil::reg::key_heap_string_nothrow_iterator{hkey};
    //     for (const auto& key_data : wil::make_range(iterate_keys, wil::reg::key_heap_string_nothrow_iterator{}))
    //     {
    //         key_data.name.get(); // the PCWSTR of the enumerated key
    //     }
    //     if (FAILED(iterate_keys.last_error()))
    //     {
    //         // the HRESULT last_error() returns the registry error that prevented enumeration
    //     }
    //
    //     auto iterate_values = wil::reg::value_heap_string_nothrow_iterator{hkey};
    //     for (const auto& value_data : wil::make_range(iterate_values, wil::reg::value_heap_string_nothrow_iterator{}))
    //     {
    //         value_data.name.get(); // the PCWSTR of the enumerated value
    //         value_data.type; // the REG_ type of the enumerated value
    //     }
    //     if (FAILED(iterate_values.last_error()))
    //     {
    //         // the HRESULT last_error() returns the registry error that prevented enumeration
    //     }
    //
#if defined(WIL_ENABLE_EXCEPTIONS)

#if WIL_USE_STL || defined(WIL_DOXYGEN)
    using key_iterator = ::wil::reg::iterator_t<::wil::reg::key_iterator_data<::std::wstring>>;
    using value_iterator = ::wil::reg::iterator_t<::wil::reg::value_iterator_data<::std::wstring>>;
#endif

#if defined(__WIL_OLEAUTO_H_) || defined(WIL_DOXYGEN)
    using key_bstr_iterator = ::wil::reg::iterator_t<::wil::reg::key_iterator_data<::wil::unique_bstr>>;
    using value_bstr_iterator = ::wil::reg::iterator_t<::wil::reg::value_iterator_data<::wil::unique_bstr>>;
#endif // #if defined(__WIL_OLEAUTO_H_)

    using key_heap_string_iterator = ::wil::reg::iterator_t<::wil::reg::key_iterator_data<::wil::unique_process_heap_string>>;
    using value_heap_string_iterator = ::wil::reg::iterator_t<::wil::reg::value_iterator_data<::wil::unique_process_heap_string>>;

#endif // #if defined(WIL_ENABLE_EXCEPTIONS)

    // no-throw versions of applicable registry iterators
#if defined(__WIL_OLEAUTO_H_) || defined(WIL_DOXYGEN)
    using key_bstr_nothrow_iterator = ::wil::reg::iterator_nothrow_t<::wil::reg::key_iterator_data<::wil::unique_bstr>>;
    using value_bstr_nothrow_iterator = ::wil::reg::iterator_nothrow_t<::wil::reg::value_iterator_data<::wil::unique_bstr>>;
#endif // #if defined(__WIL_OLEAUTO_H_)

    using key_heap_string_nothrow_iterator =
        ::wil::reg::iterator_nothrow_t<::wil::reg::key_iterator_data<::wil::unique_process_heap_string>>;
    using value_heap_string_nothrow_iterator =
        ::wil::reg::iterator_nothrow_t<::wil::reg::value_iterator_data<::wil::unique_process_heap_string>>;

    /**
     * @brief Queries for number of sub-keys
     * @param key The HKEY to query for number of sub-keys
     * @param[out] numSubKeys A pointer to a DWORD to receive the returned count
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_child_key_count_nothrow(HKEY key, _Out_ DWORD* numSubKeys) WI_NOEXCEPT
    {
        RETURN_IF_WIN32_ERROR(RegQueryInfoKeyW(
            key,
            nullptr, // null class
            nullptr, // null class character count,
            nullptr, // null reserved
            numSubKeys,
            nullptr,   // null max subkey length
            nullptr,   // null max class length
            nullptr,   // null value count
            nullptr,   // null max value name length
            nullptr,   // null max value length
            nullptr,   // null security descriptor
            nullptr)); // null last write filetime
        return S_OK;
    }

    inline HRESULT get_child_key_count_nothrow(HKEY key, _Out_ uint32_t* numSubKeys) WI_NOEXCEPT
    {
        DWORD subKeys{};
        RETURN_IF_FAILED(::wil::reg::get_child_key_count_nothrow(key, &subKeys));
        *numSubKeys = subKeys;
        return S_OK;
    }

    /**
     * @brief Queries for number of values
     * @param key The HKEY to query for number of values
     * @param[out] numSubValues A pointer to a DWORD to receive the returned count
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_child_value_count_nothrow(HKEY key, _Out_ DWORD* numSubValues) WI_NOEXCEPT
    {
        RETURN_IF_WIN32_ERROR(RegQueryInfoKeyW(
            key,
            nullptr, // null class
            nullptr, // null class char count,
            nullptr, // null reserved
            nullptr, // null subkey count
            nullptr, // null max subkey length
            nullptr, // null max class length
            numSubValues,
            nullptr,   // null max value name length
            nullptr,   // null max value length
            nullptr,   // null security descriptor
            nullptr)); // null last write filetime
        return S_OK;
    }

    inline HRESULT get_child_value_count_nothrow(HKEY key, _Out_ uint32_t* numSubValues) WI_NOEXCEPT
    {
        DWORD subValues{};
        RETURN_IF_FAILED(::wil::reg::get_child_value_count_nothrow(key, &subValues));
        *numSubValues = subValues;
        return S_OK;
    }

    /**
     * @brief Queries for the filetime when the registry key was last written
     * @param key The HKEY to query for number of values
     * @param[out] lastModified A pointer to a FILETIME to receive the last write time
     * @exception std::exception (including wil::ResultException) will be thrown on all failures
     */
    inline HRESULT get_last_write_filetime_nothrow(HKEY key, _Out_ FILETIME* lastModified) WI_NOEXCEPT
    {
        RETURN_IF_WIN32_ERROR(RegQueryInfoKeyW(
            key,
            nullptr, // null class
            nullptr, // null class char count,
            nullptr, // null reserved
            nullptr, // null subkey count
            nullptr, // null max subkey length
            nullptr, // null max class length
            nullptr, // null value count
            nullptr, // null max value name length
            nullptr, // null max value length
            nullptr, // null security descriptor
            lastModified));
        return S_OK;
    }

#if defined(WIL_ENABLE_EXCEPTIONS)
    /**
     * @brief Queries for number of sub-keys
     * @param key The HKEY to query for number of sub-keys
     * @return The queried number of sub-keys if succeeded
     * @exception std::exception (including wil::ResultException) will be thrown on all failures
     */
    inline uint32_t get_child_key_count(HKEY key)
    {
        uint32_t numSubKeys{};
        THROW_IF_FAILED(::wil::reg::get_child_key_count_nothrow(key, &numSubKeys));
        return numSubKeys;
    }

    /**
     * @brief Queries for number of values
     * @param key The HKEY to query for number of values
     * @return The queried number of value if succeeded
     * @exception std::exception (including wil::ResultException) will be thrown on all failures
     */
    inline uint32_t get_child_value_count(HKEY key)
    {
        uint32_t numSubValues{};
        THROW_IF_FAILED(::wil::reg::get_child_value_count_nothrow(key, &numSubValues));
        return numSubValues;
    }

    /**
     * @brief Queries for the filetime when the registry key was last written
     * @param key The HKEY to query for number of values
     * @return The queried filetime if succeeded
     * @exception std::exception (including wil::ResultException) will be thrown on all failures
     */
    inline FILETIME get_last_write_filetime(HKEY key)
    {
        FILETIME lastModified{};
        THROW_IF_FAILED(::wil::reg::get_last_write_filetime_nothrow(key, &lastModified));
        return lastModified;
    }
#endif // #if defined(WIL_ENABLE_EXCEPTIONS)

#if defined(WIL_ENABLE_EXCEPTIONS)
    //
    // template <typename T>
    // void set_value(...)
    //
    //  - Writes a value to a specified key and subkey, deducing the type from the given data
    //  - Throws a std::exception on failure (including wil::ResultException)
    //
    // Examples of usage (the template type does not need to be explicitly specified)
    //     wil::reg::set_value(key, L"subkey", L"dword_value_name", 0); // writes a REG_DWORD
    //     wil::reg::set_value(key, L"subkey", L"qword_value_name", 0ull); // writes a REG_QWORD
    //     wil::reg::set_value(key, L"subkey", L"string_value_name", L"hello"); // writes a REG_SZ
    //
    // A subkey is not required if the key is opened where this should write the value:
    //     wil::reg::set_value(key, L"dword_value_name", 0); // writes a REG_DWORD
    //     wil::reg::set_value(key, L"qword_value_name", 0ull); // writes a REG_QWORD
    //     wil::reg::set_value(key, L"string_value_name", L"hello"); // writes a REG_SZ
    //
    // Example usage writing a vector of wstrings to a REG_MULTI_SZ
    //     std::vector<std::wstring> data { L"string1", L"string2", L"string3" };
    //     wil::reg::set_value(key, L"multi_string_value_name", data);
    //     wil::reg::set_value(key, L"multi_string_value_name", data);
    //
    // Example of usage writing directly to a registry value from a raw byte vector
    //  - notice the registry type is required, not implied
    //     std::vector<BYTE> data { 0x00, 0xff, 0xee, 0xdd, 0xcc };
    //     wil::reg::set_value_binary(key, L"binary_value_name", REG_BINARY, data);
    //     wil::reg::set_value_binary(key, L"binary_value_name", REG_BINARY, data);
    //

    /**
     * @brief Writes a value to a specified key and subkey, deducing the type from the given data.
     * @tparam T The type of the data being set (the registry value type is deduced from T).
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data The data (of type T) to write to the specified registry value
     * @exception std::exception (including wil::ResultException) will be thrown on all failures
     */
    template <typename T>
    void set_value(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, const T& data)
    {
        const reg_view_details::reg_view regview{key};
        regview.set_value(subkey, value_name, data);
    }

    /**
     * @brief Writes a value under a specified key, the registry type based off the templated type passed as data
     * @tparam T The type of the data being set (the registry value type is deduced from T).
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data The data (of type T) to write to the specified registry value
     * @exception std::exception (including wil::ResultException) will be thrown on all failures
     */
    template <typename T>
    void set_value(HKEY key, _In_opt_ PCWSTR value_name, const T& data)
    {
        ::wil::reg::set_value(key, nullptr, value_name, data);
    }

    /**
     * @brief Writes a null-terminated string value under a specified key
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data The null-terminated string to write to the specified registry value
     * @exception std::exception (including wil::ResultException) will be thrown on all failures
     */
    inline void set_value(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, PCWSTR data)
    {
        const reg_view_details::reg_view regview{key};
        regview.set_value(subkey, value_name, data);
    }

    /**
     * @brief Writes a null-terminated string value under a specified key
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data The null-terminated string to write to the specified registry value
     * @exception std::exception (including wil::ResultException) will be thrown on all failures
     */
    inline void set_value(HKEY key, _In_opt_ PCWSTR value_name, PCWSTR data)
    {
        ::wil::reg::set_value(key, nullptr, value_name, data);
    }

    /**
     * @brief Writes a REG_DWORD value from a uint32_t
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data The 32-bit value to write to the specified registry value
     * @exception std::exception (including wil::ResultException) will be thrown on all failures
     */
    inline void set_value_dword(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, uint32_t data)
    {
        ::wil::reg::set_value(key, subkey, value_name, data);
    }

    /**
     * @brief Writes a REG_DWORD value from a uint32_t
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data The 32-bit value to write to the specified registry value
     * @exception std::exception (including wil::ResultException) will be thrown on all failures
     */
    inline void set_value_dword(HKEY key, _In_opt_ PCWSTR value_name, uint32_t data)
    {
        ::wil::reg::set_value(key, nullptr, value_name, data);
    }

    /**
     * @brief Writes a REG_QWORD value from a uint64_t
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data The 64-bit value to write to the specified registry value
     * @exception std::exception (including wil::ResultException) will be thrown on all failures
     */
    inline void set_value_qword(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, uint64_t data)
    {
        ::wil::reg::set_value(key, subkey, value_name, data);
    }

    /**
     * @brief Writes a REG_QWORD value from a uint64_t
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data The 64-bit value to write to the specified registry value
     * @exception std::exception (including wil::ResultException) will be thrown on all failures
     */
    inline void set_value_qword(HKEY key, _In_opt_ PCWSTR value_name, uint64_t data)
    {
        ::wil::reg::set_value(key, nullptr, value_name, data);
    }

    /**
     * @brief Writes a REG_SZ value from a null-terminated string
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data The null-terminated string value to write to the specified registry value
     * @exception std::exception (including wil::ResultException) will be thrown on all failures
     */
    inline void set_value_string(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, PCWSTR data)
    {
        ::wil::reg::set_value(key, subkey, value_name, data);
    }

    /**
     * @brief Writes a REG_SZ value from a null-terminated string
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data The null-terminated string value to write to the specified registry value
     * @exception std::exception (including wil::ResultException) will be thrown on all failures
     */
    inline void set_value_string(HKEY key, _In_opt_ PCWSTR value_name, PCWSTR data)
    {
        ::wil::reg::set_value(key, nullptr, value_name, data);
    }

    /**
     * @brief Writes a REG_EXPAND_SZ value from a null-terminated string
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data The null-terminated, unexpanded string value to write to the specified registry value. For example, `%PATH%`.
     * @exception std::exception (including wil::ResultException) will be thrown on all failures
     */
    inline void set_value_expanded_string(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, PCWSTR data)
    {
        const reg_view_details::reg_view regview{key};
        regview.set_value(subkey, value_name, data, REG_EXPAND_SZ);
    }

    /**
     * @brief Writes a REG_EXPAND_SZ value from a null-terminated string
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data The null-terminated, unexpanded string value to write to the specified registry value. For example, `%PATH%`.
     * @exception std::exception (including wil::ResultException) will be thrown on all failures
     */
    inline void set_value_expanded_string(HKEY key, _In_opt_ PCWSTR value_name, PCWSTR data)
    {
        ::wil::reg::set_value_expanded_string(key, nullptr, value_name, data);
    }

#if WIL_USE_STL || defined(WIL_DOXYGEN)
    /**
     * @brief The generic set_value template function to write a REG_MULTI_SZ value from a std::vector<std::wstring>
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data A std::vector<std::wstring> to write to the specified registry value.
     *        Each string will be marshaled to a contiguous null-terminator-delimited multi-sz string
     * @exception std::exception (including wil::ResultException) will be thrown on all failures
     */
    inline void set_value(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, const ::std::vector<::std::wstring>& data)
    {
        const auto multiStringWcharVector(reg_view_details::get_multistring_from_wstrings(::std::begin(data), ::std::end(data)));
        const reg_view_details::reg_view regview{key};
        regview.set_value(subkey, value_name, multiStringWcharVector, REG_MULTI_SZ);
    }

    /**
     * @brief The generic set_value template function to write a REG_MULTI_SZ value from a std::vector<std::wstring>
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data A std::vector<std::wstring> to write to the specified registry value.
     *        Each string will be marshaled to a contiguous null-terminator-delimited multi-sz string.
     * @exception std::exception (including wil::ResultException) will be thrown on all failures
     */
    inline void set_value(HKEY key, _In_opt_ PCWSTR value_name, const ::std::vector<::std::wstring>& data)
    {
        ::wil::reg::set_value(key, nullptr, value_name, data);
    }

    /**
     * @brief Writes a REG_MULTI_SZ value from a std::vector<std::wstring>
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data A std::vector<std::wstring> to write to the specified registry value.
     *        Each string will be marshaled to a contiguous null-terminator-delimited multi-sz string
     * @exception std::exception (including wil::ResultException) will be thrown on all failures
     */
    inline void set_value_multistring(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, const ::std::vector<::std::wstring>& data)
    {
        ::wil::reg::set_value(key, subkey, value_name, data);
    }

    /**
     * @brief Writes a REG_MULTI_SZ value from a std::vector<std::wstring>
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data A std::vector<std::wstring> to write to the specified registry value.
     *        Each string will be marshaled to a contiguous null-terminator-delimited multi-sz string.
     * @exception std::exception (including wil::ResultException) will be thrown on all failures
     */
    inline void set_value_multistring(HKEY key, _In_opt_ PCWSTR value_name, const ::std::vector<::std::wstring>& data)
    {
        ::wil::reg::set_value(key, nullptr, value_name, data);
    }
#endif

#if WIL_USE_STL || defined(WIL_DOXYGEN)
    /**
     * @brief Writes a registry value of the specified type from a `std::vector<uint8_t>`/`std::vector<BYTE>`
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param type The registry type for the specified registry value - see RegSetKeyValueW
     * @param data A `std::vector<uint8_t>`/`std::vector<BYTE>` to write to the specified registry value.
     *        The vector contents will be directly marshaled to the specified value.
     * @exception std::exception (including wil::ResultException) will be thrown on all failures
     */
    inline void set_value_binary(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, uint32_t type, const ::std::vector<uint8_t>& data)
    {
        const reg_view_details::reg_view regview{key};
        regview.set_value(subkey, value_name, data, type);
    }

    /**
     * @brief Writes a registry value of the specified type from a `std::vector<uint8_t>`/`std::vector<BYTE>`
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param type The registry type for the specified registry value - see RegSetKeyValueW
     * @param data A `std::vector<uint8_t>`/`std::vector<BYTE>` to write to the specified registry value.
     *        The vector contents will be directly marshaled to the specified value.
     * @exception std::exception (including wil::ResultException) will be thrown on all failures
     */
    inline void set_value_binary(HKEY key, _In_opt_ PCWSTR value_name, uint32_t type, const ::std::vector<uint8_t>& data)
    {
        ::wil::reg::set_value_binary(key, nullptr, value_name, type, data);
    }
#endif
#endif

    //
    // template <typename T>
    // HRESULT set_value_nothrow(...)
    //
    //  - Writes a value under a specified key
    //  - The type of registry value is determined by the template type T of data given
    //  - Returns an HRESULT error code indicating success or failure (does not throw C++ exceptions)
    //
    // Examples of usage (the template type does not need to be explicitly specified)
    //     hr = wil::reg::set_value_nothrow(key, L"subkey", L"dword_value_name", 0); // writes a REG_DWORD
    //     hr = wil::reg::set_value_nothrow(key, L"subkey", L"qword_value_name", 0ull); // writes a REG_QWORD
    //     hr = wil::reg::set_value_nothrow(key, L"subkey", L"string_value_name", L"hello"); // writes a REG_SZ
    //
    // A subkey is not required if the key is opened where this should write the value:
    //     hr = wil::reg::set_value_nothrow(key, L"dword_value_name", 0); // writes a REG_DWORD
    //     hr = wil::reg::set_value_nothrow(key, L"qword_value_name", 0ull); // writes a REG_QWORD
    //     hr = wil::reg::set_value_nothrow(key, L"string_value_name", L"hello"); // writes a REG_SZ
    //
    // Example of usage writing a REG_MULTI_SZ
    //     std::vector<std::wstring> multisz_data { L"string1", L"string2", L"string3" };
    //     hr = wil::reg::set_value_nothrow(key, L"multi_string_value_name", multisz_data);
    //
    // Values can be written directly from a vector of bytes - the registry type must be specified; e.g.:
    //     std::vector<BYTE> data { 0x00, 0xff, 0xee, 0xdd, 0xcc };
    //     hr = wil::reg::set_value_binary_nothrow(key, L"binary_value_name", REG_BINARY, data);
    //
    /**
     * @brief Writes a value to a specified key and subkey, deducing the type from the given data.
     * @tparam T The type of the data being set (the registry value type is deduced from T).
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data The data (of type T) to write to the specified registry value
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    template <typename T>
    HRESULT set_value_nothrow(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, const T& data) WI_NOEXCEPT
    {
        const reg_view_details::reg_view_nothrow regview{key};
        return regview.set_value(subkey, value_name, data);
    }

    /**
     * @brief Writes a value under a specified key, the registry type based off the templated type passed as data
     * @tparam T The type of the data being set (the registry value type is deduced from T).
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data The data (of type T) to write to the specified registry value
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    template <typename T>
    HRESULT set_value_nothrow(HKEY key, _In_opt_ PCWSTR value_name, const T& data) WI_NOEXCEPT
    {
        return ::wil::reg::set_value_nothrow(key, nullptr, value_name, data);
    }

    /**
     * @brief Writes a null-terminated string value under a specified key
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data The null-terminated string to write to the specified registry value
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT set_value_nothrow(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, PCWSTR data) WI_NOEXCEPT
    {
        const reg_view_details::reg_view_nothrow regview{key};
        return regview.set_value(subkey, value_name, data);
    }

    /**
     * @brief Writes a null-terminated string value under a specified key
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data The null-terminated string to write to the specified registry value
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT set_value_nothrow(HKEY key, _In_opt_ PCWSTR value_name, PCWSTR data) WI_NOEXCEPT
    {
        return ::wil::reg::set_value_nothrow(key, nullptr, value_name, data);
    }

    /**
     * @brief Writes a REG_DWORD value from a uint32_t
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data The 32-bit value to write to the specified registry value
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT set_value_dword_nothrow(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, uint32_t data) WI_NOEXCEPT
    {
        return ::wil::reg::set_value_nothrow(key, subkey, value_name, data);
    }

    /**
     * @brief Writes a REG_DWORD value from a uint32_t
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data The 32-bit value to write to the specified registry value
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT set_value_dword_nothrow(HKEY key, _In_opt_ PCWSTR value_name, uint32_t data) WI_NOEXCEPT
    {
        return ::wil::reg::set_value_nothrow(key, nullptr, value_name, data);
    }

    /**
     * @brief Writes a REG_QWORD value from a uint64_t
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data The 64-bit value to write to the specified registry value
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT set_value_qword_nothrow(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, uint64_t data) WI_NOEXCEPT
    {
        return ::wil::reg::set_value_nothrow(key, subkey, value_name, data);
    }

    /**
     * @brief Writes a REG_QWORD value from a uint64_t
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data The 64-bit value to write to the specified registry value
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT set_value_qword_nothrow(HKEY key, _In_opt_ PCWSTR value_name, uint64_t data) WI_NOEXCEPT
    {
        return ::wil::reg::set_value_nothrow(key, nullptr, value_name, data);
    }

    /**
     * @brief Writes a REG_SZ value from a null-terminated string
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data The null-terminated string value to write to the specified registry value
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT set_value_string_nothrow(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, PCWSTR data) WI_NOEXCEPT
    {
        return ::wil::reg::set_value_nothrow(key, subkey, value_name, data);
    }

    /**
     * @brief Writes a REG_SZ value from a null-terminated string
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data The null-terminated string value to write to the specified registry value
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT set_value_string_nothrow(HKEY key, _In_opt_ PCWSTR value_name, PCWSTR data) WI_NOEXCEPT
    {
        return ::wil::reg::set_value_nothrow(key, nullptr, value_name, data);
    }

    /**
     * @brief Writes a REG_EXPAND_SZ value from a null-terminated string
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data The null-terminated string value to write to the specified registry value
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT set_value_expanded_string_nothrow(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, PCWSTR data) WI_NOEXCEPT
    {
        const reg_view_details::reg_view_nothrow regview{key};
        return regview.set_value(subkey, value_name, data, REG_EXPAND_SZ);
    }

    /**
     * @brief Writes a REG_EXPAND_SZ value from a null-terminated string
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param data The null-terminated string value to write to the specified registry value
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT set_value_expanded_string_nothrow(HKEY key, _In_opt_ PCWSTR value_name, PCWSTR data) WI_NOEXCEPT
    {
        return ::wil::reg::set_value_expanded_string_nothrow(key, nullptr, value_name, data);
    }

#if defined(__WIL_OBJBASE_H_) || defined(WIL_DOXYGEN)
    /**
     * @brief Writes raw bytes into a registry value under a specified key of the specified type
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param type The registry type for the specified registry value to write to - see RegSetValue
     * @param value A ::wil::unique_cotaskmem_array_ptr<BYTE> holding the bytes to write into the specified registry value
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT set_value_binary_nothrow(
        HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, uint32_t type, const ::wil::unique_cotaskmem_array_ptr<uint8_t>& value) WI_NOEXCEPT
    {
        const reg_view_details::reg_view_nothrow regview{key};
        RETURN_IF_FAILED(regview.set_value<::wil::unique_cotaskmem_array_ptr<uint8_t>>(subkey, value_name, value, type));
        return S_OK;
    }

    /**
     * @brief Writes raw bytes into a registry value under a specified key of the specified type
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to write to the unnamed default registry value.
     * @param type The registry type for the specified registry value to write to - see RegSetValue
     * @param value A ::wil::unique_cotaskmem_array_ptr<BYTE> holding the bytes to write into the specified registry value
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT set_value_binary_nothrow(
        HKEY key, _In_opt_ PCWSTR value_name, uint32_t type, const ::wil::unique_cotaskmem_array_ptr<uint8_t>& value) WI_NOEXCEPT
    {
        return ::wil::reg::set_value_binary_nothrow(key, nullptr, value_name, type, value);
    }
#endif

#if defined(WIL_ENABLE_EXCEPTIONS)
    //
    // template <typename T>
    // T get_value(...)
    //
    //  - Reads a value under a specified key.
    //  - Requires a type T to be specified.
    //  - Throws a std::exception on failure (including wil::ResultException), including registry value not found.
    //    If you don't want an exception when the value does not exist, use try_get_value(...)
    //
    // Examples of usage (ensure the code handles a possible std::exception that will be thrown on all errors)
    //     uint32_t dword_value = wil::reg::get_value<uint32_t>(key, L"subkey", L"dword_value_name");
    //     uint64_t qword_value = wil::reg::get_value<uint64_t>(key, L"subkey", L"qword_value_name);
    //     std::wstring string_value = wil::reg::get_value<std::wstring>(key, L"subkey", L"string_value_name");
    //
    // A subkey is not required if the key is opened where this should write the value:
    //     uint32_t dword_value = wil::reg::get_value<uint32_t>(key, L"dword_value_name");
    //     uint64_t qword_value = wil::reg::get_value<uint64_t>(key, L"qword_value_name);
    //     std::wstring string_value = wil::reg::get_value<std::wstring>(key, L"string_value_name");
    //
    // The template type does not need to be specified if using functions written for a targeted type
    //     uint32_t dword_value = wil::reg::get_value_dword(key, L"dword_value_name");
    //     uint64_t qword_value = wil::reg::get_value_qword(key, L"qword_value_name");
    //     std::wstring string_value = wil::reg::get_value_string(key, L"string_value_name");
    //
    // Values with REG_EXPAND_SZ can be read into each of the string types; e.g.:
    //     std::wstring expanded_string_value = wil::reg::get_value_expanded_string(key, L"string_value_name_with_environment_variables");
    //
    // Values can be read directly into a vector of bytes - the registry type must be specified; e.g.:
    //     std::vector<BYTE> data = wil::reg::get_value_binary(key, L"binary_value_name", REG_BINARY);
    //
    // Multi-string values can be read into a vector<wstring>; e.g.:
    //     std::vector<std::wstring> multi_string_value = wil::reg::get_value_multistring(key, L"multi_string_value_name");
    //     for (const auto& sub_string_value : multi_string_value)
    //     {
    //         // can read each string parsed from the multi-string
    //         PCWSTR string_value = sub_string_value.c_str();
    //     }
    //
    // Reading REG_SZ and REG_EXPAND_SZ types are done through the below templated get_value_string and get_value_expanded_string functions
    // Where the template type is the type to receive the string value
    // The default template type is std::wstring, available if the caller has included the STL <string> header
    //
    // Reading a bstr can be stored in a wil::shared_bstr or wil::unique_bstr - wil::shared_bstr has a c'tor taking a wil::unique_bstr
    //     wil::unique_bstr unique_value { wil::reg::get_value_string<::wil::unique_bstr>(key, L"string_value_name") };
    //     wil::shared_bstr shared_value { wil::reg::get_value_string<::wil::shared_bstr>(key, L"string_value_name") };
    //
    // Reading a cotaskmem string can be stored in a wil::unique_cotaskmem_string or wil::shared_cotaskmem_string
    //     wil::unique_cotaskmem_string unique_value { wil::reg::get_value_string<wil::unique_cotaskmem_string>(key, L"string_value_name") };
    //     wil::shared_cotaskmem_string shared_value { wil::reg::get_value_string<wil::shared_cotaskmem_string>(key, L"string_value_name") };
    //
    // Blocking get_value_string template types that are not already specialized - this gives a much friendlier compiler error message
    template <typename T>
    T get_value_string(HKEY /*key*/, _In_opt_ PCWSTR /*subkey*/, _In_opt_ PCWSTR /*value_name*/)
    {
        static_assert(sizeof(T) != sizeof(T), "Unsupported type for get_value_string");
    }

    template <typename T>
    T get_value_string(HKEY /*key*/, _In_opt_ PCWSTR /*value_name*/)
    {
        static_assert(sizeof(T) != sizeof(T), "Unsupported type for get_value_string");
    }

    template <typename T>
    T get_value_expanded_string(HKEY /*key*/, _In_opt_ PCWSTR /*subkey*/, _In_opt_ PCWSTR /*value_name*/)
    {
        static_assert(sizeof(T) != sizeof(T), "Unsupported type for get_value_expanded_string");
    }

    template <typename T>
    T get_value_expanded_string(HKEY /*key*/, _In_opt_ PCWSTR /*value_name*/)
    {
        static_assert(sizeof(T) != sizeof(T), "Unsupported type for get_value_expanded_string");
    }

    /**
     * @brief Reads a value from a specified key and subkey, deducing registry type from the type parameter T.
     * @tparam T The type to read (the registry value type is deduced from T)
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value read from the registry value of the template type T
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    template <typename T>
    T get_value(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        T return_value{};
        const reg_view_details::reg_view regview{key};
        regview.get_value<T>(subkey, value_name, return_value);
        return return_value;
    }

    /**
     * @brief Reads a value under a specified key, deducing registry type from the type parameter T.
     * @tparam T The type to read (the registry value type is deduced from T)
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value read from the registry value of the template type T
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    template <typename T>
    T get_value(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::get_value<T>(key, nullptr, value_name);
    }

    /**
     * @brief Reads a REG_DWORD value, returning a uint32_t
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The uint32_t value read from the registry
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    inline uint32_t get_value_dword(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::get_value<uint32_t>(key, subkey, value_name);
    }

    /**
     * @brief Reads a REG_DWORD value, returning a uint32_t
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The uint32_t value read from the registry
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    inline uint32_t get_value_dword(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::get_value<uint32_t>(key, nullptr, value_name);
    }

    /**
     * @brief Reads a REG_QWORD value, returning a uint64_t
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The uint64_t value read from the registry
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    inline uint64_t get_value_qword(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::get_value<uint64_t>(key, subkey, value_name);
    }

    /**
     * @brief Reads a REG_QWORD value, returning a uint64_t
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The uint64_t value read from the registry
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    inline uint64_t get_value_qword(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::get_value<uint64_t>(key, nullptr, value_name);
    }

#if WIL_USE_STL || defined(WIL_DOXYGEN)
    /**
     * @brief Reads a REG_SZ value, returning a std::wstring
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A std::wstring created from the string value read from the registry
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    inline ::std::wstring get_value_string(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::get_value<::std::wstring>(key, subkey, value_name);
    }

    /**
     * @brief Reads a REG_SZ value, returning a std::wstring
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A std::wstring created from the string value read from the registry
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    inline ::std::wstring get_value_string(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::get_value<::std::wstring>(key, nullptr, value_name);
    }

    /**
     * @brief Reads a REG_SZ value, returning a std::wstring
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A std::wstring created from the string value read from the registry
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    template <>
    inline ::std::wstring get_value_string<::std::wstring>(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::get_value<::std::wstring>(key, subkey, value_name);
    }

    /**
     * @brief Reads a REG_SZ value, returning a std::wstring
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A std::wstring created from the string value read from the registry
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    template <>
    inline ::std::wstring get_value_string<::std::wstring>(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::get_value<::std::wstring>(key, nullptr, value_name);
    }

    /**
     * @brief Reads a REG_EXPAND_SZ value, returning a std::wstring
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A std::wstring created from the string value read from the registry,
     *         with environment variables expanded, as though passed through ExpandEnvironmentStringsW
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    inline ::std::wstring get_value_expanded_string(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        ::std::wstring value;
        const reg_view_details::reg_view regview{key};
        regview.get_value(subkey, value_name, value, REG_EXPAND_SZ);
        return value;
    }

    /**
     * @brief Reads a REG_EXPAND_SZ value, returning a std::wstring
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A std::wstring created from the string value read from the registry,
     *         with environment variables expanded, as though passed through ExpandEnvironmentStringsW
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    inline ::std::wstring get_value_expanded_string(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::get_value_expanded_string(key, nullptr, value_name);
    }

    /**
     * @brief Reads a REG_EXPAND_SZ value, returning a std::wstring
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A std::wstring created from the string value read from the registry,
     *         with environment variables expanded, as though passed through ExpandEnvironmentStringsW
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    template <>
    inline ::std::wstring get_value_expanded_string<::std::wstring>(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::get_value_expanded_string(key, subkey, value_name);
    }

    /**
     * @brief Reads a REG_EXPAND_SZ value, returning a std::wstring
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A std::wstring created from the string value read from the registry,
     *         with environment variables expanded, as though passed through ExpandEnvironmentStringsW
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    template <>
    inline ::std::wstring get_value_expanded_string<::std::wstring>(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::get_value_expanded_string(key, nullptr, value_name);
    }
#endif

#if defined(__WIL_OLEAUTO_H_) || defined(WIL_DOXYGEN)
    /**
     * @brief Reads a REG_SZ value, returning a wil::unique_bstr
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A wil::unique_bstr created from the string value read from the registry
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    template <>
    inline ::wil::unique_bstr get_value_string<::wil::unique_bstr>(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::get_value<::wil::unique_bstr>(key, subkey, value_name);
    }

    /**
     * @brief Reads a REG_SZ value, returning a wil::unique_bstr
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A wil::unique_bstr created from the string value read from the registry
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    template <>
    inline ::wil::unique_bstr get_value_string<::wil::unique_bstr>(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::get_value<::wil::unique_bstr>(key, nullptr, value_name);
    }

    /**
     * @brief Reads a REG_EXPAND_SZ value, returning a wil::unique_bstr
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A wil::unique_bstr created from the string value read from the registry,
     *         with environment variables expanded, as though passed through ExpandEnvironmentStringsW
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    template <>
    inline ::wil::unique_bstr get_value_expanded_string<::wil::unique_bstr>(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        ::wil::unique_bstr value;
        const reg_view_details::reg_view regview{key};
        regview.get_value(subkey, value_name, value, REG_EXPAND_SZ);
        return value;
    }

    /**
     * @brief Reads a REG_EXPAND_SZ value, returning a wil::unique_bstr
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A wil::unique_bstr created from the string value read from the registry,
     *         with environment variables expanded, as though passed through ExpandEnvironmentStringsW
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    template <>
    inline ::wil::unique_bstr get_value_expanded_string<::wil::unique_bstr>(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::get_value_expanded_string<::wil::unique_bstr>(key, nullptr, value_name);
    }

#if defined(__WIL_OLEAUTO_H_STL) || defined(WIL_DOXYGEN)
    /**
     * @brief Reads a REG_SZ value, returning a wil::shared_bstr
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A wil::shared_bstr created from the string value read from the registry
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    template <>
    inline ::wil::shared_bstr get_value_string<::wil::shared_bstr>(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::get_value<::wil::shared_bstr>(key, subkey, value_name);
    }

    /**
     * @brief Reads a REG_SZ value, returning a wil::shared_bstr
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A wil::shared_bstr created from the string value read from the registry
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    template <>
    inline ::wil::shared_bstr get_value_string<::wil::shared_bstr>(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::get_value<::wil::shared_bstr>(key, nullptr, value_name);
    }

    /**
     * @brief Reads a REG_EXPAND_SZ value, returning a wil::unique_bstr
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A wil::shared_bstr created from the string value read from the registry,
     *         with environment variables expanded, as though passed through ExpandEnvironmentStringsW
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    template <>
    inline ::wil::shared_bstr get_value_expanded_string<::wil::shared_bstr>(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        ::wil::shared_bstr value;
        const reg_view_details::reg_view regview{key};
        regview.get_value(subkey, value_name, value, REG_EXPAND_SZ);
        return value;
    }

    /**
     * @brief Reads a REG_EXPAND_SZ value, returning a wil::shared_bstr
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A wil::shared_bstr created from the string value read from the registry,
     *         with environment variables expanded, as though passed through ExpandEnvironmentStringsW
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    template <>
    inline ::wil::shared_bstr get_value_expanded_string<::wil::shared_bstr>(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::get_value_expanded_string<::wil::shared_bstr>(key, nullptr, value_name);
    }
#endif // #if defined(__WIL_OLEAUTO_H_STL)
#endif // #if defined(__WIL_OLEAUTO_H_)

#if defined(__WIL_OBJBASE_H_) || defined(WIL_DOXYGEN)
    /**
     * @brief Reads a REG_SZ value, returning a wil::unique_cotaskmem_string
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A wil::unique_cotaskmem_string created from the string value read from the registry
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    template <>
    inline ::wil::unique_cotaskmem_string get_value_string<::wil::unique_cotaskmem_string>(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::get_value<::wil::unique_cotaskmem_string>(key, subkey, value_name);
    }

    /**
     * @brief Reads a REG_SZ value, returning a wil::unique_cotaskmem_string
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A wil::unique_cotaskmem_string created from the string value read from the registry
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    template <>
    inline ::wil::unique_cotaskmem_string get_value_string<::wil::unique_cotaskmem_string>(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::get_value<::wil::unique_cotaskmem_string>(key, nullptr, value_name);
    }

    /**
     * @brief Reads a REG_EXPAND_SZ value, returning a wil::unique_cotaskmem_string
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A wil::unique_cotaskmem_string created from the string value read from the registry,
     *         with environment variables expanded, as though passed through ExpandEnvironmentStringsW
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    template <>
    inline ::wil::unique_cotaskmem_string get_value_expanded_string<::wil::unique_cotaskmem_string>(
        HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        ::wil::unique_cotaskmem_string value;
        const reg_view_details::reg_view regview{key};
        regview.get_value(subkey, value_name, value, REG_EXPAND_SZ);
        return value;
    }

    /**
     * @brief Reads a REG_EXPAND_SZ value, returning a wil::unique_cotaskmem_string
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A wil::unique_cotaskmem_string created from the string value read from the registry,
     *         with environment variables expanded, as though passed through ExpandEnvironmentStringsW
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    template <>
    inline ::wil::unique_cotaskmem_string get_value_expanded_string<::wil::unique_cotaskmem_string>(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return wil::reg::get_value_expanded_string<::wil::unique_cotaskmem_string>(key, nullptr, value_name);
    }

#if defined(__WIL_OBJBASE_H_STL) || defined(WIL_DOXYGEN)
    /**
     * @brief Reads a REG_SZ value, returning a wil::shared_cotaskmem_string
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A wil::shared_cotaskmem_string created from the string value read from the registry
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    template <>
    inline ::wil::shared_cotaskmem_string get_value_string<::wil::shared_cotaskmem_string>(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::get_value<::wil::shared_cotaskmem_string>(key, subkey, value_name);
    }

    /**
     * @brief Reads a REG_SZ value, returning a wil::shared_cotaskmem_string
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A wil::shared_cotaskmem_string created from the string value read from the registry
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    template <>
    inline ::wil::shared_cotaskmem_string get_value_string<::wil::shared_cotaskmem_string>(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::get_value<::wil::shared_cotaskmem_string>(key, nullptr, value_name);
    }

    /**
     * @brief Reads a REG_EXPAND_SZ value, returning a wil::shared_cotaskmem_string
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A wil::shared_cotaskmem_string created from the string value read from the registry,
     *         with environment variables expanded, as though passed through ExpandEnvironmentStringsW
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    template <>
    inline ::wil::shared_cotaskmem_string get_value_expanded_string<::wil::shared_cotaskmem_string>(
        HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        ::wil::shared_cotaskmem_string value;
        const reg_view_details::reg_view regview{key};
        regview.get_value(subkey, value_name, value, REG_EXPAND_SZ);
        return value;
    }

    /**
     * @brief Reads a REG_EXPAND_SZ value, returning a wil::shared_cotaskmem_string
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A wil::shared_cotaskmem_string created from the string value read from the registry,
     *         with environment variables expanded, as though passed through ExpandEnvironmentStringsW
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    template <>
    inline ::wil::shared_cotaskmem_string get_value_expanded_string<::wil::shared_cotaskmem_string>(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return wil::reg::get_value_expanded_string<::wil::shared_cotaskmem_string>(key, nullptr, value_name);
    }
#endif // #if defined(__WIL_OBJBASE_H_STL)
#endif // defined(__WIL_OBJBASE_H_)

#if WIL_USE_STL || defined(WIL_DOXYGEN)
    /**
     * @brief Reads a registry value of the specified type, returning a std::vector<BYTE>
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param type The registry type for the specified registry value to read from - see RegGetValueW
     * @return A std::vector<BYTE> containing the bytes of the specified registry value
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    inline ::std::vector<uint8_t> get_value_binary(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, uint32_t type)
    {
        ::std::vector<uint8_t> return_value{};
        const reg_view_details::reg_view regview{key};
        regview.get_value(subkey, value_name, return_value, type);
        return return_value;
    }

    /**
     * @brief Reads a registry value of the specified type, returning a std::vector<BYTE>
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param type The registry type for the specified registry value to read from - see RegGetValueW
     * @return A std::vector<BYTE> containing the bytes of the specified registry value
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     */
    inline ::std::vector<uint8_t> get_value_binary(HKEY key, _In_opt_ PCWSTR value_name, uint32_t type)
    {
        return ::wil::reg::get_value_binary(key, nullptr, value_name, type);
    }
#endif

#if WIL_USE_STL || defined(WIL_DOXYGEN)
    /**
     * @brief Reads a REG_MULTI_SZ value, returning a std::vector<std::wstring>
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A vector of strings read from the REG_MULTI_SZ. Note: embedded nulls will be read as empty strings. See remarks.
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     *
     * @remark Note that will return empty strings for embedded nulls - it won't stop at the first double-null character
     *         e.g. a REG_MULTI_SZ of L"string1\0\0string2\0\0string3\0\0"
     *              returns a vector of size 5: L"string1", empty-string, L"string2", empty-string, L"string3"
     */
    template <>
    inline ::std::vector<::std::wstring> get_value<::std::vector<::std::wstring>>(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        ::std::vector<::std::wstring> return_value;
        ::std::vector<uint8_t> rawData{::wil::reg::get_value_binary(key, subkey, value_name, REG_MULTI_SZ)};
        if (!rawData.empty())
        {
            auto* const begin = reinterpret_cast<wchar_t*>(rawData.data());
            auto* const end = begin + rawData.size() / sizeof(wchar_t);
            return_value = ::wil::reg::reg_view_details::get_wstring_vector_from_multistring(begin, end);
        }

        return return_value;
    }

    /**
     * @brief Reads a REG_MULTI_SZ value, returning a std::vector<std::wstring>
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A vector of strings read from the REG_MULTI_SZ. Note: embedded nulls will be read as empty strings. See remarks.
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     *
     * @remark Note that will return empty strings for embedded nulls - it won't stop at the first double-null character
     *         e.g. a REG_MULTI_SZ of L"string1\0\0string2\0\0string3\0\0"
     *              returns a vector of size 5: L"string1", empty-string, L"string2", empty-string, L"string3"
     */
    template <>
    inline ::std::vector<::std::wstring> get_value<::std::vector<::std::wstring>>(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::get_value<::std::vector<::std::wstring>>(key, nullptr, value_name);
    }

    /**
     * @brief Reads a REG_MULTI_SZ value, returning a std::vector<std::wstring>
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A vector of strings read from the REG_MULTI_SZ. Note: embedded nulls will be read as empty strings. See remarks.
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     *
     * @remark Note that will return empty strings for embedded nulls - it won't stop at the first double-null character
     *         e.g. a REG_MULTI_SZ of L"string1\0\0string2\0\0string3\0\0"
     *              returns a vector of size 5: L"string1", empty-string, L"string2", empty-string, L"string3"
     */
    inline ::std::vector<::std::wstring> get_value_multistring(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::get_value<::std::vector<::std::wstring>>(key, subkey, value_name);
    }

    /**
     * @brief Reads a REG_MULTI_SZ value, returning a std::vector<std::wstring>
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return A vector of strings read from the REG_MULTI_SZ. Note: embedded nulls will be read as empty strings. See remarks.
     * @exception std::exception (including wil::ResultException) will be thrown on all failures, including value not found
     *
     * @remark Note that will return empty strings for embedded nulls - it won't stop at the first double-null character
     *         e.g. a REG_MULTI_SZ of L"string1\0\0string2\0\0string3\0\0"
     *              returns a vector of size 5: L"string1", empty-string, L"string2", empty-string, L"string3"
     */
    inline ::std::vector<::std::wstring> get_value_multistring(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::get_value<::std::vector<::std::wstring>>(key, nullptr, value_name);
    }
#endif

#if (WIL_USE_STL && (__cpp_lib_optional >= 201606L)) || defined(WIL_DOXYGEN)
    //
    // template <typename T>
    // void try_get_value(...)
    //
    //  - Reads a value under a specified key and subkey, deducing registry type from the type parameter T.
    //  - throws a std::exception on failure (including wil::ResultException), except if the registry value was not found
    //    returns a std::nullopt if the registry value is not found
    //
    // Examples using the returned std::optional<uint32_t>
    //  - Caller should ensure the code handles a possible std::exception that will be thrown on all errors except value not found
    //
    //     std::optional<uint32_t> opt_dword_value = wil::reg::try_get_value<uint32_t>(key, L"dword_value_name");
    //     if (opt_dword_value.has_value())
    //     {
    //         // opt_dword_value.value() returns the uint32_t read from the registry
    //     }
    //     else
    //     {
    //         // the registry value did not exist
    //     }
    //     // if the caller wants to apply a default value of 0, they can call value_or()
    //     uint32_t opt_dword_value = wil::reg::try_get_value<uint32_t>(key, L"dword_value_name").value_or(0);
    //
    // Examples using the returned std::optional<std::wstring>
    //     std::optional<std::wstring> opt_string_value = wil::reg::try_get_value_string(key, L"string_value_name");
    //     if (opt_string_value.has_value())
    //     {
    //         // opt_string_value.value() returns the std::wstring read from the registry
    //         // the below avoids copying the std::wstring as value() here returns a std::wstring&
    //         PCWSTR string_value = opt_string_value.value().c_str();
    //     }
    //     else
    //     {
    //         // the registry value did not exist
    //     }
    //
    //     // if the caller wants to apply a default value of L"default", they can call value_or()
    //     // note that std::optional only attempts to construct a std::wstring for L"default" if the std::optional is empty (std::nullopt)
    //     // thus only allocating a new std::wstring for the default value when it's needed
    //     std::optional<std::wstring> opt_string_value = wil::reg::try_get_value_string(key, L"string_value_name").value_or(L"default");
    //
    // Examples of usage:
    //     std::optional<uint32_t> opt_dword_value = wil::reg::try_get_value<uint32_t>(key, L"subkey", L"dword_value_name");
    //     std::optional<uint64_t> opt_qword_value = wil::reg::try_get_value<uint64_t>(key, L"subkey", L"qword_value_name);
    //     std::optional<std::wstring> opt_string_value = wil::reg::try_get_value<std::wstring>(key, L"subkey", L"string_value_name");
    //
    // A subkey is not required if the key is opened where this should write the value; e.g.
    //     std::optional<uint32_t> opt_dword_value = wil::reg::try_get_value<uint32_t>(key, L"dword_value_name");
    //     std::optional<uint64_t> opt_qword_value = wil::reg::try_get_value<uint64_t>(key, L"qword_value_name);
    //     std::optional<std::wstring> opt_string_value = wil::reg::try_get_value<std::wstring>(key, L"string_value_name");
    //
    // The template type does not need to be specified if using functions written for a targeted type; e.g.
    //     std::optional<uint32_t> opt_dword_value = wil::reg::try_get_value_dword(key, L"dword_value_name");
    //     std::optional<uint64_t> opt_qword_value = wil::reg::try_get_value_qword(key, L"qword_value_name");
    //     std::optional<std::wstring> opt_string_value = wil::reg::try_get_value_string(key, L"string_value_name");
    //
    // Values with REG_EXPAND_SZ can be read into each of the string types; e.g.:
    //     std::optional<std::wstring> opt_expanded_string_value = wil::reg::try_get_value_expanded_string(key, L"string_value_name_with_environment_variables");
    //
    // Values can be read directly into a vector of bytes - the registry type must be specified; e.g.:
    //     std::optional<std::vector<BYTE>> opt_data = wil::reg::try_get_value_binary(key, L"binary_value_name", REG_BINARY);
    //
    // Multi-string values can be read into a std::vector<std::wstring>; e.g.:
    //     std::optional<::std::vector<::std::wstring>> try_get_value_multistring(key, L"multi_string_value_name");
    //     See the definition of try_get_value_multistring before for usage guidance
    //
    // Reading REG_SZ and REG_EXPAND_SZ types are done through the below templated try_get_value_string and try_get_value_expanded_string functions
    // Where the template type is the type to receive the string value
    // The default template type is std::wstring, available if the caller has included the STL <string> header
    //
    // Reading a bstr is returned in a std::optional<wil::shared_bstr> - because wil::unique_bstr cannot be copied and thus is difficult to work with a std::optional
    //     std::optional<wil::shared_bstr> shared_value { wil::reg::try_get_value_string<::wil::shared_bstr>(key, L"string_value_name") };
    //
    // Reading a cotaskmem string is returned in a std::optional<wil::shared_cotaskmem_string> - because wil::unique_cotaskmem_string cannot be copied and thus is difficult to work with a std::optional
    //     std::optional<wil::shared_cotaskmem_string> opt_shared_value { wil::reg::try_get_value_string<wil::shared_cotaskmem_string>(key, L"string_value_name") };
    //
    // Blocking try_get_value_string template types that are not already specialized - this gives a much friendlier compiler error message
    template <typename T>
    ::std::optional<T> try_get_value_string(HKEY /*key*/, _In_opt_ PCWSTR /*subkey*/, _In_opt_ PCWSTR /*value_name*/)
    {
        static_assert(sizeof(T) != sizeof(T), "Unsupported type for try_get_value_string");
    }

    template <typename T>
    ::std::optional<T> try_get_value_string(HKEY /*key*/, _In_opt_ PCWSTR /*value_name*/)
    {
        static_assert(sizeof(T) != sizeof(T), "Unsupported type for try_get_value_string");
    }

    template <typename T>
    ::std::optional<T> try_get_value_expanded_string(HKEY /*key*/, _In_opt_ PCWSTR /*subkey*/, _In_opt_ PCWSTR /*value_name*/)
    {
        static_assert(sizeof(T) != sizeof(T), "Unsupported type for try_get_value_expanded_string");
    }

    template <typename T>
    ::std::optional<T> try_get_value_expanded_string(HKEY /*key*/, _In_opt_ PCWSTR /*value_name*/)
    {
        static_assert(sizeof(T) != sizeof(T), "Unsupported type for try_get_value_expanded_string");
    }

    /**
     * @brief Attempts to read a value under a specified key and subkey, returning in a std::optional, deducing registry type from
     *        the type parameter T.
     * @tparam T The type to read, which will be placed into a std::optional (the registry value type is deduced from T)
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value (of type T) read from the registry value, in a std::optional<T>.
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    template <typename T>
    ::std::optional<T> try_get_value(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
#if defined(__WIL_OLEAUTO_H_)
        // not allowing unique types with try_get_value: wil::unique_bstr cannot be copied and thus is difficult to work with a std::optional
        static_assert(!wistd::is_same_v<T, ::wil::unique_bstr>, "try_get with wil::unique_bstr is disabled");
#endif // #if defined(__WIL_OLEAUTO_H_)
#if defined(__WIL_OBJBASE_H_)
        // not allowing unique types with try_get_value: wil::unique_cotaskmem_string cannot be copied and thus is difficult to work with a std::optional
        static_assert(!wistd::is_same_v<T, ::wil::unique_cotaskmem_string>, "try_get with wil::unique_cotaskmem_string is disabled");
#endif // #if defined(__WIL_OBJBASE_H_)

        const reg_view_details::reg_view regview{key};
        return regview.try_get_value<T>(subkey, value_name);
    }

    /**
     * @brief Attempts to read a value under a specified key, returning the value in a std::optional, deducing registry type from
     *        the type parameter T.
     * @tparam T The type to read, which will be placed into a std::optional (the registry value type is deduced from T)
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value (of type T) read from the registry value, in a std::optional<T>.
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    template <typename T>
    ::std::optional<T> try_get_value(HKEY key, _In_opt_ PCWSTR value_name)
    {
#if defined(__WIL_OLEAUTO_H_)
        // not allowing unique types with try_get_value: wil::unique_bstr cannot be copied and thus is difficult to work with a std::optional
        static_assert(!wistd::is_same_v<T, ::wil::unique_bstr>, "try_get with wil::unique_bstr is disabled");
#endif // #if defined(__WIL_OLEAUTO_H_)
#if defined(__WIL_OBJBASE_H_)
        // not allowing unique types with try_get_value: wil::unique_cotaskmem_string cannot be copied and thus is difficult to work with a std::optional
        static_assert(!wistd::is_same_v<T, ::wil::unique_cotaskmem_string>, "try_get with wil::unique_cotaskmem_string is disabled");
#endif // #if defined(__WIL_OBJBASE_H_)

        return ::wil::reg::try_get_value<T>(key, nullptr, value_name);
    }

    /**
     * @brief Attempts to read a REG_DWORD value under a specified key, returning the value in a std::optional
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value read from the registry value, in a std::optional.
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    inline ::std::optional<uint32_t> try_get_value_dword(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::try_get_value<uint32_t>(key, subkey, value_name);
    }

    /**
     * @brief Attempts to read a REG_DWORD value under a specified key, returning the value in a std::optional
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value read from the registry value, in a std::optional.
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    inline ::std::optional<uint32_t> try_get_value_dword(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::try_get_value<uint32_t>(key, nullptr, value_name);
    }

    /**
     * @brief Attempts to read a REG_QWORD value under a specified key, returning the value in a std::optional
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value read from the registry value, in a std::optional.
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    inline ::std::optional<uint64_t> try_get_value_qword(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::try_get_value<uint64_t>(key, subkey, value_name);
    }

    /**
     * @brief Attempts to read a REG_QWORD value under a specified key, returning the value in a std::optional
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value read from the registry value, in a std::optional.
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    inline ::std::optional<uint64_t> try_get_value_qword(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::try_get_value<uint64_t>(key, nullptr, value_name);
    }

#if WIL_USE_STL || defined(WIL_DOXYGEN)
    /**
     * @brief Attempts to read a value under a specified key requiring the specified type, returning the raw bytes in a
     *        std::optional
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param type The registry type for the specified registry value to read from - see RegGetValueW
     * @return The raw bytes read from the registry value stored in a std::optional<std::vector<BYTE>>.
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    inline ::std::optional<::std::vector<uint8_t>> try_get_value_binary(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, uint32_t type)
    {
        const reg_view_details::reg_view regview{key};
        return regview.try_get_value<::std::vector<uint8_t>>(subkey, value_name, type);
    }

    /**
     * @brief Attempts to read a value under a specified key requiring the specified type, returning the raw bytes in a
     *        std::optional
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param type The registry type for the specified registry value to read from - see RegGetValueW
     * @return The raw bytes read from the registry value stored in a std::optional<std::vector<BYTE>>.
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    inline ::std::optional<::std::vector<uint8_t>> try_get_value_binary(HKEY key, _In_opt_ PCWSTR value_name, uint32_t type)
    {
        return ::wil::reg::try_get_value_binary(key, nullptr, value_name, type);
    }
#endif

#if WIL_USE_STL || defined(WIL_DOXYGEN)
    /**
     * @brief Attempts to read a REG_SZ value under a specified key, returning the value in a std::optional
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value read from the registry value, in a std::optional.
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    inline ::std::optional<::std::wstring> try_get_value_string(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        const reg_view_details::reg_view regview{key};
        return regview.try_get_value<::std::wstring>(subkey, value_name);
    }

    /**
     * @brief Attempts to read a REG_SZ value under a specified key, returning the value in a std::optional
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value read from the registry value, in a std::optional.
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    inline ::std::optional<::std::wstring> try_get_value_string(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::try_get_value_string(key, nullptr, value_name);
    }

    /**
     * @brief Attempts to read a REG_SZ value under a specified key, returning the value in a std::optional
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value read from the registry value, in a std::optional.
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    template <>
    inline ::std::optional<::std::wstring> try_get_value_string<::std::wstring>(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::try_get_value_string(key, subkey, value_name);
    }

    /**
     * @brief Attempts to read a REG_SZ value under a specified key, returning the value in a std::optional
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value read from the registry value, in a std::optional.
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    template <>
    inline ::std::optional<::std::wstring> try_get_value_string<::std::wstring>(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::try_get_value_string(key, nullptr, value_name);
    }

    /**
     * @brief Attempts to read a REG_EXPAND_SZ value under a specified key, returning the value in a std::optional
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value read from the registry value of the template type std::optional<std::wstring>,
     *         with environment variables expanded, as though passed through ExpandEnvironmentStringsW.
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    inline ::std::optional<::std::wstring> try_get_value_expanded_string(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        const reg_view_details::reg_view regview{key};
        return regview.try_get_value<::std::wstring>(subkey, value_name, REG_EXPAND_SZ);
    }

    /**
     * @brief Attempts to read a REG_EXPAND_SZ value under a specified key, returning the value in a std::optional
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value read from the registry value of the template type std::optional<std::wstring>,
     *         with environment variables expanded, as though passed through ExpandEnvironmentStringsW.
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    inline ::std::optional<::std::wstring> try_get_value_expanded_string(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::try_get_value_expanded_string(key, nullptr, value_name);
    }

    /**
     * @brief Attempts to read a REG_EXPAND_SZ value under a specified key, returning the value in a std::optional
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value read from the registry value of the template type std::optional<std::wstring>,
     *         with environment variables expanded, as though passed through ExpandEnvironmentStringsW.
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    template <>
    inline ::std::optional<::std::wstring> try_get_value_expanded_string<::std::wstring>(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::try_get_value_expanded_string(key, subkey, value_name);
    }

    /**
     * @brief Attempts to read a REG_EXPAND_SZ value under a specified key, returning the value in a std::optional
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value read from the registry value of the template type std::optional<std::wstring>,
     *         with environment variables expanded, as though passed through ExpandEnvironmentStringsW.
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    template <>
    inline ::std::optional<::std::wstring> try_get_value_expanded_string<::std::wstring>(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::try_get_value_expanded_string(key, nullptr, value_name);
    }
#endif

#if defined(__WIL_OLEAUTO_H_STL) || defined(WIL_DOXYGEN)
    /**
     * @brief Attempts to read a REG_SZ value under a specified key, returning the value in a std::optional
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value read from the registry value of the template type std::optional<wil::shared_bstr>.
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    template <>
    inline ::std::optional<::wil::shared_bstr> try_get_value_string<::wil::shared_bstr>(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        const reg_view_details::reg_view regview{key};
        return regview.try_get_value<::wil::shared_bstr>(subkey, value_name);
    }

    /**
     * @brief Attempts to read a REG_SZ value under a specified key, returning the value in a std::optional
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value read from the registry value of the template type std::optional<wil::shared_bstr>.
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    template <>
    inline ::std::optional<::wil::shared_bstr> try_get_value_string<::wil::shared_bstr>(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::try_get_value_string<::wil::shared_bstr>(key, nullptr, value_name);
    }

    /**
     * @brief Attempts to read a REG_EXPAND_SZ value under a specified key, returning the value in a std::optional
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value read from the registry value of the template type std::optional<wil::shared_bstr>,
     *         with environment variables expanded, as though passed through ExpandEnvironmentStringsW.
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    template <>
    inline ::std::optional<::wil::shared_bstr> try_get_value_expanded_string<::wil::shared_bstr>(
        HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        const reg_view_details::reg_view regview{key};
        return regview.try_get_value<::wil::shared_bstr>(subkey, value_name, REG_EXPAND_SZ);
    }

    /**
     * @brief Attempts to read a REG_EXPAND_SZ value under a specified key, returning the value in a std::optional
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value read from the registry value of the template type std::optional<wil::shared_bstr>,
     *         with environment variables expanded, as though passed through ExpandEnvironmentStringsW.
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    template <>
    inline ::std::optional<::wil::shared_bstr> try_get_value_expanded_string<::wil::shared_bstr>(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::try_get_value_expanded_string<::wil::shared_bstr>(key, nullptr, value_name);
    }
#endif // #if defined(__WIL_OLEAUTO_H_STL)

#if defined(__WIL_OBJBASE_H_STL) || defined(WIL_DOXYGEN)
    /**
     * @brief Attempts to read a REG_SZ value under a specified key, returning the value in a std::optional
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value read from the registry value of the template type std::optional<wil::shared_cotaskmem_string>.
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    template <>
    inline ::std::optional<::wil::shared_cotaskmem_string> try_get_value_string<::wil::shared_cotaskmem_string>(
        HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        const reg_view_details::reg_view regview{key};
        return regview.try_get_value<::wil::shared_cotaskmem_string>(subkey, value_name);
    }

    /**
     * @brief Attempts to read a REG_SZ value under a specified key, returning the value in a std::optional
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value read from the registry value of the template type std::optional<wil::shared_cotaskmem_string>
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    template <>
    inline ::std::optional<::wil::shared_cotaskmem_string> try_get_value_string<::wil::shared_cotaskmem_string>(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::try_get_value_string<::wil::shared_cotaskmem_string>(key, nullptr, value_name);
    }

    /**
     * @brief Attempts to read a REG_EXPAND_SZ value under a specified key, returning the value in a std::optional
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value read from the registry value of the template type std::optional<:wil::shared_cotaskmem_string>,
     *         with environment variables expanded, as though passed through ExpandEnvironmentStringsW.
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    template <>
    inline ::std::optional<::wil::shared_cotaskmem_string> try_get_value_expanded_string<::wil::shared_cotaskmem_string>(
        HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        const reg_view_details::reg_view regview{key};
        return regview.try_get_value<::wil::shared_cotaskmem_string>(subkey, value_name, REG_EXPAND_SZ);
    }

    /**
     * @brief Attempts to read a REG_EXPAND_SZ value under a specified key, returning the value in a std::optional
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value read from the registry value of the template type std::optional<wil::shared_cotaskmem_string>,
     *         with environment variables expanded, as though passed through ExpandEnvironmentStringsW.
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    template <>
    inline ::std::optional<::wil::shared_cotaskmem_string> try_get_value_expanded_string<::wil::shared_cotaskmem_string>(
        HKEY key, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::try_get_value_expanded_string<::wil::shared_cotaskmem_string>(key, nullptr, value_name);
    }
#endif // defined(__WIL_OBJBASE_H_STL)

#if WIL_USE_STL || defined(WIL_DOXYGEN)
    /**
     * @brief Attempts to read a REG_MULTI_SZ value under a specified key, returning the value in a std::optional
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value read from the registry value marshaled to a std::optional<std::vector<std::wstring>>.
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    template <>
    inline ::std::optional<::std::vector<::std::wstring>> try_get_value<::std::vector<::std::wstring>>(
        HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        ::std::vector<::std::wstring> value;
        const auto hr = ::wil::ResultFromException([&] {
            value = ::wil::reg::get_value_multistring(key, subkey, value_name);
        });
        if (SUCCEEDED(hr))
        {
            return value;
        }

        if (!::wil::reg::is_registry_not_found(hr))
        {
            THROW_HR(HRESULT_FROM_WIN32(hr));
        }

        return ::std::nullopt;
    }

    /**
     * @brief Attempts to read a REG_MULTI_SZ value under a specified key, returning the value in a std::optional
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value read from the registry value marshaled to a std::optional<std::vector<std::wstring>>.
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    template <>
    inline ::std::optional<::std::vector<::std::wstring>> try_get_value<::std::vector<::std::wstring>>(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::try_get_value<::std::vector<::std::wstring>>(key, nullptr, value_name);
    }

    /**
     * @brief Attempts to read a REG_MULTI_SZ value under a specified key, returning the value in a std::optional
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value read from the registry value marshaled to a std::optional<std::vector<std::wstring>>.
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    inline ::std::optional<::std::vector<::std::wstring>> try_get_value_multistring(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::try_get_value<::std::vector<::std::wstring>>(key, subkey, value_name);
    }

    /**
     * @brief Attempts to read a REG_MULTI_SZ value under a specified key, returning the value in a std::optional
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be updated.
     *        Can be nullptr to read from the unnamed default registry value.
     * @return The value read from the registry value marshaled to a std::optional<std::vector<std::wstring>>.
     *         Returns std::nullopt if the value does not exist.
     * @exception std::exception (including wil::ResultException) will be thrown on failures except value not found
     */
    inline ::std::optional<::std::vector<::std::wstring>> try_get_value_multistring(HKEY key, _In_opt_ PCWSTR value_name)
    {
        return ::wil::reg::try_get_value<::std::vector<::std::wstring>>(key, nullptr, value_name);
    }
#endif
#endif
#endif

    //
    // template <typename T>
    // HRESULT get_value_nothrow(...)
    //
    //  - Reads a value from under a specified key
    //  - The required type of registry value being read from is determined by the template type T
    //  - Returns an HRESULT error code indicating success or failure (does not throw C++ exceptions)
    //
    // Examples of usage (the template type does not need to be explicitly specified)
    //     uint32_t dword_value{};
    //     hr = wil::reg::get_value_nothrow(key, L"subkey", L"dword_value_name", &dword_value); // reads a REG_DWORD
    //     uint64_t qword_value{};
    //     hr = wil::reg::get_value_nothrow(key, L"subkey", L"qword_value_name", &qword_value); // reads a REG_QWORD
    //     wil::unique_bstr string_value{};
    //     hr = wil::reg::get_value_nothrow(key, L"subkey", L"string_value_name", string_value); // reads a REG_SZ
    //
    // A subkey is not required if the key is opened where this should write the value:
    //     hr = wil::reg::get_value_nothrow(key, L"dword_value_name", &dword_value); // reads a REG_DWORD
    //     hr = wil::reg::get_value_nothrow(key, L"qword_value_name", &qword_value); // reads a REG_QWORD
    //     hr = wil::reg::get_value_nothrow(key, L"string_value_name", string_value); // reads a REG_SZ
    //
    // Can also specify the registry type in the function name:
    //     hr = wil::reg::get_value_dword_nothrow(key, L"dword_value_name", &dword_value); // reads a REG_DWORD
    //     hr = wil::reg::get_value_qword_nothrow(key, L"qword_value_name", &qword_value); // reads a REG_QWORD
    //     hr = wil::reg::get_value_string_nothrow(key, L"string_value_name", string_value); // reads a REG_SZ
    //
    // Example storing directly into a WCHAR array - note will return the required number of bytes if the supplied array is too small
    //     WCHAR string_value[100]{};
    //     uint32_t requiredBytes{};
    //     hr = wil::reg::get_value_string_nothrow(key, L"string_value_name", string_value, &requiredBytes);
    //
    // Example of usage writing a REG_MULTI_SZ
    //     wil::unique_cotaskmem_array_ptr<wil::unique_cotaskmem_string> string_values{};
    //     hr = wil::reg::get_value_multistring_nothrow(key, L"multi_string_value_name", string_values);
    //
    // Values can be written directly from a vector of bytes - the registry type must be specified; e.g.:
    //     wil::unique_cotaskmem_array_ptr<BYTE> raw_value{};
    //     hr = wil::reg::get_value_binary_nothrow(key, L"binary_value_name", REG_BINARY, raw_value);
    //
    // Reading REG_SZ and REG_EXPAND_SZ types are done through the below templated get_value_string_nothrow and get_value_expanded_string_nothrow functions
    // Where the template type is the type to receive the string value
    // The default template type is std::wstring, available if the caller has included the STL <string> header
    //
    // Example storing a string in a wil::unique_bstr, wil::shared_bstr, wil::unique_cotaskmem_string, or wil::shared_cotaskmem_string
    /// - These string types are passed by reference, not by pointer, because the wil types overload the & operator
    //
    //     wil::unique_bstr bstr_value{};
    //     hr = wil::reg::get_value_nothrow(key, L"string_value_name", bstr_value);
    //     // or can specify explicitly reading a string into a wil::unique_bstr type
    //     hr = wil::reg::get_value_string_nothrow(key, L"string_value_name", bstr_value);
    //
    //     wil::shared_bstr shared_bstr_value{};
    //     hr = wil::reg::get_value_nothrow(key, L"string_value_name", shared_bstr_value);
    //     // or can specify explicitly reading a string into a wil::shared_bstr type
    //     hr = wil::reg::get_value_string_nothrow(key, L"string_value_name", shared_bstr_value);
    //
    //     wil::unique_cotaskmem_string string_value{};
    //     hr = wil::reg::get_value_nothrow(key, L"string_value_name", string_value);
    //     // or can specify explicitly reading a string into a wil::unique_cotaskmem_string type
    //     hr = wil::reg::get_value_string_nothrow(key, L"string_value_name", string_value);
    //
    //     wil::shared_cotaskmem_string shared_string_value{};
    //     hr = wil::reg::get_value_nothrow(key, L"string_value_name", shared_string_value);
    //     // or can specify explicitly reading a string into a wil::shared_cotaskmem_string type
    //     hr = wil::reg::get_value_string_nothrow(key, L"string_value_name", shared_string_value);
    //

    /**
     * @brief Reads a value under a specified key, the registry type based off the templated type passed as data
     * @tparam T The type of the data being set (the registry value type is deduced from T).
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A pointer-to-T receiving the value read from the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    template <typename T, wistd::enable_if_t<!wistd::is_same_v<T, wchar_t>>* = nullptr>
    HRESULT get_value_nothrow(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, _Out_ T* return_value) WI_NOEXCEPT
    {
        const reg_view_details::reg_view_nothrow regview{key};
        return regview.get_value<T>(subkey, value_name, *return_value);
    }

    /**
     * @brief Reads a value under a specified key, the registry type based off the templated type passed as data
     * @tparam T The type of the data being set (the registry value type is deduced from T).
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A pointer-to-T receiving the value read from the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    template <typename T, wistd::enable_if_t<!wistd::is_same_v<T, wchar_t>>* = nullptr>
    HRESULT get_value_nothrow(HKEY key, _In_opt_ PCWSTR value_name, _Out_ T* return_value) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_nothrow(key, nullptr, value_name, return_value);
    }

    /**
     * @brief Reads a REG_SZ value under a specified key
     * @tparam Length The length of the WCHAR array passed as an OUT parameter
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A WCHAR array receiving the value read from the registry.
     *             Will write to the WCHAR array the string value read from the registry, guaranteeing null-termination
     * @param[out] requiredBytes An optional pointer to a unsigned 32-bit value to receive the required bytes of the string in the
     *             registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    template <size_t Length, typename DwordType, wistd::enable_if_t<wistd::is_same_v<DwordType, uint32_t> || wistd::is_same_v<DwordType, unsigned long>>* = nullptr>
    HRESULT get_value_string_nothrow(
        HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, WCHAR (&return_value)[Length], _Out_opt_ DwordType* requiredBytes) WI_NOEXCEPT
    {
        const reg_view_details::reg_view_nothrow regview{key};
        return regview.get_value_char_array(subkey, value_name, return_value, REG_SZ, requiredBytes);
    }

    /**
     * @brief Reads a REG_SZ value under a specified key
     * @tparam Length The length of the WCHAR array passed as an OUT parameter
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A WCHAR array receiving the value read from the registry.
     *             Will write to the WCHAR array the string value read from the registry, guaranteeing null-termination
     * @param[out] requiredBytes An optional pointer to an unsigned 32-bit value to receive the required bytes of the string to be
     *             read
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    template <size_t Length, typename DwordType, wistd::enable_if_t<wistd::is_same_v<DwordType, uint32_t> || wistd::is_same_v<DwordType, unsigned long>>* = nullptr>
    HRESULT get_value_string_nothrow(HKEY key, _In_opt_ PCWSTR value_name, WCHAR (&return_value)[Length], _Out_opt_ DwordType* requiredBytes) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_string_nothrow<Length>(key, nullptr, value_name, return_value, requiredBytes);
    }

    /**
     * @brief Reads a REG_SZ value under a specified key
     * @tparam Length The length of the WCHAR array passed as an OUT parameter
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A WCHAR array receiving the value read from the registry.
     *             Will write to the WCHAR array the string value read from the registry, guaranteeing null-termination
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    template <size_t Length>
    HRESULT get_value_string_nothrow(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, WCHAR (&return_value)[Length]) WI_NOEXCEPT
    {
        constexpr uint32_t* null_out_param = nullptr;
        const reg_view_details::reg_view_nothrow regview{key};
        return regview.get_value_char_array(subkey, value_name, return_value, REG_SZ, null_out_param);
    }

    /**
     * @brief Reads a REG_SZ value under a specified key
     * @tparam Length The length of the WCHAR array passed as an OUT parameter
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A WCHAR array receiving the value read from the registry.
     *             Will write to the WCHAR array the string value read from the registry, guaranteeing null-termination
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    template <size_t Length>
    HRESULT get_value_string_nothrow(HKEY key, _In_opt_ PCWSTR value_name, WCHAR (&return_value)[Length]) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_string_nothrow<Length>(key, nullptr, value_name, return_value);
    }

    /**
     * @brief Reads a REG_SZ value under a specified key
     * @tparam Length The length of the WCHAR array passed as an OUT parameter
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A WCHAR array receiving the value read from the registry.
     *             Will write to the WCHAR array the string value read from the registry, guaranteeing null-termination
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    template <size_t Length>
    HRESULT get_value_nothrow(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, WCHAR (&return_value)[Length]) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_string_nothrow<Length>(key, subkey, value_name, return_value);
    }

    /**
     * @brief Reads a REG_SZ value under a specified key
     * @tparam Length The length of the WCHAR array passed as an OUT parameter
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A WCHAR array receiving the value read from the registry.
     *             Will write to the WCHAR array the string value read from the registry, guaranteeing null-termination
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    template <size_t Length>
    HRESULT get_value_nothrow(HKEY key, _In_opt_ PCWSTR value_name, WCHAR (&return_value)[Length]) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_string_nothrow<Length>(key, nullptr, value_name, return_value);
    }

    /**
     * @brief Reads a REG_DWORD value under a specified key
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A pointer to an unsigned 32-bit value receiving the value read from the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    template <typename DwordType, wistd::enable_if_t<wistd::is_same_v<DwordType, uint32_t> || wistd::is_same_v<DwordType, unsigned long>>* = nullptr>
    HRESULT get_value_dword_nothrow(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, _Out_ DwordType* return_value) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_nothrow(key, subkey, value_name, return_value);
    }

    /**
     * @brief Reads a REG_DWORD value under a specified key
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A pointer to an unsigned 32-bit value receiving the value read from the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    template <typename DwordType, wistd::enable_if_t<wistd::is_same_v<DwordType, uint32_t> || wistd::is_same_v<DwordType, unsigned long>>* = nullptr>
    HRESULT get_value_dword_nothrow(HKEY key, _In_opt_ PCWSTR value_name, _Out_ DwordType* return_value) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_nothrow(key, nullptr, value_name, return_value);
    }

    /**
     * @brief Reads a REG_QWORD value under a specified key
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A uint64_t receiving the value read from the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    template <typename QwordType, wistd::enable_if_t<wistd::is_same_v<QwordType, uint64_t> || wistd::is_same_v<QwordType, unsigned long long>>* = nullptr>
    HRESULT get_value_qword_nothrow(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, _Out_ QwordType* return_value) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_nothrow(key, subkey, value_name, return_value);
    }

    /**
     * @brief Reads a REG_QWORD value under a specified key
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A uint64_t receiving the value read from the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    template <typename QwordType, wistd::enable_if_t<wistd::is_same_v<QwordType, uint64_t> || wistd::is_same_v<QwordType, unsigned long long>>* = nullptr>
    HRESULT get_value_qword_nothrow(HKEY key, _In_opt_ PCWSTR value_name, _Out_ QwordType* return_value) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_nothrow(key, nullptr, value_name, return_value);
    }

#if defined(__WIL_OLEAUTO_H_) || defined(WIL_DOXYGEN)
    /**
     * @brief Reads a REG_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A wil::unique_bstr receiving the value read from the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_nothrow(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, ::wil::unique_bstr& return_value) WI_NOEXCEPT
    {
        const reg_view_details::reg_view_nothrow regview{key};
        return regview.get_value(subkey, value_name, return_value);
    }

    /**
     * @brief Reads a REG_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A wil::unique_bstr receiving the value read from the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_nothrow(HKEY key, _In_opt_ PCWSTR value_name, ::wil::unique_bstr& return_value) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_nothrow(key, nullptr, value_name, return_value);
    }

    /**
     * @brief Reads a REG_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A wil::unique_bstr receiving the value read from the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_string_nothrow(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, ::wil::unique_bstr& return_value) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_nothrow(key, subkey, value_name, return_value.addressof());
    }

    /**
     * @brief Reads a REG_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A wil::unique_bstr receiving the value read from the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_string_nothrow(HKEY key, _In_opt_ PCWSTR value_name, ::wil::unique_bstr& return_value) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_string_nothrow(key, nullptr, value_name, return_value);
    }

#if defined(__WIL_OLEAUTO_H_STL) || defined(WIL_DOXYGEN)
    /**
     * @brief Reads a REG_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A wil::shared_bstr receiving the value read from the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_nothrow(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, ::wil::shared_bstr& return_value) WI_NOEXCEPT
    {
        const reg_view_details::reg_view_nothrow regview{key};
        return regview.get_value(subkey, value_name, return_value);
    }

    /**
     * @brief Reads a REG_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A wil::shared_bstr receiving the value read from the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_nothrow(HKEY key, _In_opt_ PCWSTR value_name, ::wil::shared_bstr& return_value) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_nothrow(key, nullptr, value_name, return_value);
    }

    /**
     * @brief Reads a REG_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A wil::shared_bstr receiving the value read from the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_string_nothrow(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, ::wil::shared_bstr& return_value) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_nothrow(key, subkey, value_name, return_value.addressof());
    }

    /**
     * @brief Reads a REG_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A wil::shared_bstr receiving the value read from the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_string_nothrow(HKEY key, _In_opt_ PCWSTR value_name, ::wil::shared_bstr& return_value) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_string_nothrow(key, nullptr, value_name, return_value);
    }
#endif // #if defined(__WIL_OLEAUTO_H_STL)
#endif // #if defined(__WIL_OLEAUTO_H_)

#if defined(__WIL_OBJBASE_H_) || defined(WIL_DOXYGEN)
    /**
     * @brief Reads a REG_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A wil::unique_cotaskmem_string receiving the value read from the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_nothrow(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, ::wil::unique_cotaskmem_string& return_value) WI_NOEXCEPT
    {
        return_value.reset();
        const reg_view_details::reg_view_nothrow regview{key};
        return regview.get_value(subkey, value_name, return_value);
    }

    /**
     * @brief Reads a REG_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A wil::unique_cotaskmem_string receiving the value read from the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_nothrow(HKEY key, _In_opt_ PCWSTR value_name, ::wil::unique_cotaskmem_string& return_value) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_nothrow(key, nullptr, value_name, return_value);
    }

    /**
     * @brief Reads a REG_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A wil::unique_cotaskmem_string receiving the value read from the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_string_nothrow(
        HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, ::wil::unique_cotaskmem_string& return_value) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_nothrow(key, subkey, value_name, return_value);
    }

    /**
     * @brief Reads a REG_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A wil::unique_cotaskmem_string receiving the value read from the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_string_nothrow(HKEY key, _In_opt_ PCWSTR value_name, ::wil::unique_cotaskmem_string& return_value) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_nothrow(key, nullptr, value_name, return_value);
    }

#if defined(__WIL_OBJBASE_H_STL) || defined(WIL_DOXYGEN)
    /**
     * @brief Reads a REG_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A wil::shared_cotaskmem_string receiving the value read from the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_nothrow(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, ::wil::shared_cotaskmem_string& return_value) WI_NOEXCEPT
    {
        return_value.reset();
        const reg_view_details::reg_view_nothrow regview{key};
        return regview.get_value(subkey, value_name, return_value);
    }

    /**
     * @brief Reads a REG_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A wil::shared_cotaskmem_string receiving the value read from the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_nothrow(HKEY key, _In_opt_ PCWSTR value_name, ::wil::shared_cotaskmem_string& return_value) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_nothrow(key, nullptr, value_name, return_value);
    }

    /**
     * @brief Reads a REG_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A wil::shared_cotaskmem_string receiving the value read from the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_string_nothrow(
        HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, ::wil::shared_cotaskmem_string& return_value) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_nothrow(key, subkey, value_name, return_value);
    }

    /**
     * @brief Reads a REG_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A wil::shared_cotaskmem_string receiving the value read from the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_string_nothrow(HKEY key, _In_opt_ PCWSTR value_name, ::wil::shared_cotaskmem_string& return_value) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_nothrow(key, nullptr, value_name, return_value);
    }
#endif // #if defined(__WIL_OBJBASE_H_STL)
#endif // defined(__WIL_OBJBASE_H_)

#if defined(__WIL_OBJBASE_H_) || defined(WIL_DOXYGEN)
    /**
     * @brief Reads the raw bytes from a registry value under a specified key of the specified type
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param type The registry type for the specified registry value to read from - see RegGetValueW
     * @param[out] return_value A ::wil::unique_cotaskmem_array_ptr<BYTE> receiving the value read from the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_binary_nothrow(
        HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, uint32_t type, ::wil::unique_cotaskmem_array_ptr<uint8_t>& return_value) WI_NOEXCEPT
    {
        // zero the vector if it already had a buffer
        for (auto& byte_value : return_value)
        {
            byte_value = 0x00;
        }
        const reg_view_details::reg_view_nothrow regview{key};
        RETURN_IF_FAILED(regview.get_value<::wil::unique_cotaskmem_array_ptr<uint8_t>>(subkey, value_name, return_value, type));
        return S_OK;
    }

    /**
     * @brief Reads the raw bytes from a registry value under a specified key of the specified type
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param type The registry type for the specified registry value to read from - see RegGetValueW
     * @param[out] return_value A ::wil::unique_cotaskmem_array_ptr<BYTE> receiving the value read from the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_binary_nothrow(
        HKEY key, _In_opt_ PCWSTR value_name, uint32_t type, ::wil::unique_cotaskmem_array_ptr<uint8_t>& return_value) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_binary_nothrow(key, nullptr, value_name, type, return_value);
    }
#endif // #if defined(__WIL_OBJBASE_H_)

    /**
     * @brief Reads a REG_EXPAND_SZ value under a specified key
     * @tparam Length The length of the WCHAR array passed as an OUT parameter
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A WCHAR array receiving the value read from the registry,
     *             with environment variables expanded, as though passed through ExpandEnvironmentStringsW.
     *             Will write to the WCHAR array the string value read from the registry, guaranteeing null-termination
     * @param[out] requiredBytes An optional pointer to a uint32_t to receive the required bytes of the string to be read
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    template <size_t Length, typename DwordType, wistd::enable_if_t<wistd::is_same_v<DwordType, uint32_t> || wistd::is_same_v<DwordType, unsigned long>>* = nullptr>
    HRESULT get_value_expanded_string_nothrow(
        HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, WCHAR (&return_value)[Length], _Out_opt_ DwordType* requiredBytes) WI_NOEXCEPT
    {
        const reg_view_details::reg_view_nothrow regview{key};
        return regview.get_value_char_array(subkey, value_name, return_value, REG_EXPAND_SZ, requiredBytes);
    }

    /**
     * @brief Reads a REG_EXPAND_SZ value under a specified key
     * @tparam Length The length of the WCHAR array passed as an OUT parameter
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A WCHAR array receiving the value read from the registry,
     *             with environment variables expanded, as though passed through ExpandEnvironmentStringsW.
     *             Will write to the WCHAR array the string value read from the registry, guaranteeing null-termination
     * @param[out] requiredBytes An optional pointer to a uint32_t to receive the required bytes of the string to be read
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    template <size_t Length, typename DwordType, wistd::enable_if_t<wistd::is_same_v<DwordType, uint32_t> || wistd::is_same_v<DwordType, unsigned long>>* = nullptr>
    HRESULT get_value_expanded_string_nothrow(
        HKEY key, _In_opt_ PCWSTR value_name, WCHAR (&return_value)[Length], _Out_opt_ DwordType* requiredBytes) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_expanded_string_nothrow<Length>(key, nullptr, value_name, return_value, requiredBytes);
    }

    /**
     * @brief Reads a REG_EXPAND_SZ value under a specified key
     * @tparam Length The length of the WCHAR array passed as an OUT parameter
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A WCHAR array receiving the value read from the registry,
     *             with environment variables expanded, as though passed through ExpandEnvironmentStringsW.
     *             Will write to the WCHAR array the string value read from the registry, guaranteeing null-termination
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    template <size_t Length>
    HRESULT get_value_expanded_string_nothrow(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, WCHAR (&return_value)[Length]) WI_NOEXCEPT
    {
        constexpr uint32_t* null_out_param = nullptr;
        const reg_view_details::reg_view_nothrow regview{key};
        return regview.get_value_char_array(subkey, value_name, return_value, REG_EXPAND_SZ, null_out_param);
    }

    /**
     * @brief Reads a REG_EXPAND_SZ value under a specified key
     * @tparam Length The length of the WCHAR array passed as an OUT parameter
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A WCHAR array receiving the value read from the registry,
     *             with environment variables expanded, as though passed through ExpandEnvironmentStringsW.
     *             Will write to the WCHAR array the string value read from the registry, guaranteeing null-termination
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    template <size_t Length>
    HRESULT get_value_expanded_string_nothrow(HKEY key, _In_opt_ PCWSTR value_name, WCHAR (&return_value)[Length]) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_expanded_string_nothrow<Length>(key, nullptr, value_name, return_value);
    }

#if defined(__WIL_OLEAUTO_H_) || defined(WIL_DOXYGEN)
    /**
     * @brief Reads a REG_EXPAND_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A wil::unique_bstr receiving the value read from the registry,
     *             with environment variables expanded, as though passed through ExpandEnvironmentStringsW.
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_expanded_string_nothrow(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, ::wil::unique_bstr& return_value) WI_NOEXCEPT
    {
        const reg_view_details::reg_view_nothrow regview{key};
        return regview.get_value<::wil::unique_bstr>(subkey, value_name, return_value, REG_EXPAND_SZ);
    }

    /**
     * @brief Reads a REG_EXPAND_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A wil::unique_bstr receiving the value read from the registry,
     *             with environment variables expanded, as though passed through ExpandEnvironmentStringsW.
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_expanded_string_nothrow(HKEY key, _In_opt_ PCWSTR value_name, ::wil::unique_bstr& return_value) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_expanded_string_nothrow(key, nullptr, value_name, return_value);
    }

#if defined(__WIL_OLEAUTO_H_STL) || defined(WIL_DOXYGEN)
    /**
     * @brief Reads a REG_EXPAND_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A wil::shared_bstr receiving the value read from the registry,
     *             with environment variables expanded, as though passed through ExpandEnvironmentStringsW.
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_expanded_string_nothrow(HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, ::wil::shared_bstr& return_value) WI_NOEXCEPT
    {
        const reg_view_details::reg_view_nothrow regview{key};
        return regview.get_value<::wil::shared_bstr>(subkey, value_name, return_value, REG_EXPAND_SZ);
    }

    /**
     * @brief Reads a REG_EXPAND_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A wil::shared_bstr receiving the value read from the registry,
     *             with environment variables expanded, as though passed through ExpandEnvironmentStringsW.
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_expanded_string_nothrow(HKEY key, _In_opt_ PCWSTR value_name, ::wil::shared_bstr& return_value) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_expanded_string_nothrow(key, nullptr, value_name, return_value);
    }
#endif // #if defined(__WIL_OLEAUTO_H_STL)
#endif // #if defined(__WIL_OLEAUTO_H_)

#if defined(__WIL_OBJBASE_H_) || defined(WIL_DOXYGEN)
    /**
     * @brief Reads a REG_EXPAND_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A wil::unique_cotaskmem_string receiving the value read from the registry,
     *             with environment variables expanded, as though passed through ExpandEnvironmentStringsW.
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_expanded_string_nothrow(
        HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, ::wil::unique_cotaskmem_string& return_value) WI_NOEXCEPT
    {
        const reg_view_details::reg_view_nothrow regview{key};
        return regview.get_value<::wil::unique_cotaskmem_string>(subkey, value_name, return_value, REG_EXPAND_SZ);
    }

    /**
     * @brief Reads a REG_EXPAND_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A wil::unique_cotaskmem_string receiving the value read from the registry,
     *             with environment variables expanded, as though passed through ExpandEnvironmentStringsW.
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_expanded_string_nothrow(HKEY key, _In_opt_ PCWSTR value_name, ::wil::unique_cotaskmem_string& return_value) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_expanded_string_nothrow(key, nullptr, value_name, return_value);
    }

#if defined(__WIL_OBJBASE_H_STL) || defined(WIL_DOXYGEN)
    /**
     * @brief Reads a REG_EXPAND_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A wil::shared_cotaskmem_string receiving the value read from the registry,
     *             with environment variables expanded, as though passed through ExpandEnvironmentStringsW.
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_expanded_string_nothrow(
        HKEY key, _In_opt_ PCWSTR subkey, _In_opt_ PCWSTR value_name, ::wil::shared_cotaskmem_string& return_value) WI_NOEXCEPT
    {
        const reg_view_details::reg_view_nothrow regview{key};
        return regview.get_value<::wil::shared_cotaskmem_string>(subkey, value_name, return_value, REG_EXPAND_SZ);
    }

    /**
     * @brief Reads a REG_EXPAND_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A wil::shared_cotaskmem_string receiving the value read from the registry,
     *             with environment variables expanded, as though passed through ExpandEnvironmentStringsW.
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_expanded_string_nothrow(HKEY key, _In_opt_ PCWSTR value_name, ::wil::shared_cotaskmem_string& return_value) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_expanded_string_nothrow(key, nullptr, value_name, return_value);
    }
#endif // #if defined(__WIL_OBJBASE_H_STL)
#endif // defined(__WIL_OBJBASE_H_)

#if defined(__WIL_OBJBASE_H_) || defined(WIL_DOXYGEN)
    /**
     * @brief Reads a REG_MULTI_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A ::wil::unique_cotaskmem_array_ptr<::wil::unique_cotaskmem_string> receiving the value read from
     *             the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_nothrow(
        HKEY key,
        _In_opt_ PCWSTR subkey,
        _In_opt_ PCWSTR value_name,
        ::wil::unique_cotaskmem_array_ptr<::wil::unique_cotaskmem_string>& return_value) WI_NOEXCEPT
    {
        ::wil::unique_cotaskmem_array_ptr<uint8_t> rawData;
        RETURN_IF_FAILED(::wil::reg::get_value_binary_nothrow(key, subkey, value_name, REG_MULTI_SZ, rawData));
        if (!rawData.empty())
        {
            auto* const begin = reinterpret_cast<wchar_t*>(rawData.data());
            auto* const end = begin + rawData.size() / sizeof(wchar_t);
            ::wil::reg::reg_view_details::get_cotaskmemstring_array_from_multistring_nothrow(begin, end, return_value);
        }
        return S_OK;
    }

    /**
     * @brief Reads a REG_MULTI_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A ::wil::unique_cotaskmem_array_ptr<::wil::unique_cotaskmem_string> receiving the value read from
     *             the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_nothrow(
        HKEY key, _In_opt_ PCWSTR value_name, ::wil::unique_cotaskmem_array_ptr<::wil::unique_cotaskmem_string>& return_value) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_nothrow(key, nullptr, value_name, return_value);
    }

    /**
     * @brief Reads a REG_MULTI_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param subkey The name of the subkey to append to `key`.
     *        If `nullptr`, then `key` is used without modification.
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A ::wil::unique_cotaskmem_array_ptr<::wil::unique_cotaskmem_string> receiving the value read from
     *             the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_multistring_nothrow(
        HKEY key,
        _In_opt_ PCWSTR subkey,
        _In_opt_ PCWSTR value_name,
        ::wil::unique_cotaskmem_array_ptr<::wil::unique_cotaskmem_string>& return_value) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_nothrow(key, subkey, value_name, return_value);
    }

    /**
     * @brief Reads a REG_MULTI_SZ value under a specified key
     * @param key An open or well-known registry key
     * @param value_name The name of the registry value whose data is to be read.
     *        Can be nullptr to read from the unnamed default registry value.
     * @param[out] return_value A ::wil::unique_cotaskmem_array_ptr<::wil::unique_cotaskmem_string> receiving the value read from
     *             the registry
     * @return HRESULT error code indicating success or failure (does not throw C++ exceptions)
     */
    inline HRESULT get_value_multistring_nothrow(
        HKEY key, _In_opt_ PCWSTR value_name, ::wil::unique_cotaskmem_array_ptr<::wil::unique_cotaskmem_string>& return_value) WI_NOEXCEPT
    {
        return ::wil::reg::get_value_nothrow(key, nullptr, value_name, return_value);
    }
#endif // #if defined(__WIL_OBJBASE_H_)
} // namespace reg

// unique_registry_watcher/unique_registry_watcher_nothrow/unique_registry_watcher_failfast
// These classes make it easy to execute a provided function when a
// registry key changes (optionally recursively). Specify the key
// either as a root key + path, or an open registry handle as wil::unique_hkey
// or a raw HKEY value (that will be duplicated).
//
// Example use with exceptions base error handling:
// auto watcher = wil::make_registry_watcher(HKEY_CURRENT_USER, L"Software\\MyApp", true, wil::RegistryChangeKind changeKind[]
//     {
//          if (changeKind == RegistryChangeKind::Delete)
//          {
//              watcher.reset();
//          }
//         // invalidate cached registry data here
//     });
//
// Example use with error code base error handling:
// auto watcher = wil::make_registry_watcher_nothrow(HKEY_CURRENT_USER, L"Software\\MyApp", true, wil::RegistryChangeKind[]
//     {
//         // invalidate cached registry data here
//     });
// RETURN_IF_NULL_ALLOC(watcher);

enum class RegistryChangeKind
{
    Modify = 0,
    Delete = 1,
};

/// @cond
namespace details
{
    struct registry_watcher_state
    {
        registry_watcher_state(unique_hkey&& keyToWatch, bool isRecursive, wistd::function<void(RegistryChangeKind)>&& callback) :
            m_callback(wistd::move(callback)), m_keyToWatch(wistd::move(keyToWatch)), m_isRecursive(isRecursive)
        {
        }
        wistd::function<void(RegistryChangeKind)> m_callback;
        unique_hkey m_keyToWatch;
        unique_event_nothrow m_eventHandle;

        // While not strictly needed since this is ref counted the thread pool wait
        // should be last to ensure that the other members are valid
        // when it is destructed as it will reference them.
        unique_threadpool_wait m_threadPoolWait;
        bool m_isRecursive;

        volatile long m_refCount = 1;
        srwlock m_lock;

        // Returns true if the ref-count can be increased from a non zero value,
        // false it was zero implying that the object is in or on the way to the destructor.
        // In this case ReleaseFromCallback() should not be called.
        bool TryAddRef()
        {
            return ::InterlockedIncrement(&m_refCount) > 1;
        }

        void Release()
        {
            auto lock = m_lock.lock_exclusive();
            if (0 == ::InterlockedDecrement(&m_refCount))
            {
                lock.reset(); // leave the lock before deleting it.
                delete this;
            }
        }

        void ReleaseFromCallback(bool rearm)
        {
            auto lock = m_lock.lock_exclusive();
            if (0 == ::InterlockedDecrement(&m_refCount))
            {
                // Destroy the thread pool wait now to avoid the wait that would occur in the
                // destructor. That wait would cause a deadlock since we are doing this from the callback.
                ::CloseThreadpoolWait(m_threadPoolWait.release());
                lock.reset(); // leave the lock before deleting it.
                delete this;
                // Sleep(1); // Enable for testing to find use after free bugs.
            }
            else if (rearm)
            {
                ::SetThreadpoolWait(m_threadPoolWait.get(), m_eventHandle.get(), nullptr);
            }
        }
    };

    inline void delete_registry_watcher_state(_In_opt_ registry_watcher_state* watcherStorage)
    {
        watcherStorage->Release();
    }

    typedef resource_policy<registry_watcher_state*, decltype(&details::delete_registry_watcher_state), details::delete_registry_watcher_state, details::pointer_access_none>
        registry_watcher_state_resource_policy;
} // namespace details
/// @endcond

template <typename storage_t, typename err_policy = err_exception_policy>
class registry_watcher_t : public storage_t
{
public:
    // forward all base class constructors...
    template <typename... args_t>
    explicit registry_watcher_t(args_t&&... args) WI_NOEXCEPT : storage_t(wistd::forward<args_t>(args)...)
    {
    }

    // HRESULT or void error handling...
    typedef typename err_policy::result result;

    // Exception-based constructors
    registry_watcher_t(HKEY rootKey, _In_ PCWSTR subKey, bool isRecursive, wistd::function<void(RegistryChangeKind)>&& callback)
    {
        static_assert(wistd::is_same<void, result>::value, "this constructor requires exceptions; use the create method");
        create(rootKey, subKey, isRecursive, wistd::move(callback));
    }

    registry_watcher_t(unique_hkey&& keyToWatch, bool isRecursive, wistd::function<void(RegistryChangeKind)>&& callback)
    {
        static_assert(wistd::is_same<void, result>::value, "this constructor requires exceptions; use the create method");
        create(wistd::move(keyToWatch), isRecursive, wistd::move(callback));
    }

    // Pass a root key, sub key pair or use an empty string to use rootKey as the key to watch.
    result create(HKEY rootKey, _In_ PCWSTR subKey, bool isRecursive, wistd::function<void(RegistryChangeKind)>&& callback)
    {
        // Most use will want to create the key, consider adding an option for open as a future design change.
        unique_hkey keyToWatch;
        HRESULT hr = HRESULT_FROM_WIN32(RegCreateKeyExW(rootKey, subKey, 0, nullptr, 0, KEY_NOTIFY, nullptr, &keyToWatch, nullptr));
        if (FAILED(hr))
        {
            return err_policy::HResult(hr);
        }
        return err_policy::HResult(create_common(wistd::move(keyToWatch), isRecursive, wistd::move(callback)));
    }

    result create(unique_hkey&& keyToWatch, bool isRecursive, wistd::function<void(RegistryChangeKind)>&& callback)
    {
        return err_policy::HResult(create_common(wistd::move(keyToWatch), isRecursive, wistd::move(callback)));
    }

private:
    // Factored into a standalone function to support Clang which does not support conversion of stateless lambdas
    // to __stdcall
    static void __stdcall callback(PTP_CALLBACK_INSTANCE, void* context, TP_WAIT*, TP_WAIT_RESULT)
    {
#ifndef __WIL_REGISTRY_CHANGE_CALLBACK_TEST
#define __WIL_REGISTRY_CHANGE_CALLBACK_TEST
#endif
        __WIL_REGISTRY_CHANGE_CALLBACK_TEST
        const auto watcherState = static_cast<details::registry_watcher_state*>(context);
        if (watcherState->TryAddRef())
        {
            // using auto reset event so don't need to manually reset.

            // failure here is a programming error.
            const LSTATUS error = RegNotifyChangeKeyValue(
                watcherState->m_keyToWatch.get(),
                watcherState->m_isRecursive,
                REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_THREAD_AGNOSTIC,
                watcherState->m_eventHandle.get(),
                TRUE);

            // Call the client before re-arming to ensure that multiple callbacks don't
            // run concurrently.
            switch (error)
            {
            case ERROR_SUCCESS:
            case ERROR_ACCESS_DENIED:
                // Normal modification: send RegistryChangeKind::Modify and re-arm.
                watcherState->m_callback(RegistryChangeKind::Modify);
                watcherState->ReleaseFromCallback(true);
                break;

            case ERROR_KEY_DELETED:
                // Key deleted, send RegistryChangeKind::Delete, do not re-arm.
                watcherState->m_callback(RegistryChangeKind::Delete);
                watcherState->ReleaseFromCallback(false);
                break;

            case ERROR_HANDLE_REVOKED:
                // Handle revoked.  This can occur if the user session ends before
                // the watcher shuts-down.  Disarm silently since there is generally no way to respond.
                watcherState->ReleaseFromCallback(false);
                break;

            default:
                FAIL_FAST_HR(HRESULT_FROM_WIN32(error));
            }
        }
    }

    // This function exists to avoid template expansion of this code based on err_policy.
    HRESULT create_common(unique_hkey&& keyToWatch, bool isRecursive, wistd::function<void(RegistryChangeKind)>&& callback)
    {
        wistd::unique_ptr<details::registry_watcher_state> watcherState(
            new (std::nothrow) details::registry_watcher_state(wistd::move(keyToWatch), isRecursive, wistd::move(callback)));
        RETURN_IF_NULL_ALLOC(watcherState);
        RETURN_IF_FAILED(watcherState->m_eventHandle.create());
        RETURN_IF_WIN32_ERROR(RegNotifyChangeKeyValue(
            watcherState->m_keyToWatch.get(),
            watcherState->m_isRecursive,
            REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_THREAD_AGNOSTIC,
            watcherState->m_eventHandle.get(),
            TRUE));

        watcherState->m_threadPoolWait.reset(CreateThreadpoolWait(&registry_watcher_t::callback, watcherState.get(), nullptr));
        RETURN_LAST_ERROR_IF(!watcherState->m_threadPoolWait);
        storage_t::reset(watcherState.release()); // no more failures after this, pass ownership
        SetThreadpoolWait(storage_t::get()->m_threadPoolWait.get(), storage_t::get()->m_eventHandle.get(), nullptr);
        return S_OK;
    }
};

typedef unique_any_t<registry_watcher_t<details::unique_storage<details::registry_watcher_state_resource_policy>, err_returncode_policy>> unique_registry_watcher_nothrow;
typedef unique_any_t<registry_watcher_t<details::unique_storage<details::registry_watcher_state_resource_policy>, err_failfast_policy>> unique_registry_watcher_failfast;

inline unique_registry_watcher_nothrow make_registry_watcher_nothrow(
    HKEY rootKey, _In_ PCWSTR subKey, bool isRecursive, wistd::function<void(RegistryChangeKind)>&& callback) WI_NOEXCEPT
{
    unique_registry_watcher_nothrow watcher;
    watcher.create(rootKey, subKey, isRecursive, wistd::move(callback));
    return watcher; // caller must test for success using if (watcher)
}

inline unique_registry_watcher_nothrow make_registry_watcher_nothrow(
    unique_hkey&& keyToWatch, bool isRecursive, wistd::function<void(RegistryChangeKind)>&& callback) WI_NOEXCEPT
{
    unique_registry_watcher_nothrow watcher;
    watcher.create(wistd::move(keyToWatch), isRecursive, wistd::move(callback));
    return watcher; // caller must test for success using if (watcher)
}

inline unique_registry_watcher_failfast make_registry_watcher_failfast(
    HKEY rootKey, _In_ PCWSTR subKey, bool isRecursive, wistd::function<void(RegistryChangeKind)>&& callback)
{
    return unique_registry_watcher_failfast(rootKey, subKey, isRecursive, wistd::move(callback));
}

inline unique_registry_watcher_failfast make_registry_watcher_failfast(
    unique_hkey&& keyToWatch, bool isRecursive, wistd::function<void(RegistryChangeKind)>&& callback)
{
    return unique_registry_watcher_failfast(wistd::move(keyToWatch), isRecursive, wistd::move(callback));
}

#ifdef WIL_ENABLE_EXCEPTIONS
typedef unique_any_t<registry_watcher_t<details::unique_storage<details::registry_watcher_state_resource_policy>, err_exception_policy>> unique_registry_watcher;

inline unique_registry_watcher make_registry_watcher(
    HKEY rootKey, _In_ PCWSTR subKey, bool isRecursive, wistd::function<void(RegistryChangeKind)>&& callback)
{
    return unique_registry_watcher(rootKey, subKey, isRecursive, wistd::move(callback));
}

inline unique_registry_watcher make_registry_watcher(unique_hkey&& keyToWatch, bool isRecursive, wistd::function<void(RegistryChangeKind)>&& callback)
{
    return unique_registry_watcher(wistd::move(keyToWatch), isRecursive, wistd::move(callback));
}
#endif // WIL_ENABLE_EXCEPTIONS
} // namespace wil

#endif
