I have a GSM modem connected via USB. The modem creates 2 serial ports. The first is automatically attached to the modem, the second shows in Device Manager as \"HUAWEI Mo
After you determine a Serial Port device is the one you want (by looking at its Friendly Name, by checking its parent device etc.), the proper way to get the port's name would probably be:
SetupDiOpenDevRegKey(hDevInfo, devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ)
to get the HKEY
to the so-called device keyREG_SZ
value "PortName"HKEY
:)However, this might require so much interop in C# it's not even funny, so I don't blame you if you keep to the string parsing solution.
I built a library for serial port control. It can search the friendly name in registry. Here is the link.
https://github.com/kcwongjoe/serial_port
std::vector<SerialPortInfo> comPorts = SerialPort::getSerialPortList();
std::cout << comPorts[0].friendlyName << std::endl;
Based off a combination of answer here is a solution that gets the COM number, VID / PID and friendly name etc.
Here is some example code for getting the list of connected devices.
public static class SerialPortUtils
{
private static Guid GUID_DEVCLASS_PORTS = new Guid(0x4d36e978u, 0xe325, 0x11ce, 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18);
private unsafe static bool GetPortRegistryProperty(HDEVINFO classHandle, SP_DEVINFO_DATA* deviceInfo, uint spdrp, out string result)
{
DWORD size;
SetupAPI.SetupDiGetDeviceRegistryPropertyW(classHandle, deviceInfo, spdrp, null, null, 0, &size);
if (size == 0)
{
result = null;
return false;
}
var resultBuffer = new byte[(int)size];
fixed (byte* resultBufferPtr = resultBuffer)
{
if (SetupAPI.SetupDiGetDeviceRegistryPropertyW(classHandle, deviceInfo, spdrp, null, resultBufferPtr, size, null))
{
result = Encoding.Unicode.GetString(resultBufferPtr, (int)size - sizeof(char));
return true;
}
else
{
result = null;
return false;
}
}
}
public unsafe static List<SerialPortDeviceDesc> GetSerialPortDevices()
{
var results = new List<SerialPortDeviceDesc>();
// get present ports handle
var classHandle = SetupAPI.SetupDiGetClassDevsW(ref GUID_DEVCLASS_PORTS, null, IntPtr.Zero, SetupAPI.DIGCF_PRESENT);
if (classHandle == Common.INVALID_HANDLE_VALUE || classHandle == HDEVINFO.Zero) throw new Exception("SetupDiGetClassDevsW failed");
// enumerate all ports
var deviceInfo = new SP_DEVINFO_DATA();
uint deviceInfoSize = (uint)Marshal.SizeOf<SP_DEVINFO_DATA>();
deviceInfo.cbSize = deviceInfoSize;
uint index = 0;
while (SetupAPI.SetupDiEnumDeviceInfo(classHandle, index, &deviceInfo))
{
// get port name
string portName;
HKEY regKey = SetupAPI.SetupDiOpenDevRegKey(classHandle, &deviceInfo, SetupAPI.DICS_FLAG_GLOBAL, 0, SetupAPI.DIREG_DEV, WinNT.KEY_READ);
if (regKey == Common.INVALID_HANDLE_VALUE || regKey == IntPtr.Zero) continue;
using (var regHandle = new SafeRegistryHandle(regKey, true))
using (var key = RegistryKey.FromHandle(regHandle))
{
portName = key.GetValue("PortName") as string;
if (string.IsNullOrEmpty(portName)) continue;
}
// get registry values
if (!GetPortRegistryProperty(classHandle, &deviceInfo, SetupAPI.SPDRP_FRIENDLYNAME, out string friendlyName)) continue;
if (!GetPortRegistryProperty(classHandle, &deviceInfo, SetupAPI.SPDRP_HARDWAREID, out string hardwareID)) continue;
// add device
results.Add(new SerialPortDeviceDesc(friendlyName, portName, hardwareID));
// setup for next device
++index;
deviceInfo = new SP_DEVINFO_DATA();
deviceInfo.cbSize = deviceInfoSize;
}
// finish
SetupAPI.SetupDiDestroyDeviceInfoList(classHandle);
return results;
}
}
Here is the SerialPortDeviceDesc class
public enum SerialPortType
{
Unknown,
COM
}
public class SerialPortDeviceDesc
{
public readonly string friendlyName, portName, hardwareID;
public readonly string vid, pid;
public readonly int portNumber = -1;
public readonly SerialPortType portType = SerialPortType.Unknown;
public SerialPortDeviceDesc(string friendlyName, string portName, string hardwareID)
{
this.friendlyName = friendlyName;
this.portName = portName;
this.hardwareID = hardwareID;
if (portName.StartsWith("COM") && int.TryParse(portName.Substring("COM".Length), out portNumber))
{
portType = SerialPortType.COM;
}
else
{
portNumber = -1;
}
var rx = Regex.Match(hardwareID, @"VID_(\w*)&PID_(\w*)", RegexOptions.IgnoreCase);
if (rx.Success)
{
vid = rx.Groups[1].Value;
pid = rx.Groups[2].Value;
}
}
}
A long time ago I wrote a utility for a client to do just this, but for a GPS rather than a modem.
I have just looked at it, and bits that jump-out as being possibly helpful are:
GUID guid = GUID_DEVCLASS_PORTS;
SP_DEVICE_INTERFACE_DATA interfaceData;
ZeroMemory(&interfaceData, sizeof(interfaceData));
interfaceData.cbSize = sizeof(interfaceData);
SP_DEVINFO_DATA devInfoData;
ZeroMemory(&devInfoData, sizeof(devInfoData));
devInfoData.cbSize = sizeof(devInfoData);
if(SetupDiEnumDeviceInfo(
hDeviceInfo, // Our device tree
nDevice, // The member to look for
&devInfoData
))
{
DWORD regDataType;
BYTE hardwareId[300];
if(SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, ®DataType, hardwareId, sizeof(hardwareId), NULL))
{
...
(You call this bit in a loop with incrementing nDevice)
and then
BYTE friendlyName[300];
if(SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, NULL, friendlyName, sizeof(friendlyName), NULL))
{
strFriendlyNames += (LPCTSTR)friendlyName;
strFriendlyNames += '\n';
}
which finds the name of the device.
Hopefully that will help you in the right direction.
The C++ version based on @Will Dean answer.
#include <windows.h>
#include <initguid.h>
#include <devguid.h>
#include <setupapi.h>
void enumerateSerialPortsFriendlyNames()
{
SP_DEVINFO_DATA devInfoData = {};
devInfoData.cbSize = sizeof(devInfoData);
// get the tree containing the info for the ports
HDEVINFO hDeviceInfo = SetupDiGetClassDevs(&GUID_DEVCLASS_PORTS,
0,
nullptr,
DIGCF_PRESENT
);
if (hDeviceInfo == INVALID_HANDLE_VALUE)
{
return;
}
// iterate over all the devices in the tree
int nDevice = 0;
while (SetupDiEnumDeviceInfo(hDeviceInfo, // Our device tree
nDevice++, // The member to look for
&devInfoData))
{
DWORD regDataType;
DWORD reqSize = 0;
// find the size required to hold the device info
SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, nullptr, nullptr, 0, &reqSize);
BYTE* hardwareId = new BYTE[(reqSize > 1) ? reqSize : 1];
// now store it in a buffer
if (SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, ®DataType, hardwareId, sizeof(hardwareId) * reqSize, nullptr))
{
// find the size required to hold the friendly name
reqSize = 0;
SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, nullptr, nullptr, 0, &reqSize);
BYTE* friendlyName = new BYTE[(reqSize > 1) ? reqSize : 1];
// now store it in a buffer
if (!SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, nullptr, friendlyName, sizeof(friendlyName) * reqSize, nullptr))
{
// device does not have this property set
memset(friendlyName, 0, reqSize > 1 ? reqSize : 1);
}
// use friendlyName here
delete[] friendlyName;
}
delete[] hardwareId;
}
}
Glad it worked.
You could try:
Regex.Match(tmpstring, @"COM\s\d+").ToString()
for your string matching.
As .NET style points, I'd add a "using System.Text", and I wouldn't start local variable names with capitals, and if I was feeling really virtuous, I would probably put the SetupDiDestroyDeviceInfoList in a finally{} clause.