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

6

9

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?

Typhon answered 27/4, 2012 at 12:6 Comment(4)
This is unguessable, at least a stack trace is required.Orangeman
this method return list of com ports which attach our device ex: COM3,COM4 etc, problem is this doesen't work on x64Typhon
Since it might be relevant for others that comes here: The simple reason is probably due to rk6 being null since "Device Parameters" Key does not exist on every device node, and as such is unrelated to 32bit or 64bit version of OSBurette
@UdayaLakmal, did Youkko's answer resolve your issue? If so, can you accept it?User
R
7

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!

Renewal answered 27/8, 2012 at 14:22 Comment(2)
Also, use "if (!String.IsNullOrEmpty(location))" instead of "if (location != string.Empty)"... I found that location can be null.Peddada
LocationInformation is hardware location of USB device, not COM port numberBurette
H
12

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;
}
Hyoscyamine answered 21/1, 2016 at 8:13 Comment(1)
At the time I answered, even Windows 8 didn't exist. My answer works under Windows 7. But thank you very much for this contribution. Every answer need updates sometimes :-)Renewal
R
7

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!

Renewal answered 27/8, 2012 at 14:22 Comment(2)
Also, use "if (!String.IsNullOrEmpty(location))" instead of "if (location != string.Empty)"... I found that location can be null.Peddada
LocationInformation is hardware location of USB device, not COM port numberBurette
B
5

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);
        }
    }
    
Burette answered 7/9, 2016 at 19:39 Comment(0)
B
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);
        }
Brittenybrittingham answered 21/11, 2016 at 12:55 Comment(0)
I
2

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

Here's an example for virtual COM ports.

Interrupt answered 27/4, 2012 at 12:51 Comment(2)
To add to this, try downloading the WMI Code Creator microsoft.com/en-us/download/confirmation.aspx?id=8572 . Set the namespace to "root\WMI" and the classes to "MSSerial_PortName", then you should be able to modify the ManagementObjectSearcher searcher to query that the 'InstanceName' (the USB information for a com port) contains a particular string (eg "PID_7523") . If I get a working example I will put it as an answer here later.Immethodical
There seem to be various problems with this method (eg windows doesn't always put the com port number in the description, access is denied errors when using root\WMI / outright no com ports displayed on win10). PeterJ's method seems the best sofar.Immethodical
H
0

The way to enumerate the COM ports that are present in a modern system and identify by VID and PID is as follows:

The HKLM\SYSTEM\CurrentControlSet\Services\Serenum\Enum key holds a Count and a list of found ports identified by values with names "0" "1" .."Count-1". The REG_SZ data for each value is the Registry path from HKLM\SYSTEM\CurrentContolSet\Enum to the key holding the port information. Within this key you will find the FriendlyName and the PortName ("COMnn"). The Registry path includes the VID and PID, for example:

FTDIBUS\VID_0403+PID_6001+A600FZC1A\0000

This is the Vendor ID (in this case FTDI and a PID for one of their chips). The format appears to be: /VID_NNNN+PID_NNNN+<Serial#>/

Hiltonhilum answered 6/9, 2023 at 9:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.