1 /*
2  * PROJECT:     NT Object Namespace shell extension
3  * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4  * PURPOSE:     System Registry folder class implementation
5  * COPYRIGHT:   Copyright 2015-2017 David Quintana <gigaherz@gmail.com>
6  */
7 
8 #include "precomp.h"
9 
10 // {1C6D6E08-2332-4A7B-A94D-6432DB2B5AE6}
11 const GUID CLSID_RegistryFolder = { 0x1c6d6e08, 0x2332, 0x4a7b, { 0xa9, 0x4d, 0x64, 0x32, 0xdb, 0x2b, 0x5a, 0xe6 } };
12 
13 // {18A4B504-F6D8-4D8A-8661-6296514C2CF0}
14 //static const GUID GUID_RegistryColumns = { 0x18a4b504, 0xf6d8, 0x4d8a, { 0x86, 0x61, 0x62, 0x96, 0x51, 0x4c, 0x2c, 0xf0 } };
15 
16 enum RegistryColumns
17 {
18     REGISTRY_COLUMN_NAME = 0,
19     REGISTRY_COLUMN_TYPE,
20     REGISTRY_COLUMN_VALUE,
21     REGISTRY_COLUMN_END
22 };
23 
24 // -------------------------------
25 // CRegistryFolderExtractIcon
CRegistryFolderExtractIcon()26 CRegistryFolderExtractIcon::CRegistryFolderExtractIcon() :
27     m_pcidlFolder(NULL),
28     m_pcidlChild(NULL)
29 {
30 
31 }
32 
~CRegistryFolderExtractIcon()33 CRegistryFolderExtractIcon::~CRegistryFolderExtractIcon()
34 {
35     if (m_pcidlFolder)
36         ILFree((LPITEMIDLIST)m_pcidlFolder);
37     if (m_pcidlChild)
38         ILFree((LPITEMIDLIST)m_pcidlChild);
39 }
40 
Initialize(LPCWSTR ntPath,PCIDLIST_ABSOLUTE parent,UINT cidl,PCUITEMID_CHILD_ARRAY apidl)41 HRESULT CRegistryFolderExtractIcon::Initialize(LPCWSTR ntPath, PCIDLIST_ABSOLUTE parent, UINT cidl, PCUITEMID_CHILD_ARRAY apidl)
42 {
43     m_pcidlFolder = ILClone(parent);
44     if (cidl != 1)
45         return E_INVALIDARG;
46     m_pcidlChild = ILClone(apidl[0]);
47     return S_OK;
48 }
49 
GetIconLocation(UINT uFlags,LPWSTR szIconFile,UINT cchMax,INT * piIndex,UINT * pwFlags)50 HRESULT STDMETHODCALLTYPE CRegistryFolderExtractIcon::GetIconLocation(
51     UINT uFlags,
52     LPWSTR szIconFile,
53     UINT cchMax,
54     INT *piIndex,
55     UINT *pwFlags)
56 {
57     const RegPidlEntry * entry = (RegPidlEntry *)m_pcidlChild;
58 
59     if ((entry->cb < sizeof(RegPidlEntry)) || (entry->magic != REGISTRY_PIDL_MAGIC))
60         return E_INVALIDARG;
61 
62     UINT flags = 0;
63 
64     switch (entry->entryType)
65     {
66         case REG_ENTRY_KEY:
67         case REG_ENTRY_ROOT:
68             GetModuleFileNameW(g_hInstance, szIconFile, cchMax);
69             *piIndex = -IDI_REGISTRYKEY;
70             *pwFlags = flags;
71             return S_OK;
72 
73         case REG_ENTRY_VALUE:
74         case REG_ENTRY_VALUE_WITH_CONTENT:
75             GetModuleFileNameW(g_hInstance, szIconFile, cchMax);
76             *piIndex = -IDI_REGISTRYVALUE;
77             *pwFlags = flags;
78             return S_OK;
79 
80         default:
81             GetModuleFileNameW(g_hInstance, szIconFile, cchMax);
82             *piIndex = -IDI_NTOBJECTITEM;
83             *pwFlags = flags;
84             return S_OK;
85     }
86 }
87 
Extract(LPCWSTR pszFile,UINT nIconIndex,HICON * phiconLarge,HICON * phiconSmall,UINT nIconSize)88 HRESULT STDMETHODCALLTYPE CRegistryFolderExtractIcon::Extract(
89     LPCWSTR pszFile,
90     UINT nIconIndex,
91     HICON *phiconLarge,
92     HICON *phiconSmall,
93     UINT nIconSize)
94 {
95     return SHDefExtractIconW(pszFile, nIconIndex, 0, phiconLarge, phiconSmall, nIconSize);
96 }
97 
98 // CRegistryFolder
99 
CRegistryFolder()100 CRegistryFolder::CRegistryFolder()
101 {
102 }
103 
~CRegistryFolder()104 CRegistryFolder::~CRegistryFolder()
105 {
106 }
107 
108 // IShellFolder
EnumObjects(HWND hwndOwner,SHCONTF grfFlags,IEnumIDList ** ppenumIDList)109 HRESULT STDMETHODCALLTYPE CRegistryFolder::EnumObjects(
110     HWND hwndOwner,
111     SHCONTF grfFlags,
112     IEnumIDList **ppenumIDList)
113 {
114     if (m_NtPath[0] == 0 && m_hRoot == NULL)
115     {
116         return GetEnumRegistryRoot(ppenumIDList);
117     }
118     else
119     {
120         return GetEnumRegistryKey(m_NtPath, m_hRoot, ppenumIDList);
121     }
122 }
123 
InternalBindToObject(PWSTR path,const RegPidlEntry * info,LPITEMIDLIST first,LPCITEMIDLIST rest,LPITEMIDLIST fullPidl,LPBC pbcReserved,IShellFolder ** ppsfChild)124 HRESULT STDMETHODCALLTYPE CRegistryFolder::InternalBindToObject(
125     PWSTR path,
126     const RegPidlEntry * info,
127     LPITEMIDLIST first,
128     LPCITEMIDLIST rest,
129     LPITEMIDLIST fullPidl,
130     LPBC pbcReserved,
131     IShellFolder** ppsfChild)
132 {
133     if (wcslen(m_NtPath) == 0 && m_hRoot == NULL)
134     {
135         return ShellObjectCreatorInit<CRegistryFolder>(fullPidl, L"", info->rootKey, IID_PPV_ARG(IShellFolder, ppsfChild));
136     }
137 
138     return ShellObjectCreatorInit<CRegistryFolder>(fullPidl, path, m_hRoot, IID_PPV_ARG(IShellFolder, ppsfChild));
139 }
140 
Initialize(PCIDLIST_ABSOLUTE pidl)141 HRESULT STDMETHODCALLTYPE CRegistryFolder::Initialize(PCIDLIST_ABSOLUTE pidl)
142 {
143     m_shellPidl = ILClone(pidl);
144     m_hRoot = NULL;
145 
146     StringCbCopyW(m_NtPath, sizeof(m_NtPath), L"");
147     return S_OK;
148 }
149 
Initialize(PCIDLIST_ABSOLUTE pidl,PCWSTR ntPath,HKEY hRoot)150 HRESULT STDMETHODCALLTYPE CRegistryFolder::Initialize(PCIDLIST_ABSOLUTE pidl, PCWSTR ntPath, HKEY hRoot)
151 {
152     m_shellPidl = ILClone(pidl);
153     m_hRoot = hRoot;
154 
155     StringCbCopyW(m_NtPath, sizeof(m_NtPath), ntPath);
156     return S_OK;
157 }
158 
GetDefaultColumnState(UINT iColumn,SHCOLSTATEF * pcsFlags)159 HRESULT STDMETHODCALLTYPE CRegistryFolder::GetDefaultColumnState(
160     UINT iColumn,
161     SHCOLSTATEF *pcsFlags)
162 {
163     switch (iColumn)
164     {
165         case REGISTRY_COLUMN_NAME:
166             *pcsFlags = SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT;
167             return S_OK;
168 
169         case REGISTRY_COLUMN_TYPE:
170             *pcsFlags = SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT;
171             return S_OK;
172 
173         case REGISTRY_COLUMN_VALUE:
174             *pcsFlags = SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT | SHCOLSTATE_SLOW;
175             return S_OK;
176     }
177 
178     return E_INVALIDARG;
179 }
180 
GetDetailsEx(LPCITEMIDLIST pidl,const SHCOLUMNID * pscid,VARIANT * pv)181 HRESULT STDMETHODCALLTYPE CRegistryFolder::GetDetailsEx(
182     LPCITEMIDLIST pidl,
183     const SHCOLUMNID *pscid,
184     VARIANT *pv)
185 {
186     const RegPidlEntry * info;
187 
188     TRACE("GetDetailsEx\n");
189 
190     if (pidl)
191     {
192         HRESULT hr = GetInfoFromPidl(pidl, &info);
193         if (FAILED_UNEXPECTEDLY(hr))
194             return hr;
195 
196         static const GUID storage = PSGUID_STORAGE;
197         if (IsEqualGUID(pscid->fmtid, storage))
198         {
199             if (pscid->pid == PID_STG_NAME)
200             {
201                 if (info->entryNameLength > 0)
202                 {
203                     return MakeVariantString(pv, info->entryName);
204                 }
205                 return  MakeVariantString(pv, L"(Default)");
206             }
207             else if (pscid->pid == PID_STG_STORAGETYPE)
208             {
209                 if (info->entryType == REG_ENTRY_ROOT)
210                 {
211                     return MakeVariantString(pv, L"Key");
212                 }
213 
214                 if (info->entryType == REG_ENTRY_KEY)
215                 {
216                     if (info->contentsLength > 0)
217                     {
218                         PWSTR td = (PWSTR)(((PBYTE)info) + FIELD_OFFSET(RegPidlEntry, entryName) + info->entryNameLength + sizeof(WCHAR));
219 
220                         return MakeVariantString(pv, td);
221                     }
222                     return MakeVariantString(pv, L"Key");
223                 }
224 
225                 return MakeVariantString(pv, RegistryTypeNames[info->contentType]);
226             }
227             else if (pscid->pid == PID_STG_CONTENTS)
228             {
229                 PCWSTR strValueContents;
230 
231                 hr = FormatContentsForDisplay(info, m_hRoot, m_NtPath, &strValueContents);
232                 if (FAILED_UNEXPECTEDLY(hr))
233                     return hr;
234 
235                 if (hr == S_FALSE)
236                 {
237                     V_VT(pv) = VT_EMPTY;
238                     return S_OK;
239                 }
240 
241                 hr = MakeVariantString(pv, strValueContents);
242 
243                 CoTaskMemFree((PVOID)strValueContents);
244 
245                 return hr;
246 
247             }
248         }
249     }
250 
251     return E_INVALIDARG;
252 }
253 
GetDetailsOf(LPCITEMIDLIST pidl,UINT iColumn,SHELLDETAILS * psd)254 HRESULT STDMETHODCALLTYPE CRegistryFolder::GetDetailsOf(
255     LPCITEMIDLIST pidl,
256     UINT iColumn,
257     SHELLDETAILS *psd)
258 {
259     const RegPidlEntry * info;
260 
261     TRACE("GetDetailsOf\n");
262 
263     if (pidl)
264     {
265         HRESULT hr = GetInfoFromPidl(pidl, &info);
266         if (FAILED_UNEXPECTEDLY(hr))
267             return hr;
268 
269         switch (iColumn)
270         {
271             case REGISTRY_COLUMN_NAME:
272             {
273                 psd->fmt = LVCFMT_LEFT;
274 
275                 if (info->entryNameLength > 0)
276                 {
277                     return MakeStrRetFromString(info->entryName, info->entryNameLength, &(psd->str));
278                 }
279                 return MakeStrRetFromString(L"(Default)", &(psd->str));
280             }
281 
282             case REGISTRY_COLUMN_TYPE:
283             {
284                 psd->fmt = LVCFMT_LEFT;
285 
286                 if (info->entryType == REG_ENTRY_ROOT)
287                 {
288                     return MakeStrRetFromString(L"Key", &(psd->str));
289                 }
290 
291                 if (info->entryType == REG_ENTRY_KEY)
292                 {
293                     if (info->contentsLength > 0)
294                     {
295                         PWSTR td = (PWSTR)(((PBYTE)info) + FIELD_OFFSET(RegPidlEntry, entryName) + info->entryNameLength + sizeof(WCHAR));
296 
297                         return MakeStrRetFromString(td, info->contentsLength, &(psd->str));
298                     }
299 
300                     return MakeStrRetFromString(L"Key", &(psd->str));
301                 }
302 
303                 return MakeStrRetFromString(RegistryTypeNames[info->contentType], &(psd->str));
304             }
305 
306             case REGISTRY_COLUMN_VALUE:
307             {
308                 psd->fmt = LVCFMT_LEFT;
309 
310                 PCWSTR strValueContents;
311 
312                 hr = FormatContentsForDisplay(info, m_hRoot, m_NtPath, &strValueContents);
313                 if (FAILED_UNEXPECTEDLY(hr))
314                     return hr;
315 
316                 if (hr == S_FALSE)
317                 {
318                     return MakeStrRetFromString(L"(Empty)", &(psd->str));
319                 }
320 
321                 hr = MakeStrRetFromString(strValueContents, &(psd->str));
322 
323                 CoTaskMemFree((PVOID)strValueContents);
324 
325                 return hr;
326             }
327         }
328     }
329     else
330     {
331         switch (iColumn)
332         {
333             case REGISTRY_COLUMN_NAME:
334                 psd->fmt = LVCFMT_LEFT;
335                 psd->cxChar = 30;
336 
337                 // TODO: Make localizable
338                 MakeStrRetFromString(L"Object Name", &(psd->str));
339                 return S_OK;
340 
341             case REGISTRY_COLUMN_TYPE:
342                 psd->fmt = LVCFMT_LEFT;
343                 psd->cxChar = 20;
344 
345                 // TODO: Make localizable
346                 MakeStrRetFromString(L"Content Type", &(psd->str));
347                 return S_OK;
348 
349             case REGISTRY_COLUMN_VALUE:
350                 psd->fmt = LVCFMT_LEFT;
351                 psd->cxChar = 20;
352 
353                 // TODO: Make localizable
354                 MakeStrRetFromString(L"Value", &(psd->str));
355                 return S_OK;
356         }
357     }
358 
359     return E_INVALIDARG;
360 }
361 
MapColumnToSCID(UINT iColumn,SHCOLUMNID * pscid)362 HRESULT STDMETHODCALLTYPE CRegistryFolder::MapColumnToSCID(
363     UINT iColumn,
364     SHCOLUMNID *pscid)
365 {
366     static const GUID storage = PSGUID_STORAGE;
367     switch (iColumn)
368     {
369         case REGISTRY_COLUMN_NAME:
370             pscid->fmtid = storage;
371             pscid->pid = PID_STG_NAME;
372             return S_OK;
373 
374         case REGISTRY_COLUMN_TYPE:
375             pscid->fmtid = storage;
376             pscid->pid = PID_STG_STORAGETYPE;
377             return S_OK;
378 
379         case REGISTRY_COLUMN_VALUE:
380             pscid->fmtid = storage;
381             pscid->pid = PID_STG_CONTENTS;
382             return S_OK;
383     }
384     return E_INVALIDARG;
385 }
386 
CompareIDs(LPARAM lParam,const RegPidlEntry * first,const RegPidlEntry * second)387 HRESULT CRegistryFolder::CompareIDs(LPARAM lParam, const RegPidlEntry * first, const RegPidlEntry * second)
388 {
389     HRESULT hr;
390 
391     DWORD sortMode = lParam & 0xFFFF0000;
392     DWORD column = lParam & 0x0000FFFF;
393 
394     if (sortMode == SHCIDS_ALLFIELDS)
395     {
396         if (column != 0)
397             return E_INVALIDARG;
398 
399         int minsize = min(first->cb, second->cb);
400         hr = MAKE_COMPARE_HRESULT(memcmp(second, first, minsize));
401         if (hr != S_EQUAL)
402             return hr;
403 
404         return MAKE_COMPARE_HRESULT(second->cb - first->cb);
405     }
406 
407     switch (column)
408     {
409         case REGISTRY_COLUMN_NAME:
410             return CompareName(lParam, first, second);
411 
412         case REGISTRY_COLUMN_TYPE:
413         {
414             if (first->entryType != second->entryType)
415                 return MAKE_COMPARE_HRESULT(second->entryType - first->entryType);
416 
417             if (first->entryType == REG_ENTRY_KEY)
418             {
419                 if (first->contentsLength == 0 || second->contentsLength == 0)
420                     return (first->contentsLength == 0) ? S_GREATERTHAN : S_LESSTHAN;
421 
422                 PWSTR firstKey = (PWSTR)(((PBYTE)first) + FIELD_OFFSET(RegPidlEntry, entryName) + first->entryNameLength + sizeof(WCHAR));
423                 PWSTR secondKey = (PWSTR)(((PBYTE)second) + FIELD_OFFSET(RegPidlEntry, entryName) + second->entryNameLength + sizeof(WCHAR));
424                 return MAKE_COMPARE_HRESULT(lstrcmpW(firstKey, secondKey));
425             }
426 
427             return CompareName(lParam, first, second);
428         }
429 
430         case REGISTRY_COLUMN_VALUE:
431         {
432             PCWSTR firstContent, secondContent;
433 
434             if (FAILED_UNEXPECTEDLY(FormatContentsForDisplay(first, m_hRoot, m_NtPath, &firstContent)))
435                 return E_INVALIDARG;
436 
437             if (FAILED_UNEXPECTEDLY(FormatContentsForDisplay(second, m_hRoot, m_NtPath, &secondContent)))
438                 return E_INVALIDARG;
439 
440             hr = MAKE_COMPARE_HRESULT(lstrcmpW(firstContent, secondContent));
441 
442             CoTaskMemFree((LPVOID)firstContent);
443             CoTaskMemFree((LPVOID)secondContent);
444 
445             return hr;
446         }
447     }
448 
449     DbgPrint("Unsupported sorting mode.\n");
450     return E_INVALIDARG;
451 }
452 
ConvertAttributes(const RegPidlEntry * entry,PULONG inMask)453 ULONG CRegistryFolder::ConvertAttributes(const RegPidlEntry * entry, PULONG inMask)
454 {
455     ULONG mask = inMask ? *inMask : 0xFFFFFFFF;
456     ULONG flags = 0;
457 
458     if ((entry->entryType == REG_ENTRY_KEY) ||
459         (entry->entryType == REG_ENTRY_ROOT))
460         flags |= SFGAO_FOLDER | SFGAO_HASSUBFOLDER | SFGAO_BROWSABLE;
461 
462     return flags & mask;
463 }
464 
IsFolder(const RegPidlEntry * info)465 BOOL CRegistryFolder::IsFolder(const RegPidlEntry * info)
466 {
467     return (info->entryType == REG_ENTRY_KEY) ||(info->entryType == REG_ENTRY_ROOT);
468 }
469 
GetInfoFromPidl(LPCITEMIDLIST pcidl,const RegPidlEntry ** pentry)470 HRESULT CRegistryFolder::GetInfoFromPidl(LPCITEMIDLIST pcidl, const RegPidlEntry ** pentry)
471 {
472     if (!pcidl)
473     {
474         DbgPrint("PCIDL is NULL\n");
475         return E_INVALIDARG;
476     }
477 
478     RegPidlEntry * entry = (RegPidlEntry*) &(pcidl->mkid);
479     if (entry->cb < sizeof(RegPidlEntry))
480     {
481         DbgPrint("PCIDL too small %l (required %l)\n", entry->cb, sizeof(RegPidlEntry));
482         return E_INVALIDARG;
483     }
484 
485     if (entry->magic != REGISTRY_PIDL_MAGIC)
486     {
487         DbgPrint("PCIDL magic mismatch %04x (expected %04x)\n", entry->magic, REGISTRY_PIDL_MAGIC);
488         return E_INVALIDARG;
489     }
490 
491     *pentry = entry;
492     return S_OK;
493 }
494 
FormatValueData(DWORD contentType,PVOID td,DWORD contentsLength,PCWSTR * strContents)495 HRESULT CRegistryFolder::FormatValueData(DWORD contentType, PVOID td, DWORD contentsLength, PCWSTR * strContents)
496 {
497     switch (contentType)
498     {
499         case REG_NONE:
500         {
501             PCWSTR strTodo = L"";
502             DWORD bufferLength = (wcslen(strTodo) + 1) * sizeof(WCHAR);
503             PWSTR strValue = (PWSTR)CoTaskMemAlloc(bufferLength);
504             StringCbCopyW(strValue, bufferLength, strTodo);
505             *strContents = strValue;
506             return S_OK;
507         }
508 
509         case REG_SZ:
510         case REG_EXPAND_SZ:
511         {
512             PWSTR strValue = (PWSTR)CoTaskMemAlloc(contentsLength + sizeof(WCHAR));
513             StringCbCopyNW(strValue, contentsLength + sizeof(WCHAR), (LPCWSTR)td, contentsLength);
514             *strContents = strValue;
515             return S_OK;
516         }
517 
518         case REG_MULTI_SZ:
519         {
520             PCWSTR separator = L" "; // To match regedit
521             size_t sepChars = wcslen(separator);
522             int strings = 0;
523             int stringChars = 0;
524 
525             PCWSTR strData = (PCWSTR)td;
526             while (*strData)
527             {
528                 size_t len = wcslen(strData);
529                 stringChars += len;
530                 strData += len + 1; // Skips null-terminator
531                 strings++;
532             }
533 
534             int cch = stringChars + (strings - 1) * sepChars + 1;
535 
536             PWSTR strValue = (PWSTR)CoTaskMemAlloc(cch * sizeof(WCHAR));
537 
538             strValue[0] = 0;
539 
540             strData = (PCWSTR)td;
541             while (*strData)
542             {
543                 StrCatW(strValue, strData);
544                 strData += wcslen(strData) + 1;
545                 if (*strData)
546                     StrCatW(strValue, separator);
547             }
548 
549             *strContents = strValue;
550             return S_OK;
551         }
552 
553         case REG_DWORD:
554         {
555             DWORD bufferLength = 64 * sizeof(WCHAR);
556             PWSTR strValue = (PWSTR)CoTaskMemAlloc(bufferLength);
557             StringCbPrintfW(strValue, bufferLength, L"0x%08x (%d)",
558                 *(DWORD*)td, *(DWORD*)td);
559             *strContents = strValue;
560             return S_OK;
561         }
562 
563         case REG_QWORD:
564         {
565             DWORD bufferLength = 64 * sizeof(WCHAR);
566             PWSTR strValue = (PWSTR)CoTaskMemAlloc(bufferLength);
567             StringCbPrintfW(strValue, bufferLength, L"0x%016llx (%lld)",
568                 *(LARGE_INTEGER*)td, ((LARGE_INTEGER*)td)->QuadPart);
569             *strContents = strValue;
570             return S_OK;
571         }
572 
573         case REG_BINARY:
574         {
575             DWORD bufferLength = (contentsLength * 3 + 1) * sizeof(WCHAR);
576             PWSTR strValue = (PWSTR)CoTaskMemAlloc(bufferLength);
577             PWSTR strTemp = strValue;
578             PBYTE data = (PBYTE)td;
579             for (DWORD i = 0; i < contentsLength; i++)
580             {
581                 StringCbPrintfW(strTemp, bufferLength, L"%02x ", data[i]);
582                 strTemp += 3;
583                 bufferLength -= 3;
584             }
585             *strContents = strValue;
586             return S_OK;
587         }
588 
589         default:
590         {
591             PCWSTR strFormat = L"<Unimplemented value type %d>";
592             DWORD bufferLength = (wcslen(strFormat) + 15) * sizeof(WCHAR);
593             PWSTR strValue = (PWSTR)CoTaskMemAlloc(bufferLength);
594             StringCbPrintfW(strValue, bufferLength, strFormat, contentType);
595             *strContents = strValue;
596             return S_OK;
597         }
598     }
599 }
600 
FormatContentsForDisplay(const RegPidlEntry * info,HKEY rootKey,LPCWSTR ntPath,PCWSTR * strContents)601 HRESULT CRegistryFolder::FormatContentsForDisplay(const RegPidlEntry * info, HKEY rootKey, LPCWSTR ntPath, PCWSTR * strContents)
602 {
603     PVOID td = (((PBYTE)info) + FIELD_OFFSET(RegPidlEntry, entryName) + info->entryNameLength + sizeof(WCHAR));
604 
605     if (info->entryType == REG_ENTRY_VALUE_WITH_CONTENT)
606     {
607         if (info->contentsLength > 0)
608         {
609             return FormatValueData(info->contentType, td, info->contentsLength, strContents);
610         }
611     }
612     else if (info->entryType == REG_ENTRY_VALUE)
613     {
614         PVOID valueData;
615         DWORD valueLength;
616         HRESULT hr = ReadRegistryValue(rootKey, ntPath, info->entryName, &valueData, &valueLength);
617         if (FAILED_UNEXPECTEDLY(hr))
618         {
619             PCWSTR strEmpty = L"(Error reading value)";
620             DWORD bufferLength = (wcslen(strEmpty) + 1) * sizeof(WCHAR);
621             PWSTR strValue = (PWSTR)CoTaskMemAlloc(bufferLength);
622             StringCbCopyW(strValue, bufferLength, strEmpty);
623             *strContents = strValue;
624             return S_OK;
625         }
626 
627         if (valueLength > 0)
628         {
629             hr = FormatValueData(info->contentType, valueData, valueLength, strContents);
630 
631             CoTaskMemFree(valueData);
632 
633             return hr;
634         }
635     }
636     else
637     {
638         PCWSTR strEmpty = L"";
639         DWORD bufferLength = (wcslen(strEmpty) + 1) * sizeof(WCHAR);
640         PWSTR strValue = (PWSTR)CoTaskMemAlloc(bufferLength);
641         StringCbCopyW(strValue, bufferLength, strEmpty);
642         *strContents = strValue;
643         return S_OK;
644     }
645 
646     PCWSTR strEmpty = L"(Empty)";
647     DWORD bufferLength = (wcslen(strEmpty) + 1) * sizeof(WCHAR);
648     PWSTR strValue = (PWSTR)CoTaskMemAlloc(bufferLength);
649     StringCbCopyW(strValue, bufferLength, strEmpty);
650     *strContents = strValue;
651     return S_OK;
652 }
653