WPD: Transferring Content
August 13, 2011
About two months ago I wrote a couple of articles which covered the usage of the Windows Portable Device API, namely:
These articles explain how you can detect WPD-compatible devices connected to your PC and how to list their contents. Today I received an e-mail from a reader asking me if it is possible to transfer/download the content of such a device through the WPD API.
No idea actually. Never tried it. Let’s find out…
Before you get started, have a quick glance at the previous two articles if you haven’t already done so and afterwards download the source of the second article (WPD: Enumerating Content). We’ll be using it as a starting point.
WPD: Enumerating Content Source Code
After you’ve downloaded the source code, unzip it and open the WPD solution in Visual Studio.
It’s a simple console application which scans for WPD-compatible devices and lists the contents stored on it. I decided to connect my Kindle for a change and see what I could find on it.
PS: If you don’t have a Kindle, get one! It’s a fantastic e-reader and the price is more than reasonable. Any other WPD-compatible devices such as a digital camera, web cam…etc. will also do.
After a bit of searching I found a MSDN page, which offers a bit more information on the topic of transferring content.
http://msdn.microsoft.com/en-us/library/dd388996(v=vs.85).aspx
Apparently you have to follow these steps in order to transfer content found on a WPD device.
- Retrieve the ID of the device
- Create a new IPortableDevice object which represents the device (PortableDeviceClass)
- Create a new IPortableDeviceContent object to access content-specific methods
- Create a new IPortableDeviceResources object to access resource-specific methods
- Retrieve an IStream object to read the data from the device
- Read the IStream object and copy it to the destination on the PC
We’re already halfway there as we’ve already implemented the first three steps during the first two articles of this series. Let’s implement the remaining steps.
Open up the PortableDevice.cs code file and add a new method to it called “DownloadFile”.
public void DownloadFile(PortableDeviceFile file, string saveToPath) { //... }
This method takes two parameters, the file to transfer and a folder in which to save it. Each file is wrapped in an instance of the PortableDeviceFile type, which is a small wrapper we created in the second article to represent a file residing on a WPD-compatible device.
Now we need to make sure that this method actually performs a useful task. First we need to create a new IPortableDeviceContent object so that we can access content-specific methods.
IPortableDeviceContent content; this._device.Content(out content);
Then we must create an IPortableDeviceResources object to access resource-specific methods.
IPortableDeviceResources resources;
content.Transfer(out resources);
Now we can create an IStream object to read the data from the device.
PortableDeviceApiLib.IStream wpdStream; uint optimalTransferSize = 0; var property = new _tagpropertykey(); property.fmtid = new Guid(0xE81E79BE, 0x34F0, 0x41BF, 0xB5, 0x3F, 0xF1, 0xA0, 0x6A, 0xE8, 0x78, 0x42); property.pid = 0; resources.GetStream(file.Id, ref property, 0, ref optimalTransferSize, out wpdStream); System.Runtime.InteropServices.ComTypes.IStream sourceStream = (System.Runtime.InteropServices.ComTypes.IStream) wpdStream;
Finally it’s simply a matter of reading the stream and saving it to a file.
var filename = Path.GetFileName(file.Id); FileStream targetStream = new FileStream(Path.Combine(saveToPath, filename), FileMode.Create, FileAccess.Write); unsafe { var buffer = new byte[1024]; int bytesRead; do { sourceStream.Read(buffer, 1024, new IntPtr(&bytesRead)); targetStream.Write(buffer, 0, 1024); } while (bytesRead > 0); targetStream.Close(); }
The previous code denotes an unsafe context because there is some pointer magic happening (IntPtr).
Don’t forget to check the “Allow unsafe code” option in the project’s Build options.
- Right-click on the project in the solution explorer
- Select Properties
- Navigate to the Build tab
- Check the option “Allow unsafe code”
If you don’t enable this option the compiler will throw an error.
Now we can slightly adjust the demo application. Just open up the Program.cs code file and add the following bit of code to the DisplayFolderContents(…) method. Place it inside of the foreach loop.
if (item is PortableDeviceFile) { device.DownloadFile((PortableDeviceFile)item, @"c:\kindle\"); }
If you run the demo application you’ll notice that the contents of your Kindle (or other device) are copied to your local hard drive. Here I used the hard-coded folder “C:\Kindle” (Hey, it’s a demo).
It took me a bit or searching and trial and error to get this working. There isn’t a lot of information available where you’re trying to use the WPD API in a managed environment. An official managed wrapper for .NET would be nice. You can find the source code for this article on the download page of this blog.







September 8, 2011 at 21:35u
Hello there,
I’ am following your post since “Enumerating WPD devices”. I’ am doing some progress, but right now I’ am stucked with an error that I cant figure out. After I changed the Interop.PortableDeviceApiLib.dll to return all devices connected to my computer, I’m getting the following error:
Marshaler restriction: Excessively long string.
The first call to getDevices(null, ref _devCount) returns the right number of devices, but the second call gets me this error. Here’s my piece of code:
public static List GetConnectedDevices()
{
List result = new List();
uint _devcount = 0;
string[] _devid = null;
PortableDeviceManager _devmanager = new PortableDeviceManager();
_devmanager.RefreshDeviceList();
_devmanager.GetDevices( _devid, ref _devcount);
_devid = new string[_devcount];
try
{
_devmanager.GetDevices(_devid, ref _devcount);
}
catch (Exception ex)
{
}
return result;
}
Have you ever seen this before? Any tips?
Regards
Bruno
February 28, 2013 at 16:32u
Hi!
I have faced with the same problem the solution is you should replace the following lines
instance void GetDevices([in][out] string[] marshal([]) pPnPDeviceIDs
to
instance void GetDevices([in][out] string[] marshal(lpwstr[]) pPnPDeviceIDs
I found the tip here: http://www.codeproject.com/Messages/3187340/Re-Using-WPD-api-Windows-Portable-Devices-modified.aspx
I write the whole solution process to be it in one place:)
So if you have a problem that only one portable device is in the list but there are more plugged in you should apply the following modification:
1.Disassemble the PortableDeviceApi interop using the command -
ildasm Interop.PortableDeviceApiLib.dll /out:pdapi.il
2.Open the IL in Notepad and search for the following string
instance void GetDevices([in][out] string& marshal( lpwstr) pPnPDeviceIDs,
3. Replace all instances of the string above with the following string
instance void GetDevices([in][out] string[] marshal(lpwstr[]) pPnPDeviceIDs,
4. Save the IL and reassemble the interop using the command -
ilasm pdapi.il /dll /output=Interop.PortableDeviceApiLib.dll
Regards,
Laszlo
November 2, 2011 at 19:12u
please help
How to connect to digital camera and open it live in win control.
please help
thanks in advance.
November 28, 2011 at 16:11u
I wanna ask for the part when the files are downloaded. While reading the stream:
unsafe
{
var buffer = new byte[1024];
int bytesRead;
do
{
sourceStream.Read(buffer, 1024, new IntPtr(&bytesRead));
targetStream.Write(buffer, 0, 1024);
} while (bytesRead > 0);
targetStream.Close();
}
only one file is saved and then an error is thrown on line ‘sourceStream.Read(buffer, 1024, new IntPtr(&bytesRead));’ : Requested resource is in use. (Exception from HRESULT: 0x800700AA)
Can you help me with that?
January 13, 2012 at 16:08u
Hello
I have tried your code with Nokia E51 smart phone. And i get this error “The requested resource is in use. (Exception from HRESULT: 0x800700AA)”. Any idea how to fix this?
January 15, 2012 at 18:44u
Are they any background applications / services running that are using the phone and thus blocking your .NET application?
April 17, 2012 at 18:22u
Hey, I just wanted to post a fix for anyone who gets here by googling. You need to release the IStream objects after you’re done reading them. This should do the trick:
Marshal.ReleaseComObject(sourceStream);
Marshal.ReleaseComObject(wpdStream);
August 2, 2012 at 4:59u
THANK YOU! You just saved a night, I owe you a beer!
January 30, 2013 at 12:26u
Thanks! It took me a while to figure this out.
I was trying to copy multiple files from a folder and ran into problems when I didn’t release the streams after reading each file.
January 18, 2012 at 23:13u
This is absolutly fantastic. I’ve been following along today and learning a ton.
Do you know how to delete the source file after the Download is finished?
January 19, 2012 at 9:28u
Not directly of the top of my head. Check out the following library on CodePlex:
http://portabledevicelib.codeplex.com/
It seems to be dead, but it might have some clues about how you can remove a file from your device using the WPD API.
March 31, 2012 at 3:06u
Christophe,
This really is absolutely fantastic. I too have been learning a ton.
With Android 3.0 abandoning Mass Storage Controller interface in favor of Message Transfer Protocol, this is becoming more important everyday. There are so many posts for people asking how to transfer files and this is a HUGE help.
From your explanation, I understand completely ho to traverse the folders and files on the device to READ one.
However, COPYING a file TO the Portable Device seems more complex. I do not see how to specify its “folder”. I think I have to call CreateObject first, but I don’t understand. If you could supply a sample for copying an MP4 file to the device, it would be truely fantastic.
Thank you,
Sean
April 12, 2012 at 11:55u
Deer Christophe!
Your codes absolutely working with my Pidion BIP-1300 and HTC HD-2. How to delete file from WPD?
April 12, 2012 at 21:23u
Hi Vince,
I’ll write a post about deleting items from a device this weekend. Got it working after some research (Googling). If you can’t wait, you can already download the source code for the upcoming article:
http://dl.dropbox.com/u/40603470/WPDDeletingContent.zip
Just search for the DeleteContentFromDevice(…) method and have a look at how it is implemented.
Regards,
Christophe
April 13, 2012 at 15:21u
Thank you so much for your posts about WPD. And if you complete the series with an article about copying/writing files to a device you will make me even more thankful.
Best Regards
Magnus
April 16, 2012 at 21:25u
Hi Magnus,
You can find an example on how to transfer a file to a device here:
http://dl.dropbox.com/u/40603470/WPDTransferToDevice.zip
It’s a quick write up. Just inspect the TransferContentToDevice(…) method to see how it works.
I’ll write a post about it coming weekend.
Regards,
Christophe
April 17, 2012 at 13:50u
Hi Christophe!
Thanx the code. (..content.zip) Correct, and working. On my Pidion Bip1300 is two partition. If file does not exists the linq is drop an exception. Of curse this ease treatable.
Best Regards
Vince
June 6, 2012 at 20:39u
Hi Christophe,
I have found your articles on WPD fascinating. I have run into an issue while trying to transfer file contents. I am trying to read an inventory file from a barcode scanner. The portable device exposes the file name as “f|F|\\Application\\Inventory\\inventory.dat” The code does identify the device and iterate the folders and files as expected (which is brilliant). The c# errror i get refers to “Illegal characters in path”. Any thoughts why my device is expressed as “f|F|” ? Many Thanks in advance of a response. Your article is really the only resource that I have found to help overcome my task. Thanks.
July 24, 2012 at 12:43u
Dear Christophe:
Your code is very useful to me!!
But I have a new problem now.
Do you know how to create a folder in WPD device?
I have try the function “content.CreateObjectWithPropertiesOnly”
but it’s not work
Would you give me some suggestion?
Thinks a lot.
Filbert.
September 28, 2012 at 23:35u
Does anyone know why the PortableDeviceAPI does NOT show up on the COM tab when adding references. Visual Studio 2008 (which I think installs Windows SDK 6.0A). I have tried also manually adding SDK 6.1. no difference. When I search I see some of the .h files, but no PortableDeviceAPI on the add references list? Windows XP.
Thanks
November 26, 2012 at 4:17u
Great article. I have a question though, how can I get the extension of a file along with the name? for example right now I get the file name but extension is missing from the name(e.g. mp3 from music files). Thanks.
March 17, 2013 at 17:47u
Hello Razi,
I had the same problem. Replace
return new PortableDeviceFile(objectId, name);
with
property.pid = 12;//WPD_OBJECT_ORIGINAL_FILE_NAME
values.GetStringValue(property, out name);
return new PortableDeviceFile(objectId, name);
This will return the filename with extension. Important values can be found here: https://nmparsers.svn.codeplex.com/svn/Develop_Branch/NPL/Windows/etl_WPDAPI.events.npl
Hope this helps
.
Cheers
Sascha
January 7, 2013 at 5:51u
[...] cgeers.com/2011/08/13/wpd-transferring-content/ [...]
March 3, 2013 at 1:41u
if (item is PortableDeviceFile)
{
device.DownloadFile((PortableDeviceFile)item, @”c:\kindle\”);
}
give an error. device does not exist