1 /*
2  * PROJECT:     NT Object Namespace shell extension
3  * LICENSE:     LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later)
4  * PURPOSE:     NT Object Namespace enumeration functions
5  * COPYRIGHT:   Copyright 2004-2005 Martin Fuchs <martin-fuchs@gmx.net>
6  */
7 
8 #include "precomp.h"
9 #include <strsafe.h>
10 
11 static struct RootKeyEntry {
12     HKEY key;
13     PCWSTR keyName;
14 } RootKeys [] = {
15     { HKEY_CLASSES_ROOT, L"HKEY_CLASSES_ROOT" },
16     { HKEY_CURRENT_USER, L"HKEY_CURRENT_USER" },
17     { HKEY_LOCAL_MACHINE, L"HKEY_LOCAL_MACHINE" },
18     { HKEY_USERS, L"HKEY_USERS" },
19     { HKEY_CURRENT_CONFIG, L"HKEY_CURRENT_CONFIG" }
20 };
21 
22 typedef NTSTATUS(__stdcall* pfnNtGenericOpen)(PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES);
23 typedef NTSTATUS(__stdcall* pfnNtOpenFile)(PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, PIO_STATUS_BLOCK, ULONG, ULONG);
24 
25 const LPCWSTR ObjectTypeNames [] = {
26     L"Directory", L"SymbolicLink",
27     L"Mutant", L"Section", L"Event", L"Semaphore",
28     L"Timer", L"Key", L"EventPair", L"IoCompletion",
29     L"Device", L"File", L"Controller", L"Profile",
30     L"Type", L"Desktop", L"WindowStation", L"Driver",
31     L"Token", L"Process", L"Thread", L"Adapter", L"Port",
32     0
33 };
34 
35 const LPCWSTR RegistryTypeNames [] = {
36     L"REG_NONE",
37     L"REG_SZ",
38     L"REG_EXPAND_SZ",
39     L"REG_BINARY",
40     L"REG_DWORD",
41     L"REG_DWORD_BIG_ENDIAN",
42     L"REG_LINK",
43     L"REG_MULTI_SZ",
44     L"REG_RESOURCE_LIST",
45     L"REG_FULL_RESOURCE_DESCRIPTOR",
46     L"REG_RESOURCE_REQUIREMENTS_LIST",
47     L"REG_QWORD"
48 };
49 
50 static DWORD NtOpenObject(OBJECT_TYPE type, PHANDLE phandle, DWORD access, LPCWSTR path)
51 {
52     UNICODE_STRING ustr;
53 
54     RtlInitUnicodeString(&ustr, path);
55 
56     OBJECT_ATTRIBUTES open_struct = { sizeof(OBJECT_ATTRIBUTES), 0x00, &ustr, 0x40 };
57 
58     if (type != FILE_OBJECT)
59         access |= STANDARD_RIGHTS_READ;
60 
61     IO_STATUS_BLOCK ioStatusBlock;
62 
63     switch (type)
64     {
65         case DIRECTORY_OBJECT:      return NtOpenDirectoryObject(phandle, access, &open_struct);
66         case SYMBOLICLINK_OBJECT:   return NtOpenSymbolicLinkObject(phandle, access, &open_struct);
67         case MUTANT_OBJECT:         return NtOpenMutant(phandle, access, &open_struct);
68         case SECTION_OBJECT:        return NtOpenSection(phandle, access, &open_struct);
69         case EVENT_OBJECT:          return NtOpenEvent(phandle, access, &open_struct);
70         case SEMAPHORE_OBJECT:      return NtOpenSemaphore(phandle, access, &open_struct);
71         case TIMER_OBJECT:          return NtOpenTimer(phandle, access, &open_struct);
72         case KEY_OBJECT:            return NtOpenKey(phandle, access, &open_struct);
73         case EVENTPAIR_OBJECT:      return NtOpenEventPair(phandle, access, &open_struct);
74         case IOCOMPLETION_OBJECT:   return NtOpenIoCompletion(phandle, access, &open_struct);
75         case FILE_OBJECT:           return NtOpenFile(phandle, access, &open_struct, &ioStatusBlock, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0);
76         default:
77             return ERROR_INVALID_FUNCTION;
78     }
79 }
80 
81 OBJECT_TYPE MapTypeNameToType(LPCWSTR TypeName, DWORD cbTypeName)
82 {
83     if (!TypeName)
84         return UNKNOWN_OBJECT_TYPE;
85 
86     for (UINT i = 0; i < _countof(ObjectTypeNames); i++)
87     {
88         LPCWSTR typeName = ObjectTypeNames[i];
89         if (!StrCmpNW(typeName, TypeName, cbTypeName / sizeof(WCHAR)))
90         {
91             return (OBJECT_TYPE) i;
92         }
93     }
94 
95     return UNKNOWN_OBJECT_TYPE;
96 }
97 
98 HRESULT ReadRegistryValue(HKEY root, PCWSTR path, PCWSTR valueName, PVOID * valueData, PDWORD valueLength)
99 {
100     HKEY hkey;
101 
102     DWORD res;
103     if (root)
104     {
105         res = RegOpenKeyExW(root, *path == '\\' ? path + 1 : path, 0, STANDARD_RIGHTS_READ | KEY_QUERY_VALUE, &hkey);
106     }
107     else
108     {
109         res = NtOpenObject(KEY_OBJECT, (PHANDLE) &hkey, STANDARD_RIGHTS_READ | KEY_QUERY_VALUE, path);
110     }
111     if (!NT_SUCCESS(res))
112     {
113         ERR("RegOpenKeyExW failed for path %S with status=%x\n", path, res);
114         return HRESULT_FROM_NT(res);
115     }
116 
117     res = RegQueryValueExW(hkey, valueName, NULL, NULL, NULL, valueLength);
118     if (!NT_SUCCESS(res))
119     {
120         ERR("RegQueryValueExW failed for path %S with status=%x\n", path, res);
121         return HRESULT_FROM_NT(res);
122     }
123 
124     if (*valueLength > 0)
125     {
126         PBYTE data = (PBYTE) CoTaskMemAlloc(*valueLength);
127         *valueData = data;
128 
129         res = RegQueryValueExW(hkey, valueName, NULL, NULL, data, valueLength);
130         if (!NT_SUCCESS(res))
131         {
132             CoTaskMemFree(data);
133             *valueData = NULL;
134 
135             RegCloseKey(hkey);
136 
137             ERR("RegOpenKeyExW failed for path %S with status=%x\n", path, res);
138             return HRESULT_FROM_NT(res);
139         }
140     }
141     else
142     {
143         *valueData = NULL;
144     }
145 
146     RegCloseKey(hkey);
147 
148     return S_OK;
149 }
150 
151 HRESULT GetNTObjectSymbolicLinkTarget(LPCWSTR path, LPCWSTR entryName, PUNICODE_STRING LinkTarget)
152 {
153     HANDLE handle;
154     WCHAR buffer[MAX_PATH];
155     LPWSTR pend = buffer;
156 
157     StringCbCopyExW(buffer, sizeof(buffer), path, &pend, NULL, 0);
158 
159     if (pend[-1] != '\\')
160     {
161         *pend++ = '\\';
162         *pend = 0;
163     }
164 
165     StringCbCatW(buffer, sizeof(buffer), entryName);
166 
167     DbgPrint("GetNTObjectSymbolicLinkTarget %d\n", buffer);
168 
169     LinkTarget->Length = 0;
170 
171     DWORD err = NtOpenObject(SYMBOLICLINK_OBJECT, &handle, SYMBOLIC_LINK_QUERY, buffer);
172     if (!NT_SUCCESS(err))
173         return HRESULT_FROM_NT(err);
174 
175     err = NtQuerySymbolicLinkObject(handle, LinkTarget, NULL);
176     if (!NT_SUCCESS(err))
177         return HRESULT_FROM_NT(err);
178 
179     NtClose(handle);
180 
181     return S_OK;
182 }
183 
184 class CEnumRegRoot :
185     public CComObjectRootEx<CComMultiThreadModelNoCS>,
186     public IEnumIDList
187 {
188     UINT m_idx;
189 
190 public:
191     CEnumRegRoot()
192         : m_idx(0)
193     {
194     }
195 
196     ~CEnumRegRoot()
197     {
198     }
199 
200     HRESULT EnumerateNext(LPITEMIDLIST* ppidl)
201     {
202         if (m_idx >= _countof(RootKeys))
203             return S_FALSE;
204 
205         RootKeyEntry& key = RootKeys[m_idx++];
206 
207         PCWSTR name = key.keyName;
208         DWORD cchName = wcslen(name);
209 
210         REG_ENTRY_TYPE otype = REG_ENTRY_ROOT;
211 
212         DWORD entryBufferLength = FIELD_OFFSET(RegPidlEntry, entryName) + sizeof(WCHAR) + cchName * sizeof(WCHAR);
213 
214         // allocate space for the terminator
215         entryBufferLength += FIELD_OFFSET(SHITEMID, abID);
216 
217         RegPidlEntry* entry = (RegPidlEntry*) CoTaskMemAlloc(entryBufferLength);
218         if (!entry)
219             return E_OUTOFMEMORY;
220 
221         memset(entry, 0, entryBufferLength);
222 
223         entry->cb = FIELD_OFFSET(RegPidlEntry, entryName);
224         entry->magic = REGISTRY_PIDL_MAGIC;
225         entry->entryType = otype;
226         entry->rootKey = key.key;
227 
228         if (cchName > 0)
229         {
230             entry->entryNameLength = cchName * sizeof(WCHAR);
231             StringCbCopyNW(entry->entryName, entryBufferLength, name, entry->entryNameLength);
232             entry->cb += entry->entryNameLength + sizeof(WCHAR);
233         }
234         else
235         {
236             entry->entryNameLength = 0;
237             entry->entryName[0] = 0;
238             entry->cb += sizeof(WCHAR);
239         }
240 
241         if (ppidl)
242             *ppidl = (LPITEMIDLIST) entry;
243         return S_OK;
244     }
245 
246     STDMETHODIMP Next(ULONG celt, LPITEMIDLIST *rgelt, ULONG *pceltFetched) override
247     {
248         if (pceltFetched)
249             *pceltFetched = 0;
250 
251         while (celt-- > 0)
252         {
253             HRESULT hr = EnumerateNext(rgelt);
254             if (hr != S_OK)
255                 return hr;
256 
257             if (pceltFetched)
258                 (*pceltFetched)++;
259             if (rgelt)
260                 rgelt++;
261         }
262 
263         return S_OK;
264     }
265 
266     STDMETHODIMP Skip(ULONG celt) override
267     {
268         while (celt > 0)
269         {
270             HRESULT hr = EnumerateNext(NULL);
271             if (FAILED(hr))
272                 return hr;
273             if (hr != S_OK)
274                 break;
275         }
276 
277         return S_OK;
278     }
279 
280     STDMETHODIMP Reset() override
281     {
282         return E_NOTIMPL;
283     }
284 
285     STDMETHODIMP Clone(IEnumIDList **ppenum) override
286     {
287         return E_NOTIMPL;
288     }
289 
290     DECLARE_NOT_AGGREGATABLE(CEnumRegRoot)
291     DECLARE_PROTECT_FINAL_CONSTRUCT()
292 
293     BEGIN_COM_MAP(CEnumRegRoot)
294         COM_INTERFACE_ENTRY_IID(IID_IEnumIDList, IEnumIDList)
295     END_COM_MAP()
296 
297 };
298 
299 class CEnumRegKey :
300     public CComObjectRootEx<CComMultiThreadModelNoCS>,
301     public IEnumIDList
302 {
303     PCWSTR m_path;
304     HKEY m_hkey;
305     BOOL m_values;
306     int m_idx;
307 
308 public:
309     CEnumRegKey()
310         : m_path(NULL), m_hkey(NULL), m_values(FALSE), m_idx(0)
311     {
312     }
313 
314     ~CEnumRegKey()
315     {
316         RegCloseKey(m_hkey);
317     }
318 
319     HRESULT Initialize(PCWSTR path, HKEY root)
320     {
321         m_path = path;
322 
323         DWORD res;
324         if (root)
325         {
326             res = RegOpenKeyExW(root, *path == '\\' ? path + 1 : path, 0, STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, &m_hkey);
327         }
328         else
329         {
330             res = NtOpenObject(KEY_OBJECT, (PHANDLE) &m_hkey, STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, path);
331         }
332         if (!NT_SUCCESS(res))
333         {
334             ERR("RegOpenKeyExW failed for path %S with status=%x\n", path, res);
335             return HRESULT_FROM_NT(res);
336         }
337 
338         return S_OK;
339     }
340 
341     HRESULT NextKey(LPITEMIDLIST* ppidl)
342     {
343         WCHAR name[MAX_PATH];
344         DWORD cchName = _countof(name);
345 
346         WCHAR className[MAX_PATH];
347         DWORD cchClass = _countof(className);
348 
349         if (RegEnumKeyExW(m_hkey, m_idx++, name, &cchName, 0, className, &cchClass, NULL))
350             return S_FALSE;
351 
352         name[cchName] = 0;
353         className[cchClass] = 0;
354 
355         REG_ENTRY_TYPE otype = REG_ENTRY_KEY;
356 
357         DWORD entryBufferLength = FIELD_OFFSET(RegPidlEntry, entryName) + sizeof(WCHAR) + cchName * sizeof(WCHAR);
358 
359         if (cchClass > 0)
360         {
361             entryBufferLength += sizeof(WCHAR) + cchClass * sizeof(WCHAR);
362         }
363 
364         // allocate space for the terminator
365         entryBufferLength += FIELD_OFFSET(SHITEMID, abID);
366 
367         RegPidlEntry* entry = (RegPidlEntry*) CoTaskMemAlloc(entryBufferLength);
368         if (!entry)
369             return E_OUTOFMEMORY;
370 
371         memset(entry, 0, entryBufferLength);
372 
373         entry->cb = FIELD_OFFSET(RegPidlEntry, entryName);
374         entry->magic = REGISTRY_PIDL_MAGIC;
375         entry->entryType = otype;
376 
377         if (cchName > 0)
378         {
379             entry->entryNameLength = cchName * sizeof(WCHAR);
380             StringCbCopyNW(entry->entryName, entryBufferLength, name, entry->entryNameLength);
381             entry->cb += entry->entryNameLength + sizeof(WCHAR);
382         }
383         else
384         {
385             entry->entryNameLength = 0;
386             entry->entryName[0] = 0;
387             entry->cb += sizeof(WCHAR);
388         }
389 
390         if (cchClass)
391         {
392             PWSTR contentData = (PWSTR) ((PBYTE) entry + entry->cb);
393             DWORD remainingSpace = entryBufferLength - entry->cb;
394 
395             entry->contentsLength = cchClass * sizeof(WCHAR);
396             StringCbCopyNW(contentData, remainingSpace, className, entry->contentsLength);
397 
398             entry->cb += entry->contentsLength + sizeof(WCHAR);
399         }
400 
401         if (ppidl)
402             *ppidl = (LPITEMIDLIST) entry;
403         return S_OK;
404     }
405 
406     HRESULT NextValue(LPITEMIDLIST* ppidl)
407     {
408         WCHAR name[MAX_PATH];
409         DWORD cchName = _countof(name);
410         DWORD type = 0;
411         DWORD dataSize = 0;
412 
413         if (RegEnumValueW(m_hkey, m_idx++, name, &cchName, 0, &type, NULL, &dataSize))
414             return S_FALSE;
415 
416         REG_ENTRY_TYPE otype = REG_ENTRY_VALUE;
417 
418         DWORD entryBufferLength = FIELD_OFFSET(RegPidlEntry, entryName) + sizeof(WCHAR) + cchName * sizeof(WCHAR);
419 
420 #define MAX_EMBEDDED_DATA 32
421         BOOL copyData = dataSize <= MAX_EMBEDDED_DATA;
422         if (copyData)
423         {
424             entryBufferLength += dataSize + sizeof(WCHAR);
425 
426             otype = REG_ENTRY_VALUE_WITH_CONTENT;
427         }
428 
429         // allocate space for the terminator
430         entryBufferLength += FIELD_OFFSET(SHITEMID, abID);
431 
432         RegPidlEntry* entry = (RegPidlEntry*) CoTaskMemAlloc(entryBufferLength);
433         if (!entry)
434             return E_OUTOFMEMORY;
435 
436         memset(entry, 0, entryBufferLength);
437 
438         entry->cb = FIELD_OFFSET(RegPidlEntry, entryName);
439         entry->magic = REGISTRY_PIDL_MAGIC;
440         entry->entryType = otype;
441         entry->contentType = type;
442 
443         if (cchName > 0)
444         {
445             entry->entryNameLength = cchName * sizeof(WCHAR);
446             StringCbCopyNW(entry->entryName, entryBufferLength, name, entry->entryNameLength);
447             entry->cb += entry->entryNameLength + sizeof(WCHAR);
448         }
449         else
450         {
451             entry->entryNameLength = 0;
452             entry->entryName[0] = 0;
453             entry->cb += sizeof(WCHAR);
454         }
455 
456         if (copyData)
457         {
458             PBYTE contentData = (PBYTE) ((PBYTE) entry + entry->cb);
459 
460             entry->contentsLength = dataSize;
461 
462             // In case it's an unterminated string, RegGetValue will add the NULL termination
463             dataSize += sizeof(WCHAR);
464 
465             if (!RegQueryValueExW(m_hkey, name, NULL, NULL, contentData, &dataSize))
466             {
467                 entry->cb += entry->contentsLength + sizeof(WCHAR);
468             }
469             else
470             {
471                 entry->contentsLength = 0;
472                 entry->cb += sizeof(WCHAR);
473             }
474 
475         }
476 
477         if (ppidl)
478             *ppidl = (LPITEMIDLIST) entry;
479         return S_OK;
480     }
481 
482     HRESULT EnumerateNext(LPITEMIDLIST* ppidl)
483     {
484         if (!m_values)
485         {
486             HRESULT hr = NextKey(ppidl);
487             if (hr != S_FALSE)
488                 return hr;
489 
490             // switch to values.
491             m_values = TRUE;
492             m_idx = 0;
493         }
494 
495         return NextValue(ppidl);
496     }
497 
498     STDMETHODIMP Next(ULONG celt, LPITEMIDLIST *rgelt, ULONG *pceltFetched) override
499     {
500         if (pceltFetched)
501             *pceltFetched = 0;
502 
503         while (celt-- > 0)
504         {
505             HRESULT hr = EnumerateNext(rgelt);
506             if (hr != S_OK)
507                 return hr;
508 
509             if (pceltFetched)
510                 (*pceltFetched)++;
511             if (rgelt)
512                 rgelt++;
513         }
514 
515         return S_OK;
516     }
517 
518     STDMETHODIMP Skip(ULONG celt) override
519     {
520         while (celt > 0)
521         {
522             HRESULT hr = EnumerateNext(NULL);
523             if (FAILED(hr))
524                 return hr;
525             if (hr != S_OK)
526                 break;
527         }
528 
529         return S_OK;
530     }
531 
532     STDMETHODIMP Reset() override
533     {
534         return E_NOTIMPL;
535     }
536 
537     STDMETHODIMP Clone(IEnumIDList **ppenum) override
538     {
539         return E_NOTIMPL;
540     }
541 
542     DECLARE_NOT_AGGREGATABLE(CEnumRegKey)
543     DECLARE_PROTECT_FINAL_CONSTRUCT()
544 
545     BEGIN_COM_MAP(CEnumRegKey)
546         COM_INTERFACE_ENTRY_IID(IID_IEnumIDList, IEnumIDList)
547     END_COM_MAP()
548 };
549 
550 class CEnumNTDirectory :
551     public CComObjectRootEx<CComMultiThreadModelNoCS>,
552     public IEnumIDList
553 {
554     WCHAR buffer[MAX_PATH];
555     HANDLE m_directory;
556     BOOL m_first;
557     ULONG m_enumContext;
558     PWSTR m_pend;
559 
560 public:
561     CEnumNTDirectory()
562         : m_directory(NULL), m_first(TRUE), m_enumContext(0), m_pend(NULL)
563     {
564     }
565 
566     ~CEnumNTDirectory()
567     {
568         NtClose(m_directory);
569     }
570 
571     HRESULT Initialize(PCWSTR path)
572     {
573         StringCbCopyExW(buffer, sizeof(buffer), path, &m_pend, NULL, 0);
574 
575         DWORD err = NtOpenObject(DIRECTORY_OBJECT, &m_directory, FILE_LIST_DIRECTORY, buffer);
576         if (!NT_SUCCESS(err))
577         {
578             ERR("NtOpenDirectoryObject failed for path %S with status=%x\n", buffer, err);
579             return HRESULT_FROM_NT(err);
580         }
581 
582         if (m_pend[-1] != '\\')
583             *m_pend++ = '\\';
584 
585         return S_OK;
586     }
587 
588     HRESULT EnumerateNext(LPITEMIDLIST* ppidl)
589     {
590         BYTE dirbuffer[2048];
591         if (!NT_SUCCESS(NtQueryDirectoryObject(m_directory, dirbuffer, 2048, TRUE, m_first, &m_enumContext, NULL)))
592             return S_FALSE;
593 
594         m_first = FALSE;
595 
596         // if ppidl is NULL, assume the caller was Skip(),
597         // so we don't care about the info
598         if (!ppidl)
599             return S_OK;
600 
601         POBJECT_DIRECTORY_INFORMATION info = (POBJECT_DIRECTORY_INFORMATION) dirbuffer;
602 
603         if (info->Name.Buffer)
604         {
605             StringCbCopyNW(m_pend, sizeof(buffer), info->Name.Buffer, info->Name.Length);
606         }
607 
608         OBJECT_TYPE otype = MapTypeNameToType(info->TypeName.Buffer, info->TypeName.Length);
609 
610         DWORD entryBufferLength = FIELD_OFFSET(NtPidlEntry, entryName) + sizeof(WCHAR);
611         if (info->Name.Buffer)
612             entryBufferLength += info->Name.Length;
613 
614         if (otype < 0)
615         {
616             entryBufferLength += FIELD_OFFSET(NtPidlTypeData, typeName) + sizeof(WCHAR);
617 
618             if (info->TypeName.Buffer)
619             {
620                 entryBufferLength += info->TypeName.Length;
621             }
622         }
623 
624         // allocate space for the terminator
625         entryBufferLength += FIELD_OFFSET(SHITEMID, abID);
626 
627         NtPidlEntry* entry = (NtPidlEntry*) CoTaskMemAlloc(entryBufferLength);
628         if (!entry)
629             return E_OUTOFMEMORY;
630 
631         memset(entry, 0, entryBufferLength);
632 
633         entry->cb = FIELD_OFFSET(NtPidlEntry, entryName);
634         entry->magic = NT_OBJECT_PIDL_MAGIC;
635         entry->objectType = otype;
636 
637         if (info->Name.Buffer)
638         {
639             entry->entryNameLength = info->Name.Length;
640             StringCbCopyNW(entry->entryName, entryBufferLength, info->Name.Buffer, info->Name.Length);
641             entry->cb += entry->entryNameLength + sizeof(WCHAR);
642         }
643         else
644         {
645             entry->entryNameLength = 0;
646             entry->entryName[0] = 0;
647             entry->cb += sizeof(WCHAR);
648         }
649 
650         if (otype < 0)
651         {
652             NtPidlTypeData * typedata = (NtPidlTypeData*) ((PBYTE) entry + entry->cb);
653             DWORD remainingSpace = entryBufferLength - ((PBYTE) (typedata->typeName) - (PBYTE) entry);
654 
655             if (info->TypeName.Buffer)
656             {
657                 typedata->typeNameLength = info->TypeName.Length;
658                 StringCbCopyNW(typedata->typeName, remainingSpace, info->TypeName.Buffer, info->TypeName.Length);
659 
660                 entry->cb += typedata->typeNameLength + sizeof(WCHAR);
661             }
662             else
663             {
664                 typedata->typeNameLength = 0;
665                 typedata->typeName[0] = 0;
666                 entry->cb += typedata->typeNameLength + sizeof(WCHAR);
667             }
668         }
669 
670         *ppidl = (LPITEMIDLIST) entry;
671 
672         return S_OK;
673     }
674 
675     STDMETHODIMP Next(ULONG celt, LPITEMIDLIST *rgelt, ULONG *pceltFetched) override
676     {
677         if (pceltFetched)
678             *pceltFetched = 0;
679 
680         while (celt-- > 0)
681         {
682             HRESULT hr = EnumerateNext(rgelt);
683             if (hr != S_OK)
684                 return hr;
685 
686             if (pceltFetched)
687                 (*pceltFetched)++;
688             if (rgelt)
689                 rgelt++;
690         }
691 
692         return S_OK;
693     }
694 
695     STDMETHODIMP Skip(ULONG celt) override
696     {
697         while (celt > 0)
698         {
699             HRESULT hr = EnumerateNext(NULL);
700             if (FAILED(hr))
701                 return hr;
702             if (hr != S_OK)
703                 break;
704         }
705 
706         return S_OK;
707     }
708 
709     STDMETHODIMP Reset() override
710     {
711         return E_NOTIMPL;
712     }
713 
714     STDMETHODIMP Clone(IEnumIDList **ppenum) override
715     {
716         return E_NOTIMPL;
717     }
718 
719     DECLARE_NOT_AGGREGATABLE(CEnumNTDirectory)
720     DECLARE_PROTECT_FINAL_CONSTRUCT()
721 
722     BEGIN_COM_MAP(CEnumNTDirectory)
723         COM_INTERFACE_ENTRY_IID(IID_IEnumIDList, IEnumIDList)
724     END_COM_MAP()
725 };
726 
727 HRESULT GetEnumRegistryRoot(IEnumIDList ** ppil)
728 {
729     return ShellObjectCreator<CEnumRegRoot>(IID_PPV_ARG(IEnumIDList, ppil));
730 }
731 
732 HRESULT GetEnumRegistryKey(LPCWSTR path, HKEY root, IEnumIDList ** ppil)
733 {
734     return ShellObjectCreatorInit<CEnumRegKey>(path, root, IID_PPV_ARG(IEnumIDList, ppil));
735 }
736 
737 HRESULT GetEnumNTDirectory(LPCWSTR path, IEnumIDList ** ppil)
738 {
739     return ShellObjectCreatorInit<CEnumNTDirectory>(path, IID_PPV_ARG(IEnumIDList, ppil));
740 }
741