¿Cómo abrir un puerto serie por nombre amigable?


Nombre descriptivo = el nombre que aparece en "Administrador de dispositivos" en "Puertos (COM & LPT).

EDITAR: dos soluciones proporcionadas a continuación. Uno con WMI y otro con SetupAPI.

Author: Pavel Radzivilovsky, 2010-05-30

8 answers

Intente ejecutar una consulta WMI en la clase Win32_SerialPort. Descargue WmiCodeCreator para experimentar y generar automáticamente el código C#.

 0
Author: Hans Passant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-30 10:35:17

Publicando el código de esta noche, para el disfrute de todos:

public class SetupDiWrap
{
    static public string ComPortNameFromFriendlyNamePrefix(string friendlyNamePrefix)
    {
        const string className = "Ports";
        Guid[] guids = GetClassGUIDs(className);

        System.Text.RegularExpressions.Regex friendlyNameToComPort =
            new System.Text.RegularExpressions.Regex(@".? \((COM\d+)\)$");  // "..... (COMxxx)" -> COMxxxx

        foreach (Guid guid in guids)
        {
            // We start at the "root" of the device tree and look for all
            // devices that match the interface GUID of a disk
            Guid guidClone = guid;
            IntPtr h = SetupDiGetClassDevs(ref guidClone, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT | DIGCF_PROFILE);
            if (h.ToInt32() != INVALID_HANDLE_VALUE)
            {
                int nDevice = 0;
                while (true)
                {
                    SP_DEVINFO_DATA da = new SP_DEVINFO_DATA();
                    da.cbSize = (uint)Marshal.SizeOf(da);

                    if (0 == SetupDiEnumDeviceInfo(h, nDevice++, ref da))
                        break;

                    uint RegType;
                    byte[] ptrBuf = new byte[BUFFER_SIZE];
                    uint RequiredSize;
                    if (SetupDiGetDeviceRegistryProperty(h, ref da,
                        (uint)SPDRP.FRIENDLYNAME, out RegType, ptrBuf,
                        BUFFER_SIZE, out RequiredSize))
                    {
                        const int utf16terminatorSize_bytes = 2;
                        string friendlyName = System.Text.UnicodeEncoding.Unicode.GetString(ptrBuf, 0, (int)RequiredSize - utf16terminatorSize_bytes);

                        if (!friendlyName.StartsWith(friendlyNamePrefix))
                            continue;

                        if (!friendlyNameToComPort.IsMatch(friendlyName))
                            continue;

                        return friendlyNameToComPort.Match(friendlyName).Groups[1].Value;
                    }
                } // devices
                SetupDiDestroyDeviceInfoList(h);
            }
        } // class guids

        return null;
    }

    /// <summary>
    /// The SP_DEVINFO_DATA structure defines a device instance that is a member of a device information set.
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    private struct SP_DEVINFO_DATA
    {
        /// <summary>Size of the structure, in bytes.</summary>
        public uint cbSize;
        /// <summary>GUID of the device interface class.</summary>
        public Guid ClassGuid;
        /// <summary>Handle to this device instance.</summary>
        public uint DevInst;
        /// <summary>Reserved; do not use.</summary>
        public uint Reserved;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct SP_DEVICE_INTERFACE_DATA
    {
        public Int32 cbSize;
        public Guid interfaceClassGuid;
        public Int32 flags;
        private UIntPtr reserved;
    }

    const int BUFFER_SIZE = 1024;

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    private struct SP_DEVICE_INTERFACE_DETAIL_DATA
    {
        public int cbSize;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = BUFFER_SIZE)]
        public string DevicePath;
    }

    private enum SPDRP
    {
        DEVICEDESC = 0x00000000,
        HARDWAREID = 0x00000001,
        COMPATIBLEIDS = 0x00000002,
        NTDEVICEPATHS = 0x00000003,
        SERVICE = 0x00000004,
        CONFIGURATION = 0x00000005,
        CONFIGURATIONVECTOR = 0x00000006,
        CLASS = 0x00000007,
        CLASSGUID = 0x00000008,
        DRIVER = 0x00000009,
        CONFIGFLAGS = 0x0000000A,
        MFG = 0x0000000B,
        FRIENDLYNAME = 0x0000000C,
        LOCATION_INFORMATION = 0x0000000D,
        PHYSICAL_DEVICE_OBJECT_NAME = 0x0000000E,
        CAPABILITIES = 0x0000000F,
        UI_NUMBER = 0x00000010,
        UPPERFILTERS = 0x00000011,
        LOWERFILTERS = 0x00000012,
        MAXIMUM_PROPERTY = 0x00000013,
    }

    [DllImport("setupapi.dll", SetLastError = true)]
    static extern bool SetupDiClassGuidsFromName(string ClassName,
        ref Guid ClassGuidArray1stItem, UInt32 ClassGuidArraySize,
        out UInt32 RequiredSize);

    [DllImport("setupapi.dll")]
    internal static extern IntPtr SetupDiGetClassDevsEx(IntPtr ClassGuid,
        [MarshalAs(UnmanagedType.LPStr)]String enumerator,
        IntPtr hwndParent, Int32 Flags, IntPtr DeviceInfoSet,
        [MarshalAs(UnmanagedType.LPStr)]String MachineName, IntPtr Reserved);

    [DllImport("setupapi.dll")]
    internal static extern Int32 SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);

    [DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern Boolean SetupDiEnumDeviceInterfaces(
       IntPtr hDevInfo,
       IntPtr optionalCrap, //ref SP_DEVINFO_DATA devInfo,
       ref Guid interfaceClassGuid,
       UInt32 memberIndex,
       ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData
    );

    [DllImport("setupapi.dll")]
    private static extern Int32 SetupDiEnumDeviceInfo(IntPtr DeviceInfoSet,
        Int32 MemberIndex, ref SP_DEVINFO_DATA DeviceInterfaceData);

    [DllImport("setupapi.dll")]
    private static extern Int32 SetupDiClassNameFromGuid(ref Guid ClassGuid,
        StringBuilder className, Int32 ClassNameSize, ref Int32 RequiredSize);

    [DllImport("setupapi.dll")]
    private static extern Int32 SetupDiGetClassDescription(ref Guid ClassGuid,
        StringBuilder classDescription, Int32 ClassDescriptionSize, ref Int32 RequiredSize);

    [DllImport("setupapi.dll")]
    private static extern Int32 SetupDiGetDeviceInstanceId(IntPtr DeviceInfoSet,
        ref SP_DEVINFO_DATA DeviceInfoData,
        StringBuilder DeviceInstanceId, Int32 DeviceInstanceIdSize, ref Int32 RequiredSize);

    [DllImport("setupapi.dll", CharSet = CharSet.Auto)]
    static extern IntPtr SetupDiGetClassDevs(           // 1st form using a ClassGUID only, with null Enumerator
       ref Guid ClassGuid,
       IntPtr Enumerator,
       IntPtr hwndParent,
       int Flags
    );

    [DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern Boolean SetupDiGetDeviceInterfaceDetail(
       IntPtr hDevInfo,
       ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData,
       ref SP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData,
       UInt32 deviceInterfaceDetailDataSize,
       out UInt32 requiredSize,
       ref SP_DEVINFO_DATA deviceInfoData
    );

    /// <summary>
    /// The SetupDiGetDeviceRegistryProperty function retrieves the specified device property.
    /// This handle is typically returned by the SetupDiGetClassDevs or SetupDiGetClassDevsEx function.
    /// </summary>
    /// <param Name="DeviceInfoSet">Handle to the device information set that contains the interface and its underlying device.</param>
    /// <param Name="DeviceInfoData">Pointer to an SP_DEVINFO_DATA structure that defines the device instance.</param>
    /// <param Name="Property">Device property to be retrieved. SEE MSDN</param>
    /// <param Name="PropertyRegDataType">Pointer to a variable that receives the registry data Type. This parameter can be NULL.</param>
    /// <param Name="PropertyBuffer">Pointer to a buffer that receives the requested device property.</param>
    /// <param Name="PropertyBufferSize">Size of the buffer, in bytes.</param>
    /// <param Name="RequiredSize">Pointer to a variable that receives the required buffer size, in bytes. This parameter can be NULL.</param>
    /// <returns>If the function succeeds, the return value is nonzero.</returns>
    [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern bool SetupDiGetDeviceRegistryProperty(
        IntPtr DeviceInfoSet,
        ref SP_DEVINFO_DATA DeviceInfoData,
        uint Property,
        out UInt32 PropertyRegDataType,
        byte[] PropertyBuffer,
        uint PropertyBufferSize,
        out UInt32 RequiredSize);


    const int DIGCF_DEFAULT = 0x1;
    const int DIGCF_PRESENT = 0x2;
    const int DIGCF_ALLCLASSES = 0x4;
    const int DIGCF_PROFILE = 0x8;
    const int DIGCF_DEVICEINTERFACE = 0x10;
    const int INVALID_HANDLE_VALUE = -1;

    private static Guid[] GetClassGUIDs(string className)
    {
        UInt32 requiredSize = 0;
        Guid[] guidArray = new Guid[1];

        bool status = SetupDiClassGuidsFromName(className, ref guidArray[0], 1, out requiredSize);
        if (true == status)
        {
            if (1 < requiredSize)
            {
                guidArray = new Guid[requiredSize];
                SetupDiClassGuidsFromName(className, ref guidArray[0], requiredSize, out requiredSize);
            }
        }
        else
            throw new System.ComponentModel.Win32Exception();

        return guidArray;
    }


}
 25
Author: Pavel Radzivilovsky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-30 04:49:44

El código de este artículo hizo el trabajo por mí (enlaza a este post, pero no parece haber proporcionado una respuesta aquí él mismo). Aquí está el código del autor:

using System.Management;
internal class ProcessConnection { 

   public static ConnectionOptions ProcessConnectionOptions()
   {
     ConnectionOptions options = new ConnectionOptions();
     options.Impersonation = ImpersonationLevel.Impersonate;
     options.Authentication = AuthenticationLevel.Default;
     options.EnablePrivileges = true;
     return options;
   }

   public static ManagementScope ConnectionScope(string machineName, ConnectionOptions options, string path)
   {
     ManagementScope connectScope = new ManagementScope();
     connectScope.Path = new ManagementPath(@"\\" + machineName + path);
     connectScope.Options = options;
     connectScope.Connect();
     return connectScope;
   }
}

public class COMPortInfo
{
   public string Name { get; set; }
   public string Description { get; set; }

   public COMPortInfo() { }     

   public static List<COMPortInfo> GetCOMPortsInfo()
   {
     List<COMPortInfo> comPortInfoList = new List<COMPortInfo>();

     ConnectionOptions options = ProcessConnection.ProcessConnectionOptions();
     ManagementScope connectionScope = ProcessConnection.ConnectionScope(Environment.MachineName, options, @"\root\CIMV2");

     ObjectQuery objectQuery = new ObjectQuery("SELECT * FROM Win32_PnPEntity WHERE ConfigManagerErrorCode = 0");
     ManagementObjectSearcher comPortSearcher = new ManagementObjectSearcher(connectionScope, objectQuery);

     using (comPortSearcher)
     {
       string caption = null;
       foreach (ManagementObject obj in comPortSearcher.Get())
       {
         if (obj != null)
         {
           object captionObj = obj["Caption"];
           if (captionObj != null)
           {
              caption = captionObj.ToString();
              if (caption.Contains("(COM"))
              {
                COMPortInfo comPortInfo = new COMPortInfo();
                comPortInfo.Name = caption.Substring(caption.LastIndexOf("(COM")).Replace("(", string.Empty).Replace(")",
                                                     string.Empty);
                comPortInfo.Description = caption;
                comPortInfoList.Add(comPortInfo);
              }
           }
         }
       }
     } 
     return comPortInfoList;
   } 
}

Uso:

foreach (COMPortInfo comPort in COMPortInfo.GetCOMPortsInfo())
{
  Console.WriteLine(string.Format("{0} – {1}", comPort.Name, comPort.Description));
}
 11
Author: Pat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-15 20:41:03

Sé que esto fue publicado en C#, pero estoy seguro de que esto se puede convertir fácilmente...

    Public Function foo() As Integer
    Try
        Dim searcher As New ManagementObjectSearcher( _
            "root\CIMV2", _
            "SELECT * FROM Win32_SerialPort")

        For Each queryObj As ManagementObject In searcher.Get()
            Debug.WriteLine(queryObj("Caption"))
            Debug.WriteLine(queryObj("Description"))
            Debug.WriteLine(queryObj("DeviceID"))
            Debug.WriteLine(queryObj("Name"))
            Debug.WriteLine(queryObj("PNPDeviceID"))

        Next
    Catch err As ManagementException
        Stop
    End Try
End Function

Public Function bar() As Integer
    Try
        Dim searcher As New ManagementObjectSearcher( _
            "root\CIMV2", _
            "SELECT * FROM Win32_PnPEntity WHERE ConfigManagerErrorCode = 0")

        For Each queryObj As ManagementObject In searcher.Get()
            If queryObj("Caption").ToString.Contains("(COM") Then
                Debug.WriteLine(queryObj("Caption"))
                Debug.WriteLine(queryObj("Description"))
                Debug.WriteLine(queryObj("DeviceID"))
                Debug.WriteLine(queryObj("Name"))
                Debug.WriteLine(queryObj("PNPDeviceID"))
            End If
        Next
    Catch err As ManagementException
        Stop
    End Try
End Function

Encuentra todos mis puertos com, módem, serie, usb y bluetooth.

 5
Author: dbasnett,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-31 15:37:11

La clase de Pavel SetupDiWrap funciona muy bien, solo necesita unos pequeños ajustes para Windows 7.

Con suerte, esta actualización ayudará a otras personas que luchan (como yo) para obtener números de puerto COM de nombres VCP en Windows 7.

1) El SP_DEVINFO_DATA ha cambiado en Windows 7, la longitud total ya no es de 28 bytes, es de 32 bytes de largo. Esto es lo que funciona para mí:

   private struct SP_DEVINFO_DATA
        {
            /// <summary>Size of the structure, in bytes.</summary>
            public int cbSize;
            /// <summary>GUID of the device interface class.</summary>
            public Guid ClassGuid;
            /// <summary>Handle to this device instance.</summary>
            public int DevInst;
            /// <summary>Reserved; do not use.</summary>
            public ulong Reserved;
        }

Note ulong para Reservado en lugar de un int. Cambiar uint {[8] } a int cbSize me ahorró un yeso más tarde, de lo contrario, puede dejarlo como uint.

2) También escribí la línea:

 da.cbSize = (uint)Marshal.SizeOf(da);

Un poco diferente, para mayor claridad, para obtener el cbSize a 32 bits:

da.cbSize = Marshal.SizeOf(typeof(SP_DEVINFO_DATA));

3) Cambié

 [DllImport("setupapi.dll", CharSet = CharSet.Auto)]
        static extern IntPtr SetupDiGetClassDevs(       
           ref Guid ClassGuid,
           IntPtr Enumerator,
           IntPtr hwndParent,
           int Flags
        );

A

 [DllImport("setupapi.dll", CharSet = CharSet.Auto)]
        private static extern IntPtr SetupDiGetClassDevs( 
           ref Guid ClassGuid,
           UInt32 Enumerator,
           IntPtr hwndParent,
           UInt32 Flags
        );

El Enumerador ya no es un IntPtr, por lo que debe llamar a SetupDiGetClassDevs de esta manera:

IntPtr h = SetupDiGetClassDevs(ref guidClone, 0, IntPtr.Zero, DIGCF_PRESENT | DIGCF_PROFILE);

Note "0" en lugar de IntPtr.Zero al pasar el Enumerador.

El código ahora funciona como un encanto en Windows 7!

 2
Author: vardelean,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-19 13:46:23

Es posible que también desee considerar el uso del registro, ya que he encontrado WMI bastante lento (5 o 6 segundos)

En mi caso, quería identificar el nombre del puerto COM de un dispositivo con un nombre amigable conocido. Usando regedit busqué en el registro el nombre descriptivo en una clave que también contenía el puerto COM. Debido a que la clave que encontré fue algún tipo de identificación aleatoria junto con otros 10 subí algunos niveles para encontrar una clave adecuada para buscar dentro.

El código que se me ocurrió es como sigue:

Dim searchFriendlyName = "Your Device Name".ToLower
Dim k0 = Registry.LocalMachine.OpenSubKey("SYSTEM\CurrentControlSet\Enum\USB\", False)
For Each k1Name In k0.GetSubKeyNames
    Dim k1 = k0.OpenSubKey(k1Name, False)
    For Each k2name In k1.GetSubKeyNames
        Dim k2 = k1.OpenSubKey(k2name, False)
        If k2.GetValueNames.Contains("FriendlyName") AndAlso k2.GetValue("FriendlyName").ToString.ToLower.Contains(searchFriendlyName) Then
            If k2.GetSubKeyNames.Contains("Device Parameters") Then
                Dim k3 = k2.OpenSubKey("Device Parameters", False)
                If k3.GetValueNames.Contains("PortName") Then
                    For Each s In SerialPort.GetPortNames
                        If k3.GetValue("PortName").ToString.ToLower = s.ToLower Then
                            Return s
                        End If
                    Next
                End If
            End If
        End If
    Next
Next

Esto, por supuesto, tendrá que ser modificado dependiendo de cómo y dónde su dispositivo se muestra en el registro, pero si usted está tratando de 'Auto Detectar' el Puerto Com de un tipo específico de deivce entonces usted debe ser capaz de hacer que esto funcione.

Recuerde que si tiene que buscar recursivamente muchas claves, esto ralentizará la solución anterior, así que intente encontrar el lugar correcto en el registro para buscar.

También incluí el código WMI después de la búsqueda del registro en caso de que el la búsqueda del registro no tuvo respuesta:

Dim mg As New System.Management.ManagementClass("Win32_SerialPort")
Try

    For Each i In mg.GetInstances()
        Dim name = i.GetPropertyValue("Name")
        If name IsNot Nothing AndAlso name.ToString.ToLower.Contains(searchFriendlyName.ToLower) Then
            Return i.GetPropertyValue("DeviceId").ToString
        End If
    Next
Finally
    mg.Dispose()
End Try
 0
Author: apc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-02 11:37:55

Si está utilizando dispositivos USB específicamente y no algún otro tipo de puerto COM, la biblioteca USB multiplataforma libusbp tiene un ejemplo que puede ejecutar que muestra cómo encontrar el nombre del puerto COM basado en el ID del producto USB y el ID del proveedor del puerto COM.

Esa es una opción diferente pero probablemente mejor que intentar usar el nombre descriptivo en el Administrador de dispositivos. Tal vez libusbp podría ampliarse para permitir el acceso al nombre descriptivo si realmente lo desea.

 0
Author: David Grayson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-29 19:35:02

Utilizo puertos virtuales generados por com0com. Con eso puedes nombrar los puertos como quieras, por lo que tengo subtítulos de puertos como los siguientes: "com0com - bus for serial port pair emulator 0 (COMA <-> COMB)"

Con el código WMI publicado anteriormente, obtendrá el nombre "COMA <-> COMB" por ese puerto. Parece que tales construcciones no son puertos, pero el código anterior lo tratará como puerto serie...

POR cierto, "COMA" es un nombre totalmente válido para un puerto... (eso significa que buscar solo números al final no es suficiente).

Así que me pregunto cómo puedo distinguir confiablemente entre un nombre de puerto serie válido y existente y construcciones tan extrañas...

 -2
Author: Mich0815,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-27 17:18:00