Sample code to walk the WPD object hierarchy

Here's a quick example of a console app that can enumerate the contents of a WPD device. You'll need to link against portabledeviceguids.lib as well.

 // Disclaimer: The code presented is not endorsed in anyway by Microsoft. Use at your own risk.
#include <portabledevice.h>
#include <portabledeviceapi.h>

//=============================================================================
// Helper to print indents
//=============================================================================
void PrintIndentSpaces(DWORD dwNumSpaces)
{
    const DWORD MAX_INDENT = 16;
    char szIndent[MAX_INDENT] = {0};

    if (SUCCEEDED(StringCchPrintfA(szIndent, MAX_INDENT, "%%%ds", dwNumSpaces)))
    {
        printf(szIndent, "");
    }
}

//=============================================================================
// Helper to print object properties
//=============================================================================
HRESULT PrintProperties(IPortableDeviceProperties* pProperties, LPCWSTR pwszObjectID, DWORD dwLevel = 0)
{
    HRESULT hr = S_OK;

    // Specify the properties we are interested in spPropertyKeys
    CComPtr<IPortableDeviceKeyCollection> spPropertyKeys;
    if (hr == S_OK)
    {
        hr = CoCreateInstance(CLSID_PortableDeviceKeyCollection,
                              NULL,
                              CLSCTX_INPROC_SERVER,
                              IID_IPortableDeviceKeyCollection,
                              (VOID**)&spPropertyKeys);
    }

    if (hr == S_OK)
    {
        hr = spPropertyKeys-<Add(WPD_OBJECT_NAME);
    }

    if (hr == S_OK)
    {
        hr = spPropertyKeys->Add(WPD_OBJECT_SIZE);
    }

    // Use the GetValues API to get the desired values
    CComPtr<IPortableDeviceValues> spPropertyValues;
    if (hr == S_OK)
    {
        hr = pProperties->GetValues(pwszObjectID, spPropertyKeys, &spPropertyValues);

        // GetValues may return S_FALSE if one or more properties could not be retrieved
        if (hr == S_FALSE)
        {
            hr = S_OK;
        }
    }

    // Get value of each requested property
    LPWSTR pwszName = NULL;
    if (hr == S_OK)
    {               
        hr = spPropertyValues->GetStringValue(WPD_OBJECT_NAME, &pwszName);
    }

    ULONGLONG ullSize = 0;
    if (hr == S_OK)
    {
        hr = spPropertyValues->GetUnsignedLargeIntegerValue(WPD_OBJECT_SIZE, &ullSize);

        // WPD_OBJECT_SIZE may not be supported some objects
        if (FAILED(hr))
        {
            hr = S_OK;
        }
    }

    // Display object properties
    if (hr == S_OK)
    {
        PrintIndentSpaces(dwLevel * 4);
        printf("[%ws] %ws (%I64u bytes)\n", pwszObjectID, pwszName, ullSize);
    }

    // Free any memory allocated by GetStringValue
    if (pwszName != NULL)
    {
        CoTaskMemFree(pwszName);
    }

    return hr;
}

//=============================================================================
// Enumeration function (recursive)
//=============================================================================
HRESULT Enumerate(IPortableDeviceContent* pContent, LPCWSTR pwszParentObjectId, DWORD dwLevel)
{
    HRESULT hr = S_OK;

    // Display properties of supplied object
    CComPtr<IPortableDeviceProperties> spProperties;
    hr = pContent->Properties(&spProperties);

    if (hr == S_OK)
    {
        hr = PrintProperties(spProperties, pwszParentObjectId, dwLevel);
    }

    // Enumerate children (if any) of provided object
    CComPtr<IEnumPortableDeviceObjectIds> spEnum;
    if (hr == S_OK)
    {
        hr = pContent->EnumObjects(0, pwszParentObjectId, NULL, &spEnum);
    }

    while (hr == S_OK)
    {
        // We'll enumerate one object at a time, but be aware that an array can
        // be supplied to the Next API to optimize enumeration
        LPWSTR pwszObjectId = NULL;
        ULONG celtFetched = 0;
        hr = spEnum->Next(1, &pwszObjectId, &celtFetched);

        // Try enumerating children of this object
        if (hr == S_OK)
        {
            hr = Enumerate(pContent, pwszObjectId, dwLevel + 1);
        }
    }

    // Once no more children are available, S_FALSE is returned which we promote to S_OK
    if (hr == S_FALSE)
    {
        hr = S_OK;
    }

    return hr;
}

//=============================================================================
// Start of enumeration
//=============================================================================
HRESULT StartEnumeration(IPortableDevice* pDevice)
{
    HRESULT hr = S_OK;

    // Get content interface for use with enumeration
    CComPtr<IPortableDeviceContent> spContent;
    if (hr == S_OK)
    {
        hr = pDevice->Content(&spContent);
    }

    // Start recursive call for enumeration
    if (hr == S_OK)
    {
        // WPD object hierarchies start at the "DEVICE" level
        hr = Enumerate(spContent, L"DEVICE", 0);
    }

    return hr;
}

//=============================================================================
/ Main entry point 
//-----------------------------------------------------------------------------
int _cdecl _tmain(int argc, TCHAR* argv[])
{
    UNREFERENCED_PARAMETER(argc);
    UNREFERENCED_PARAMETER(argv);

    HRESULT hr = S_OK;
    hr = CoInitialize(NULL);

    if (hr == S_OK)
    {
        // Get an instance of the WPD device manager
        CComPtr<IPortableDeviceManager> spDevMgr;
        hr = CoCreateInstance(CLSID_PortableDeviceManager,
                              NULL,
                              CLSCTX_INPROC_SERVER,
                              IID_IPortableDeviceManager,
                              (VOID**) &spDevMgr);       

        // Get the path to the first WPD device for now
        LPWSTR pwszDevice = NULL;
        DWORD cdwDevices = 1;

        if (hr == S_OK)
        {
            hr = spDevMgr->GetDevices(&pwszDevice, &cdwDevices);
            // S_FALSE may be returned if more than one device is connected
            if (hr == S_FALSE)
            {
                hr = S_OK;
            }
        }

        // Create an instance of the first WPD device 
        CComPtr<IPortableDevice> spDevice;
        if (hr == S_OK)
        {
            hr = CoCreateInstance(CLSID_PortableDevice,
                                  NULL,
                                  CLSCTX_INPROC_SERVER,
                                  IID_IPortableDevice,
                                  (VOID**) &spDevice);
        }

        // Create an instance of a Values collection to hold the client information 
        CComPtr<IPortableDeviceValues> spClientInfo;
        if (hr == S_OK)
        {
            hr = CoCreateInstance(CLSID_PortableDeviceValues,
                                  NULL,
                                  CLSCTX_INPROC_SERVER,
                                  IID_IPortableDeviceValues,
                                  (VOID**)&spClientInfo);
        }

        // Add client information to the Values collection
        if (hr == S_OK)
        {
            hr = spClientInfo->SetStringValue(WPD_CLIENT_NAME, L"Sample Enumerator");        
        }

        if (hr == S_OK)
        {
            hr = spClientInfo->SetUnsignedIntegerValue(WPD_CLIENT_MAJOR_VERSION, 1);
        }

        if (hr == S_OK)
        {
            hr = spClientInfo->SetUnsignedIntegerValue(WPD_CLIENT_MINOR_VERSION, 0);
        }

        if (hr == S_OK)
        {
            hr = spClientInfo->SetUnsignedIntegerValue(WPD_CLIENT_REVISION, 0);
        }    

        // Connect to the device
        BOOL bDeviceOpened = TRUE;
        if (hr == S_OK)
        {
            hr = spDevice->Open(pwszDevice, spClientInfo);
        }

        // Begin enumeration
        if (hr == S_OK)
        {
            hr = StartEnumeration(spDevice);
        }

        if (hr != S_OK)
        {
            printf("Enumeration failed with hr=0x%08X", hr);
        }        

        // Close connection to the device
        if (bDeviceOpened)
        {
            hr = spDevice->Close();
        }

        // Free memory allocated by the GetDevices call
        if (pwszDevice != NULL)
        {
            CoTaskMemFree(pwszDevice);
        }
    }

    CoUninitialize();

    return 0;
}

We use a recursive function to walk through the hierarchy. For each object we visit, we display its object ID, name and size (if applicable). We then try to enumerate any children for the object and if found, call the recursive function again with the child object ID. (This is, of course, not the most elegant way but it's the best I could do to make it compact.)

The output of this would look something like this:

 [DEVICE] MTP Device (0 bytes)
    [s10001] Store0 (0 bytes)
        [o1] Music (0 bytes)
            [o2] Aaron Goldberg (0 bytes)
                [o3] Worlds (0 bytes)
                    [o4] OAM's Blues (5407802 bytes)
            [o5] Aisha Duo (0 bytes)
                [o6] Quiet Songs (0 bytes)
                    [o7] Amanda (4990823 bytes)
                    [o8] Despertar (6214617 bytes)
            [o9] Karsh Kale (0 bytes)
                [oA] Realize (0 bytes)
                    [oB] Distance (6623806 bytes)
                    [oC] One Step Beyond (7407286 bytes)
        [oD] Sunset.jpg (71189 bytes)
        [oE] WMPInfo.xml (296 bytes)
    [s20002] Store1 (0 bytes)
        [oF] New Folder (0 bytes)
        [o10] Tmp_001y.dat (0 bytes)
    [RenderingInformation] RenderingInformation (0 bytes)
    [StillCapture] StillCapture (0 bytes)
    [NetworkConfig] NetworkConfig (0 bytes)

The hierarchy starts with the DEVICE node and we then drill down into the storage node (s10001), which in turn has folders and files. This particular device has two storages - s10001 and s20002. We also see that the functional objects - RenderingInformation, StillCapture and NetworkConfig - appear as children of the DEVICE node.

You may have noticed an interesting thing about the object IDs. They follow a pattern - the storages' object IDs have an "s" prefixed while the objects' IDs have an "o" prefixed. This is specific only to MTP devices and, in fact, correspond to the MTP storage IDs and object handles. In a later post, we'll see how we can use this relation to get some MTP information out of the device. Note that this "pattern" is /not/ a feature and may not exist in future releases of WPD.