Identify COM port using VID and PID for USB device attached to x64

19,784

Solution 1

By reading your code, I found out that the current path you're looking at in registry does not contain any information about ports. But I found a way to read it by doing this little change:

    static List<string> ComPortNames(String VID, String PID)
    {
        String pattern = String.Format("^VID_{0}.PID_{1}", VID, PID);
        Regex _rx = new Regex(pattern, RegexOptions.IgnoreCase);
        List<string> comports = new List<string>();

        RegistryKey rk1 = Registry.LocalMachine;
        RegistryKey rk2 = rk1.OpenSubKey("SYSTEM\\CurrentControlSet\\Enum");

        foreach (String s3 in rk2.GetSubKeyNames())
        {

            RegistryKey rk3 = rk2.OpenSubKey(s3);
            foreach (String s in rk3.GetSubKeyNames())
            {
                if (_rx.Match(s).Success)
                {
                    RegistryKey rk4 = rk3.OpenSubKey(s);
                    foreach (String s2 in rk4.GetSubKeyNames())
                    {
                        RegistryKey rk5 = rk4.OpenSubKey(s2);
                        string location = (string)rk5.GetValue("LocationInformation");
                        if (!String.IsNullOrEmpty(location))
                        {
                            string port = location.Substring(location.IndexOf('#') + 1, 4).TrimStart('0');
                            if (!String.IsNullOrEmpty(port)) comports.Add(String.Format("COM{0:####}", port));
                        }
                        //RegistryKey rk6 = rk5.OpenSubKey("Device Parameters");
                        //comports.Add((string)rk6.GetValue("PortName"));
                    }
                }
            }
        }
        return comports;
    }

It did work perfectly. Thank you for your code, by the way... It helped me a lot!

Solution 2

While I was testing the answer from Youkko under Windows 10 x64 I was getting some strange results and looking at the registry on my machine the LocationInformation keys contained strings such as Port_#0002.Hub_#0003so they are related to the USB hub / port the device is connected to not the COM port allocated by Windows.

So in my case I was getting COM2 included which is a hardware port on my motherboard and it skipped the COM5 port I was expecting but that was located under the PortName registry key. I'm not sure if something has changed since the version of Windows you were using but I think your main problem might have been not checking for null values on the keys.

The following slightly modified version seems to work fine on a variety or Windows 7 / 10 and x32 / 64 systems and I've also added a to check of SerialPort.GetPortNames() to make sure the device is available and plugged into the system before returning it:

static List<string> ComPortNames(String VID, String PID)
{
    String pattern = String.Format("^VID_{0}.PID_{1}", VID, PID);
    Regex _rx = new Regex(pattern, RegexOptions.IgnoreCase);
    List<string> comports = new List<string>();

    RegistryKey rk1 = Registry.LocalMachine;
    RegistryKey rk2 = rk1.OpenSubKey("SYSTEM\\CurrentControlSet\\Enum");

    foreach (String s3 in rk2.GetSubKeyNames())
    {
        RegistryKey rk3 = rk2.OpenSubKey(s3);
        foreach (String s in rk3.GetSubKeyNames())
        {
            if (_rx.Match(s).Success)
            {
                RegistryKey rk4 = rk3.OpenSubKey(s);
                foreach (String s2 in rk4.GetSubKeyNames())
                {
                    RegistryKey rk5 = rk4.OpenSubKey(s2);
                    string location = (string)rk5.GetValue("LocationInformation");
                    RegistryKey rk6 = rk5.OpenSubKey("Device Parameters");
                    string portName = (string)rk6.GetValue("PortName");
                    if (!String.IsNullOrEmpty(portName) && SerialPort.GetPortNames().Contains(portName))
                        comports.Add((string)rk6.GetValue("PortName"));
                }
            }
        }
    }
    return comports;
}

Solution 3

Here is my take on this (even if it is not a direct A to the Q)

  • USB devices is always (and has always) enumerated under HKLM\SYSTEM\CurrentControlSet\Enum\USB (note USB at the end)
    • Device nodes have the format VID_xxxx&PID_xxxx* where xxxx is hexadecimal, there might be some extra function data at the end
    • Each device node has a sub identifier node based on serialnumber or other data of the device and functions
    • identifier node can have value "FriendlyName" which some times have the COM in parantheses such as "Virtual Serial Port (COM6)"
    • Resulting path: HKLM\SYSTEM\CurrentControlSet\Enum\USB\VID_xxxx&PID_xxxx*\*\Device Parameters\ has value named "PortName"
  • Currently available com ports is listed by System.IO.Ports.SerialPort.GetPortNames()
  • OpenSubKey implements IDisposable and should have using or .Dispose() on them

    using System.IO.Ports;
    using System.Linq;
    using Microsoft.Win32;
    
    public class UsbSerialPort
    {
        public readonly string PortName;
        public readonly string DeviceId;
        public readonly string FriendlyName;
    
        private UsbSerialPort(string name, string id, string friendly)
        {
            PortName = name;
            DeviceId = id;
            FriendlyName = friendly;
        }
    
        private static IEnumerable<RegistryKey> GetSubKeys(RegistryKey key)
        {
            foreach (string keyName in key.GetSubKeyNames())
                using (var subKey = key.OpenSubKey(keyName))
                    yield return subKey;
        }
    
        private static string GetName(RegistryKey key)
        {
            string name = key.Name;
            int idx;
            return (idx = name.LastIndexOf('\\')) == -1 ?
                name : name.Substring(idx + 1);
        }
    
        public static IEnumerable<UsbSerialPort> GetPorts()
        {
            var existingPorts = SerialPort.GetPortNames();
            using (var enumUsbKey = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Enum\USB"))
            {
                if (enumUsbKey == null)
                    throw new ArgumentNullException("USB", "No enumerable USB devices found in registry");
                foreach (var devBaseKey in GetSubKeys(enumUsbKey))
                {
                    foreach (var devFnKey in GetSubKeys(devBaseKey))
                    {
                        string friendlyName =
                            (string) devFnKey.GetValue("FriendlyName") ??
                            (string) devFnKey.GetValue("DeviceDesc");
                        using (var devParamsKey = devFnKey.OpenSubKey("Device Parameters"))
                        {
                            string portName = (string) devParamsKey?.GetValue("PortName");
                            if (!string.IsNullOrEmpty(portName) &&
                                existingPorts.Contains(portName))
                                yield return new UsbSerialPort(portName, GetName(devBaseKey) + @"\" + GetName(devFnKey), friendlyName);
                        }
                    }
    
                }
            }
        }
    
        public override string ToString()
        {
            return string.Format("{0} Friendly: {1} DeviceId: {2}", PortName, FriendlyName, DeviceId);
        }
    }
    

Solution 4

I think ManagementObjectSearcher may be a better approach than directly reading the registry.

Here's an example for virtual COM ports.

Solution 5

Ok, using ManagementObjectSearcher (it gives COM-port index and VID and PID if they exist):

        List < List <string>> USBCOMlist = new List<List<string>>();
        try
        {
            ManagementObjectSearcher searcher =
                new ManagementObjectSearcher("root\\CIMV2",
                "SELECT * FROM Win32_PnPEntity");

            foreach (ManagementObject queryObj in searcher.Get())
            {
                if (queryObj["Caption"].ToString().Contains("(COM"))
                {
                    List<string> DevInfo = new List<string>();

                    string Caption = queryObj["Caption"].ToString();
                    int CaptionIndex = Caption.IndexOf("(COM");
                    string CaptionInfo = Caption.Substring(CaptionIndex + 1).TrimEnd(')'); // make the trimming more correct                 

                    DevInfo.Add(CaptionInfo);

                    string deviceId = queryObj["deviceid"].ToString(); //"DeviceID"

                    int vidIndex = deviceId.IndexOf("VID_");
                    int pidIndex = deviceId.IndexOf("PID_");
                    string vid = "", pid = "";

                    if (vidIndex != -1 && pidIndex != -1)
                    {
                        string startingAtVid = deviceId.Substring(vidIndex + 4); // + 4 to remove "VID_"                    
                        vid = startingAtVid.Substring(0, 4); // vid is four characters long
                                                             //Console.WriteLine("VID: " + vid);
                        string startingAtPid = deviceId.Substring(pidIndex + 4); // + 4 to remove "PID_"                    
                        pid = startingAtPid.Substring(0, 4); // pid is four characters long
                    }

                    DevInfo.Add(vid);
                    DevInfo.Add(pid);

                    USBCOMlist.Add(DevInfo);
                }

            }
        }
        catch (ManagementException e)
        {
            MessageBox.Show(e.Message);
        }
Share:
19,784
UdayaLakmal
Author by

UdayaLakmal

Mobile app developer, Android enthusiast

Updated on June 15, 2022

Comments

  • UdayaLakmal
    UdayaLakmal almost 2 years

    As following i able to get usb com port names attached to 32bit win7OS machine, by given pid and vid,but when running in x64 it stuck in the following line:

    comports.Add((string)rk6.GetValue("PortName"));
    

    This is my code

    static List<string> ComPortNames(String VID, String PID)
        {
            String pattern = String.Format("^VID_{0}.PID_{1}", VID, PID);
            Regex _rx = new Regex(pattern, RegexOptions.IgnoreCase);
            List<string> comports = new List<string>();
    
            RegistryKey rk1 = Registry.LocalMachine;
            RegistryKey rk2 = rk1.OpenSubKey("SYSTEM\\CurrentControlSet\\Enum");
    
            foreach (String s3 in rk2.GetSubKeyNames())
            {
    
                RegistryKey rk3 = rk2.OpenSubKey(s3);
                foreach (String s in rk3.GetSubKeyNames())
                {
                    if (_rx.Match(s).Success)
                    {
                        RegistryKey rk4 = rk3.OpenSubKey(s);
                        foreach (String s2 in rk4.GetSubKeyNames())
                        {
                            RegistryKey rk5 = rk4.OpenSubKey(s2);
                            RegistryKey rk6 = rk5.OpenSubKey("Device Parameters");
                            comports.Add((string)rk6.GetValue("PortName"));
                        }
                    }
                }
            }
            return comports;
        }
    

    actual code get here, So how to get com port names in x64, any suggestion?