Beispiel #1
0
PIMAGE_RESOURCE_DIRECTORY_ENTRY
FindResourceDirectoryEntryWithName(PVOID root, PIMAGE_RESOURCE_DIRECTORY rd, LPCWSTR name)
{
	PIMAGE_RESOURCE_DIRECTORY_ENTRY entry;
	entry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY) GetPtr(rd, sizeof(*rd));

	ULONG cnt = rd->NumberOfNamedEntries + rd->NumberOfIdEntries;

	for(ULONG i = 0; i < cnt; ++i)
	{
		if(entry[i].NameIsString)
		{
			PIMAGE_RESOURCE_DIR_STRING_U rname;
			rname = (PIMAGE_RESOURCE_DIR_STRING_U) GetPtr(root, entry[i].NameOffset);

			WCHAR tmp[MAX_PATH];
			StringCbCopyNW(tmp, sizeof(tmp), rname->NameString, rname->Length);

			if(wcscmp(tmp, name) == 0)
				return &entry[i]; 
		}
	}

	return NULL;
}
Beispiel #2
0
LPWSTR WINAPI StringDupW(_In_ HANDLE hHeap, _In_ LPCWSTR lpszString)
{
	LPWSTR lpszCopy;
	size_t cbCopy;
	HRESULT hr;

	if (!lpszString)
		return NULL;

	hr = StringCbLengthW(lpszString, (STRSAFE_MAX_CCH-1) * sizeof(WCHAR), &cbCopy);
	if (FAILED(hr))
		return NULL;

	if ((cbCopy + sizeof(WCHAR)) <= cbCopy)
		return NULL;

	cbCopy += sizeof(WCHAR);
	lpszCopy = (LPWSTR) HeapAlloc(hHeap, HEAP_ZERO_MEMORY, cbCopy);
	if (!lpszCopy)
		return NULL;

	hr = StringCbCopyNW(lpszCopy, cbCopy, lpszString, cbCopy);
	if (FAILED(hr)) {
		HeapSafeFree(hHeap, 0, lpszCopy);
		return NULL;
	}

	return lpszCopy;
}
STDMETHODIMP CContextMenuHandler::GetCommandString(UINT_PTR idCmd, UINT uType, UINT *pReserved, LPSTR pszName,UINT cchMax)
{
#ifdef DEBUG_TO_LOG_FILES
	char	tbuf[200];
	sprintf_s(tbuf,200,__FUNCTION__ ": cmd=%lu, type=%u, name=%s",(DWORD)idCmd,uType,pszName);
	f_log(tbuf);
#endif

	HRESULT  hr = E_INVALIDARG;

	const char *aHelp = "";
	size_t aHelpSize = 0;
	const wchar_t *wHelp = L"";
	size_t wHelpSize = 0;

	const char *aVerb = "";
	size_t aVerbSize = 0;
	const wchar_t *wVerb = L"";
	size_t wVerbSize = 0;

	if (idCmd == m_idCmdFirst + eMC_RunConsole) {
		aHelp = szDescrRunConsoleA;
		aHelpSize = sizeof(szDescrRunConsoleA);

		wHelp = szDescrRunConsoleW;
		wHelpSize = sizeof(szDescrRunConsoleW);

		aVerb = szVerbRunConsoleA;
		aVerbSize = sizeof(szVerbRunConsoleA);

		wVerb = szVerbRunConsoleW;
		wVerbSize = sizeof(szVerbRunConsoleW);
	}
	else if (idCmd == m_idCmdFirst + eMC_PostConsole) {
		aHelp = szDescrPostConsoleA;
		aHelpSize = sizeof(szDescrPostConsoleA);

		wHelp = szDescrPostConsoleW;
		wHelpSize = sizeof(szDescrPostConsoleW);

		aVerb = szVerbPostConsoleA;
		aVerbSize = sizeof(szVerbPostConsoleA);

		wVerb = szVerbPostConsoleW;
		wVerbSize = sizeof(szVerbPostConsoleW);
	}
	if (idCmd == m_idCmdFirst + eMC_RunConsoleWithTabFake) {
		aHelp = szDescrRunConsoleWithTabA;
		aHelpSize = sizeof(szDescrRunConsoleWithTabA);

		wHelp = szDescrRunConsoleWithTabW;
		wHelpSize = sizeof(szDescrRunConsoleWithTabW);

		aVerb = szVerbRunConsoleWithTabA;
		aVerbSize = sizeof(szVerbRunConsoleWithTabA);

		wVerb = szVerbRunConsoleW;
		wVerbSize = sizeof(szVerbRunConsoleWithTabW);
	}
	if (idCmd == m_idCmdFirst + eMC_PostConsoleWithTabFake) {
		aHelp = szDescrPostConsoleWithTabA;
		aHelpSize = sizeof(szDescrPostConsoleWithTabA);

		wHelp = szDescrPostConsoleWithTabW;
		wHelpSize = sizeof(szDescrPostConsoleWithTabW);

		aVerb = szVerbPostConsoleWithTabA;
		aVerbSize = sizeof(szVerbPostConsoleWithTabA);

		wVerb = szVerbPostConsoleWithTabW;
		wVerbSize = sizeof(szVerbPostConsoleWithTabW);
	}
	else {
	}

	switch(uType) {
	case GCS_HELPTEXTA:
		hr = StringCbCopyNA(pszName, cchMax, aHelp, aHelpSize);
		break; 

	case GCS_HELPTEXTW:
		hr = StringCbCopyNW((LPWSTR)pszName, cchMax * sizeof(wchar_t), wHelp, wHelpSize);
		break; 

	case GCS_VERBA:
		hr = StringCbCopyNA(pszName, cchMax, aVerb, aVerbSize);
		break; 

	case GCS_VERBW:
		hr = StringCbCopyNW((LPWSTR)pszName, cchMax * sizeof(wchar_t), wVerb, wVerbSize);
		break; 

	default:
		hr = S_OK;
		break; 
	}
	return hr;
}
Beispiel #4
0
static BOOLEAN
CheckForValidPEAndVendor(
    IN HANDLE RootDirectory OPTIONAL,
    IN PCWSTR PathNameToFile,
    OUT PUNICODE_STRING VendorName
    )
{
    BOOLEAN Success = FALSE;
    NTSTATUS Status;
    HANDLE FileHandle, SectionHandle;
    // SIZE_T ViewSize;
    PVOID ViewBase;
    PVOID VersionBuffer = NULL; // Read-only
    PVOID pvData = NULL;
    UINT BufLen = 0;

    if (VendorName->MaximumLength < sizeof(UNICODE_NULL))
        return FALSE;

    *VendorName->Buffer = UNICODE_NULL;
    VendorName->Length = 0;

    Status = OpenAndMapFile(RootDirectory, PathNameToFile,
                            &FileHandle, &SectionHandle, &ViewBase,
                            NULL, FALSE);
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("Failed to open and map file '%S', Status 0x%08lx\n", PathNameToFile, Status);
        return FALSE; // Status;
    }

    /* Make sure it's a valid PE file */
    if (!RtlImageNtHeader(ViewBase))
    {
        DPRINT1("File '%S' does not seem to be a valid PE, bail out\n", PathNameToFile);
        Status = STATUS_INVALID_IMAGE_FORMAT;
        goto UnmapFile;
    }

    /*
     * Search for a valid executable version and vendor.
     * NOTE: The module is loaded as a data file, it should be marked as such.
     */
    Status = NtGetVersionResource((PVOID)((ULONG_PTR)ViewBase | 1), &VersionBuffer, NULL);
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("Failed to get version resource for file '%S', Status 0x%08lx\n", PathNameToFile, Status);
        goto UnmapFile;
    }

    Status = NtVerQueryValue(VersionBuffer, L"\\VarFileInfo\\Translation", &pvData, &BufLen);
    if (NT_SUCCESS(Status))
    {
        USHORT wCodePage = 0, wLangID = 0;
        WCHAR FileInfo[MAX_PATH];

        wCodePage = LOWORD(*(ULONG*)pvData);
        wLangID   = HIWORD(*(ULONG*)pvData);

        StringCchPrintfW(FileInfo, ARRAYSIZE(FileInfo),
                         L"StringFileInfo\\%04X%04X\\CompanyName",
                         wCodePage, wLangID);

        Status = NtVerQueryValue(VersionBuffer, FileInfo, &pvData, &BufLen);

        /* Fixup the Status in case pvData is NULL */
        if (NT_SUCCESS(Status) && !pvData)
            Status = STATUS_NOT_FOUND;

        if (NT_SUCCESS(Status) /*&& pvData*/)
        {
            /* BufLen includes the NULL terminator count */
            DPRINT1("Found version vendor: \"%S\" for file '%S'\n", pvData, PathNameToFile);

            StringCbCopyNW(VendorName->Buffer, VendorName->MaximumLength,
                           pvData, BufLen * sizeof(WCHAR));
            VendorName->Length = wcslen(VendorName->Buffer) * sizeof(WCHAR);

            Success = TRUE;
        }
    }

    if (!NT_SUCCESS(Status))
        DPRINT1("No version vendor found for file '%S'\n", PathNameToFile);

UnmapFile:
    /* Finally, unmap and close the file */
    UnMapFile(SectionHandle, ViewBase);
    NtClose(FileHandle);

    return Success;
}
Beispiel #5
0
HRESULT StringCbCopyW(
        LPWSTR pszDest,
        size_t cbDest,
        LPCWSTR pszSrc){
    return StringCbCopyNW(pszDest, cbDest, pszSrc, cbDest);
}
Beispiel #6
0
PEVENTLOGRECORD
LogfAllocAndBuildNewRecord(PSIZE_T pRecSize,
                           ULONG   Time,
                           USHORT  wType,
                           USHORT  wCategory,
                           ULONG   dwEventId,
                           PUNICODE_STRING SourceName,
                           PUNICODE_STRING ComputerName,
                           ULONG   dwSidLength,
                           PSID    pUserSid,
                           USHORT  wNumStrings,
                           PWSTR   pStrings,
                           ULONG   dwDataSize,
                           PVOID   pRawData)
{
    SIZE_T RecSize;
    SIZE_T SourceNameSize, ComputerNameSize, StringLen;
    PBYTE Buffer;
    PEVENTLOGRECORD pRec;
    PWSTR str;
    UINT i, pos;

    SourceNameSize   = (SourceName   && SourceName->Buffer)   ? SourceName->Length   : 0;
    ComputerNameSize = (ComputerName && ComputerName->Buffer) ? ComputerName->Length : 0;

    RecSize = sizeof(EVENTLOGRECORD) + /* Add the sizes of the strings, NULL-terminated */
        SourceNameSize + ComputerNameSize + 2*sizeof(UNICODE_NULL);

    /* Align on DWORD boundary for the SID */
    RecSize = ROUND_UP(RecSize, sizeof(ULONG));

    RecSize += dwSidLength;

    /* Add the sizes for the strings array */
    ASSERT((pStrings == NULL && wNumStrings == 0) ||
           (pStrings != NULL && wNumStrings >= 0));
    for (i = 0, str = pStrings; i < wNumStrings; i++)
    {
        StringLen = wcslen(str) + 1; // str must be != NULL
        RecSize += StringLen * sizeof(WCHAR);
        str += StringLen;
    }

    /* Add the data size */
    RecSize += dwDataSize;

    /* Align on DWORD boundary for the full structure */
    RecSize = ROUND_UP(RecSize, sizeof(ULONG));

    /* Size of the trailing 'Length' member */
    RecSize += sizeof(ULONG);

    Buffer = RtlAllocateHeap(GetProcessHeap(), HEAP_ZERO_MEMORY, RecSize);
    if (!Buffer)
    {
        DPRINT1("Cannot allocate heap!\n");
        return NULL;
    }

    pRec = (PEVENTLOGRECORD)Buffer;
    pRec->Length = RecSize;
    pRec->Reserved = LOGFILE_SIGNATURE;

    /*
     * Do not assign here any precomputed record number to the event record.
     * The true record number will be assigned atomically and sequentially in
     * LogfWriteRecord, so that all the event records will have consistent and
     * unique record numbers.
     */
    pRec->RecordNumber = 0;

    /*
     * Set the generated time, and temporarily set the written time
     * with the generated time.
     */
    pRec->TimeGenerated = Time;
    pRec->TimeWritten   = Time;

    pRec->EventID = dwEventId;
    pRec->EventType = wType;
    pRec->EventCategory = wCategory;

    pos = sizeof(EVENTLOGRECORD);

    /* NOTE: Equivalents of RtlStringCbCopyUnicodeString calls */
    if (SourceNameSize)
    {
        StringCbCopyNW((PWSTR)(Buffer + pos), SourceNameSize + sizeof(UNICODE_NULL),
                       SourceName->Buffer, SourceNameSize);
    }
    pos += SourceNameSize + sizeof(UNICODE_NULL);
    if (ComputerNameSize)
    {
        StringCbCopyNW((PWSTR)(Buffer + pos), ComputerNameSize + sizeof(UNICODE_NULL),
                       ComputerName->Buffer, ComputerNameSize);
    }
    pos += ComputerNameSize + sizeof(UNICODE_NULL);

    /* Align on DWORD boundary for the SID */
    pos = ROUND_UP(pos, sizeof(ULONG));

    pRec->UserSidLength = 0;
    pRec->UserSidOffset = 0;
    if (dwSidLength)
    {
        RtlCopyMemory(Buffer + pos, pUserSid, dwSidLength);
        pRec->UserSidLength = dwSidLength;
        pRec->UserSidOffset = pos;
        pos += dwSidLength;
    }

    pRec->StringOffset = pos;
    for (i = 0, str = pStrings; i < wNumStrings; i++)
    {
        StringLen = wcslen(str) + 1; // str must be != NULL
        StringCchCopyW((PWSTR)(Buffer + pos), StringLen, str);
        str += StringLen;
        pos += StringLen * sizeof(WCHAR);
    }
    pRec->NumStrings = wNumStrings;

    pRec->DataLength = 0;
    pRec->DataOffset = 0;
    if (dwDataSize)
    {
        RtlCopyMemory(Buffer + pos, pRawData, dwDataSize);
        pRec->DataLength = dwDataSize;
        pRec->DataOffset = pos;
        pos += dwDataSize;
    }

    /* Align on DWORD boundary for the full structure */
    pos = ROUND_UP(pos, sizeof(ULONG));

    /* Initialize the trailing 'Length' member */
    *((PDWORD)(Buffer + pos)) = RecSize;

    *pRecSize = RecSize;
    return pRec;
}
// This enumerates a tile for the info in _pkiulSetSerialization.  See the SetSerialization function comment for
// more information.
HRESULT CSampleProvider::_EnumerateSetSerialization()
{
    KERB_INTERACTIVE_LOGON* pkil = &_pkiulSetSerialization->Logon;

    _bAutoSubmitSetSerializationCred = false;
    _bDefaultToFirstCredential = false;

    // Since this provider only enumerates local users (not domain users) we are ignoring the domain passed in.
    // However, please note that if you receive a serialized cred of just a domain name, that domain name is meant 
    // to be the default domain for the tiles (or for the empty tile if you have one).  Also, depending on your scenario,
    // the presence of a domain other than what you're expecting might be a clue that you shouldn't handle
    // the SetSerialization.  For example, in this sample, we could choose to not accept a serialization for a cred
    // that had something other than the local machine name as the domain.

    // Use a "long" (MAX_PATH is arbitrary) buffer because it's hard to predict what will be
    // in the incoming values.  A DNS-format domain name, for instance, can be longer than DNLEN.
    WCHAR wszUsername[MAX_PATH] = {0};
    WCHAR wszPassword[MAX_PATH] = {0};

    // since this sample assumes local users, we'll ignore domain.  If you wanted to handle the domain
    // case, you'd have to update CSampleCredential::Initialize to take a domain.
    HRESULT hr = StringCbCopyNW(wszUsername, sizeof(wszUsername), pkil->UserName.Buffer, pkil->UserName.Length);

    if (SUCCEEDED(hr))
    {
        hr = StringCbCopyNW(wszPassword, sizeof(wszPassword), pkil->Password.Buffer, pkil->Password.Length);

        if (SUCCEEDED(hr))
        {
            CSampleCredential* pCred = new CSampleCredential();

            if (pCred)
            {
                hr = pCred->Initialize(_cpus, s_rgCredProvFieldDescriptors, s_rgFieldStatePairs, _dwCredUIFlags, wszUsername, wszPassword);

                if (SUCCEEDED(hr))
                {
                    // for the purposes of this sample, when we enumerate the SetSerialization cred, we only enumerate
                    // that cred and no others, so we can assume it just goes in slot 0.
                    _rgpCredentials[0] = pCred;

                    //if we were able to create a cred, default to it
                    _bDefaultToFirstCredential = true;  
                }
            }
            else
            {
                hr = E_OUTOFMEMORY;
            }

            // If we were passed all the info we need (in this case username & password), we're going to automatically submit this credential.
            // (if we're in CPUS_LOGON that is.  In credUI we want the user to at least click the tile to choose to use those creds)
            if (SUCCEEDED(hr) && (0 < wcslen(wszPassword)))
            {
                _bAutoSubmitSetSerializationCred = true;
            }
        }
    }


    return hr;
}
Beispiel #8
0
    HRESULT EnumerateNext(LPITEMIDLIST* ppidl)
    {
        BYTE dirbuffer[2048];
        if (!NT_SUCCESS(NtQueryDirectoryObject(m_directory, dirbuffer, 2048, TRUE, m_first, &m_enumContext, NULL)))
            return S_FALSE;

        m_first = FALSE;

        // if ppidl is NULL, assume the caller was Skip(),
        // so we don't care about the info
        if (!ppidl)
            return S_OK;

        POBJECT_DIRECTORY_INFORMATION info = (POBJECT_DIRECTORY_INFORMATION) dirbuffer;

        if (info->Name.Buffer)
        {
            StringCbCopyNW(m_pend, sizeof(buffer), info->Name.Buffer, info->Name.Length);
        }

        OBJECT_TYPE otype = MapTypeNameToType(info->TypeName.Buffer, info->TypeName.Length);

        DWORD entryBufferLength = FIELD_OFFSET(NtPidlEntry, entryName) + sizeof(WCHAR);
        if (info->Name.Buffer)
            entryBufferLength += info->Name.Length;

        if (otype < 0)
        {
            entryBufferLength += FIELD_OFFSET(NtPidlTypeData, typeName) + sizeof(WCHAR);

            if (info->TypeName.Buffer)
            {
                entryBufferLength += info->TypeName.Length;
            }
        }

        // allocate space for the terminator
        entryBufferLength += FIELD_OFFSET(SHITEMID, abID);

        NtPidlEntry* entry = (NtPidlEntry*) CoTaskMemAlloc(entryBufferLength);
        if (!entry)
            return E_OUTOFMEMORY;

        memset(entry, 0, entryBufferLength);

        entry->cb = FIELD_OFFSET(NtPidlEntry, entryName);
        entry->magic = NT_OBJECT_PIDL_MAGIC;
        entry->objectType = otype;

        if (info->Name.Buffer)
        {
            entry->entryNameLength = info->Name.Length;
            StringCbCopyNW(entry->entryName, entryBufferLength, info->Name.Buffer, info->Name.Length);
            entry->cb += entry->entryNameLength + sizeof(WCHAR);
        }
        else
        {
            entry->entryNameLength = 0;
            entry->entryName[0] = 0;
            entry->cb += sizeof(WCHAR);
        }

        if (otype < 0)
        {
            NtPidlTypeData * typedata = (NtPidlTypeData*) ((PBYTE) entry + entry->cb);
            DWORD remainingSpace = entryBufferLength - ((PBYTE) (typedata->typeName) - (PBYTE) entry);

            if (info->TypeName.Buffer)
            {
                typedata->typeNameLength = info->TypeName.Length;
                StringCbCopyNW(typedata->typeName, remainingSpace, info->TypeName.Buffer, info->TypeName.Length);

                entry->cb += typedata->typeNameLength + sizeof(WCHAR);
            }
            else
            {
                typedata->typeNameLength = 0;
                typedata->typeName[0] = 0;
                entry->cb += typedata->typeNameLength + sizeof(WCHAR);
            }
        }

        *ppidl = (LPITEMIDLIST) entry;

        return S_OK;
    }
Beispiel #9
0
    HRESULT NextValue(LPITEMIDLIST* ppidl)
    {
        WCHAR name[MAX_PATH];
        DWORD cchName = _countof(name);
        DWORD type = 0;
        DWORD dataSize = 0;

        if (RegEnumValueW(m_hkey, m_idx++, name, &cchName, 0, &type, NULL, &dataSize))
            return S_FALSE;

        REG_ENTRY_TYPE otype = REG_ENTRY_VALUE;

        DWORD entryBufferLength = FIELD_OFFSET(RegPidlEntry, entryName) + sizeof(WCHAR) + cchName * sizeof(WCHAR);

#define MAX_EMBEDDED_DATA 32
        BOOL copyData = dataSize <= MAX_EMBEDDED_DATA;
        if (copyData)
        {
            entryBufferLength += dataSize + sizeof(WCHAR);

            otype = REG_ENTRY_VALUE_WITH_CONTENT;
        }

        // allocate space for the terminator
        entryBufferLength += FIELD_OFFSET(SHITEMID, abID);

        RegPidlEntry* entry = (RegPidlEntry*) CoTaskMemAlloc(entryBufferLength);
        if (!entry)
            return E_OUTOFMEMORY;

        memset(entry, 0, entryBufferLength);

        entry->cb = FIELD_OFFSET(RegPidlEntry, entryName);
        entry->magic = REGISTRY_PIDL_MAGIC;
        entry->entryType = otype;
        entry->contentType = type;

        if (cchName > 0)
        {
            entry->entryNameLength = cchName * sizeof(WCHAR);
            StringCbCopyNW(entry->entryName, entryBufferLength, name, entry->entryNameLength);
            entry->cb += entry->entryNameLength + sizeof(WCHAR);
        }
        else
        {
            entry->entryNameLength = 0;
            entry->entryName[0] = 0;
            entry->cb += sizeof(WCHAR);
        }

        if (copyData)
        {
            PBYTE contentData = (PBYTE) ((PBYTE) entry + entry->cb);

            entry->contentsLength = dataSize;

            // In case it's an unterminated string, RegGetValue will add the NULL termination
            dataSize += sizeof(WCHAR);

            if (!RegQueryValueExW(m_hkey, name, NULL, NULL, contentData, &dataSize))
            {
                entry->cb += entry->contentsLength + sizeof(WCHAR);
            }
            else
            {
                entry->contentsLength = 0;
                entry->cb += sizeof(WCHAR);
            }

        }

        if (ppidl)
            *ppidl = (LPITEMIDLIST) entry;
        return S_OK;
    }
Beispiel #10
0
    HRESULT NextKey(LPITEMIDLIST* ppidl)
    {
        WCHAR name[MAX_PATH];
        DWORD cchName = _countof(name);

        WCHAR className[MAX_PATH];
        DWORD cchClass = _countof(className);

        if (RegEnumKeyExW(m_hkey, m_idx++, name, &cchName, 0, className, &cchClass, NULL))
            return S_FALSE;

        name[cchName] = 0;
        className[cchClass] = 0;

        REG_ENTRY_TYPE otype = REG_ENTRY_KEY;

        DWORD entryBufferLength = FIELD_OFFSET(RegPidlEntry, entryName) + sizeof(WCHAR) + cchName * sizeof(WCHAR);

        if (cchClass > 0)
        {
            entryBufferLength += sizeof(WCHAR) + cchClass * sizeof(WCHAR);
        }

        // allocate space for the terminator
        entryBufferLength += FIELD_OFFSET(SHITEMID, abID);

        RegPidlEntry* entry = (RegPidlEntry*) CoTaskMemAlloc(entryBufferLength);
        if (!entry)
            return E_OUTOFMEMORY;

        memset(entry, 0, entryBufferLength);

        entry->cb = FIELD_OFFSET(RegPidlEntry, entryName);
        entry->magic = REGISTRY_PIDL_MAGIC;
        entry->entryType = otype;

        if (cchName > 0)
        {
            entry->entryNameLength = cchName * sizeof(WCHAR);
            StringCbCopyNW(entry->entryName, entryBufferLength, name, entry->entryNameLength);
            entry->cb += entry->entryNameLength + sizeof(WCHAR);
        }
        else
        {
            entry->entryNameLength = 0;
            entry->entryName[0] = 0;
            entry->cb += sizeof(WCHAR);
        }

        if (cchClass)
        {
            PWSTR contentData = (PWSTR) ((PBYTE) entry + entry->cb);
            DWORD remainingSpace = entryBufferLength - entry->cb;

            entry->contentsLength = cchClass * sizeof(WCHAR);
            StringCbCopyNW(contentData, remainingSpace, className, entry->contentsLength);

            entry->cb += entry->contentsLength + sizeof(WCHAR);
        }

        if (ppidl)
            *ppidl = (LPITEMIDLIST) entry;
        return S_OK;
    }