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.

Comments (1)
  1. Ricky says:

    I have a question for you, that:

    I am using MFC and how to list objects(image, video, audio,…) by their format? Such as, i am only obtain list images on device, how to do that?

    Thank's so much!

Comments are closed.

Skip to main content