WPD: Transferring Content

August 13, 2011

IntroductionKindle

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…

Table Of Contents

Source Code

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.

Solution Explorer

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.

Kindle Contents

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.

Top of page

Transferring Content

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.

  1. Retrieve the ID of the device
  2. Create a new IPortableDevice object which represents the device (PortableDeviceClass)
  3. Create a new IPortableDeviceContent object to access content-specific methods
  4. Create a new IPortableDeviceResources object to access resource-specific methods
  5. Retrieve an IStream object to read the data from the device
  6. 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.

  1. Right-click on the project in the solution explorer
  2. Select Properties
  3. Navigate to the Build tab
  4. Check the option “Allow unsafe code”

If you don’t enable this option the compiler will throw an error.

Build Optoins - Allow unsafe code

Top of page

Demo Application

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.

Top of page

About these ads

32 Responses to “WPD: Transferring Content”

  1. Bruno Silveira Says:

    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

    • Laci Says:

      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

  2. ahmed ali Says:

    please help

    How to connect to digital camera and open it live in win control.

    please help

    thanks in advance.

  3. Galina Says:

    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?

  4. Mitko Says:

    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?

    • Christophe Says:

      Are they any background applications / services running that are using the phone and thus blocking your .NET application?

    • Tristan Says:

      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);

      • Max Says:

        THANK YOU! You just saved a night, I owe you a beer!

      • Chris Says:

        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.

  5. nitewulf Says:

    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?

  6. Sean Devoy Says:

    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

  7. Vince Jakus Says:

    Deer Christophe!

    Your codes absolutely working with my Pidion BIP-1300 and HTC HD-2. How to delete file from WPD?

    • Christophe Says:

      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

      • Magnus S Says:

        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

      • Christophe Says:

        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

      • Vince Jakus Says:

        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

  8. Elmer Sands Says:

    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.

  9. Filbert Says:

    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.

  10. Ben Says:

    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

  11. Razi Says:

    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.


  12. [...] cgeers.com/2011/08/13/wpd-transferring-content/ [...]

  13. Enrico Says:

    if (item is PortableDeviceFile)
    {
    device.DownloadFile((PortableDeviceFile)item, @”c:\kindle\”);
    }
    give an error. device does not exist

  14. Hack~ok Says:

    Thanks for your articles about WPD. But I have a question, I can transfer media to a device windows phone 8. But the phone can’t not read the files that the pc can open, and it’s able to open the files in pc from the phone at USB mode. Can you help me please.

    • Sascha Says:

      Hi Hack~ok,

      copying files via WPD might lead to unwritten bytes on the phone. I was able to solve this issue by installing a “sd card sync app” on my Android phone. It must be run after copying. Dirty!

      This is why I did not include WPD functionality in my Software.

      Maybe there is another way for Windows Phone 8.

      Cheers
      Sascha

      • Hack~ok Says:

        We were able to solve this issue by set format property with IPortableDeviceValues , may it helps you others. By the way, we are developing sync app. -_-
        “https://twitter.com/hack256″

    • Sascha Says:

      Hello Hack~ok,

      how did you sync the data in detail? Would you be so kind and post a short code-snippet?

      Cheers
      Sascha

  15. metalhead89 Says:

    At first I want to thank you for this awesome article, it really helped me a lot.
    But but anyone be so kind to tell me, why I am always getting the dopple size of a downloaded file? For example I download a text file from the device to my PC using:

    unsafe
    {
    var buffer = new byte[optimalTransferSize];
    int bytesRead;

    // sourceStream.Read(buffer, (int)optimalTransferSize, new IntPtr(&bytesRead));
    do
    {
    sourceStream.Read(buffer, (int)optimalTransferSize, new IntPtr(&bytesRead));

    logger.Debug(buffer.Length);
    targetStream.Write(buffer, 0, (int)optimalTransferSize);

    } while (bytesRead > 0);

    targetStream.Close();
    }

    Marshal.ReleaseComObject(sourceStream);
    Marshal.ReleaseComObject(wpdStream);

    But when I open the created text on my PC, the text has doubled and so also the size of the file. What I am doing wrong?

    I would really appreciate an answer. Thanks

  16. Tigran Says:

    Thank you for a great tutorial.
    @metalhead89 did you find an answer to your double question? I have the same situation.

    @Sascha not sure if I get your answer. I’m using DownloadFile() which I placed in PortableDevice.cs file just like the article says.

  17. vkx Says:

    targetStream.Write(buffer, 0, (int)optimalTransferSize);
    to
    targetStream.Write(buffer, 0, (int)bytesRead);


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 335 other followers

%d bloggers like this: