RegQueryValueEx没有返回值的数据

时间:2016-05-02 19:40:46

标签: c++ winapi

我正在尝试RegOpenKeyEx,然后是RegEnumValue,最后是RegQueryValueEx。我确实得到了返回的数据,但不是我要搜索的数据。

HKCU \ Microsoft \ Windows \ CurrentVersion \ Run - 我想要搜索数据的密钥。下面的代码只是整个程序的一部分,仅供阅读之用。

我猜测问题在于我尝试使用RegEnumValue并且值名称不存在因此RegQueryValue甚至不尝试查询它。我正在考虑使用Ntopenkey,因为即使Windows注册表也无法读取密钥。有什么想法吗?

此外,当在proc mon中查看程序的事件时,它似乎确实找到了一个值,但错误是NAME_NOT_FOUND并且没有给出lPdata。我知道该值不存在,我只想搜索其数据。

if (cValues) // Enumerate the key values. 
{
    vector<BYTE> buffer(cbMaxValueData + 1);

    for (i = 0; i < cValues; ++i)
    {
        cchValue = MAX_VALUE_NAME;
        retCode = RegEnumValue(hKey, i, achValue, &cchValue, NULL, NULL, NULL, NULL);
        if (retCode == ERROR_SUCCESS)
        {
            DWORD dwType = REG_SZ;
            DWORD lpData = cbMaxValueData;
            retCode = RegQueryValueEx(hKey, achValue, 0, &dwType, &buffer[0], &lpData);
            if (retCode == ERROR_SUCCESS)
            {
                tstring str((TCHAR*)&buffer[0], lpData / sizeof(TCHAR));
                scanstartup = str.find(_T("data"));
                if (scanstartup != string::npos) {
                    _tprintf(TEXT("Value name: (%u) %s\n"), i + 1, achValue);
                } 
                else
                {
                    _tprintf(TEXT("Not here."));
                }
            }
        }
    }
}

2 个答案:

答案 0 :(得分:1)

本土化

这是一个自包含的Native代码块,用于枚举Subkeys和Values。该演示显示了所有用户的价值观。 &#34;运行&#34;键,以及HKLM \ Software ... \ Run键,用于32位和64位视图。

值名称存储在std :: wstring对象中,嵌入的空值保持不变。为了显示这些字符串,&#34;转义&#34;函数用于将嵌入的空值转换为&#34; \ 0&#34;,因此您可能会看到类似&#34; Value \ 0Name&#34;。

键名不显示为&#34;转义&#34;值(但子键名称的wstring对象仍将包含空值,如果存在)。

此处包含所有必需的符号,定义等,位于nt命名空间内,因此您不需要包含任何外部文件。只需创建一个新的Console Project并将其粘贴到其主.cpp文件中。

这适用于Visual Studio 2013。

<强>更新 现在价值观&#39;字符串数据包含在枚举中。新版本将数据字符串显示为另一个&#34;转义&#34;值名称后的字符串。

<强>更新 添加了示例代码,演示了如何在包含注册表值数据(原始,未转义)的wstring内查找包含空值的模式。 取消注释main()中的第一行以运行此演示。

<强>更新StringSearchExample演示移动到自己的代码框并添加其输出。

#include <iostream>
#include <string>
#include <vector>
#include <utility>

// For a quick build, create a new Console Application, without precompiled headers.
// Then delete stdafx.h, stdafx.cpp, targetver.h (keep your main .cpp file).
// Then paste this into your main .cpp file.
// This defines all types and values and function prototypes it needs to run,
// so you don't need to include <windows.h>.

namespace nt {
    typedef int BOOL;
    typedef unsigned short USHORT, WORD;
    typedef unsigned long NTSTATUS, ULONG, DWORD;
    typedef void *HANDLE;
    typedef struct HMODULE__ {int unused;}* HMODULE;
    static const NTSTATUS STATUS_SUCCESS = 0;
    enum { KEY_QUERY_VALUE=1, KEY_SET_VALUE=2, KEY_CREATE_SUB_KEY=4, KEY_ENUMERATE_SUB_KEYS=8, KEY_WOW64_64KEY=0x100, KEY_WOW64_32KEY=0x200 };
    static const ULONG OBJ_CASE_INSENSITIVE = 0x40;

    struct UNICODE_STRING {
        unsigned short   Length, MaximumLength;
        wchar_t*         Buffer;
    };

    struct OBJECT_ATTRIBUTES {
        ULONG           Length;
        HANDLE          RootDirectory;
        UNICODE_STRING* ObjectName;
        ULONG           Attributes;
        void            *SecurityDescriptor, *SecurityQualityOfService;
        OBJECT_ATTRIBUTES() {
            Length = sizeof(OBJECT_ATTRIBUTES);
            RootDirectory = 0;
            ObjectName = 0;
            Attributes = OBJ_CASE_INSENSITIVE;
            SecurityQualityOfService = SecurityDescriptor = 0;
        }
    };

    enum KEY_VALUE_INFORMATION_CLASS { KeyValueBasicInformation = 0, KeyValueFullInformation };
    enum KEY_INFORMATION_CLASS       { KeyBasicInformation = 0 };      // truncated version

                                                                       // These versions of KEY_VALUE_BASIC_INFORMATION and KEY_BASIC_INFORMATION
                                                                       // are not the standard C struct definitions.  These are defined as templates
                                                                       // for specifying the Name string's length.
    template <int max_length>
    struct KEY_VALUE_BASIC_INFORMATION {
        ULONG TitleIndex, Type, NameLength;
        wchar_t Name[max_length+1];
    };
    template <int max_name_and_data_length>
    struct KEY_VALUE_FULL_INFORMATION {
        ULONG TitleIndex, Type, DataOffset, DataLength, NameLength;
        wchar_t Name[max_name_and_data_length+2];
    };
    template <int max_length>
    struct KEY_BASIC_INFORMATION {
        long long  LastWriteTime;
        ULONG      TitleIndex, NameLength;
        wchar_t    Name[max_length+1];
    };

    extern "C" {
        // Kernel32 imports (assumed linked by default)
        typedef int (__stdcall *FARPROC)();
        HMODULE __stdcall LoadLibraryA(const char*);
        BOOL    __stdcall FreeLibrary(HMODULE);
        FARPROC __stdcall GetProcAddress(HMODULE, const char*);

        // Native NT Function Definitions
        typedef NTSTATUS (__stdcall RTLINITUNICODESTRING)(UNICODE_STRING*, wchar_t*);
        typedef NTSTATUS (__stdcall NTOPENKEY)(HANDLE*, ULONG DesiredAccess, OBJECT_ATTRIBUTES*);
        typedef NTSTATUS (__stdcall NTQUERYVALUEKEY)(HANDLE, UNICODE_STRING* ValueName, KEY_VALUE_INFORMATION_CLASS, void* KeyValueInformation, ULONG Length, ULONG* ResultLength);
        typedef NTSTATUS (__stdcall NTENUMERATEKEY)(HANDLE, ULONG Index, KEY_INFORMATION_CLASS, void* KeyInformation, ULONG KeyInformationLength, ULONG* ResultLength);
        typedef NTSTATUS (__stdcall NTENUMERATEVALUEKEY)(HANDLE, ULONG Index, KEY_VALUE_INFORMATION_CLASS, void* KeyValueInformation, ULONG KeyValueInformationLength, ULONG* ResultLength);
        typedef NTSTATUS (__stdcall NTCLOSE)(HANDLE);
    }
    // static global function pointers
    static RTLINITUNICODESTRING* RtlInitUnicodeString = 0;
    static NTOPENKEY*            NtOpenKey = 0;
    static NTQUERYVALUEKEY*      NtQueryValueKey = 0;
    static NTENUMERATEKEY*       NtEnumerateKey = 0;
    static NTENUMERATEVALUEKEY*  NtEnumerateValueKey = 0;
    static NTCLOSE*              NtClose = 0;

    // During construction, NtDllScopedLoader loads the native library dll and initializes
    // the global function pointers above.
    // When destroyed, it unloads the dll.
    class NtDllScopedLoader {
        HMODULE hNtDll;
    public:
        NtDllScopedLoader() {
            hNtDll = LoadLibraryA("ntdll.dll");
            if(!hNtDll) {
                std::wcout << L"LoadLibraryA failed loading ntdll.dll\n";
                return;
            }
            RtlInitUnicodeString = (RTLINITUNICODESTRING*) GetProcAddress(hNtDll, "RtlInitUnicodeString");
            NtOpenKey            = (NTOPENKEY*)            GetProcAddress(hNtDll, "NtOpenKey");
            NtQueryValueKey      = (NTQUERYVALUEKEY*)      GetProcAddress(hNtDll, "NtQueryValueKey");
            NtEnumerateKey       = (NTENUMERATEKEY*)       GetProcAddress(hNtDll, "NtEnumerateKey");
            NtEnumerateValueKey  = (NTENUMERATEVALUEKEY*)  GetProcAddress(hNtDll, "NtEnumerateValueKey");
            NtClose              = (NTCLOSE*)              GetProcAddress(hNtDll, "NtClose");
        }
        ~NtDllScopedLoader() { if(hNtDll) FreeLibrary(hNtDll); }
    };
    // everything happens during static initialization and destruction
    static const NtDllScopedLoader static_ntdll_loader;
}

// Gets an "escaped" version of a wstring for display,
// so embedded nuls, etc. can be seen.
std::wstring GetEscaped(const std::wstring& str) {
    std::wstring r;
    for(auto ch : str) {
        switch(ch) {
        case L'\\': r += L"\\\\"; break;
        case L'"':  r += L"\\\""; break;
        case L'\n': r += L"\\n"; break;
        case L'\r': r += L"\\r"; break;
        case L'\t': r += L"\\t"; break;
        case 0:     r += L"\\0"; break;
        default:
            if(ch < L' ') {
                static const wchar_t hexdigs[] = L"0123456789abcdef";
                r += L"\\x";
                r += hexdigs[ch / 16];
                r += hexdigs[ch % 16];
            } else {
                r += ch;
            }
            break;
        }   }
    return r;
}
// OpenKey wraps NtOpenKey.  Returns 0 on failure
nt::HANDLE OpenKey(std::wstring key_path, nt::ULONG desired_access) {
    using namespace nt;
    if(key_path.back() == L'\\') key_path.pop_back();
    UNICODE_STRING pathname;
    OBJECT_ATTRIBUTES path;
    path.ObjectName = &pathname;
    RtlInitUnicodeString(path.ObjectName, &key_path[0]);
    HANDLE hKey = 0;
    if(STATUS_SUCCESS != NtOpenKey(&hKey, desired_access, &path)) {
        //std::wcout << "NtOpenKey failed for " << key_path << L'\n';
    }
    return hKey;
}

// GetSubkeyNames gets a vector of wstrings containing the names of a key's sub-keys
std::vector<std::wstring> GetSubkeyNames(std::wstring key_path, bool include_parent=false, nt::ULONG bitness_flag=0) {
    using namespace nt;
    HANDLE hKey = OpenKey(key_path, KEY_ENUMERATE_SUB_KEYS | bitness_flag);
    std::vector<std::wstring> result;
    if(!hKey) return result;
    for(ULONG index=0;;++index) {
        KEY_BASIC_INFORMATION<256> ki;
        ULONG result_size = 0;
        NTSTATUS status = NtEnumerateKey(hKey, index, KeyBasicInformation, &ki, sizeof(ki), &result_size);
        if(status != STATUS_SUCCESS) break;
        std::wstring subkey_name(ki.Name, ki.NameLength / sizeof(ki.Name[0]));
        if(include_parent) {
            subkey_name = key_path + L'\\' + subkey_name;
        }
        result.push_back(subkey_name);
    }
    NtClose(hKey);
    return result;
}

// GetStringValues enumerates a key's values, returning a vector of std::pair<wstring, wsitring>
// the pair's first element is the value's name, the second element is the value's data, copied into
// a wstring (this should only be used if expecting 16-bit character string data).
std::vector<std::pair<std::wstring, std::wstring> >
GetStringValues(std::wstring key_path, nt::ULONG bitness_flag=0) {
    using namespace nt;
    HANDLE hKey = OpenKey(key_path, KEY_QUERY_VALUE | bitness_flag);
    std::vector<std::pair<std::wstring, std::wstring> > result;
    if(!hKey) return result;

    for(ULONG index=0;;++index) {
        KEY_VALUE_FULL_INFORMATION<1024> vi;
        ULONG result_size = 0;
        NTSTATUS status = NtEnumerateValueKey(hKey, index, KeyValueFullInformation, &vi, sizeof(vi), &result_size);
        if(status != STATUS_SUCCESS) break;
        std::wstring value_name(vi.Name, vi.NameLength/sizeof(vi.Name[0]));
        // Value data for registry strings include the terminating null character,
        // and this code displays the value data, exactly as it is stored -- so these data strings
        // will have the extra null character at the end.
        std::wstring value_data(reinterpret_cast<const wchar_t*>(reinterpret_cast<const char*>(&vi) + vi.DataOffset),
            vi.DataLength/sizeof(wchar_t));

        result.push_back(std::pair<std::wstring, std::wstring>(value_name, value_data));
    }
    NtClose(hKey);
    return result;
}

int main() {
    // There is no "Current User" registry path for the native API
    // So this just iterates over all existing users and displays any values found
    // inside each user's Software\Microsoft\Windows\CurrentVersion\Run key.
    // It also shows values found under HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
    // In addition, both the 32 and 64 bit views are searched

    for(nt::ULONG bitness_flag : { nt::KEY_WOW64_32KEY, nt::KEY_WOW64_64KEY }) {
        // Display the current view
        std::wcout << (bitness_flag == nt::KEY_WOW64_32KEY ? L"\n32 Bit View:\n" : L"\n64 Bit View:\n");

        // Get a list of subkeys under HK_USERS
        auto subkeys = GetSubkeyNames(L"\\Registry\\User", true, bitness_flag);

        // Append the Run path to each user key
        for(auto& keypath : subkeys) {
            keypath += L"\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
        }

        // add the HKLM Run key path to the list
        subkeys.push_back(L"\\Registry\\Machine\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run");

        // Iterate over all paths in subkeys, get a list of each subkey's values, then print their names
        for(const auto& key_path : subkeys) {
            auto values = GetStringValues(key_path, bitness_flag);
            // The subkey path is only displayed if it contains values
            if(!values.empty()) {
                std::wcout << L"    " << key_path << L'\n';
                for(const auto& value_pair : values) {
                    // Display the "escaped" name
                    auto escaped_name = GetEscaped(value_pair.first);
                    auto escaped_data = GetEscaped(value_pair.second);
                    std::wcout << L"        " << L'"' << escaped_name << L"\" = \"" << escaped_data << L"\"\n"; 
}   }   }   }   }

下面的代码演示了在wstring值内搜索空嵌入模式。

#include <iostream>
#include <string>

// FromLit constructs a std::wstring from a string literal,
// even when the literal contains an embedded null character.
// Only use this for literals.
template <std::wstring::size_type char_count>
inline std::wstring FromLit(const wchar_t (& str)[char_count]) {
    return std::wstring(&str[0], char_count-1);
}

void StringSearchExample() {
    std::wcout << L"o Naive Construction:\n";
    // First, naively attempt to construct a wstring from a literal
    // which contains an embedded null character:
    std::wstring a(L"null\0separated");
    // Use wcout to print the wstring...darn! just "null" and nothing after it.
    std::wcout << a << L"\n\n";

    std::wcout << L"o Using FromLit:\n";
    // Ok, now stop being naive and use a template function which
    // knows the literal's character count
    std::wstring b = FromLit(L"null\0separated");
    // wcout prints the null character like a space character,
    std::wcout << b << L'\n';
    // just to be sure, check the numeric value of the character at
    // index 4, expecting a numeric 0 for the null character.
    std::wcout << static_cast<int>(b[4]) << L"\n\n";

    // Create a fake registry value with an embedded null character.
    // (Includes the terminating null found in all registry value strings.)
    auto regval_evil = FromLit(L"Win32 sees this\0NT also can see this\0");
    // Create a fake normal registry string:
    auto regval_innocent = FromLit(L"I have nothing to hide\0");

    // We want to search for patterns containing embedded null characters,
    // to simplify this, remove any terminating nulls before the search:
    if(!regval_evil.back()) regval_evil.pop_back();
    if(!regval_innocent.back()) regval_innocent.pop_back();

    // If you just want to check for the presence of an embedded null character,
    // you can just use the character version of wstring::find, FromLit is not
    // needed or useful for this:
    std::wcout << L"o Checking for embedded null characters:\n";
    std::wcout << L"regval_evil:     " << (regval_evil.find(L'\0')     == std::wstring::npos ? L"CLEAN" : L"DIRTY") << L'\n';
    std::wcout << L"regval_innocent: " << (regval_innocent.find(L'\0') == std::wstring::npos ? L"CLEAN" : L"DIRTY") << L'\n';
    std::wcout << L'\n';

    // But for a null-embedded substring search, FromLit is helpful
    std::wcout << L"o Checking for a substring containing a null:\n";
    // Maybe "\0NT" (null + "NT") is something we need to look for
    // Create another fake registery value with an embedded null, but
    // which does not contain this substring:
    auto regval_legit_null = FromLit(L"Legit\0Secret DRM Value\0");
    if(!regval_legit_null.back()) regval_legit_null.pop_back();
    const auto find_substring = FromLit(L"\0NT");
    std::wcout << L"regval_evil:       " << (regval_evil.find(find_substring)       == std::wstring::npos ? L"CLEAN" : L"DIRTY") << L'\n';
    std::wcout << L"regval_innocent:   " << (regval_innocent.find(find_substring)   == std::wstring::npos ? L"CLEAN" : L"DIRTY") << L'\n';
    std::wcout << L"regval_legit_null: " << (regval_legit_null.find(find_substring) == std::wstring::npos ? L"CLEAN" : L"DIRTY") << L'\n';
    std::wcout << L'\n';
}


int main() {
    StringSearchExample();
}

这是StringSearchExample显示的内容:

o Naive Construction:
"null"

o Using FromLit:
"null separated"
(int)wstring[4] = 0

o Checking for embedded null characters:
regval_evil:     DIRTY
regval_innocent: CLEAN

o Checking for a substring containing a null:
regval_evil:       DIRTY
regval_innocent:   CLEAN
regval_legit_null: CLEAN

答案 1 :(得分:1)

这是一个更完整的答案,但由于我必须使用紧凑的风格使其低于3k字符限制,因此可读性受损。

它还使用C ++类(Udc),它自动管理UNICODE_STRING数据并允许无缝使用空嵌入字符串文字。这有助于实现可用性,但额外的抽象行为可以模糊低层次上发生的事情。

所以,我把它作为一个单独的答案来保留原始的直接逻辑。

这个新版本的嵌入式API包括创建密钥,查询和设置值,删除值和密钥,处理空嵌入的键和值名称,以及查找HKEY_CURRENT_USER的路径。用户可以从几个演示中选择要运行 - 在没有命令行参数的情况下启动以查看菜单。

#include <iostream>
#include <string>
#include <vector>
#include <utility>

namespace nt {
    typedef int BOOL;
    typedef unsigned short USHORT, WORD;
    typedef unsigned long NTSTATUS, ULONG, DWORD;
    typedef struct HMODULE__ {int unused;} *HMODULE;
    typedef struct HANDLE__ {int unused;} *HANDLE;

    enum { STATUS_SUCCESS=0, STATUS_OBJECT_NAME_NOT_FOUND=0xC0000034 };
    enum {
        KEY_QUERY_VALUE=1, KEY_SET_VALUE=2, KEY_ENUMERATE_SUB_KEYS=8,
        KEY_WOW64_64KEY=0x100, KEY_WOW64_32KEY=0x200, DELETE=0x10000
    };
    enum { REG_OPTION_NON_VOLATILE=0, REG_OPTION_VOLATILE=1 };
    enum { REG_CREATED_NEW_KEY=1, REG_OPENED_EXISTING_KEY=2 };
    enum { REG_NONE=0, REG_SZ=1, REG_EXPAND_SZ=2, REG_BINARY=3, REG_DWORD=4, REG_MULTI_SZ=7 };
    static const ULONG OBJ_CASE_INSENSITIVE = 0x40;

    struct UNICODE_STRING {
        USHORT   Length, MaximumLength;
        wchar_t* Buffer;
    };

    struct OBJECT_ATTRIBUTES {
        ULONG           Length;
        HANDLE          RootDirectory;
        UNICODE_STRING* ObjectName;
        ULONG           Attributes;
        void            *SecurityDescriptor, *SecurityQualityOfService;
        OBJECT_ATTRIBUTES() {
            Length = sizeof(OBJECT_ATTRIBUTES);
            RootDirectory = 0;
            ObjectName = 0;
            Attributes = OBJ_CASE_INSENSITIVE;
            SecurityQualityOfService = SecurityDescriptor = 0;
        }
    };

    enum KEY_VALUE_INFORMATION_CLASS { KeyValueBasicInformation=0, KeyValueFullInformation };
    enum KEY_INFORMATION_CLASS       { KeyBasicInformation=0 };

    template <int max_length>
    struct KEY_VALUE_BASIC_INFORMATION {
        ULONG TitleIndex, Type, NameLength;
        wchar_t Name[max_length+1];
    };
    template <int max_name_and_data_length>
    struct KEY_VALUE_FULL_INFORMATION {
        ULONG TitleIndex, Type, DataOffset, DataLength, NameLength;
        wchar_t Name[max_name_and_data_length+2];
        const void* GetData() const {
            return (const void*)((const char*)this + DataOffset);
        }
    };
    template <int max_length>
    struct KEY_BASIC_INFORMATION {
        long long  LastWriteTime;
        ULONG      TitleIndex, NameLength;
        wchar_t    Name[max_length+1];
    };

    extern "C" {
        // Kernel32 imports (assumed linked by default)
        typedef int(__stdcall *FARPROC)();
        HMODULE  __stdcall LoadLibraryA(const char*);
        BOOL     __stdcall FreeLibrary(HMODULE);
        FARPROC  __stdcall GetProcAddress(HMODULE, const char*);

        // Native NT API Functions
        typedef NTSTATUS(__stdcall RTLFORMATCURRENTUSERKEYPATH)(UNICODE_STRING*);
        typedef NTSTATUS(__stdcall NTCREATEKEY)(HANDLE*, ULONG DesiredAccess, OBJECT_ATTRIBUTES*, ULONG, UNICODE_STRING* Class, ULONG CreateOptions, ULONG* Disposition);
        typedef NTSTATUS(__stdcall NTOPENKEY)(HANDLE*, ULONG DesiredAccess, OBJECT_ATTRIBUTES*);
        typedef NTSTATUS(__stdcall NTENUMERATEKEY)(HANDLE, ULONG Index, KEY_INFORMATION_CLASS, void* KeyInformation, ULONG KeyInformationLength, ULONG* ResultLength);
        typedef NTSTATUS(__stdcall NTENUMERATEVALUEKEY)(HANDLE, ULONG Index, KEY_VALUE_INFORMATION_CLASS, void* KeyValueInformation, ULONG KeyValueInformationLength, ULONG* ResultLength);
        typedef NTSTATUS(__stdcall NTQUERYVALUEKEY)(HANDLE, UNICODE_STRING* ValueName, KEY_VALUE_INFORMATION_CLASS, void* KeyValueInformation, ULONG Length, ULONG* ResultLength);
        typedef NTSTATUS(__stdcall NTSETVALUEKEY)(HANDLE, UNICODE_STRING* ValueName, ULONG TitleIndex, ULONG Type, void* Data, ULONG DataSize);
        typedef NTSTATUS(__stdcall NTDELETEVALUEKEY)(HANDLE, UNICODE_STRING* ValueName);
        typedef NTSTATUS(__stdcall NTDELETEKEY)(HANDLE);
        typedef NTSTATUS(__stdcall NTCLOSE)(HANDLE);
    }

    static RTLFORMATCURRENTUSERKEYPATH* RtlFormatCurrentUserKeyPath;
    static NTCREATEKEY*          NtCreateKey;
    static NTOPENKEY*            NtOpenKey;
    static NTENUMERATEKEY*       NtEnumerateKey;
    static NTENUMERATEVALUEKEY*  NtEnumerateValueKey;
    static NTQUERYVALUEKEY*      NtQueryValueKey;
    static NTSETVALUEKEY*        NtSetValueKey;
    static NTDELETEVALUEKEY*     NtDeleteValueKey;
    static NTDELETEKEY*          NtDeleteKey;
    static NTCLOSE*              NtClose;

    class NtDllScopedLoader {
        HMODULE hNtDll;
    public:
        NtDllScopedLoader() {
            hNtDll = LoadLibraryA("ntdll.dll");
            if(!hNtDll) {
                std::wcout << L"LoadLibraryA failed loading ntdll.dll\n";
                return;
            }
            RtlFormatCurrentUserKeyPath = (RTLFORMATCURRENTUSERKEYPATH*)GetProcAddress(hNtDll, "RtlFormatCurrentUserKeyPath");
            NtCreateKey          = (NTCREATEKEY*)GetProcAddress(hNtDll, "NtCreateKey");
            NtOpenKey            = (NTOPENKEY*)GetProcAddress(hNtDll, "NtOpenKey");
            NtEnumerateKey       = (NTENUMERATEKEY*)GetProcAddress(hNtDll, "NtEnumerateKey");
            NtEnumerateValueKey  = (NTENUMERATEVALUEKEY*)GetProcAddress(hNtDll, "NtEnumerateValueKey");
            NtQueryValueKey      = (NTQUERYVALUEKEY*)GetProcAddress(hNtDll, "NtQueryValueKey");
            NtSetValueKey        = (NTSETVALUEKEY*)GetProcAddress(hNtDll, "NtSetValueKey");
            NtDeleteValueKey     = (NTDELETEVALUEKEY*)GetProcAddress(hNtDll, "NtDeleteValueKey");
            NtDeleteKey          = (NTDELETEKEY*)GetProcAddress(hNtDll, "NtDeleteKey");
            NtClose              = (NTCLOSE*)GetProcAddress(hNtDll, "NtClose");
        }
        ~NtDllScopedLoader() { if(hNtDll) FreeLibrary(hNtDll); }
    };
    // everything happens during static initialization and destruction
    static const NtDllScopedLoader static_ntdll_loader;
}

// The C++ wrappers below provide an intuitive interface for common Registry Tasks
namespace nt_cpp {
    typedef nt::HANDLE           HANDLE;
    typedef nt::ULONG            ULONG,Tt;
    typedef nt::USHORT           USHORT;
    typedef nt::DWORD            DWORD;
    typedef std::string          string;
    typedef std::wstring         wstring;
    typedef wstring::size_type   St;
    // Udc: Universal Data Class
    class Udc {
        enum {None=0,Str=1,StrEx=2,Bin=3,Dw=4};
        nt::UNICODE_STRING ucstr;
        wstring buf;
        void SyUsSz(St size) { ucstr.Buffer=&buf[0]; ucstr.Length=USHORT(size); ucstr.MaximumLength=USHORT(buf.length()*2); }
        void SyUs(St len) {SyUsSz(len*2);}
        static void Cpy(void*d, const void*s, St sz) { for(St i=0;i<sz;++i) {*((char*)d+i)=*((const char*)s+i);}}
        void unalgn_asgn(const void*s, St sz) { buf.assign((sz+3)/2, L'\0'); Cpy(&buf[0], s, sz); SyUsSz(sz); }
        static wstring hex(char b) {
            static const wchar_t hd[]=L"0123456789abcdef";
            return wstring(1,hd[(b>>4)&15]) + hd[b&15];
        }
    public:
        ULONG type;
        const static St npos = ~St(0);
        St size() const {return ucstr.Length;}
        St length() const {return size()/2;}
        bool empty() const {return !size();}
        void* data() {return ucstr.Buffer;}
        const void* data() const {return ucstr.Buffer;}
        Udc(St reserve=0) : buf(reserve+1,L'\0'),type(reserve?Str:None) { SyUs(0); }
        Udc(const Udc& s) : type(s.type) { unalgn_asgn(s.data(), s.size()); }
        Udc(const wstring& ws) : buf(ws+L'\0'),type(Str) { SyUs(ws.length()); }
        template <St ct> Udc(const wchar_t(&s)[ct]) : buf(s,ct),type(Str) { SyUs(ct - !s[ct-1]); }
        Udc(const void* s, St sz, ULONG tp=Bin) : buf((sz+3)/2, L'\0'),type(tp){SyUsSz(sz); Cpy(data(),s,sz);}
        Udc(const wchar_t*s, St len) : buf(s,len),type(Str) {buf+=L'\0';SyUs(len);}
        Udc(St len, wchar_t wc) : buf(len+1,wc),type(Str) {buf[len]=0; SyUs(len);}
        void pop_back() {if(ucstr.Length>=2) ucstr.Length-=2;}
        wchar_t back() const {return length()?ucstr.Buffer[length()-1]:L'\0';}
        wchar_t* begin() { return ucstr.Buffer; }
        wchar_t* end()   { return begin() + length(); }
        const wchar_t* begin() const { return ucstr.Buffer; }
        const wchar_t* end()   const { return begin() + length(); }
        Udc& operator = (Udc s) {type=s.type; unalgn_asgn(s.data(), s.size()); return *this;}
        operator nt::UNICODE_STRING* () {return &ucstr;}
        operator void* () { return data(); }
        operator wstring () const {
            switch(type) {
                case Str: case StrEx: {
                    wstring ws(length(), L'\0');
                    Cpy(&ws[0], data(), length()*2);
                    return ws;
                }
                case Bin: {
                    const char* p = (const char*)data();
                    wstring ws;
                    for(St i=0; i<size(); ++i) {if(i)ws+=L' ';ws+=hex(p[i]);}
                    return ws;
                }
                case Dw: {
                    if(size()<4) return wstring();
                    const char* p = (const char*)data();
                    return L"0x"+hex(p[3])+hex(p[2])+hex(p[1])+hex(p[0]);
                }
                case None: default: return L"REG_NONE";
            }
        }
        friend std::wostream& operator << (std::wostream& os, const Udc& udc) {return os << wstring(udc);}
        Udc& operator += (const Udc& s) {
            if(s.type == None) return *this;
            if(type == None) return *this = s;
            buf = wstring(*this) + wstring(s) + L'\0';
            type=Str;
            SyUs(buf.length()-1);
            return *this;
        }
        Udc& operator += (wchar_t wc) { return operator += (Udc(1,wc)); }
        friend Udc operator + (Udc a, const Udc &b) { return a += b; }
        friend Udc operator + (wchar_t a, const Udc& b) { Udc r(1,a); return r += b; }
        friend Udc operator + (Udc a, wchar_t b) { return a += b; }
        template <St ct> friend Udc operator + (Udc a, const wchar_t(&s)[ct]) { return a += Udc(s); }
        template <St ct> friend Udc operator + (const wchar_t(&s)[ct], const Udc& b) { Udc a(s); return a += b; }
        DWORD dword() const { return size()==4?*(const DWORD*)data():0; }
        St find(wchar_t wc) const { return wstring(*this).find(wc); }
        St rfind(wchar_t wc) const { return wstring(*this).rfind(wc); }
        Udc substr(St start, St len=npos) const { return wstring(*this).substr(start,len); }
    };

    inline Udc GetEscaped(Udc str) {
        Udc r;
        for(auto ch : str) {
            switch(ch) {
                case L'\\': r += L"\\\\"; break;
                case L'"':  r += L"\\\""; break;
                case L'\n': r += L"\\n"; break;
                case L'\r': r += L"\\r"; break;
                case L'\t': r += L"\\t"; break;
                case 0:     r += L"\\0"; break;
                default:
                    if(ch < L' ') {
                        static const wchar_t hd[] = L"0123456789abcdef";
                        r += L"\\x";
                        r += hd[ch / 16];
                        r += hd[ch % 16];
                    }
                    else { r += ch; }
                    break;
        }   }
        return r;
    }
    inline Udc EscapeData(Udc data) {
        if(data.type == nt::REG_SZ || data.type == nt::REG_EXPAND_SZ) return L'"' + GetEscaped(data) + L'"';
        return wstring(data);
    }

    inline std::pair<Udc, Udc> SplitFullPath(Udc full_path) {
        const auto delim = full_path.rfind(L'\\');
        if(delim == Udc::npos) return std::pair<Udc, Udc>(full_path, Udc());
        return std::pair<Udc, Udc>(full_path.substr(0, delim), full_path.substr(delim + 1));
    }

    static void CloseKey(HANDLE hkey) { nt::NtClose(hkey); }

    static HANDLE OpenKey(Udc key_path, ULONG desired_access) {
        while(key_path.back() == L'\\') key_path.pop_back();
        nt::OBJECT_ATTRIBUTES path;
        path.ObjectName = key_path;
        HANDLE hkey = 0;
        return !nt::NtOpenKey(&hkey, desired_access, &path) ? hkey : 0;
    }
    // CreateKey wraps NtCreateKey.  returns 0 on failure.
    // CreateKey will try to recursively create all missing parent keys in key_path.
    static HANDLE CreateKey(Udc key_path, ULONG desired_access=nt::KEY_QUERY_VALUE|nt::KEY_SET_VALUE|nt::KEY_ENUMERATE_SUB_KEYS, ULONG create_options=nt::REG_OPTION_NON_VOLATILE) {
        using namespace nt;
        while(key_path.back() == L'\\') { key_path.pop_back(); }
        OBJECT_ATTRIBUTES path;
        path.ObjectName = key_path;
        ULONG disposition = 0;
        for(int i=0; i<2; ++i) {
            HANDLE hkey = 0;
            NTSTATUS status = NtCreateKey(&hkey, desired_access, &path, 0, 0, create_options, &disposition);
            if(!status) return hkey;
            if(i || status != STATUS_OBJECT_NAME_NOT_FOUND) return 0;
            auto i_path_up = key_path.rfind(L'\\');
            if(i_path_up == Udc::npos) return 0;
            Udc path_up = key_path.substr(0, i_path_up);
            hkey = CreateKey(path_up, desired_access, create_options);
            if(!hkey) return 0;
            CloseKey(hkey);
        }
        return 0;
    }
    // GetSubkeyNames gets a vector of wstrings containing the names of a key's sub-keys
    std::vector<Udc> static GetSubkeyNames(Udc key_path, bool include_parent=false, ULONG bitness_flag=0) {
        using namespace nt;
        HANDLE hKey = OpenKey(key_path, KEY_ENUMERATE_SUB_KEYS | bitness_flag);
        std::vector<Udc> result;
        if(!hKey) return result;
        for(ULONG index=0;; ++index) {
            KEY_BASIC_INFORMATION<256> ki;
            ULONG result_size = 0;
            NTSTATUS status = NtEnumerateKey(hKey, index, KeyBasicInformation, &ki, sizeof(ki), &result_size);
            if(status) break;
            Udc subkey_name(ki.Name, ki.NameLength/2);
            if(include_parent) { subkey_name = key_path + L'\\' + subkey_name; }
            result.push_back(subkey_name);
        }
        CloseKey(hKey);
        return result;
    }
    // GetValues enumerates a key's values, returning a vector of std::pair<StrArg, ValueData>
    // containing the value's name and data
    std::vector<std::pair<Udc, Udc> > static GetValues(Udc key_path, ULONG bitness_flag=0) {
        using namespace nt;
        HANDLE hKey = OpenKey(key_path, KEY_QUERY_VALUE | bitness_flag);
        std::vector<std::pair<Udc, Udc> > result;
        if(!hKey) return result;
        for(ULONG index=0;; ++index) {
            KEY_VALUE_FULL_INFORMATION<2048> vi;
            ULONG result_size = 0;
            NTSTATUS status = NtEnumerateValueKey(hKey, index, KeyValueFullInformation, &vi, sizeof(vi), &result_size);
            if(status) break;
            Udc value_name(vi.Name, vi.NameLength/2);
            // Value data for registry strings includes the terminating null character,
            result.push_back(std::pair<Udc, Udc>(value_name, Udc(vi.GetData(), vi.DataLength, vi.Type)));
        }
        CloseKey(hKey);
        return result;
    }

    // NT paths for Win32 base keys:
    // HKEY_LOCAL_MACHINE   \Registry\Machine\
    // HKEY_USERS           \Registry\User\
    // HKEY_CURRENT_USER    (use RtlFormatCurrentUserKeyPath)
    // HKEY_CLASSES_ROOT    \Registry\Machine\SOFTWARE\Classes
    // HKEY_CURRENT_CONFIG  \Registry\Machine\SYSTEM\CurrentControlSet\Hardware Profiles\Current

    static Udc GetCurrentUserPath() {
        Udc path(512);
        nt::RtlFormatCurrentUserKeyPath(path);
        return path;
    }
    // This version of QueryValue takes an open key handle and a value name
    static Udc QueryValue(HANDLE hkey, Udc value_name) {
        using namespace nt;
        KEY_VALUE_FULL_INFORMATION<2048> vi; // TODO: allow arbitrary size
        ULONG result_size = 0;
        NTSTATUS status = NtQueryValueKey(hkey, value_name, KeyValueFullInformation, &vi, sizeof(vi), &result_size);
        if(status) return Udc();
        return Udc(vi.GetData(), vi.DataLength, vi.Type);
    }
    // This QueryValue takes a <key name>\<value name> path.  bitness_flag may be supplied to force a view.
    static Udc QueryValue(Udc full_path, ULONG bitness_flag=0) {
        auto key_value_name = SplitFullPath(full_path);
        HANDLE hkey = OpenKey(key_value_name.first, nt::KEY_QUERY_VALUE | bitness_flag);
        if(!hkey) return Udc();
        const Udc value = QueryValue(hkey, key_value_name.second);
        CloseKey(hkey);
        return value;
    }

    // SetValue and DeleteValue (from a path string) try to open their keys with KEY_SET_VALUE
    // access.  This will fail for system-owned paths unless the app is run with
    // Administrator access ("run as Administrator" in the File Explorer context menu).
    // The logged-in user's path, under \Registry\User\ does not need elevation, however.

    static bool SetValue(HANDLE hkey, Udc value_name, Udc data) {
        return !nt::NtSetValueKey(hkey, value_name, 0, data.type, data, data.size());
    }
    static bool SetValue(Udc full_path, Udc data, ULONG bitness_flag=0) {
        using namespace nt;
        auto key_value_name = SplitFullPath(full_path);
        HANDLE hkey = OpenKey(key_value_name.first, nt::KEY_SET_VALUE | bitness_flag);
        if(!hkey) return false;
        bool success = SetValue(hkey, key_value_name.second, data);
        CloseKey(hkey);
        return success;
    }
    static bool DeleteValue(HANDLE hkey, Udc value_name) {
        return !nt::NtDeleteValueKey(hkey, value_name);
    }
    static bool DeleteValue(Udc full_path, ULONG bitness_flag=0) {
        auto key_value_name = SplitFullPath(full_path);
        HANDLE hkey = OpenKey(key_value_name.first, nt::KEY_SET_VALUE | bitness_flag);
        if(!hkey) return false;
        bool success = DeleteValue(hkey, key_value_name.second);
        CloseKey(hkey);
        return success;
    }
    static bool DeleteKey(HANDLE hkey) {
        return !nt::NtDeleteKey(hkey);
    }
    static bool DeleteKey(Udc key_path, ULONG bitness_flag=0) {
        HANDLE hkey = OpenKey(key_path, nt::DELETE | bitness_flag);
        if(!hkey) return false;
        bool success = DeleteKey(hkey);
        CloseKey(hkey);
        return success;
    }
}

namespace {
    // Examples and Native API Parlor Tricks

    void EnumerateRuns() {
        using namespace nt_cpp;
        for(ULONG bitness_flag : { nt::KEY_WOW64_32KEY, nt::KEY_WOW64_64KEY }) {
            // Display the current view
            std::wcout << (bitness_flag == nt::KEY_WOW64_32KEY ? L"\n32 Bit View:\n" : L"\n64 Bit View:\n");
            // Get a list of subkeys under HK_USERS
            auto subkeys = GetSubkeyNames(L"\\Registry\\User", true, bitness_flag);
            // Append the Run path to each user key
            for(auto& keypath : subkeys) {
                keypath += L"\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
            }
            // add the HKLM Run key path to the list
            subkeys.push_back(L"\\Registry\\Machine\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run");
            // Iterate over all paths in subkeys, get a list of each subkey's values, then print their names
            for(const auto& key_path : subkeys) {
                auto values = GetValues(key_path, bitness_flag);
                // The subkey path is only displayed if it contains values
                if(!values.empty()) {
                    std::wcout << L"    " << key_path << L'\n';
                    for(const auto& value_pair : values) {
                        // Display the "escaped" name and data
                        std::wcout << L"        \"" << GetEscaped(value_pair.first) << L"\" = " << EscapeData(value_pair.second) << L'\n';
    }   }   }   }   }

    const wchar_t demo_key[] = L"\\Software\\NT Registry Demo";

    nt_cpp::Udc HKCU_path() { return nt_cpp::Udc(L"HKEY_CURRENT_USER") + demo_key; }

    nt::HANDLE ReportCreateKey(nt_cpp::Udc key_path,
        nt::ULONG desired_access=nt::KEY_QUERY_VALUE|nt::KEY_SET_VALUE|nt::KEY_ENUMERATE_SUB_KEYS)
    {
        using namespace nt_cpp;
        HANDLE hkey = CreateKey(key_path, desired_access);
        if(!hkey) std::wcout << L"CreateKey failed for \"" << GetEscaped(key_path) << L"\"\n";
        return hkey;
    }
    void ReportSetValue(nt::HANDLE hkey, nt_cpp::Udc value_name, const nt_cpp::Udc& data) {
        using namespace nt_cpp;
        std::wcout << L"    Value \"" << GetEscaped(value_name) << '"';
        if(SetValue(hkey, value_name, data)) { std::wcout << L" Successfully Set\n"; }
        else                                 { std::wcout << L" Set Failed\n"; }
    }
    void ReportDeleteValue(nt::HANDLE hkey, nt_cpp::Udc value_name) {
        using namespace nt_cpp;
        std::wcout << L"    Value \"" << GetEscaped(value_name) << '"';
        if(DeleteValue(hkey, value_name)) { std::wcout << L" Successfully Deleted\n"; }
        else                              { std::wcout << L" Delete Failed\n"; }
    }
    void ReportDeleteKey(nt::HANDLE hkey, nt_cpp::Udc key_name) {
        using namespace nt_cpp;
        std::wcout << L"    Subkey \"" << GetEscaped(key_name) << '"';
        if(DeleteKey(hkey))  { std::wcout << L" Successfully Deleted\n"; }
        else                 { std::wcout << L" Delete Failed\n"; }
    }
    void ReportVerifyData(nt_cpp::Udc value_path) {
        using namespace nt_cpp;
        auto i = value_path.rfind(L'\\');
        Udc value_name = i == Udc::npos ? value_path : value_path.substr(i+1);
        Udc rb = QueryValue(value_path);
        std::wcout << L"    Value \"" << GetEscaped(value_name) << L"\" data readback: " << EscapeData(rb) << L'\n';
    }

    void NullInValueData() {
        using namespace nt_cpp;
        Udc nt_key_path    = GetCurrentUserPath() + demo_key;
        Udc value_name     = L"NullInData";
        Udc nt_value_path  = nt_key_path + L'\\' + value_name;

        HANDLE hkey = ReportCreateKey(nt_key_path);
        if(!hkey) return;
        auto data = QueryValue(hkey, value_name);
        if(data.type == nt::REG_NONE) {
            // Win32 automatically places a null at the end of string values,
            // but with the Native API, the trailing null can be omitted.
            // So here, string values must explicitly include a trailing null
            // if one is desired.
            data = L"This string has a null here ->\0<- there!\0";
            std::wcout <<
            L"    In \"" << HKCU_path() << L"\"\n"
            L"    creating a string value named \"" << value_name << L"\" with a null in its data:\n"
            L"        " << EscapeData(data) << L"\n"
            L"    o RegEdit displays just the string before the null, but you\n"
            L"      can use \"Modify Binary Data\" to see the whole thing.\n"
            L"    o Repeat this command to remove the value.\n\n";
            ReportSetValue(hkey, value_name, data);
        } else {
            std::wcout << L"    Cleaning Up:\n";
            ReportDeleteValue(hkey, value_name);
        }
        CloseKey(hkey);
        ReportVerifyData(nt_value_path);
    }

    void NullInValueName() {
        using namespace nt_cpp;
        Udc nt_key_path    = GetCurrentUserPath() + demo_key;
        Udc value_name     = L"Null\0InName";
        Udc nt_value_path  = nt_key_path + L'\\' + value_name;

        HANDLE hkey = ReportCreateKey(nt_key_path);
        if(!hkey) return;
        auto data = QueryValue(hkey, value_name);
        if(data.type == nt::REG_NONE) {
            std::wcout <<
            L"    In \"" << HKCU_path() << L"\"\n"
            L"    creating a string value whose name contains an embedded null.\n"
            L"    o Causes RegEdit to glitch because it can only see part\n"
            L"      of the value's name and is unable to find its data\n"
            L"    o Repeat this command to remove the value.\n\n";
            data = L"A Normal String Value\0";
            ReportSetValue(hkey, value_name, data);
        } else {
            std::wcout << L"    Cleaning Up:\n";
            ReportDeleteValue(hkey, value_name);
        }
        CloseKey(hkey);
        ReportVerifyData(nt_value_path);
    }

    void NullInKeyName() {
        using namespace nt_cpp;
        Udc nt_key_path     = GetCurrentUserPath() + demo_key;
        Udc subkey_name     = L"Null\0InKeyName";
        Udc value_name      = L"NormalValueName";
        Udc nt_subkey_path  = nt_key_path + L'\\' + subkey_name;
        Udc nt_value_path   = nt_subkey_path + L'\\' + value_name;
        HANDLE hkey = ReportCreateKey(nt_subkey_path, nt::KEY_QUERY_VALUE|nt::KEY_SET_VALUE|nt::DELETE);
        if(!hkey) return;
        auto data = QueryValue(hkey, value_name);
        if(data.type == nt::REG_NONE) {
            std::wcout << 
            L"    In \"" << HKCU_path() << L"\"\n"
            L"    created subkey \"" << GetEscaped(subkey_name) << L"\"\n"
            L"    whose name contains an embedded null.\n"
            L"    o RegEdit will report an error when this key is selected\n"
            L"    o Repeat this command to remove the key.\n\n";
            data = L"A normal string value, hidden in a key which Win32 cannot access\0";
            ReportSetValue(hkey, value_name, data);
        } else {
            std::wcout << L"    Cleaning Up:\n";
            ReportDeleteValue(hkey, value_name);
            ReportDeleteKey(hkey, subkey_name);
        }
        CloseKey(hkey);
        ReportVerifyData(nt_value_path);
    }

    void TwoValuesOneName() {
        using namespace nt_cpp;
        Udc nt_key_path    = GetCurrentUserPath() + demo_key;
        Udc normal_name    = L"ValueName";
        Udc abnormal_name  = L"ValueName\0OhYeah";
        Udc normal_path    = nt_key_path + L'\\' + normal_name;
        Udc abnormal_path  = nt_key_path + L'\\' + abnormal_name;

        HANDLE hkey = ReportCreateKey(nt_key_path);
        if(!hkey) return;
        Udc normal_data = QueryValue(hkey, normal_name);
        if(normal_data.type == nt::REG_NONE) {
            std::wcout << 
            L"    In \"" << HKCU_path() << L"\"\n"
            L"    creating two values with different names,\n"
            L"    but because of an embedded null in one of the names,\n"
            L"    both appear identical to Win32\n"
            L"    o RegEdit does not report an error, but it sees two values with the same name.\n"
            L"      And both values appear to share the same string data.\n"
            L"    o Repeat this command to remove the values.\n";
            normal_data       = L"Normal Name's Data\0";
            Udc abnormal_data = L"AbNorMal nAme'S daTA\0";
            ReportSetValue(hkey, normal_name, normal_data);
            ReportSetValue(hkey, abnormal_name, abnormal_data);
        } else {
            std::wcout << L"    Cleaning Up:\n";
            ReportDeleteValue(hkey, normal_name);
            ReportDeleteValue(hkey, abnormal_name);
        }
        CloseKey(hkey);
        ReportVerifyData(normal_path);
        ReportVerifyData(abnormal_path);
    }

    std::vector<nt_cpp::Udc> RecursiveEmbeddedNullSearch(nt_cpp::Udc key_path) {
        using namespace nt_cpp;
        while(key_path.back() == L'\\') { key_path.pop_back(); }
        std::vector<Udc> results;
        auto values = GetValues(key_path);
        for(const auto& nv : values) {
            if(nv.first.find(L'\0') != Udc::npos) {
                results.push_back(key_path + L'\\' + nv.first);
        }   }
        auto subkeys = GetSubkeyNames(key_path, false);
        for(const auto& kn : subkeys) {
            if(kn.find(L'\0') != Udc::npos) {
                results.push_back(key_path + L'\\' + kn + L'\\');
        }   }
        for(const auto& kn : subkeys) {
            auto sub_results = RecursiveEmbeddedNullSearch(key_path + L'\\' + kn);
            if(!sub_results.empty()) {
                results.insert(results.end(), sub_results.begin(), sub_results.end());
        }   }
        return results;
    }

    bool IsAffirmative(std::wstring answer) {
        for(auto wc : answer) {
            if(wc>=L'A'||wc<=L'Z'||wc>=L'a'||wc<=L'z'||wc==L'0'||wc==L'1') {
                return wc==L'y'||wc==L'Y'||wc==L'1';
        }   }
        return false;
    }

    void FixMistakes() {
        using namespace nt_cpp;
        using std::wcout;
        Udc root_key = GetCurrentUserPath();
        auto paths = RecursiveEmbeddedNullSearch(root_key);
        wcout << L'\n';
        for(const auto& path : paths) {
            if(path.back() == L'\\') {
                wcout << L"Found Key: \"" << GetEscaped(path) << L"\"\nDelete It?\n";
                std::wstring answer;
                std::getline(std::wcin, answer);
                if(IsAffirmative(answer)) {
                    HANDLE hkey = OpenKey(path, nt::DELETE);
                    if(hkey) {
                        if(DeleteKey(hkey)) { wcout << L"Successfully deleted " << GetEscaped(path) << L"\n"; }
                        else                { wcout << L"Failed to delete " << GetEscaped(path) << L"\n"; }
                        CloseKey(hkey);
                    }
                    else { wcout << L"Failed to open " << GetEscaped(path) << L'\n'; }
            }   }
            else {
                wcout << 
                    L"Found Value: \"" << GetEscaped(path) << L"\"\n"
                    L"Containing:  " << EscapeData(QueryValue(path)) << L"\n"
                    L"Delete It?\n";
                std::wstring answer;
                std::getline(std::wcin, answer);
                if(IsAffirmative(answer)) {
                    auto kv = SplitFullPath(path);
                    HANDLE hkey = OpenKey(kv.first, nt::KEY_SET_VALUE);
                    if(hkey) {
                        if(DeleteValue(hkey, kv.second)) wcout << L"Successfully deleted";
                        else wcout << L"Failed to delete";
                        wcout << L" value \"" << GetEscaped(kv.second) << L"\"\n";
                    }
                    else { wcout << L"Failed to open key \"" << kv.first << L"\"\n"; }
    }   }   }   }

    struct { const char *id, *desc; void(*handler)(); } const demos[] ={
        {"runs", "Displays the contents of Windows' \"Run\" keys", EnumerateRuns},
        {"nullinstring", "Creates a null-embedded string value in current user's Registry", NullInValueData},
        {"nullinvalname", "Creates a string with a null-embedded value name in current user's Registry", NullInValueName},
        {"nullinkeyname", "Creates a sub-key with a null-embedded name in current user's Registry", NullInKeyName},
        {"samename", "Creates two values in current user's Registry whose names cannot be distingushed by Win32", TwoValuesOneName},
        {"fix", "Performs recursive search for names with nulls and gives option to delete", FixMistakes}
    };
    void Usage(const char* exe) {
        using std::cout;
        for(const char* p=exe; *p; ++p) { if(*p == '\\') exe = p+1; }
        cout << "\n    Usage:   " << exe << " <command>\n\n";
        cout << "    Available Commands:\n";
        for(int i=0; i<sizeof(demos)/sizeof(demos[0]); ++i) {
            cout << "             " << demos[i].id;
            int len;
            for(len=0; demos[i].id[len]; ++len) {}
            for(; len < 16; ++len) { cout << ' '; }
            cout << demos[i].desc << '\n';
    }   }
    void ExecDemo(std::string id) {
        for(int i=0; i<sizeof(demos)/sizeof(demos[0]); ++i) {
            if(id == demos[i].id) {
                demos[i].handler();
                return;
        }   }
        std::cout << "Command ID not recognized: " << id << '\n';
    }
}

int main(int argc, const char* argv[]) {
    if(argc < 2)              { Usage(argv[0]); }
    for(int i=1; i<argc; ++i) { ExecDemo(argv[i]); }
}