Enumerating Windows Portable Devices in C#
Asked Answered
E

6

14

I am attempting to enumerate connected portable devices on Windows using the Windows Portable Devices API and the PortableDeviceManager provided by this API.

I have implemented enumeration of device IDs following the MSDN documentation link and various blogs link, but they all result in the same issue - I can only get it to give me the ID of one device when there are several connected.

Here's the snippet of C# code I am using:

PortableDeviceManagerClass deviceManager = new PortableDeviceManagerClass();
deviceManager.RefreshDeviceList();  

uint numberOfDevices = 1;            
deviceManager.GetDevices(null, ref numberOfDevices);

if (numberOfDevices == 0)
{
    return new string[0];
}

string [] deviceIds = new string[numberOfDevices];
deviceManager.GetDevices(ref deviceIds[0], ref numberOfDevices);

return deviceIds;

I have two devices connected to my computer, one Removable USB memory stick and one digital camera. When both are active, only the device ID of my camera will be returned. When I deactivate the camera, the device ID of the removable USB stick is returned.

Is there anyone with experience with this API which can point me in the direction of what I am doing wrong?

Edacity answered 28/5, 2011 at 13:54 Comment(1)
For me, I try use PortableDeviceApiLib (COM) but not compiles neither I have found any full source sample working. I try too WMI with Win32ext_WPD but it was difficult for me.Burlie
R
19

Jaran,

Take a look at the following post by the WPD team, it mentions how you can fix the interop assembly.

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

Just to be complete, I'll mention the answer 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.

  • Disassemble the PortableDeviceApi Interop assembly using the command:

    ildasm Interop.PortableDeviceApiLib.dll /out:pdapi.il

  • Open the IL in Notepad and search for the following string:

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

  • Replace all instances of the string above with the following string:

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

  • 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.

Recidivate answered 29/5, 2011 at 7:34 Comment(9)
Thank you Christophe! I'm not sure how I missed that paragraph - I've been through the blog post before! I have gotten it to report the correct number of devices now, however I am having problems with the application hanging on the second call to GetDevices().Edacity
To solve the issue with call to GetDevices() hanging, I followed the instructions here: codeproject.com/Messages/3187340/… and replaced with "instance void GetDevices([in][out] string[] marshal(lpwstr[]) pPnPDeviceIDs," instead of "instance void GetDevices([in][out] string[] marshal([]) pPnPDeviceIDs,".Edacity
How do you prevent the Interop from rebuilding into the old one?Paw
@Edacity 's comment helped me solve my "Marshaler restriction: Excessively long string." error. I've posted the IL I ended up here: gist.github.com/4327544Reconstructionism
What's about WMI Win32ext_WPD? Not well document googleing. Only reference: squadratechnologies.wordpress.com/2013/07/24/…Thriller
This might be helpful for further understanding: msdn.microsoft.com/en-us/library/ek1fb3c6(v=vs.100).aspx It describes common changes in signatures when marshalling. Also: social.msdn.microsoft.com/Forums/vstudio/en-US/…Sanctuary
Have a look at Bruno Klein's answere. For the replacement i had to replace the type of the second parameter to an array of lpwstr ( lpwstr[]) not just an array ([]). Otherwise my program crashed without any exception on the second call of GetDevices().Kalindi
I don't know where I have Exec this command : ildasm Interop.PortableDeviceApiLib.dll /out:pdapi.ilBufford
@Bufford Use the Developer Command Prompt. It's a special command prompt with several VS tools included in the path. Easiest access is from VS, Tools -> Command Line -> Developer Command Prompt.Karlmarxstadt
T
4

Just an update on the accepted answer.

The correct replacement is as follows.

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

to

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

As per Andrew Trevarrow.

Tush answered 12/11, 2015 at 1:31 Comment(1)
Just because @bash.d's comment on the accepted answer gets buried for me, make sure to have a look at Marshalling Changes for other similar replacements.Karlmarxstadt
T
1

The one line of Power-shell script below un-mounts a USB cable attached Windows Portable Device (WPD) from the Windows Operating System (XP thru W8/2012)

And just in case you have not yet started playing with Powershell, here is the equivalent VBScript (maybe be can port to C#):

Set objWMIService = GetObject ("winmgmts:\\.\root\cimv2")
Set colItems = objWMIService.ExecQuery ("Select * from Win32ext_WPD Where strFriendlyName = 'SAMSUNG-SGH-I747'")
For Each objItem in colItems
Set objWMIWPDStatic = objWMIService.Get("Win32ext_WPD")
Set objInParam = objWMIWPDStatic.Methods_("EjectDevice").inParameters.SpawnInstance_()
objInParam.Properties_.Item("strObjectDeviceId") =  objItem.strId
Set objOutParams = objWMIService.ExecMethod("Win32ext_WPD", "EjectDevice", objInParam)
Exit For
Next

Note change ‘SAMSUNG-SGH-I747′ to the phone/tablet name you see in Windows Explorer

About Win32ext_WPD

"Select * from Win32ext_WPD Where strFriendlyName = 'SAMSUNG-SGH-I747'"

Not well document googleing, not found more 2 references.

Maybe port to C# using:

var oScope = new ManagementScope(@"\\" + MachineName + @"\root\cimv2");

Reference:

http://squadratechnologies.wordpress.com/2013/07/24/windows-powershellvbscript-to-un-mount-a-smart-phone-or-tablet/

Thriller answered 26/8, 2014 at 8:46 Comment(0)
N
0

WPD-.NET-Wrapper on github - Can enumerate devices and copy files successfully. so that you don't have to waste time coding. you can just open, connect and copy...

https://github.com/kumushoq/WPD-.NET-Wrapper/blob/2d502d883b981b8bc57d32389e8280877632f6de/WindowsPortableDeviceNet/Utility.cs

Nadaha answered 3/7, 2015 at 9:39 Comment(1)
For me, not compiles, PortableDeviceApiLib; is a COM ? what I need install in my Windows 7 x64 ?Burlie
O
0

I am late to the party, but this is what worked for me:

  1. Download this NuGet package: PortableDevices

  2. Add references to these 4 COM libraries:

    • PortableDeviceClassExtension
    • PortableDeviceConnectApi
    • PortableDeviceTypes
    • PortableDeviceApi
  3. Take the dll's under obj\Debug and put them into bin\Debug:

    • Interop.PortableDeviceClassExtension.dll
    • Interop.PortableDeviceConnectApiLib.dll
    • Interop.PortableDeviceTypesLib.dll
    • Interop.PortableDeviceApiLib.dll

Now you can use the following function to list all devices, although FriendlyName does not seem to be working (it returns an empty string):

    private IDictionary<string, string> GetDeviceIds()
    {
        var deviceIds = new Dictionary<string, string>();
        var devices = new PortableDeviceCollection();
        devices.Refresh();
        foreach (var device in devices)
        {
            device.Connect();
            deviceIds.Add(device.FriendlyName, device.DeviceId);
            Console.WriteLine(@"DeviceId: {0}, FriendlyName: {1}", device.DeviceId, device.FriendlyName);
            device.Disconnect();
        }
        return deviceIds;
    }

The next step is getting the contents from the device, which is done like so:

var contents = device.GetContents();
Odilo answered 2/10, 2016 at 15:11 Comment(0)
R
-1

Maybe I'm wrong, but the line deviceManager.GetDevices(ref deviceIds[0], ref numberOfDevices); does not makes sense to me. If you want fill the array of string, you should pass whole array, not only one string.

I found a post on blgs.msdn.com where is passed the whole array.

string[] deviceIDs = new string[cDevices];
devMgr.GetDevices(deviceIDs, ref cDevices);

However it's only guess. I hope, it will help you.

EDIT: I found several code samples, where is passed whole array instead of first item: For example: http://social.msdn.microsoft.com/forums/en-US/vbgeneral/thread/22288487-caf3-4da5-855f-6447ad9fa48d

And I found the Importing PortableDeviceApiLib generates incorrect Interop in VB.NET issue - it looks like the default interop assembly is not correct. Maybe the successfull way is getting correct interop assembly from someone and use it. Or use some another bridge between managed code (C#/VB.NET/...) and native code. The C++\CLI could be good way if you are good enough in C++.

I found Portable Device Lib project on CodePlex - maybe it is the correct bridge.

Ramentum answered 28/5, 2011 at 14:4 Comment(4)
Normally I would agree with you, but the API does not offer a method like that. It requires you to pass a string pointer. My understanding was that you pass a pointer to the head of the array and the GetDevices method will populate the array with cDevices number of device IDs, starting from that address.Edacity
@Jaran: The problem is, the "string" is reference type in .NET, so if you pass reference to first string from array, there is no relation to the second string in array. Image - one string (one reference) can be in 0-N arrays of string. So, which string (reference) should be the next from the "first"?Ramentum
Why do they pass pointers? They just could return the result. I'm just curious.Outburst
In general, the string value can be large. So passing only the reference is much more cheaper. In this case, you must ask the authors of device manager API.Ramentum

© 2022 - 2024 — McMap. All rights reserved.