29

My project requires detection of a specific device when it is connected to USB. The only way I can identify this device is by its description/device name, not the com port. What I have found to perform the correct function is using a WMI query and checking the name property:

ManagementObjectSearcher searcher = new ManagementObjectSearcher("Select * from WIN32_SerialPort");
            foreach (ManagementObject port in searcher.Get())
            {
                deviceName = (string)foundPort.GetPropertyValue("Name"); 
                ...

I initially tested this by connecting my phone, and the query returned the phone found on COM3 as expected. Then, I connected another device (a USB to serial converter, which more closely resembles the device I need this project for) and the query simply did not find it. It only finds the phone. This device does, however, show up on port COM4 in Device Manager. To spite me even more, the SerialPort class finds both devices, but it does not provide the information I need to identify the device:

    string[] tempPorts = SerialPort.GetPortNames();

I have read numerous threads on SO and elsewhere and cannot find a satisfactory solution. Could someone please clarify why the WIN32_SerialPort query does not find my other device? Is it not considered a win32 serial port for some reason? And, could someone please point me in the direction of a solution to this problem?

dsolimano
  • 8,870
  • 3
  • 48
  • 63
sebo
  • 1,584
  • 4
  • 16
  • 19
  • 1
    A comment in this thread http://stackoverflow.com/questions/2548631/what-is-the-best-way-to-scan-for-com-ports-in-c says that the WMI query does not include USB-to-serial adapters.. would anyone care to elaborate? – sebo Jul 12 '12 at 19:38
  • 1
    have you tried seeing if it detects if you plug a serial device into the converter? – Jay Jul 12 '12 at 21:21
  • I haven't, but my goal is to use a device that is detected as a USB-to-serial converter to simply read one analog pin. – sebo Jul 12 '12 at 21:47
  • 2
    I found a way around this issue by using a query for Win32_PnPEntity as described here http://thunderfist-podium.blogspot.com/2009/10/com-port-listing-in-c.html – sebo Jul 12 '12 at 23:37
  • @sebo The described query is not very efficient since all devices are queried and then a string comparison is used. You only have to with a specific guid like I described in my answer. – AlexS Jun 05 '14 at 15:27

4 Answers4

28

How to list all serial ports:

There are several System-Defined Device Setup Classes available to hardware vendors. Properly written drivers for COM-Ports should use the Ports (COM & LPT ports)-class (guid: 4d36e978-e325-11ce-bfc1-08002be10318). Probably this class is used by the device manager as well.

So you can use the following query to list every serial port you also see in the devicemanager:

ManagementObjectSearcher searcher = new ManagementObjectSearcher(
    "root\\CIMV2",
    "SELECT * FROM Win32_PnPEntity WHERE ClassGuid=\"{4d36e978-e325-11ce-bfc1-08002be10318}\""
);
foreach (ManagementObject queryObj in searcher.Get())
{
    // do what you like with the Win32_PnpEntity
}

See this detailed description of the Win32_PnPEntity-class. You should have everything you need for identifying your device.

For determining the port number I examine the name property and extract it. Until now this works fine, but I don't know if the port number is garanteed to be included in the name. I haven't found any serial port device until now, that doesn't have the port number included in the name.

The above query finds every serial port device, no matter if it is a bluetooth SPP, a FTDI-chip, a port on the mainboard, an extension card or a virtual serial port generated by some modem driver (i.e. Globetrotter GTM66xxW).

To determine the type of connection (bluetooth, usb, etc.) you can examine the deviceid (have a look at the first part of the deviceid). There you can also extract the bt-mac address (be careful with that: the deviceid looks different at least on Windows 7 and Windows XP).

Regarding why some devices are not listed with Win32_SerialPort:

I suspect it depends on the driver implementation, since I have some usb-devices that get their ports listed and some that don't.

AlexS
  • 5,295
  • 3
  • 38
  • 54
  • 2
    To get **all** devices listed use this query instead: `"SELECT * FROM Win32_PnPEntity WHERE Name LIKE '%(COM[0-9]%'"` – Pithikos Apr 02 '15 at 10:07
  • 1
    @Pithikos I already mentioned that in my answer, although I didn't provide any code for that. Since I hadn't found any official statement, that the poort number will always be included in the name, I decided against filtering with `LIKE`. – AlexS Apr 02 '15 at 13:07
  • That's true. I guess the best solution would be a combination of the two. – Pithikos Apr 02 '15 at 13:40
  • @Pithikos Probably the `LIKE`-condition will be slower than filtering the `ClassGuid` and the `LIKE`-condition is not neccessary. – AlexS Jun 23 '15 at 07:09
  • If you had 4 identical scanners attached how could you tell which was which (since Windows often reassigns these virtual COM ports for no reason) if they were used at 4 operations? – topshot Oct 11 '16 at 15:39
  • @topshot You could try to distinguish the devices by `DeviceId`, `HardwareId` or `PnPDeviceId`. Just have a look at `Win32_PnpEntity` linked in my answer. – AlexS Oct 11 '16 at 21:01
  • On the issue of speed, I did 3 separate stopwatch tests on 100,000 iterations. The "Name LIKE '%(COM[0-9]%'" method was actually marginally quicker on all 3 tests than the 'WHERE ClassGuid" method. – Guru Josh Nov 04 '16 at 22:36
  • @GuruJosh That's interesting. However I mentioned the guid-approach, since it will list all COM-devices like the device manager does. Devices not listet as COM-Ports in the device manager should also not be returned by this query. – AlexS Nov 08 '16 at 14:25
  • This approach worked much better for me than the WIN32_SerialPort approach, which was VERY slow and listed devices which don't come up on Device Manager. This approach is faster and better. – Lauraducky Feb 05 '18 at 02:05
  • If you need the com port name like `COM4` for opening the port in your program you can extract the com port name via this link http://thunderfist-podium.blogspot.com/2009/10/com-port-listing-in-c.html. comPortName=item["Name"].ToString().Substring(start, end - start); – Arani May 18 '19 at 06:10
6

I think i see what you are trying to do, look at this code made using WMICodeCreator ( link to WMICodeCreator http://www.microsoft.com/en-us/download/details.aspx?id=8572 ) from this article http://www.codeproject.com/Articles/32330/A-Useful-WMI-Tool-How-To-Find-USB-to-Serial-Adapto

//Below is code pasted from WMICodeCreator
try
{
    ManagementObjectSearcher searcher =
        new ManagementObjectSearcher("root\\WMI",
        "SELECT * FROM MSSerial_PortName");

    foreach (ManagementObject queryObj in searcher.Get())
    {
        Console.WriteLine("-----------------------------------");
        Console.WriteLine("MSSerial_PortName instance");
        Console.WriteLine("-----------------------------------");
        Console.WriteLine("InstanceName: {0}", queryObj["InstanceName"]);

        Console.WriteLine("-----------------------------------");
        Console.WriteLine("MSSerial_PortName instance");
        Console.WriteLine("-----------------------------------");
        Console.WriteLine("PortName: {0}", queryObj["PortName"]);

        //If the serial port's instance name contains USB 
        //it must be a USB to serial device
        if (queryObj["InstanceName"].ToString().Contains("USB"))
        {
            Console.WriteLine(queryObj["PortName"] + " 
            is a USB to SERIAL adapter/converter");
        }
    }
}
catch (ManagementException e)
{
    MessageBox.Show("An error occurred while querying for WMI data: " + e.Message);
} 
Jay
  • 303
  • 1
  • 3
  • 3
    I had come across this post earlier but the call to Get() with this query failed with some odd errors. I think I got something like "Management not supported" and then after tweaking it I got "Permission Denied". As I said in the comment above, I already found my solution and I did it by using a query to Win32_PnPEntity, and then narrowing down my search by looking for entities whose name property contained "USB Serial Port (COM". – sebo Jul 13 '12 at 00:49
3

Alex's answer really helped me out. But I had to adjust my query based on my machine...

I had to query "root\CIMV2", but I had to select all rows from a different table: "SELECT * FROM Win32_SerialPort".

For anyone struggling to figure out how to use WMI or the Management Object Searcher, the WMI Code Creator by Microsoft really just saved me. Using the app, you can query WMI for different information in order to figure out what to put into your Management Object Searcher query.

Link might expire in the future but here it is in 2020:

https://www.microsoft.com/en-us/download/details.aspx?id=8572

chapter12
  • 61
  • 7
1

Seeing that you want to search by "Name", I think you will need to iterate through all connected devices and query them to get the product name.

Here is code to iterate through WinUSB devices:

https://github.com/MelbourneDeveloper/Device.Net/blob/master/src/Device.Net/Windows/WindowsDeviceFactoryBase.cs

  public async Task<IEnumerable<DeviceDefinition>> GetConnectedDeviceDefinitions(uint? vendorId, uint? productId)
    {
        return await Task.Run<IEnumerable<DeviceDefinition>>(() =>
        {
            var deviceDefinitions = new Collection<DeviceDefinition>();
            var spDeviceInterfaceData = new SpDeviceInterfaceData();
            var spDeviceInfoData = new SpDeviceInfoData();
            var spDeviceInterfaceDetailData = new SpDeviceInterfaceDetailData();
            spDeviceInterfaceData.CbSize = (uint)Marshal.SizeOf(spDeviceInterfaceData);
            spDeviceInfoData.CbSize = (uint)Marshal.SizeOf(spDeviceInfoData);

            var guidString = ClassGuid.ToString();
            var copyOfClassGuid = new Guid(guidString);

            var i = APICalls.SetupDiGetClassDevs(ref copyOfClassGuid, IntPtr.Zero, IntPtr.Zero, APICalls.DigcfDeviceinterface | APICalls.DigcfPresent);

            if (IntPtr.Size == 8)
            {
                spDeviceInterfaceDetailData.CbSize = 8;
            }
            else
            {
                spDeviceInterfaceDetailData.CbSize = 4 + Marshal.SystemDefaultCharSize;
            }

            var x = -1;

            while (true)
            {
                x++;

                var isSuccess = APICalls.SetupDiEnumDeviceInterfaces(i, IntPtr.Zero, ref copyOfClassGuid, (uint)x, ref spDeviceInterfaceData);
                if (!isSuccess)
                {
                    var errorCode = Marshal.GetLastWin32Error();
                    if (errorCode == APICalls.ERROR_NO_MORE_ITEMS)
                    {
                        break;
                    }

                    throw new Exception($"Could not enumerate devices. Error code: {errorCode}");
                }

                isSuccess = APICalls.SetupDiGetDeviceInterfaceDetail(i, ref spDeviceInterfaceData, ref spDeviceInterfaceDetailData, 256, out _, ref spDeviceInfoData);
                WindowsDeviceBase.HandleError(isSuccess, "Could not get device interface detail");

                //Note this is a bit nasty but we can filter Vid and Pid this way I think...
                var vendorHex = vendorId?.ToString("X").ToLower().PadLeft(4, '0');
                var productIdHex = productId?.ToString("X").ToLower().PadLeft(4, '0');
                if (vendorId.HasValue && !spDeviceInterfaceDetailData.DevicePath.ToLower().Contains(vendorHex)) continue;
                if (productId.HasValue && !spDeviceInterfaceDetailData.DevicePath.ToLower().Contains(productIdHex)) continue;

                var deviceDefinition = GetDeviceDefinition(spDeviceInterfaceDetailData.DevicePath);

                deviceDefinitions.Add(deviceDefinition);
            }

            APICalls.SetupDiDestroyDeviceInfoList(i);

            return deviceDefinitions;
        });
    }

For each of these devices you can query the device like this:

https://github.com/MelbourneDeveloper/Device.Net/blob/master/src/Usb.Net/Windows/WindowsUsbDevice.cs

        var isSuccess = WinUsbApiCalls.WinUsb_Initialize(_DeviceHandle, out var defaultInterfaceHandle);
        HandleError(isSuccess, "Couldn't initialize device");

        var bufferLength = (uint)Marshal.SizeOf(typeof(USB_DEVICE_DESCRIPTOR));
        isSuccess = WinUsbApiCalls.WinUsb_GetDescriptor(defaultInterfaceHandle, WinUsbApiCalls.DEFAULT_DESCRIPTOR_TYPE, 0, EnglishLanguageID, out _UsbDeviceDescriptor, bufferLength, out var lengthTransferred);
        HandleError(isSuccess, "Couldn't get device descriptor");

        if (_UsbDeviceDescriptor.iProduct > 0)
        {
            //Get the product name
            var buffer = new byte[256];
            isSuccess = WinUsbApiCalls.WinUsb_GetDescriptor(defaultInterfaceHandle, WinUsbApiCalls.USB_STRING_DESCRIPTOR_TYPE, _UsbDeviceDescriptor.iProduct, 1033, buffer, (uint)buffer.Length, out var transfered);
            HandleError(isSuccess, "Couldn't get product name");

            Product = new string(Encoding.Unicode.GetChars(buffer, 2, (int)transfered));
            Product = Product.Substring(0, Product.Length - 1);
        }


public static partial class WinUsbApiCalls
{
    public const uint DEVICE_SPEED = 1;
    public const byte USB_ENDPOINT_DIRECTION_MASK = 0X80;
    public const int WritePipeId = 0x80;

    /// <summary>
    /// Not sure where this constant is defined...
    /// </summary>
    public const int DEFAULT_DESCRIPTOR_TYPE = 0x01;
    public const int USB_STRING_DESCRIPTOR_TYPE = 0x03;

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_ControlTransfer(IntPtr InterfaceHandle, WINUSB_SETUP_PACKET SetupPacket, byte[] Buffer, uint BufferLength, ref uint LengthTransferred, IntPtr Overlapped);

    [DllImport("winusb.dll", SetLastError = true, CharSet = CharSet.Auto)]
    public static extern bool WinUsb_GetAssociatedInterface(SafeFileHandle InterfaceHandle, byte AssociatedInterfaceIndex, out SafeFileHandle AssociatedInterfaceHandle);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_GetDescriptor(SafeFileHandle InterfaceHandle, byte DescriptorType, byte Index, ushort LanguageID, out USB_DEVICE_DESCRIPTOR deviceDesc, uint BufferLength, out uint LengthTransfered);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_GetDescriptor(SafeFileHandle InterfaceHandle, byte DescriptorType, byte Index, UInt16 LanguageID, byte[] Buffer, UInt32 BufferLength, out UInt32 LengthTransfered);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_Free(SafeFileHandle InterfaceHandle);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_Initialize(SafeFileHandle DeviceHandle, out SafeFileHandle InterfaceHandle);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_QueryDeviceInformation(IntPtr InterfaceHandle, uint InformationType, ref uint BufferLength, ref byte Buffer);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_QueryInterfaceSettings(SafeFileHandle InterfaceHandle, byte AlternateInterfaceNumber, out USB_INTERFACE_DESCRIPTOR UsbAltInterfaceDescriptor);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_QueryPipe(SafeFileHandle InterfaceHandle, byte AlternateInterfaceNumber, byte PipeIndex, out WINUSB_PIPE_INFORMATION PipeInformation);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_ReadPipe(SafeFileHandle InterfaceHandle, byte PipeID, byte[] Buffer, uint BufferLength, out uint LengthTransferred, IntPtr Overlapped);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_SetPipePolicy(IntPtr InterfaceHandle, byte PipeID, uint PolicyType, uint ValueLength, ref uint Value);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_WritePipe(SafeFileHandle InterfaceHandle, byte PipeID, byte[] Buffer, uint BufferLength, out uint LengthTransferred, IntPtr Overlapped);
}
Christian Findlay
  • 6,770
  • 5
  • 51
  • 103