4

I am trying to access specific information about Bluetooth serial ports. I was able to find this window in my Bluetooth settings that showed me the port, direction, and name of a Bluetooth device if it was related to a COM Port.

enter image description here

Currently to try and get this information, I have been using WQL to query some of the Windows Management Classes.

# I don't really mind if it is run in a Powershell environment
gwmi -query "SELECT * FROM Win32_PnPEntity WHERE Name LIKE '%COM%' AND PNPDeviceID LIKE '%BTHENUM%' AND PNPClass = 'Ports'"

//or a C# environment
ManagementObjectCollection results = new ManagementObjectSearcher("SELECT * FROM Win32_PnPEntity WHERE Name LIKE '%COM%' AND PNPDeviceID LIKE 'USB%' AND PNPClass = 'Ports'").Get();

#This is a lot slower but it gets a bit more information about the serial ports
gwmi -query "SELECT * FROM Win32_SerialPort WHERE Name LIKE '%COM%' AND PNPDeviceID LIKE '%BTHENUM%'"

However, the queries below do not include the name (as it is in the screenshot) and the direction of the COM Ports. Is it possible to get this information using WQL?

Dan
  • 7,286
  • 6
  • 49
  • 114

2 Answers2

5

Whilst I'm not sure if this can be done 100% with WQL, I was able to write a small program in C# that handled it.

It works by looking for patterns in some of the Win32_PnPEntity properties.

It extracts an identifier out of all the stored bluetooth devices and compares that identifier with the same identifier on the COM ports. If it finds it, it has found the outgoing COM port.

Both the outgoing and incoming port have a similar hardware ID, where the first part is identical. Using this information, the program then identifies incoming COM port if it exists.

To get it in the format shown in the image in the question, it is possible to run a powershell command that queries the DEVPKEY_Device_BusReportedDeviceDesc of the outgoing port. This is the 'Dev B' part of the name on the outgoing port.

This all done asynchronously so the list is populated as the results are found.

enter image description here


XAML

<Window x:Class="BluetoothInformation.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:BluetoothInformation"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <local:DirectionConverter x:Key="directionConverter"/>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <DataGrid ItemsSource="{Binding COMPorts}"
                  CanUserAddRows="False"
                  ColumnWidth="*"
                  AutoGenerateColumns="False"
                  VerticalScrollBarVisibility="Visible"
                  Background="Transparent"
                  RowBackground="Transparent"
                  IsReadOnly="True">
            <DataGrid.CellStyle>
                <Style TargetType="{x:Type DataGridCell}">
                    <Setter Property="Focusable"
                            Value="False"/>
                    <Style.Triggers>
                        <Trigger Property="IsSelected"
                                 Value="True">
                            <Setter Property="Background"
                                    Value="Transparent" />
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </DataGrid.CellStyle>
            <DataGrid.Columns>
                <DataGridTextColumn Header="COM Port"
                                    Binding="{Binding COMPortPort}"/>
                <DataGridTextColumn Header="Direction"
                                    Binding="{Binding COMPortDirection, Converter={StaticResource directionConverter}}"/>
                <DataGridTextColumn Header="Name"
                                    Binding="{Binding COMPortName}"/>
            </DataGrid.Columns>
        </DataGrid>
        <Button Grid.Row="1"
                Content="Refresh"
                IsEnabled="{Binding CanRefresh}"
                Click="Button_Click"/>
    </Grid>
</Window>

C#

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Management;
using System.Management.Automation;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;

namespace BluetoothInformation
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        private ObservableCollection<COMPort> comPorts = new ObservableCollection<COMPort>();
        public ObservableCollection<COMPort> COMPorts {
            get => comPorts;
            set {
                comPorts = value;
                RaisePropertyChanged();
            }
        }

        private bool canRefresh = false;
        public bool CanRefresh {
            get => canRefresh;
            set {
                canRefresh = value;
                RaisePropertyChanged();
            }
        }

        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;

            GetBluetoothCOMPort();
        }

        private string ExtractBluetoothDevice(string pnpDeviceID)
        {
            int startPos = pnpDeviceID.LastIndexOf('_') + 1;
            return pnpDeviceID.Substring(startPos);
        }

        private string ExtractDevice(string pnpDeviceID)
        {
            int startPos = pnpDeviceID.LastIndexOf('&') + 1;
            int length = pnpDeviceID.LastIndexOf('_') - startPos;
            return pnpDeviceID.Substring(startPos, length);
        }

        private string ExtractCOMPortFromName(string name)
        {
            int openBracket = name.IndexOf('(');
            int closeBracket = name.IndexOf(')');
            return name.Substring(openBracket + 1, closeBracket - openBracket - 1);
        }

        private string ExtractHardwareID(string fullHardwareID)
        {
            int length = fullHardwareID.LastIndexOf('_');
            return fullHardwareID.Substring(0, length);
        }

        private bool TryFindPair(string pairsName, string hardwareID, List<ManagementObject> bluetoothCOMPorts, out COMPort comPort)
        {
            foreach (ManagementObject bluetoothCOMPort in bluetoothCOMPorts)
            {
                string itemHardwareID = ((string[])bluetoothCOMPort["HardwareID"])[0];
                if (hardwareID != itemHardwareID && ExtractHardwareID(hardwareID) == ExtractHardwareID(itemHardwareID))
                {
                    comPort = new COMPort(ExtractCOMPortFromName(bluetoothCOMPort["Name"].ToString()), Direction.INCOMING, pairsName);
                    return true;
                }
            }
            comPort = null;
            return false;
        }

        private string GetDataBusName(string pnpDeviceID)
        {
            using (PowerShell PowerShellInstance = PowerShell.Create())
            {
                PowerShellInstance.AddScript($@"Get-PnpDeviceProperty -InstanceId '{pnpDeviceID}' -KeyName 'DEVPKEY_Device_BusReportedDeviceDesc' | select-object Data");

                Collection<PSObject> PSOutput = PowerShellInstance.Invoke();

                foreach (PSObject outputItem in PSOutput)
                {
                    if (outputItem != null)
                    {
                        Console.WriteLine(outputItem.BaseObject.GetType().FullName);
                        foreach (var p in outputItem.Properties)
                        {
                            if (p.Name == "Data")
                            {
                                return p.Value?.ToString();
                            }
                        }
                    }
                }
            }
            return string.Empty;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            GetBluetoothCOMPort();
        }

        private async void GetBluetoothCOMPort()
        {
            CanRefresh = false;
            COMPorts.Clear();

            await Task.Run(() => {
                ManagementObjectCollection results = new ManagementObjectSearcher(@"SELECT PNPClass, PNPDeviceID, Name, HardwareID FROM Win32_PnPEntity WHERE (Name LIKE '%COM%' AND PNPDeviceID LIKE '%BTHENUM%' AND PNPClass = 'Ports') OR (PNPClass = 'Bluetooth' AND PNPDeviceID LIKE '%BTHENUM\\DEV%')").Get();

                List<ManagementObject> bluetoothCOMPorts = new List<ManagementObject>();
                List<ManagementObject> bluetoothDevices = new List<ManagementObject>();

                foreach (ManagementObject queryObj in results)
                {
                    if (queryObj["PNPClass"].ToString() == "Bluetooth")
                    {
                        bluetoothDevices.Add(queryObj);
                    }
                    else if (queryObj["PNPClass"].ToString() == "Ports")
                    {
                        bluetoothCOMPorts.Add(queryObj);
                    }
                }

                foreach (ManagementObject bluetoothDevice in bluetoothDevices)
                {
                    foreach (ManagementObject bluetoothCOMPort in bluetoothCOMPorts)
                    {
                        string comPortPNPDeviceID = bluetoothCOMPort["PNPDeviceID"].ToString();
                        if (ExtractBluetoothDevice(bluetoothDevice["PNPDeviceID"].ToString()) == ExtractDevice(comPortPNPDeviceID))
                        {
                            COMPort outgoingPort = new COMPort(ExtractCOMPortFromName(bluetoothCOMPort["Name"].ToString()), Direction.OUTGOING, $"{bluetoothDevice["Name"].ToString()} \'{GetDataBusName(comPortPNPDeviceID)}\'");

                            Dispatcher.Invoke(() => {
                                COMPorts.Add(outgoingPort);
                            });

                            if (TryFindPair(bluetoothDevice["Name"].ToString(), ((string[])bluetoothCOMPort["HardwareID"])[0], bluetoothCOMPorts, out COMPort incomingPort))
                            {
                                Dispatcher.Invoke(() => {
                                    COMPorts.Add(incomingPort);
                                });
                            }
                        }
                    }
                }
            });

            CanRefresh = true;
        }
    }

    public class COMPort : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        private string comPortPort;
        public string COMPortPort {
            get => comPortPort;
            set {
                comPortPort = value;
                RaisePropertyChanged();
            }
        }

        private Direction comPortDirection;
        public Direction COMPortDirection {
            get => comPortDirection;
            set {
                comPortDirection = value;
                RaisePropertyChanged();
            }
        }

        private string comPortName;
        public string COMPortName {
            get => comPortName;
            set {
                comPortName = value;
                RaisePropertyChanged();
            }
        }

        public COMPort(string comPortPort, Direction comPortDirection, string comPortName)
        {
            COMPortPort = comPortPort;
            COMPortDirection = comPortDirection;
            COMPortName = comPortName;
        }
    }

    [ValueConversion(typeof(Direction), typeof(string))]
    public class DirectionConverter : IValueConverter
    {
        private const string UNDEFINED_DIRECTION = "UNDEFINED";
        private const string INCOMING_DIRECTION = "Incoming";
        private const string OUTGOING_DIRECTION = "Outgoing";

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            switch ((Direction)value)
            {
                case Direction.UNDEFINED:
                    return UNDEFINED_DIRECTION;
                case Direction.INCOMING:
                    return INCOMING_DIRECTION;
                case Direction.OUTGOING:
                    return OUTGOING_DIRECTION;
            }

            return string.Empty;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

    public enum Direction
    {
        UNDEFINED,
        INCOMING,
        OUTGOING
    }
}
Dan
  • 7,286
  • 6
  • 49
  • 114
1

I was trying to do the same thing in list arduino bluetooth HC-05 devices in my code using Visual studio C# code

I found a way that works for me after finding the above answer using WMI (not sure how bullet proof it is) hope it helps someone else

// First get all the Ports (this is way quicker then using WMI) 
string[] aryPorts = SerialPort.GetPortNames();
foreach (string strPort in aryPorts) { 
    
    // Find COM ports using WMI
    System.Management.ManagementObjectSearcher searcher; 
    searcher = new System.Management.ManagementObjectSearcher($"SELECT * FROM Win32_PnPEntity WHERE Caption like '%({strPort})%'");

    foreach (System.Management.ManagementObject myComPorts in searcher.Get())
    {
        string strCOMCaption = myComPorts.Properties["Caption"].Value.ToString();
        string strCOMDeviceID = myComPorts.Properties["DeviceID"].Value.ToString();
        string strCOMPNPDeviceID = myComPorts.Properties["PNPDeviceID"].Value.ToString();

        // Find Bluetooth devices
        searcher = new System.Management.ManagementObjectSearcher("SELECT * FROM Win32_PnPEntity WHERE PNPClass = 'Bluetooth' AND DeviceID like '%BTHENUM%'");
        foreach (System.Management.ManagementObject myBluetooth in searcher.Get())
        {
            string stBTCaption = myBluetooth.Properties["Caption"].Value.ToString();
            string strBTPNPDeviceID = myBluetooth.Properties["PNPDeviceID"].Value.ToString();

            // Match the Bluetooth Device ID part to part of the COM port ID
            int intWhere = strBTPNPDeviceID.IndexOf("BLUETOOTHDEVICE_");
            if (intWhere > 0)
            {
                string strBluettothIDPart = strBTPNPDeviceID.Substring(intWhere + "strBTPNPDeviceID".Length);

                if (strCOMPNPDeviceID.Contains("&" + strBluettothIDPart))
                {
                    string strBluettothFriendlyName = myBluetooth.Properties["Caption"].Value.ToString();

                    Debug.Print("COM Port: " + strPort +
                                ", Display Name: " + strBluettothFriendlyName +
                                ", BluetoothID: " + strBluettothIDPart +
                                ", COM Name: " + strCOMCaption + "");

                    break;
                }
            }
        }
    }
}

this prints out

COM Port: COM5, Display Name: BuiltCleverChess, BluetoothID: 001401035465, COM Name: Standard Serial over Bluetooth link (COM5)

COM Port: COM7, Display Name: Scotts Arduino Bluetooth, BluetoothID: 98D331100629, COM Name: Standard Serial over Bluetooth link (COM7)

the display name is the one shown in the screen shot the poster asked for

I'll explain it a little

the first WMI call searching for the COM port returns a DeviceID like this BTHENUM{00001101-0000-1000-8000-00805F9B34FB}_LOCALMFG&0002\7&19E0CC7A&0&001401035465_C00000000

the second WMI call returns Bluetooth devices and has a PNPDeviceID like this

BTHENUM\DEV_001401035465\7&3A1F5C64&2&BLUETOOTHDEVICE_001401035465

This one has the display name but no mention of the COM port

I then go through the returned bluetooth devices matching parts of the IDs

the highlighted 001401035465 matches so I assume this is some sort of Bluetooth ID

Scott
  • 216
  • 3
  • 8