see what is attached to a com port and show in combo box

1k Views Asked by At

Using the code below I can create a box with a combo box that shows the current com ports What I need to do is show what is attached to the com Port, for example I want it to list COM PORT1 FTDI USB serial adapter, the reason is to let you the user know which port to enter in a batch file that runs when another button is clicked ( i have removed that part of the code as its not important) I have done some google work and found this link http://social.msdn.microsoft.com/Forums/en-US/vbgeneral/thread/331a26c1-0f42-4cf1-8adb-32fb09a18953/ But that just errors out

    Imports System
    Imports System.Threading
    Imports System.IO.Ports
    Imports System.ComponentModel


    Public Class Form1
    '------------------------------------------------
    Dim myPort As Array
    Delegate Sub SetTextCallback(ByVal [text] As String) 'Added to prevent threading                                  
    errors during receiveing of data
    '------------------------------------------------
    Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles    MyBase.Load

    myPort = IO.Ports.SerialPort.GetPortNames()
    ComboBox1.Items.AddRange(myPort)
    End Sub
    End Class
1

There are 1 best solutions below

2
On

The following shows how to get a list of COM devices in VB.NET for both .NET 6 and .NET Framework 4.8 in VS 2022. If a USB COM (serial port) device is added/removed, the ComboBox will be updated.

Option 1 - Windows Forms App: (.NET 6)

Create a new project: Windows Forms App (name: SerialPortGetComDevices)

Download/install the following NuGet packages:

  • System.IO.Ports
  • System.Management

Option 2 - Windows Forms App (.NET Framework) - v4.8:

Create a new project: Windows Forms App (.NET Framework) (name: SerialPortGetComDevices)

Add Reference:

  • In VS menu, click Project
  • Select Add Reference...
  • Select Assemblies
  • Check System.Management
  • Click OK

The instructions below are the same for both Windows Forms App and Windows Forms App (.NET Framework) (ie: the instructions below are the same regardless of which option you've chosen above).

Create a class (name: ComPortInfo.vb)

  • In VS menu, select Project
  • Select Add Class... (name: ComPortInfo.vb)
  • Click Add

ComPortInfo.vb:

Public Class ComPortInfo
    Public Property Caption As String
    Public Property PortName As String
End Class

Open Solution Explorer:

  • In VS menu, click View
  • Select Solution Explorer

Open Properties Window:

  • In VS menu, click View
  • Select Properties Window

Add Load Event Handler

  • In Solution Explorer, right-click Form1.vb and select View Designer.
  • In Properties Window, click Events
  • Double-click Load

Add FormClosing Event Handler

  • In Solution Explorer, right-click Form1.vb and select View Designer.
  • In Properties Window, click Events
  • Double-click FormClosing

Add a ComboBox to the Form (name: ComboBoxComPorts)

  • In VS menu, click View
  • Select Toolbox
  • In Toolbox, select ComboBox, and drag it to the Form.
  • In the Properties Window, change (Name) to ComboBoxComPorts
  • In the Properties Window, change DropDownStyle to DropDownList

Select one of the options below. The 1st option uses ManagementEventWatcher to detect USB device insertion and removal. The 2nd option overrides WndProc.

Note: The WndProc version (Option 2) seems to have slightly better performance.


Option 1 (ManagementEventWatcher)

Note: The code for detecting the insertion and removal of a USB device, is adapted from here.

Add the code below in your Form (ex: Form1.vb)

Form1.vb

Imports System.ComponentModel
Imports System.Management
Imports System.IO.Ports

Public Class Form1

    'create new instance
    Private _comPorts As BindingList(Of ComPortInfo) = New BindingList(Of ComPortInfo)
    Private _managementEventWatcher1 As ManagementEventWatcher = New ManagementEventWatcher()

    Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        InitializeManagementEventWatcher()
        UpdateCOM()

        'set properties
        ComboBoxComPorts.DataSource = _comPorts
        ComboBoxComPorts.DisplayMember = "Caption"
        ComboBoxComPorts.ValueMember = "PortName"
    End Sub

    Private Sub GetComPorts()
        'this method Is only called from 'UpdateCOM'
        '_comPorts' is only modified in this method

        Dim portDict As Dictionary(Of String, String) = New Dictionary(Of String, String)

        'clear existing data
        _comPorts.Clear()

        'get port names
        For Each pName As String In SerialPort.GetPortNames()
            If Not portDict.ContainsKey(pName) Then
                portDict.Add(pName, pName) 'add to Dictionary
            End If
        Next

        'get USB COM ports - this may result in a more descriptive name than 'COM1' 
        Using searcherPnPEntity As ManagementObjectSearcher = New ManagementObjectSearcher("SELECT Name FROM Win32_PnPEntity WHERE PNPClass = 'Ports'")
            For Each objPnPEntity As ManagementObject In searcherPnPEntity.Get()
                If objPnPEntity Is Nothing Then
                    Continue For
                End If

                'get name
                Dim name As String = objPnPEntity("Name")?.ToString()

                If Not String.IsNullOrEmpty(name) AndAlso name.ToUpper().Contains("COM") Then
                    Dim portName As String = name.Substring(name.IndexOf("(") + 1, name.IndexOf(")") - name.IndexOf("(") - 1)

                    If Not portDict.ContainsKey(portName) Then
                        portDict.Add(portName, name) 'add to Dictionary
                    Else
                        portDict(portName) = name 'update value
                    End If
                End If
            Next
        End Using

        'add items from Dictionary to BindingList
        For Each kvp As KeyValuePair(Of String, String) In portDict
            _comPorts.Add(New ComPortInfo() With {.Caption = kvp.Value, .PortName = kvp.Key}) 'add
        Next
    End Sub

    Private Sub InitializeManagementEventWatcher()
        'see https:'learn.microsoft.com/en-us/windows/win32/wmisdk/within-clause
        'WITHIN sets the polling interval in seconds
        'polling too frequently may reduce performance
        Dim query As WqlEventQuery = New WqlEventQuery("SELECT * FROM __InstanceOperationEvent WITHIN 2 WHERE TargetInstance ISA 'Win32_PnPEntity'")
        'Dim query As WqlEventQuery = New WqlEventQuery("SELECT * FROM __InstanceOperationEvent WITHIN 1 WHERE TargetInstance ISA 'Win32_PnPEntity'")

        'set property
        _managementEventWatcher1.Query = query

        'subscribe to event
        AddHandler _managementEventWatcher1.EventArrived, AddressOf ManagementEventWatcher_EventArrived

        'start
        _managementEventWatcher1.Start()
    End Sub

    Private Sub LogMsg(msg As String, Optional includeTimestamp As Boolean = True)
        If includeTimestamp Then
            msg = $"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff")} - {msg}"
        End If

        Debug.WriteLine(msg)
    End Sub

    Public Sub UpdateCOM()
        If ComboBoxComPorts.InvokeRequired Then
            'LogMsg("ComboBoxComPorts.InvokeRequired")
            ComboBoxComPorts.Invoke(New MethodInvoker(Sub()
                                                          GetComPorts()
                                                      End Sub))
        Else
            GetComPorts()
        End If
    End Sub

    Public Sub ManagementEventWatcher_EventArrived(sender As Object, e As EventArrivedEventArgs)
        Dim obj As ManagementBaseObject = DirectCast(e.NewEvent, ManagementBaseObject)
        Dim target As ManagementBaseObject = If(obj("TargetInstance") IsNot Nothing, DirectCast(obj("TargetInstance"), ManagementBaseObject), Nothing)

        Dim usbEventType As String = String.Empty

        Select Case target.ClassPath.ClassName
            Case "__InstanceCreationEvent"
                usbEventType = "added"
            Case "__InstanceDeletionEvent"
                usbEventType = "removed"
            Case Else
                usbEventType = target.ClassPath.ClassName
        End Select

        If target("PNPClass") IsNot Nothing AndAlso target("PNPClass").ToString() = "Ports" Then
            'update COM ports
            UpdateCOM()
        End If
    End Sub

    Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
        'stop
        _managementEventWatcher1.Stop()

        'unsubscribe from event
        RemoveHandler _managementEventWatcher1.EventArrived, AddressOf ManagementEventWatcher_EventArrived
    End Sub
End Class

Option 2 (override WndProc)

Note: The code for detecting the insertion and removal of a USB device, is adapted from here.

Add a Module (name: UsbDeviceNotification.vb)

  • In VS menu, select Project
  • Select Add Module... (name: UsbDeviceNotification.vb)
  • Click Add

UsbDeviceNotification.vb

Imports System.Runtime.InteropServices

Module UsbDeviceNotification
    Public Const DbtDeviceArrival As Integer = &H8000 'device added
    Public Const DbtDeviceRemoveComplete As Integer = &H8004 'device removed
    Public Const WM_DEVICECHANGE As Integer = &H219 'device change event
    Public Const DBT_DEVTYP_DEVICEINTERFACE As Integer = 5

    Private ReadOnly _guidDevInterfaceUSBDevice As Guid = New Guid("A5DCBF10-6530-11D2-901F-00C04FB951ED") 'USB devices
    Private _notificationHandle As IntPtr

    Declare Auto Function RegisterDeviceNotification Lib "user32" (recipient As IntPtr, notificationFilter As IntPtr, flags As Integer) As IntPtr
    Declare Auto Function UnregisterDeviceNotification Lib "user32" (hwnd As IntPtr) As Boolean

    <StructLayout(LayoutKind.Sequential)>
    Private Structure DEV_BROADCAST_DEVICEINTERFACE
        Dim Size As Integer
        Dim DeviceType As Integer
        Dim Reserved As Integer
        Dim ClassGuid As Guid
        Dim Name As Short
    End Structure

    Public Sub RegisterUsbDeviceNotification(hwnd As IntPtr)
        'Registers a window to receive notifications when USB devices are plugged or unplugged.

        Dim dbi As DEV_BROADCAST_DEVICEINTERFACE = New DEV_BROADCAST_DEVICEINTERFACE() With
            {
                .DeviceType = DBT_DEVTYP_DEVICEINTERFACE,
                .ClassGuid = _guidDevInterfaceUSBDevice
            }

        dbi.Size = Marshal.SizeOf(dbi)
        Dim buffer As IntPtr = Marshal.AllocHGlobal(dbi.Size)
        Marshal.StructureToPtr(dbi, buffer, True)

        _notificationHandle = RegisterDeviceNotification(hwnd, buffer, 0)
    End Sub

    Public Sub UnregisterUsbDeviceNotification()
        UnregisterDeviceNotification(_notificationHandle)
    End Sub
End Module

Add the code below in your Form (ex: Form1.vb)

Form1.vb

Imports System.ComponentModel
Imports System.Management
Imports System.IO.Ports
Imports System.Threading

Public Class Form1

    'create new instance
    Private _comPorts As BindingList(Of ComPortInfo) = New BindingList(Of ComPortInfo)

    Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        UpdateCOM()

        'set properties
        ComboBoxComPorts.DataSource = _comPorts
        ComboBoxComPorts.DisplayMember = "Caption"
        ComboBoxComPorts.ValueMember = "PortName"
    End Sub

    Private Sub GetComPorts()
        'this method Is only called from 'UpdateCOM'
        '_comPorts' is only modified in this method

        Dim portDict As Dictionary(Of String, String) = New Dictionary(Of String, String)

        'clear existing data
        _comPorts.Clear()

        'get port names
        For Each pName As String In SerialPort.GetPortNames()
            If Not portDict.ContainsKey(pName) Then
                portDict.Add(pName, pName) 'add to Dictionary
            End If
        Next

        'get USB COM ports - this may result in a more descriptive name than 'COM1' 
        Using searcherPnPEntity As ManagementObjectSearcher = New ManagementObjectSearcher("SELECT Name FROM Win32_PnPEntity WHERE PNPClass = 'Ports'")

            If searcherPnPEntity IsNot Nothing Then
                For Each objPnPEntity As ManagementBaseObject In searcherPnPEntity.Get()
                    If objPnPEntity Is Nothing Then
                        Continue For
                    End If

                    'get name
                    Dim name As String = objPnPEntity("Name")?.ToString()

                    If Not String.IsNullOrEmpty(name) AndAlso name.ToUpper().Contains("COM") Then
                        Dim portName As String = name.Substring(name.IndexOf("(") + 1, name.IndexOf(")") - name.IndexOf("(") - 1)

                        If Not portDict.ContainsKey(portName) Then
                            portDict.Add(portName, name) 'add to Dictionary
                        Else
                            portDict(portName) = name 'update value
                        End If
                    End If
                Next
            End If
        End Using

        'add items from Dictionary to BindingList
        For Each kvp As KeyValuePair(Of String, String) In portDict
            _comPorts.Add(New ComPortInfo() With {.Caption = kvp.Value, .PortName = kvp.Key}) 'add
        Next
    End Sub

    Private Sub LogMsg(msg As String, Optional includeTimestamp As Boolean = True)
        If includeTimestamp Then
            msg = $"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff")} - {msg}"
        End If

        Debug.WriteLine(msg)
    End Sub

    Public Sub UpdateCOM()
        'since this method/Sub is called from WndProc, 
        'it needs to run on a new thread
        Dim threadProc As System.Threading.Thread = New System.Threading.Thread(Sub()
                                                                                    If ComboBoxComPorts.InvokeRequired Then
                                                                                        'LogMsg("ComboBoxComPorts.InvokeRequired")
                                                                                        ComboBoxComPorts.Invoke(New MethodInvoker(Sub()
                                                                                                                                      GetComPorts()
                                                                                                                                  End Sub))
                                                                                    Else
                                                                                        GetComPorts()
                                                                                    End If
                                                                                End Sub)

        threadProc.SetApartmentState(System.Threading.ApartmentState.STA)
        threadProc.Start()
    End Sub

    Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)

        If m.Msg = UsbDeviceNotification.WM_DEVICECHANGE Then
            Select Case CInt(m.WParam)
                Case UsbDeviceNotification.DbtDeviceRemoveComplete
                    UpdateCOM()

                Case UsbDeviceNotification.DbtDeviceArrival
                    UpdateCOM()
            End Select
        End If

        MyBase.WndProc(m)

    End Sub

    Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing

    End Sub
End Class

The following PowerShell commands may also provide useful information.

PowerShell:

  • Get-CimInstance -Namespace Root\Cimv2 -Query "Select * From Win32_SerialPort Where Name like '%COM%'"
  • Get-CimInstance -Namespace Root\Cimv2 -Query "Select * From Win32_SerialPortConfiguration"
  • Get-CimInstance -Namespace Root\Cimv2 -Query "Select * From Win32_PnPEntity where PnPClass = 'Ports' and Name like '%COM%'"
  • mode

Resources: