Enumerating WPD devices in C#

Let's take a look at how we can enumerate WPD devices in C#. This post assumes that you already have a project set up using these instructions. (Update: You will also need to follow the disassembly/reassembly instructions below for this to work correctly.)

 static void Main(string[] args)
{
    //
    // Get an instance of the device manager
    //
    PortableDeviceApiLib.PortableDeviceManagerClass devMgr 
        = new PortableDeviceApiLib.PortableDeviceManagerClass();

    //
    // Probe for number of devices
    //
    uint cDevices = 1;
    devMgr.GetDevices(null, ref cDevices);

    //
    // Re-allocate if needed
    //
    if (cDevices > 0)
    {
        string[] deviceIDs = new string[cDevices];
        devMgr.GetDevices(deviceIDs, ref cDevices);

        for (int ndxDevices = 0; ndxDevices < cDevices; ndxDevices++)
        {
            Console.WriteLine("Device[{0}]: {1}", 
                    ndxDevices + 1, deviceIDs[ndxDevices]);
        }
    }
    else
    {
        Console.WriteLine("No WPD devices are present!");
    }
}

This code maps almost line-by-line to the C++ enumeration example. We manufacture an instance of the device manager using PortableDeviceApiLib.PortableDeviceManagerClass. We then use the instance to probe how many devices are there. If you remember, the GetDevices API will return back in cDevices the number of devices (actually the size of the string array that should be specified to retrieve all the device IDs).

Once we know the number of devices, we allocate an appropriately sized array and then call GetDevices again. This time, the array will contain all the device IDs. To display them, we simply iterate over the array and print them out.

Gotchas:

  • We could use the string[]   notation since the GetDevices API returned an array of CoTaskMemAlloc'd LPWSTR strings
  • We can't use the string datatype for API calls that require a buffer allocated by the caller. GetDeviceFriendlyName is an example of this - such API calls require prior allocation of ushort[] and then on success, character-by-character conversion to a C# string.

Update

Mike R. brought to my notice that the above sample only enumerates one device even if more than one are connected. This is a marshalling restriction - we can work around it by manually fixing up the generated Interop assembly. Follow the steps below to edit the assembly:

  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([]) pPnPDeviceIDs,
  4. Save the IL and reassemble the interop using the command -
    ilasm pdapi.il /dll /output=Interop.PortableDeviceApiLib.dll

You can now 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.

Comments

  • Anonymous
    January 29, 2007
    How can I get FriendlyName for my portable device in C#? Counld you mind to tell me the steps?

  • Anonymous
    February 14, 2007
    I tested the interop mod mentioned in the blog update, and while it works fine with .NET 2.0, a runtime MissingMethodException is thrown when attempting to run code which calls GetDevices using .NET 1.1. I guess this is just a limitation in 1.1 when defining method signatures.

  • Anonymous
    February 25, 2007
    The comment has been removed

  • Anonymous
    April 30, 2007
    Hello ! I have just started creating a c#-library for accessing a portable device. Your articles have helped me a lot, but now I am struggeling with the RemoteRead and RemoteWrite functions. I have tried fixing the interop, but it looks like the data returned is corrupted. I am trying to read the DevIcon.fil on my device. The RemoteRead function returns a buffer with the correct size, but there is a problem using the buffer (write to file or Bitmap.FromStream( MemoryStream )). Have you done anything more on your WPD project ?

  • Anonymous
    April 30, 2007
    Hi there jdawes I ran into the problem you mention a while back. And the data you are getting is garbage. I think it's because the RemoteRead/Write are driver level calls, something like that anyway. I've found you can do bad things to some devices using those remote calls. The solution is quite simple: cast the PortableDeviceLibApi.IStream object to a generalised System.Runtime.InteropServices.ComTypes.IStream. Then use the Read and Write methods on this object. It works just fine as a ComTypes.IStream object with everything I tried.

  • Anonymous
    May 01, 2007
    Hello Tree ! I have tried with ...ComTypes.IStream, but I get the same result. Have you done any changes to the IPortableDeviceResources.GetStream in the interop ? Could you please give me an example of how you do this ?

  • Anonymous
    May 02, 2007
    Here's a bit of code which I use to read an xml doc. Some of it is specific to our code, but you should get the idea. I have to admit it's not perfect. The problem exists because the pReadBytes from the read call always returns 0, because the interop can't do anything else, so you can't tell if you've finished reading without a slow check for a � the buffer. I tried fixing the interop a number of ways, but I'm no expert and had no success. My mods to the interop are fairly minimal, and not related to streams, although as I mentioned earlier I tweaks the remoteRead and remotewrite, but I don't use them anymore. Here's the code I use which works: XmlDocument GetXMLDocument(string fileId) { //need a contents object to get the propeties IPortableDeviceContent content; PortableDevice.Content(out content); //get transfer interface to this device IPortableDeviceResources resources; content.Transfer(out resources); IStream wpdStream; uint optimalTransferSize = 0; resources.GetStream(fileId, ref WPDPropertyKey.WPD_RESOURCE_DEFAULT, 0, ref optimalTransferSize, out wpdStream); //convert to a useful stream object System.Runtime.InteropServices.ComTypes.IStream sourceStream = (System.Runtime.InteropServices.ComTypes.IStream)wpdStream; XmlDocument document = new XmlDocument(); ////////Stream bit using (MemoryStream mStream = new MemoryStream(1000)) { int totalBytesRead = 0; byte[] streamBuffer = new byte[optimalTransferSize]; IntPtr pBytesRead = new IntPtr(); int writeBytes = 0; do { sourceStream.Read(streamBuffer, (int)optimalTransferSize, pBytesRead); if (pBytesRead.ToInt32() == 0) writeBytes = Array.FindIndex(streamBuffer, CheckEndBuffer); else writeBytes = pBytesRead.ToInt32(); totalBytesRead += writeBytes; mStream.Write(streamBuffer, 0, writeBytes); } while ((writeBytes > 0) && (optimalTransferSize == writeBytes)); mStream.Flush(); mStream.Seek(0, SeekOrigin.Begin); //load and interpret if (sourceStream != null) document.Load(mStream); } return document; }

  • Anonymous
    September 06, 2007
    Tree, I'm pretty new at .NET and C# and would really appriciate your help. Regarding your comment:

re: Enumerating WPD devices in C#

Thursday, February 15, 2007 5:53 AM by Tree I tested the interop mod mentioned in the blog update, and while it works fine with .NET 2.0, a runtime MissingMethodException is thrown when attempting to run code which calls GetDevices using .NET 1.1. I guess this is just a limitation in 1.1 when defining method signatures. I am getting the MissingMethodException when calling GetDevices. I modified the il as specified by dimeby8 and thus it compiles. I have .NET 2.0 as well as .NET 1.1 installed on my machine. How do I specify .NET2.0? Thanks a bunch, Chris

  • Anonymous
    September 06, 2007
    The comment has been removed

  • Anonymous
    October 03, 2007
    Tree: Would you be able to send me your WPDPropertyKey class?  I seem to be having a hard time find the required header files and creating this class.  If you can, please send it to jtackabury (AT) binaryfortress.com. Thanks!

  • Anonymous
    October 03, 2007
    jtackabury: Just digging out the contants and helper classes I use. I'll mail them to you shortly.

  • Anonymous
    August 20, 2008
    Tree, I figured out how to get at the returned pReadBytes for the sourceStream.Read function in case you are interested. Chris GCHandle hBytesRead = GCHandle.Alloc(new ulong(), GCHandleType.Pinned); do { sourceStream.Read(streamBuffer, (int)optimalBufferSize, hBytesRead.AddrOfPinnedObject()); totalBytesRead += (ulong)hBytesRead.Target; } while ((ulong)hBytesRead.Target > 0); hBytesRead.Free();

  • Anonymous
    August 20, 2008
    Thanks Chris, I'm glad there is a cleaner solution! I'll pass that on to the guys who maintain my device code now. Tree

  • Anonymous
    January 03, 2009
    Hi, I try to use the "GetStream" method but I got an exception "E_ACCESSDENIED". It is on a "Zune" ! Here is the code : uint optimalTransferSize = 0; PortableDeviceApiLib.IStream wpdStream; deviceResources.GetStream((string)music.Tag, ref PropertyKeys.WPD_RESOURCE_DEFAULT,0, ref optimalTransferSize, out wpdStream); Can you help me, I have no idea to solve this problem ? Thanks

  • Anonymous
    December 14, 2012
    I just wanted to use that disassemble -> change -> assemble trick and it worked fine but if you want to marshal long strings it will not work. The compiler throws a DirectiveException (Marshaler restriction: Excessively long string) So i changed the lines:  instance void  GetDevices([in][out] string[]  marshal([]) pPnPDeviceIDs,                                    [in][out] uint32& pcPnPDeviceIDs) runtime managed internalcall to  instance void  GetDevices([in][out] string[]  marshal( lpwstr[]) pPnPDeviceIDs,                                    [in][out] uint32& pcPnPDeviceIDs) runtime managed internalcall the changed c# code is: void GetDevices([In][Out][MarshalAsAttribute(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr)] string[] pPnPDeviceIDs, [In, Out] ref uint pcPnPDeviceIDs); Greetings from a fellow explorer ^^

  • Anonymous
    July 09, 2013
    Can I get portable device(WPD) and MTP device memory detail in this?

  • Anonymous
    November 13, 2013
    Thanks, Mathias Muenzner. I suspected something like this. You solved the puzzle!

  • Anonymous
    February 12, 2014
    I tried to get device list by your instructions, but when I first call manager.GetDevices(null, ref count); "count" is always zero. Does any one get this problem before? Environment: OS: Windows 7 32-bit version Compiler: Visual Studio 2012

  • Anonymous
    October 12, 2014
    Any final solution with full source code sample application ? IMHO, better samples for minimize learning curve are real applications with full source code and good patterns

  • Anonymous
    April 03, 2015
    Thanks a lot to Darene Lewis. dimeby8 is the best source of info about the WPD Subject. To the friend having an issue with the Framework 1.1 I solved just with the use of a string with a null content by reference. The source code: (tested on Framework 1.1 and 2.0) //    // Probe for number of devices    //    uint cDevices = 1;    string PNPNames = null;    devMgr.GetDevices(ref PNPNames, ref cDevices); Is not common to have to work with a such older framework but, sometimes things happens. hehe I hope somebody finds it useful. Best Regards José Manuel "It's my time to give something back"

  • Anonymous
    April 03, 2015
    Thanks a lot to Darene Lewis. dimeby8 is the best source of info about the WPD Subject. To the friend having an issue with the Framework 1.1 I solved just with the use of a string with a null content by reference. The source code: (tested on Framework 1.1 and 2.0) //    // Probe for number of devices    //    uint cDevices = 1;    string PNPNames = null;    devMgr.GetDevices(ref PNPNames, ref cDevices); Is not common to have to work with a such older framework but, sometimes things happens. hehe I hope somebody finds it useful. Best Regards José Manuel "It's my time to give something back"