RegQueryValueEx 不返回值的数据
RegQueryValueEx not returning a value's data
我正在尝试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) %sn"), i + 1, achValue);
}
else
{
_tprintf(TEXT("Not here."));
}
}
}
}
}
这是一个更完整的答案,但可读性较差,因为我不得不使用紧凑的样式来将其限制在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.dlln";
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' '); 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' '),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' '),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' '),type(tp){SyUsSz(sz); Cpy(data(),s,sz);}
Udc(const wchar_t*s, St len) : buf(s,len),type(Str) {buf+=L' ';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' ';}
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' ');
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' ';
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 RegistryMachine
// HKEY_USERS RegistryUser
// HKEY_CURRENT_USER (use RtlFormatCurrentUserKeyPath)
// HKEY_CLASSES_ROOT RegistryMachineSOFTWAREClasses
// HKEY_CURRENT_CONFIG RegistryMachineSYSTEMCurrentControlSetHardware ProfilesCurrent
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 RegistryUser 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 Setn"; }
else { std::wcout << L" Set Failedn"; }
}
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 Deletedn"; }
else { std::wcout << L" Delete Failedn"; }
}
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 Deletedn"; }
else { std::wcout << L" Delete Failedn"; }
}
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 ->