Enumerating Windows Portable Devices

May 22, 2011

Introduction

A week ago I wrote an article about Windows Image Acquisition (WIA) and demonstrated how you could use it to control your scanner. WIA 2.0 (released with Windows Vista) is mainly targeted towards scanners. For dealing with digital cameras and digital video devices you are better off using the Windows Portable Devices (WDP) API.

Let’s explore the WDP API and see what results we get…

Table Of Contents

Digital Camera

In order to play with the WDP API you need to hook up a compatible device to your computer. I used a digital camera (Sony Cyber-Shot DSC-W50) which I bought somewhere in 2006, but any WPD-compatible device will suffice.

Sony Cyber-Shot DSC-W50

Top of page

Project Setup

Start up Visual Studio 2010 and create a new blank solution named WPD. Next add a new Console Application called PortableDevices to it.

Solution Explorer

To access the WDP API, you’ll need to add references to the COM libraries “PortableDeviceApi 1.0 Type Library″ and “PortableDeviceTypes 1.0 Type Library”.

Type Library References

Afterwards select both references and set the “Embed Interop Types” property to false.

Embed Interop Types

You’re now ready to get your hands wet with the WDP API.

Top of page

Enumerating WPD Devices

Add a new class to the project called PortableDeviceCollection. This class represents a simple collection which keeps track of PortableDevice instances. When an instance of the PortableDeviceCollection class is created its constructor creates an instance of the PortableDeviceManager type which can be found in the PortableDeviceApi 1.0 Type Library.

public class PortableDeviceCollection : Collection<PortableDevice>
{
    private readonly PortableDeviceManager _deviceManager;

    public PortableDeviceCollection()
    {
        this._deviceManager = new PortableDeviceManager();
    }

    public void Refresh()
    {
        //...
    }
}

Go ahead and also add a PortableDevice class to the project. Think of this class as a wrapper for each WPD-compatible device. We pass in the device’s ID when creating a new instance of this class.

public class PortableDevice
{
    public PortableDevice(string deviceId)
    {
        this.DeviceId = deviceId;
    }

    public string DeviceId { get; set; }
}

Let’s build a collection of WPD-compatible devices by implementing the Refresh() method of the PortableDeviceCollection class.

public void Refresh()
{
    this._deviceManager.RefreshDeviceList();

    // Determine how many WPD devices are connected
    var deviceIds = new string[1];
    uint count = 1;
    this._deviceManager.GetDevices(ref deviceIds[0], ref count);

    // Retrieve the device id for each connected device
    deviceIds = new string[count];
    this._deviceManager.GetDevices(ref deviceIds[0], ref count);
    foreach(var deviceId in deviceIds)
    {
        Add(new PortableDevice(deviceId));
    }
}

We instruct the PortableDeviceManager to count how many devices are connected to the PC. Next we retrieve the device IDs for each connected device and add a new instance of the PortableDevice class to the collection.

Add the following code to your console application’s Main() method and hit F5 to run the application.

var collection = new PortableDeviceCollection();

collection.Refresh();

foreach(var device in collection)
{
    Console.WriteLine(device.DeviceId);
}

Console.WriteLine();
Console.WriteLine("Press any key to continue.");
Console.ReadKey();

Your output should resemble the following:

Enumerating WPD Devices

That’s some device ID. Try pronouncing that five times in a row!

Top of page

Connect to a Device

As you can see the device id is not very easy to read. Let’s display a friendlier name. But before we can do so, we need to establish a connection with the device. Adjust the PortableDevice class as shown below.

public class PortableDevice
{
    private bool _isConnected;
    private readonly PortableDeviceClass _device;

    public PortableDevice(string deviceId)
    {
        this._device = new PortableDeviceClass();
        this.DeviceId = deviceId;
    }

    public string DeviceId { get; set; }

    public void Connect()
    {
        if (this._isConnected) { return; }

        var clientInfo = (IPortableDeviceValues) new PortableDeviceValuesClass();
        this._device.Open(this.DeviceId, clientInfo);
        this._isConnected = true;
    }

    public void Disconnect()
    {
        if (!this._isConnected) { return; }
        this._device.Close();
        this._isConnected = false;
    }
}

When an object of the PortableDevice class is instantiated it creates an instance of the PortableDeviceClass type (PortableDeviceApi 1.0 Type Library). This instance’s Open() method is used in the Connect() method in order to establish a connection with the device.

Remark: When establishing a connection with a device you need to pass in an instance of the PortableDeviceValuesClass class. This instance lets you specify information about the client application that is connecting with the device. For this demo application I did not set any additional information.

Top of page

Friendly Name

Once we’ve established a connection to the device we can retrieve a friendlier name. Add the following property to the PortableDevice class:

public string FriendlyName
{
    get
    {
        if (!this._isConnected)
        {
            throw new InvalidOperationException("Not connected to device.");
        }

        // Retrieve the properties of the device
        IPortableDeviceContent content;
        IPortableDeviceProperties properties;
        this._device.Content(out content);
        content.Properties(out properties);

        // Retrieve the values for the properties
        IPortableDeviceValues propertyValues;
        properties.GetValues("DEVICE", null, out propertyValues);

        // Identify the property to retrieve
        var property = new _tagpropertykey();
        property.fmtid = new Guid(0x26D4979A, 0xE643, 0x4626, 0x9E, 0x2B,
                                    0x73, 0x6D, 0xC0, 0xC9, 0x2F, 0xDC);
        property.pid = 12;

        // Retrieve the friendly name
        string propertyValue;
        propertyValues.GetStringValue(ref property, out propertyValue);

        return propertyValue;
    }
}

Adjust the code of your application’s Main() method as follows and hit F5 to run it.

var collection = new PortableDeviceCollection();

collection.Refresh();

foreach(var device in collection)
{
    device.Connect();
    Console.WriteLine(device.FriendlyName);
    device.Disconnect();
}

Console.WriteLine();
Console.WriteLine("Press any key to continue.");
Console.ReadKey();

The output contains a friendlier name now.

Friendly Name

The digital camera is recognized as a USB mass storage device and has been attributed with a drive letter (G:\). In Windows Explorer you’ll notice an additional drive as well.

Windows Explorer

I hope you enjoyed this post on the Windows Portable Devices (WPD) API. In one of the following posts I’ll discuss how you can retrieve a list of the items (e.g. pictures, movies…) which are stored on the device. Until then, you can find the source code for this article on the download page of this blog.

Top of page

About these ads

8 Responses to “Enumerating Windows Portable Devices”

  1. Jaran Nilsen Says:

    Hi, thanks for the post!

    I’ve been working with the Portable Devices API for a while now, but I have a problem getting it to list all my devices.

    I have a removable USB memory stick and a Canon camera connected. Unfortunately, only my camera shows up when I am attempting the approach above – which is the same I attempted to use before coming across your blog post. But if I disconnect my camera and only leave the USB stick active, then the USB stick is the only device I get the ID for.

    Have you tried your approach with several portable devices connected?

    Best,
    Jaran

    • Christophe Says:

      Hi,

      Take a look at the following post by the WPD team:

      http://blogs.msdn.com/b/dimeby8/archive/2006/12/05/enumerating-wpd-devices-in-c.aspx

      To be complete I’ll include the solution here as well:

      This is due to a marshalling restriction. This sample code will only detect one device. You need to manually fix the interop assembly.

      1. Disassemble the PortableDeviceApi Interop assembly 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([]) pPnPDeviceIDs,

      4. Save the IL and reassemble the interop using the command:

      ilasm pdapi.il /dll /output=Interop.PortableDeviceApiLib.dll

      Rebuild your project. You can now first call GetDevices with a NULL parameter to get the count of devices and then call it again with an array to get the device IDs.

      Hope this helps.

      Regards,

      Christophe

      • Dom Says:

        When replacing the GetDevices function, if you don’t specify the marshalled type (for the array argument), you may have some “Excessively long string” exceptions.

        So, just use this replacement instead:

        instance void GetDevices([in][out] string[] marshal(lpwstr[]) pPnPDeviceIDs,

        Dom

  2. Bruno Silveira Says:

    Christophe,

    Congratulations! Awesome article. Thanks to your article I’ am finally getting into a Samsung Galaxy mobile phone with my app. Question: Property retrieval through WPD seems a little cryptic. Where can I find more information about WPD classes, properties GUID’s and other stuff? I’ve tried MSDN library, but I could’nt find much info.

    Thanks for the excellent writing!

    Regards,
    Bruno

    • Christophe Says:

      Hi,

      It is indeed difficult to find good information on the WPD API. Maybe the Portable Device Lib on CodePlex might be of some use to you.

      http://portabledevicelib.codeplex.com/

      It provides a set of classes for accessing the WPD API in .NET. Just browse the source code. You’ll find an entire list of GUIDs for a lot of properties.

      Regards,

      Christophe

  3. Craig Freeman Says:

    Christophe,

    This has been great so far. I have not had to do any work on Interop assemblies before so this caught me out for a while. The first thing was finding what paths to include in the PATH Environment Variable. The location for the ildasm applicationon my PC was:

    C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin;

    The location for the ilasm application was:

    C:\Windows\Microsoft.NET\Framework64\v2.0.50727

    After adding these paths, the disaambly and assembly seemed to work fine. However I still only appear to be receiving one device, even though I have connected my iPhone and inserted an SD card and other portable hard drives?

  4. Bart Mertens Says:

    Would ik be possible to enumerate WPD devices in VBScript and how?

  5. Mitko Says:

    I have wanted to make VBScript that finds all the portable devices and downloads all the images from them, but all i can find are c# examples. Is this possible in VBScript?


Comments are closed.

%d bloggers like this: