Search code examples
c++shellwinapiwindows-shell

How to enumerate files on a specific folder using Windows shell apis?


Im trying to enumerate all files on a given path and also get their properties.

On my implementation below, i dont understand whats happening, it looks like it somehow is using the computer desktop path instead of the path on folderPath

at hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &displayName); it shows:

C:\Users\Lilo\Desktop\file.txt instead of: C:\Users\Lilo\Documents\file.txt

file.txt is on the documents folder not on the desktop

and consequently both SHGetPropertyStoreFromIDList and SHGetPropertyStoreFromParsingName fails with HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) : The system cannot find the file specified.

#include <windows.h>
#include <iostream>
#include <shlobj.h>
#include <atlcomcli.h>

inline void enumFiles(const std::wstring& folderPath)
{
    CoInitialize(NULL);

    HRESULT hr;
    CComPtr<IShellItem>   pFolderItem = NULL;
    CComPtr<IShellFolder> pFolder     = NULL;

    hr = SHCreateItemFromParsingName(folderPath.c_str(), NULL, IID_PPV_ARGS(&pFolderItem));
    if (FAILED(hr))
    {
        std::cout << "SHCreateItemFromParsingName failed with HRESULT:" << hr;
        CoUninitialize();
        return;
    }
    
    hr = pFolderItem->BindToHandler(NULL, BHID_SFObject, IID_PPV_ARGS(&pFolder));
    if (FAILED(hr))
    {
        std::cout << "BindToHandler failed with HRESULT:" << hr;
        CoUninitialize();
        return;
    }
  
    CComPtr<IEnumIDList> pEnum = NULL;
    hr = pFolder->EnumObjects(NULL, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &pEnum);
    if (FAILED(hr))
    {
        std::cout << "EnumObjects failed with HRESULT:" << hr;
        CoUninitialize();
        return;
    }

    ITEMIDLIST* pidl = NULL;
    ULONG fetched;

    while (pEnum->Next(1, &pidl, &fetched) == S_OK)
    {
        CComPtr<IShellItem> pItem;
        hr = SHCreateItemFromIDList(pidl, IID_PPV_ARGS(&pItem));
        if (FAILED(hr))
        {
            std::cout << "SHCreateItemFromIDList failed with HRESULT:" << hr;
            CoTaskMemFree(pidl);
            continue;
        }

        LPWSTR displayName;
        hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &displayName);
        if (SUCCEEDED(hr))
            CoTaskMemFree(displayName);

        CComPtr<IPropertyStore> pPropertyStore;
        GETPROPERTYSTOREFLAGS flags = GPS_DEFAULT;
        hr = SHGetPropertyStoreFromIDList(pidl, flags, IID_PPV_ARGS(&pPropertyStore));
        //hr = SHGetPropertyStoreFromParsingName(displayName, NULL, GPS_DEFAULT, IID_PPV_ARGS(&pPropertyStore));
        CoTaskMemFree(pidl);
        if (FAILED(hr))
        {
            std::cout << "SHGetPropertyStoreFromIDList failed with HRESULT:" << hr;
            continue;
        }
    }

    CoUninitialize();
}

int main(int argc, char* argv[])
{
    enumFiles(L"C:\\Users\\Lilo\\Documents");
}

Solution

  • You can keep on using IShellItem and friends (IEnumShellItems) all along, IShellFolder is the underlying folder interface that's not easy to use, something like this (error-checks ommited):

    void enumFiles(const std::wstring& folderPath)
    {
        CComPtr<IShellItem> folder;
        SHCreateItemFromParsingName(folderPath.c_str(), nullptr, IID_PPV_ARGS(&folder));
    
        // create a bind context to define what we want to enumerate
        CComPtr<IPropertyBag> bag;
        PSCreateMemoryPropertyStore(IID_PPV_ARGS(&bag));
    
        CComPtr<IBindCtx> ctx;
        CreateBindCtx(0, &ctx);
        ctx->RegisterObjectParam((LPOLESTR)STR_PROPERTYBAG_PARAM, bag);
        auto flags = CComVariant(SHCONTF_FOLDERS | SHCONTF_NONFOLDERS);
        bag->Write(STR_ENUM_ITEMS_FLAGS, &flags);
    
        CComPtr<IEnumShellItems> items;
        folder->BindToHandler(ctx, BHID_EnumItems, IID_PPV_ARGS(&items));
    
        do
        {
            CComPtr<IShellItem> item;
            if (items->Next(1, &item, nullptr) != S_OK)
                break;
    
            CComHeapPtr<wchar_t> displayName;
            item->GetDisplayName(SIGDN_FILESYSPATH, &displayName);
    
            std::wcout << displayName.m_pData << std::endl;
    
            CComPtr<IPropertyStore> store;
            if (FAILED(item->BindToHandler(nullptr, BHID_PropertyStore, IID_PPV_ARGS(&store))))
            {
              CComPtr<IPropertyStoreFactory> factory;
              item->BindToHandler(nullptr, BHID_PropertyStore, IID_PPV_ARGS(&factory));
    
              factory->GetPropertyStore(GPS_BESTEFFORT, nullptr, IID_PPV_ARGS(&store));
            }
    
            // etc.
    
        } while (true);
    }
    
    int main(int argc, char* argv[])
    {
        CoInitialize(NULL);
        {
            enumFiles(L"C:\\Users\\Lilo\\Documents");
        }
        CoUninitialize();
        return 0;
    }
    

    See here for more on the possible bind contexts.

    PS 1: don't call CoInitalize in a library code, just call it once per process/thread.

    PS 2: ATL has CComHeapPtr for smart memory pointer.