See the question and my original answer on StackOverflow

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.