11

Is there any way to enumerate all the bluetooth com ports and get their names? And by name i don't mean COM10, in this case i mean GNSS:51622 'GNSS Server'.

bluetooth dialog

Using 32Feet i have been able to find the names of the ports, but still no luck mapping them to the actual com port.

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Connecting to Bluetooth");
        var client = new BluetoothClient();
        Console.WriteLine("DiscoverDevices");
        var devices = client.DiscoverDevices();
        Console.WriteLine("Enumerating");
        foreach (var device in devices)
        {
            if (!device.DeviceName.StartsWith("GNSS"))
                continue;
            Console.WriteLine(device.DeviceName);
            try
            {
                Console.WriteLine("Getting serial ports");
                var serviceRecords = device.GetServiceRecords(BluetoothService.SerialPort);
                foreach (var serviceRecord in serviceRecords)
                {
                    var name = GetName(serviceRecord);
                    Console.WriteLine(name);
                }

            }
            catch (Exception ex)
            {
                Console.WriteLine("Failed to get SerialPort");
                Console.WriteLine(ex.ToString());
            }
        }
        Console.ReadKey();
    }

    private static string GetName(ServiceRecord serviceRecord)
    {
        var nameAttribute = serviceRecord.SingleOrDefault(a => a.Id == 0);
        var name = serviceRecord.GetPrimaryMultiLanguageStringAttributeById(nameAttribute.Id);
        return name;
    }
}

Output:

Connecting to Bluetooth DiscoverDevices Enumerating GNSS:51622 Getting serial ports COM1 COM2 COM3 GNSS Server

Peter
  • 37,042
  • 39
  • 142
  • 198
  • Does this help - https://stackoverflow.com/questions/11458835/finding-information-about-all-serial-devices-connected-through-usb-in-c-sharp ? – Subbu Jul 28 '17 at 16:47
  • @Subbu sadly it dosen't finding all available Com Ports isen't the problem, figuring out what Com Port that has the name `GNSS Server` is. – Peter Jul 31 '17 at 06:06
  • 1
    Can you try that code? https://gist.github.com/smourier/6f632f37b1cfda085421b079f52d2301 I have difficulties to test with my own machine – Simon Mourier Aug 01 '17 at 06:52
  • @SimonMourier sadly that only gives the following output `COM9 -> GNSS:51622`. – Peter Aug 01 '17 at 07:26
  • Is what the windows dialogbox shows correct in your case? Mine is actually not, hence the reason why I can't really test, but you may face the same issue... The code enumerates all BT *devices*. Do you have more than one BT *device*? – Simon Mourier Aug 01 '17 at 07:43
  • @SimonMourier its one BT Device that exposes multiple COM Ports, so even if your code loops over all BT Devices it seems to be coded so that it only allows for one COM Port per device and thats not the case here.. – Peter Aug 01 '17 at 08:47
  • 1
    I don't think it's possible using documented APIs, there is no documented way to get from a COM port to a BT device. Internally there *are* APIs (exposed publicly by BluetoothApis.dll), but since they're not documented, we can't use it. Only the reverse is possible, but it's not what that dialog box displays. The only way I can think of would be to use UI automation to get the content of that dialog box... – Simon Mourier Aug 04 '17 at 08:06
  • @SimonMourier Thanks for your reply, what do you mean with "Only the reverse is possible"? – Peter Aug 04 '17 at 10:53
  • That's what the code I provided does: for each BT device, it gives an associated COM port. – Simon Mourier Aug 04 '17 at 14:23

1 Answers1

2

I've posted a gist here https://gist.github.com/peterfoot/b4f61c81023a1e181b9f3940bca344ba with code to enumerate Bluetooth virtual COM ports. The port name value from the service record is stored in the registry and so it's possible to get it based on the device path from the setup APIs. It's exposed as RemoteServiceName in the gist code. For example on my Zebra printer it returns "Serial Printer". I don't have a device to hand with multiple exposed services but this will give you the string above shown next to the device name e.g. "GNSS Server", "COM1" etc.

Code:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;

namespace BluetoothDiagnostics
{
    public sealed class BluetoothComPort
    {
        /// <summary>
        /// Returns a collection of all the Bluetooth virtual-COM ports on the system.
        /// </summary>
        /// <returns></returns>
        public static IReadOnlyList<BluetoothComPort> FindAll()
        {
            List<BluetoothComPort> ports = new List<BluetoothComPort>();

            IntPtr handle = NativeMethods.SetupDiGetClassDevs(ref NativeMethods.GUID_DEVCLASS_PORTS, null, IntPtr.Zero,  NativeMethods.DIGCF.PRESENT);
            if (handle != IntPtr.Zero)
            {
                try
                {


                    NativeMethods.SP_DEVINFO_DATA dat = new NativeMethods.SP_DEVINFO_DATA();
                    dat.cbSize = Marshal.SizeOf(dat);
                    uint i = 0;

                    while (NativeMethods.SetupDiEnumDeviceInfo(handle, i++, ref dat))
                    {
                        string remoteServiceName = string.Empty;
                        StringBuilder sbid = new StringBuilder(256);
                        int size;
                        NativeMethods.SetupDiGetDeviceInstanceId(handle, ref dat, sbid, sbid.Capacity, out size);
                        Debug.WriteLine(sbid);
                        long addr = GetBluetoothAddressFromDevicePath(sbid.ToString());

                        // only valid if an outgoing Bluetooth port
                        if (addr != long.MinValue && addr != 0)
                        {
                            IntPtr hkey = NativeMethods.SetupDiOpenDevRegKey(handle, ref dat, NativeMethods.DICS.GLOBAL, 0, NativeMethods.DIREG.DEV, 1);
                            var key = Microsoft.Win32.RegistryKey.FromHandle(new Microsoft.Win32.SafeHandles.SafeRegistryHandle(hkey, true));
                            object name = key.GetValue("PortName");

                            var pkey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey("SYSTEM\\CurrentControlSet\\Services\\BTHPORT\\Parameters\\Devices\\" + addr.ToString("x12"));
                            if (pkey != null)
                            {
                                foreach (string nm in pkey.GetSubKeyNames())
                                {
                                    if (nm.StartsWith("ServicesFor"))
                                    {
                                        var skey = pkey.OpenSubKey(nm);
                                        string s = sbid.ToString();

                                        //bluetooth service uuid from device id string
                                        int ifirst = s.IndexOf("{");
                                        string uuid = s.Substring(ifirst, s.IndexOf("}") - ifirst+1);
                                        var ukey = skey.OpenSubKey(uuid);

                                        // instance id from device id string
                                        string iid = s.Substring(s.LastIndexOf("_")+1);
                                        var instKey = ukey.OpenSubKey(iid);

                                        // registry key contains service name as a byte array
                                        object o = instKey.GetValue("PriLangServiceName");
                                        if(o != null)
                                        {
                                            byte[] chars = o as byte[];
                                            remoteServiceName = Encoding.UTF8.GetString(chars).Trim();
                                        }
                                    }
                                }
                            }
                            ports.Add(new BluetoothComPort(sbid.ToString(), addr, name.ToString(), remoteServiceName));
                            key.Dispose();
                        }
                    }

                }
                finally
                {
                    NativeMethods.SetupDiDestroyDeviceInfoList(handle);
                }
            }

            return ports;
        }

        private string _deviceId;
        private long _bluetoothAddress;
        private string _portName;
        private string _remoteServiceName;

        internal BluetoothComPort(string deviceId, long bluetoothAddress, string portName, string remoteServiceName)
        {
            _deviceId = deviceId;
            _bluetoothAddress = bluetoothAddress;
            _portName = portName;
            _remoteServiceName = remoteServiceName;
        }

        public string DeviceId
        {
            get
            {
                return _deviceId;
            }
        }

        public long BluetoothAddress
        {
            get
            {
                return _bluetoothAddress;
            }
        }

        public string PortName
        {
            get
            {
                return _portName;
            }
        }

        public string RemoteServiceName
        {
            get
            {
                return _remoteServiceName;
            }
        }


        private static long GetBluetoothAddressFromDevicePath(string path)
        {
            if (path.StartsWith("BTHENUM"))
            {
                int start = path.LastIndexOf('&');
                int end = path.LastIndexOf('_');
                string addressString = path.Substring(start + 1, (end - start) - 1);

                // may return zero if it is an incoming port (we're not interested in these)
                return long.Parse(addressString, System.Globalization.NumberStyles.HexNumber);

            }

            // not a bluetooth port
            return long.MinValue;
        }

        private static class NativeMethods
        {
            // The SetupDiGetClassDevs function returns a handle to a device information set that contains requested device information elements for a local machine. 
            [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
            internal static extern IntPtr SetupDiGetClassDevs(
                ref Guid classGuid,
                [MarshalAs(UnmanagedType.LPTStr)] string enumerator,
                IntPtr hwndParent,
                DIGCF flags);

            // The SetupDiEnumDeviceInfo function returns a SP_DEVINFO_DATA structure that specifies a device information element in a device information set. 
            [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool SetupDiEnumDeviceInfo(
                IntPtr deviceInfoSet,
                uint memberIndex,
                ref SP_DEVINFO_DATA deviceInfoData);

            // The SetupDiDestroyDeviceInfoList function deletes a device information set and frees all associated memory.
            [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool SetupDiDestroyDeviceInfoList(IntPtr deviceInfoSet);

            // The SetupDiGetDeviceInstanceId function retrieves the device instance ID that is associated with a device information element.
            [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool SetupDiGetDeviceInstanceId(
               IntPtr deviceInfoSet,
               ref SP_DEVINFO_DATA deviceInfoData,
               System.Text.StringBuilder deviceInstanceId,
               int deviceInstanceIdSize,
               out int requiredSize);

            //static Guid GUID_DEVCLASS_BLUETOOTH = new Guid("{E0CBF06C-CD8B-4647-BB8A-263B43F0F974}");
            internal static Guid GUID_DEVCLASS_PORTS = new Guid("{4d36e978-e325-11ce-bfc1-08002be10318}");

            [DllImport("setupapi.dll", SetLastError = true)]
            internal static extern IntPtr SetupDiOpenDevRegKey(IntPtr DeviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData,
                DICS Scope, int HwProfile, DIREG KeyType, int samDesired);

            [Flags]
            internal enum DICS
            {
                GLOBAL = 0x00000001,  // make change in all hardware profiles
                CONFIGSPECIFIC = 0x00000002,  // make change in specified profile only
            }

            internal enum DIREG
            {
                DEV = 0x00000001,          // Open/Create/Delete device key
                DRV = 0x00000002,          // Open/Create/Delete driver key
            }

            // changes to follow.
            // SETUPAPI.H
            [Flags()]
            internal enum DIGCF
            {
                PRESENT = 0x00000002, // Return only devices that are currently present in a system.
                ALLCLASSES = 0x00000004, // Return a list of installed devices for all device setup classes or all device interface classes. 
                PROFILE = 0x00000008, // Return only devices that are a part of the current hardware profile.
            }


            [StructLayout(LayoutKind.Sequential)]
            internal struct SP_DEVINFO_DATA
            {
                internal int cbSize; // = (uint)Marshal.SizeOf(typeof(SP_DEVINFO_DATA));
                internal Guid ClassGuid;
                internal uint DevInst;
                internal IntPtr Reserved;
            }
        }
    }
}
Peter
  • 37,042
  • 39
  • 142
  • 198
Peter Foot
  • 46
  • 3