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.