jcurl / RJCP.DLL.SerialPortStream

SerialPortStream is an independent implementation of System.IO.Ports.SerialPort and SerialStream for better reliability and maintainability. Default branch is 2.x and now has support for Mono with help of a C library.
Microsoft Public License
614 stars 196 forks source link

GetPortDescriptions() does not return USB descriptions #136

Closed wvdvegt closed 1 year ago

wvdvegt commented 1 year ago

In your code you use WMI's Win32_SerialPort to query for port names. Unfortunatly this query will not return descriptions of USB Serial devices (at least not in my code).

In order to return all descriptions I switched to querying WMI's Win32_PnPEntity instead (a much bigger list but it contains the missing descriptions).

jcurl commented 1 year ago

Do you have a description on how to map the name to a given Port? For example, I get this with my FTDI (has no description):

Caption=USB Serial Port (COM6) || Description=USB Serial Port || DeviceID=FTDIBUS\VID_0403+PID_6001+FTG6C5ETA\0000 || Manufacturer=FTDI || Name=USB Serial Port (COM6) || PNPClass=Ports || PNPDeviceID=FTDIBUS\VID_0403+PID_6001+FTG6C5ETA\0000 || Service=FTSER2K || Status=OK

I can't just grep the Caption/Name because not all of them have that, e.g. Com0Com. I have to try to map this to the device \Device\VCP0 which is COM6

Caption=com0com - serial port emulator || Description=com0com - serial port emulator || DeviceID=COM0COM\PORT\CNCA0 || Manufacturer=Vyacheslav Frolov || Name=com0com - serial port emulator || PNPClass=CNCPorts || PNPDeviceID=COM0COM\PORT\CNCA0 || Service=com0com || Status=OK

but is clearly a port to talk to:

wvdvegt commented 1 year ago

I use the following code to stitch the full WMI info onto the SerialPort/GetPortnames() list:

Note: The IniFile class is private code and not important the the process, just my method of traversing WMI info.

    Wmi wmi = new Wmi();
    using (IniFile ini = wmi[Wmi.WmiKeys.Win32_PnPEntity, new IniFile(IniFileType.Transient)])
    {
    //Unfortunatly we need the BIG list of PNP devices to get USB ports as well.

    String[] ports = SerialPort.GetPortNames();

    foreach (String section in ini.ReadSections())
    {
        String caption = ini.ReadString(section, "Caption", String.Empty);
        foreach (String port in ports)
        {
        String name = ini.ReadString(section, "Name", String.Empty);
        if (name.EndsWith(" (" + port + ")"))
        {
            Debug.WriteLine("Found Port: " + port + "[" + name + "]");

            name = name.Replace(" (" + port + ")", String.Empty);
            PortList.Add(new NameValue(port, name));
        }
        }
    }

    //Sort on Numeric part of Serial Port Name.
    PortList.Sort(delegate(NameValue c1, NameValue c2)
    {
        return Comparer<Int32>.Default.Compare(
        Int32.Parse(c1.Name.Substring(3)),
        Int32.Parse(c2.Name.Substring(3)));
    });

    //Some Diagnostics, Dump Ports from Serial.GetPortName() 
    //not present / detected in WMI List. 
    //Most of the time these seem to be Modems.
    foreach (String port in ports)
    {
        Boolean found = false;

        foreach (NameValue nv in PortList)
        {
        if (nv.Name == port)
        {
            found = true;
            break;
        }
        }

        // Missing are:
        // 1) Sierra Wireless MC8775 HSDPA Modem
        // 2) ThinkPad Modem

        if (!found)
        {
        Debug.WriteLine("Missing Port (probably a Modem): " + port);
        }
    }

    //Copy Sorted List into ComboBox.
    comboBox1.Items.AddRange(PortList.ToArray());
    }
jcurl commented 1 year ago

Thank you for your prompt reply. Your method is similar to how I have seen other implementations, assume the string at the end of the description maps to the COM port. I don't consider this reliable, as it's not really documented by Microsoft that I can see.

I can only see the proper way of using Windows API to map the COM port, device and descriptions together. I had a look at CfgMgr32.DLL or SetupApi.DLL, but this is going to be a lot of work to do it properly.

wvdvegt commented 1 year ago

Hi,

Yes, not ideal my method but it does result in at least readable descriptions for USB devices (in my case I use it to look for ‘mBed Serial Port’ devices).

With SerialPortStreams code I only see the comport number and I (and users) can’t distinguish it from modem as Intel(R) Active Management Technology - SOL ports (as for example is part of my development laptop).

If you need code for using SetupAPI you could have a look at my GhostBuster applications which enumerates all devices and allows to bulk remove ghosted devices. See https://bitbucket.org/wvd-vegt/ghostbuster/src/master/ and the screenshot below.

image

It seems to give the correct information (although only a textual link (as description/friendly name) between COM Port and device name (as did the code I posted earlier). The SetupAPI code certainly has a much higher complexity. The device properties (right click a device in GhostBuster) do not have a clear relation either (only textual).

              Device Description =  mbed Serial Port
                     Hardware Id =  USB\VID_0D28&PID_0204&MI_01
                Compatiblel ID's =  USB\CLASS_02&SUBCLASS_02&PROT_01; USB\CLASS_02&SUBCLASS_02; USB\CLASS_02
                         Service =  mbedSerial_x64
                           Class =  Ports
                      Class GUID =  {4d36e978-e325-11ce-bfc1-08002be10318}
                          Driver =  {4d36e978-e325-11ce-bfc1-08002be10318}\0003
                    Manufacturer =  mbed
                   Friendly Name =  mbed Serial Port (COM8)
            Location Information =  Port_#0003.Hub_#0005
                    Capabilities =  0094
             Enumerator Name (R) =  USB
              Device Address (R) =  0003
       Device Location Paths (R) =  PCIROOT(0)#PCI(1400)#USBROOT(0)#USB(4)#USB(2)#USB(3)#USB(3); ACPI(_SB_)#ACPI(PCI0)#ACPI(XHCI)#ACPI(URTH)#ACPI(HSP3)#USB(2)#USB(3)#USB(3)
               Base Container Id =  {7c152f30-76df-11ed-8058-448500b6227b}

Best regards

jcurl commented 1 year ago

I've implemented a small C++ program to enumerate through CfgMgr32.dll APIs and I still can't map the COM port to the device for FTDI.

I got so far as to enumerate all devices for FTDI, down to \Device\00000085 (this would change with different machines, and is represented in the Device Manager as the Physical Device Object Name).

But the port mapping in the registry is \Device\VCP0 REG_SZ COM6.

Until now, I haven't found a way to join the two together. Enumerating the devices using CfgMgr32 API, I don't see \Device\VCP0. Using WinObj, there doesn't seem to be any relationship either, \Device\00000085 and \Device\VCP0 appear to be two independent devices with \Device\VCP0 not showing up in the device tree. This makes me think the only relationship is in code, within the driver itself.

jcurl commented 1 year ago

@wvdvegt I've got a hint on how to move forward. But it will need further investigation. Perhaps you can check also for your serial port, and update your Ghostbuster to show the Physical Device? Perhaps on your system you can confirm what I've found and let me know the Win Version you're on?

In the registry Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\COM Name Arbiter\Devices

So I need to know how to find the symbolic link and get it's path, and then the CfgMgr32 API can be used to get the Friendly Name.

This testing was done on Windows 10.

wvdvegt commented 1 year ago

I'll have a look (as soon as I have some spare time).

Note: the port description is a USB only feature and it's text often originates from the HID/USB device where it's a hardcoded constant. There is also a map in the registry with comports in use (which has some sloppy housekeeping, see the Tools menu in my Ghostbuster utility). They seem to live longer then expected.

According to https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats the \?\ paths are DOS device paths (I'm not so sure about symlinks). So marked as legacy and therefor maybe best avoided. Below this section is a code fragment using 'new FileInfo(filename).Name' to match all kind of different DOS devices paths to one filename (but it seems to require a recent .net version). It would be interesting to feed the code the ftdibus device path.

jcurl commented 1 year ago

I've figured it out using the CfgMgr32 API and the usage of registry keys. Example C++ attached.

TestCfgMgr_v2.zip

Basically, I will enumerate all devices. I will use the CfgMgr32 API to get the registry key. Then query for the key "PortName". I print it out in the output of the device tree I iterate. It has a precompiled binary against VS2022 C++17 (saves me the effort of the P/Invoke for all the tests to start).

It seems to work for my FTDI, as well as a non-standard port Com0Com. When you run the test, the output should now show "PortName" with a COM port, this is the basis for getting all information about the driver.

wvdvegt commented 1 year ago

I tried the test program but i fail to see the mBed serial port (I only see the small flash drive it exposes as well).

I also fail to see the 'Intel(R) Active Management Technology - SQL (COM3)' device that is present in the device manager.

jcurl commented 1 year ago

If COM8 is ghosted, it won't show, but that is because I didn't enumerate devices that don't appear to be attached. I'll post a .NET console version later that includes also phantom devices.

jcurl commented 1 year ago

I tested phantom this morning using enumeration and it didn't show the ghosted ports as I expected. I'll have to work further on that.

@wvdvegt Could you post a screenshot of your registry key Computer\HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM, and then the output for the devices generated by the .NET tool attached? DeviceInfoDump.zip

I'm looking for any other relationship that might tie them together (if the PortName doesn't exist, perhaps the Physical Device would). If there's no mapping you can see, does searching for COM6 in the registry show anything else that can be exploited?

Sample of my registry: image

Sample of my .NET tool for the FTDI device Device: \Device\0000008f doesn't map, but Key: PortName=COM7 does:

              + FTDIBUS\VID_0403+PID_6001+FTDIU2EQA\0000
                - Status: DriverLoaded, Started, Disableable, NtEnumerator, NtDriver
                - ProbCode: None
                - Friendly Name: USB Serial Port (COM7)
                - Description: USB Serial Port
                - Service: FTSER2K
                - Manufacturer: FTDI
                - Class: Ports
                - Class GUID: {4d36e978-e325-11ce-bfc1-08002be10318}
                - Driver: {4d36e978-e325-11ce-bfc1-08002be10318}\0003
                - Device: \Device\0000008f
                - Config Flags: 0x00000000
                - Capabilities: UniqueId, SurpriseRemovalOk
                - Hardware IDs: FTDIBUS\COMPORT&VID_0403&PID_6001
                - Upper Filters: serenum
                - Base Container ID: {7ea29e85-1eac-59c9-bf8d-f87820381c2e}
                - Keys: PortName, PollingPeriod, ConfigData, MinReadTimeout, MinWriteTimeout, LatencyTimer
                \- Key: PortName=COM7
                \- Key: PollingPeriod=0
                \- Key: ConfigData=System.Byte[]
                \- Key: MinReadTimeout=0
                \- Key: MinWriteTimeout=0
                \- Key: LatencyTimer=16

The above can be found in the registry key Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\FTDIBUS\VID_0403+PID_6001+ftDIU2EQA\0000.

jcurl commented 1 year ago

I found an old T61p laptop, and I was able to install Intel AMT, that set up a COM port. It works as expected also.

        + PCI\VEN_8086&DEV_2A07&SUBSYS_20D017AA&REV_0C\3&21436425&0&1B
          - Status: DriverLoaded, Started, Disableable, NtEnumerator, NtDriver
          - ProbCode: None
          - Friendly Name: Intel(R) Active Management Technology - SOL (COM10)
          - Description: Intel(R) Active Management Technology - SOL
          - Service: Serial
          - Manufacturer: Intel
          - Class: Ports
          - Class GUID: {4d36e978-e325-11ce-bfc1-08002be10318}
          - Driver: {4d36e978-e325-11ce-bfc1-08002be10318}\0012
          - Location: PCI bus 0, device 3, function 3
          - Location Paths: PCIROOT(0)#PCI(0303)
          - Device: \Device\NTPNP_PCI0004
          - Config Flags: 0x00000000
          - Capabilities: None
          - Hardware IDs: PCI\VEN_8086&DEV_2A07&SUBSYS_20D017AA&REV_0C, PCI\VEN_8086&DEV_2A07&SUBSYS_20D017AA, PCI\VEN_8086&DEV_2A07&CC_070002, PCI\VEN_8086&DEV_2A07&CC_0700
          - Compatible IDs: PCI\VEN_8086&DEV_2A07&REV_0C, PCI\VEN_8086&DEV_2A07, PCI\VEN_8086&CC_070002, PCI\VEN_8086&CC_0700, PCI\VEN_8086, PCI\CC_070002, PCI\CC_0700
          - Upper Filters: serenum
          - Lower Filters: 
          - Base Container ID: {00000000-0000-0000-ffff-ffffffffffff}
          - Keys: PortName, PollingPeriod
          \- Key: PortName=COM10
          \- Key: PollingPeriod=0

this shows for the AMT on windows 7, I can use the proposed algorithm. It would be helpful to get more information about the results of the mbed devices. Thanks!

wvdvegt commented 1 year ago

Screenshot:

image

USB\VID_0D28&PID_0204\1010E6172A6A6963CE6906FEA340102FDE4D
                  - Status: DriverLoaded, Started, Disableable, Removable, NtEnumerator, NtDriver
                  - ProbCode: None
                  - Friendly Name: 
                  - Description: mbed Composite Device
                  - Service: mbedComposite
                  - Manufacturer: mbed
                  - Class: USB
                  - Class GUID: {36fc9e60-c465-11cf-8056-444553540000}
                  - Driver: {36fc9e60-c465-11cf-8056-444553540000}\0028
                  - Location: Port_#0003.Hub_#0005
                  - Location Paths: PCIROOT(0)#PCI(1400)#USBROOT(0)#USB(4)#USB(2)#USB(3), ACPI(_SB_)#ACPI(PCI0)#ACPI(XHCI)#ACPI(URTH)#ACPI(HSP3)#USB(2)#USB(3)
                  - Device: \Device\USBPDO-15
                  - Config Flags: 0x00000000
                  - Capabilities: Removable, UniqueId, SurpriseRemovalOk
                  - Hardware IDs: USB\VID_0D28&PID_0204&REV_0100, USB\VID_0D28&PID_0204
                  - Compatible IDs: USB\Class_08&SubClass_06&Prot_50, USB\Class_08&SubClass_06, USB\Class_08
                  - Upper Filters: 
                  - Lower Filters: 
                  - Base Container ID: {b1c77c14-2d6d-581c-a9b5-849c228d3560}
                  - Keys: UsbConfiguration, PnpIdPrefix, DriverUserInterfaceGuid, EnumerationRetryCount, SymbolicName, IfStateCon0If0, IfStateCon0If1, IfStateCon0If3
                  \- Key: UsbConfiguration=0
                  \- Key: PnpIdPrefix=MI
                  \- Key: DriverUserInterfaceGuid={4A199439-071A-44df-A8F4-0002A27CAF33}
                  \- Key: EnumerationRetryCount=0
                  \- Key: SymbolicName=\??\USB#VID_0D28&PID_0204#1010e6172a6a6963ce6906fea340102fde4d#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
                  \- Key: IfStateCon0If0=1
                  \- Key: IfStateCon0If1=1
                  \- Key: IfStateCon0If3=1
                  + USB\VID_0D28&PID_0204&MI_01\1010E6172A6A6963CE6906FEA340102FDE4D
                    - Status: DriverLoaded, Started, Disableable, Removable, NtEnumerator, NtDriver
                    - ProbCode: None
                    - Friendly Name: mbed Serial Port (COM6)
                    - Description: mbed Serial Port
                    - Service: mbedSerial_x64
                    - Manufacturer: mbed
                    - Class: Ports
                    - Class GUID: {4d36e978-e325-11ce-bfc1-08002be10318}
                    - Driver: {4d36e978-e325-11ce-bfc1-08002be10318}\0002
                    - Location: Port_#0003.Hub_#0005
                    - Location Paths: PCIROOT(0)#PCI(1400)#USBROOT(0)#USB(4)#USB(2)#USB(3)#USB(3), ACPI(_SB_)#ACPI(PCI0)#ACPI(XHCI)#ACPI(URTH)#ACPI(HSP3)#USB(2)#USB(3)#USB(3)
                    - Device: \Device\00000180
                    - Config Flags: 0x00000000
                    - Capabilities: Removable, UniqueId, SurpriseRemovalOk
                    - Hardware IDs: USB\VID_0D28&PID_0204&MI_01
                    - Compatible IDs: USB\CLASS_02&SUBCLASS_02&PROT_01, USB\CLASS_02&SUBCLASS_02, USB\CLASS_02
                    - Upper Filters: 
                    - Lower Filters: 
                    - Base Container ID: {48fa1b1b-64f4-11ed-8053-448500b6227b}
                    - Keys: PortName, PollingPeriod, ReadBufferSize, WriteBufferSize, UseLogicBuffer, ReadBufferCount, WriteBufferCount, SendLineCoding, SendLineState, SendBreak, OperationMode, ClearFeatureOnStart, VendorPipeReset, DefaultLineState, DriverUserInterfaceGuid, SendZeroOnReset
                    \- Key: PortName=COM6
                    \- Key: PollingPeriod=0
                    \- Key: ReadBufferSize=1024
                    \- Key: WriteBufferSize=1024
                    \- Key: UseLogicBuffer=0
                    \- Key: ReadBufferCount=3
                    \- Key: WriteBufferCount=3
                    \- Key: SendLineCoding=1
                    \- Key: SendLineState=1
                    \- Key: SendBreak=1
                    \- Key: OperationMode=3
                    \- Key: ClearFeatureOnStart=0
                    \- Key: VendorPipeReset=0
                    \- Key: DefaultLineState=0
                    \- Key: DriverUserInterfaceGuid={654920DB-6151-462d-B3F5-86560C7F71FE}
                    \- Key: SendZeroOnReset=0
jcurl commented 1 year ago

That's excellent news! Thank you. This shows that:

Then I do indeed have a solution I can work on.

wvdvegt commented 1 year ago

Think I found a direct link between the WMI info and the COM port.

The wmi contains a key/value: DeviceID=USB\VID_0D28&PID_0204&MI_01\1010E6172A6A6963CE6906FEA340102FDE4D key (which directly yields the full Enum\USB subkey).

under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB

Example of a regkey on my laptop is: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB\VID_0D28&PID_0204&MI_01\1010e6172a6a6963ce6906fea340102fde4d. It contains the friendly name as FriendlyName='mbed Serial Port (COM8)'.

In this key there is a subkey 'Device Parameters' that contains a key/value PortName with in my case the value 'COM6'.

jcurl commented 1 year ago

OK. Good that you found also your solution. I will only be returning devices that are currently connected and plugged in with the output of the SERIALCOMM keys. If it's not connected, SerialPortStream won't enumerate or return the values. It would be confusing to users of the API why not-connected devices appear otherwise.

But with that said, they way I'd do this is to get the list of all devices with the CfgMgr API, and then locate each node with CM_Locate_DevNode. I'll be using the NORMAL parameter, where you'd want to use the PHANTOM parameter to get the details. Then you don't have to rely on assumptions where devices appear in the registry. Also note, MS documents the device identifier as just that, a key, but it should not be interpreted in any way, thus suggesting that the CfgMgr32 API should be used instead. And indeed it does, it's called CM_Get_DevNode_Registry_Property. So you don't need to iterate over the key either.

If you wait a couple of day's I'll publish my library (.NET -> .NET Core) on my wrappers of the CfgMgr API, but i think you've got most of this already with the GhostBuster tool you mentioned.

jcurl commented 1 year ago

In this key there is a subkey 'Device Parameters' that contains a key/value PortName with in my case the value 'COM6'.

You should instead of assuming the location of the registry (CfgMgr32 API), use a code snippet like this:

        /// <summary>
        /// Gets the device property.
        /// </summary>
        /// <typeparam name="T">The type to convert the result to.</typeparam>
        /// <param name="keyName">Name of the key.</param>
        /// <param name="defValue">The value to return if the key doesn't exist.</param>
        /// <returns>The result. If the key doesn't exist, the default value is returned.</returns>
        public T GetDeviceProperty<T>(string keyName, T defValue)
        {
            using (RegistryKey driverKey = GetDeviceKey()) {
                return driverKey == null ?
                    defValue :
                    (T)driverKey.GetValue(keyName);
            }
        }

        private RegistryKey GetDeviceKey()
        {
            CfgMgr32.CONFIGRET ret = CfgMgr32.CM_Open_DevNode_Key(
                m_DevInst, Kernel32.REGSAM.KEY_READ, 0, CfgMgr32.RegDisposition.OpenExisting,
                out SafeRegistryHandle key, 0);
            if (ret != CfgMgr32.CONFIGRET.CR_SUCCESS) {
                if (ret != CfgMgr32.CONFIGRET.CR_NO_SUCH_REGISTRY_KEY) {
                    Log.CfgMgr.TraceEvent(TraceEventType.Warning,
                        $"{m_Name}: Couldn't get device key, return {ret}");
                }
                return null;
            }
            if (key.IsInvalid || key.IsClosed) {
                Log.CfgMgr.TraceEvent(TraceEventType.Error,
                    $"{m_Name}: Couldn't get device key, registry is invalid or closed");
                return null;
            }

            return RegistryKey.FromHandle(key);
        }

The handle is obtained, e.g. if you have the device iD, with CM_Locate_DevNode. That returns a handle, which you can use in the code snippet above. Use NORMAL if you think the device must be present, or PHANTOM if the device is installed, but not present.

Give it PortName as the key. In the background, it does exactly what you say. But I guess with Hardware Profiles, and other features that might be edge cases, using the CfgMgr API might be a more robust solution.

wvdvegt commented 1 year ago

I'll happily wait a few days for your update !

My wmi based approach has one big disadvantage: it's very slow. But it worked fine for what i needed it for.

I will also test your code on some other (old) machines. I remember seeing a modem device somewhere on one of them without COMx in it's name, in which case my approach fails. Luckily never an issue for me as my code never uses modems anyway. Accessing the registry directly is not something I like as it easily raises access rights issues or triggers corporate security related software.

jcurl commented 1 year ago

I still need to write the code (first for SerialPortStream 2.x), but it will be based on the code from my new repository: https://github.com/jcurl/RJCP.DLL.DeviceMgr

If you compile it, there's DeviceInstance.GetList(), and you can then get the property for PortName for all of them. The code is simple to understand. It works on Windows XP until Win11 with .NET 4 until .NET Core latest. You can use this as a starting point for your own code if you want.

jcurl commented 1 year ago

You can now test the branch v2.x, which has this code. If you need a binary, let me know.

wvdvegt commented 1 year ago

Here a test from my old development X61s laptop:

1) DeviceManager: image

2) Results: [Serial Ports] COM1 - Communications Port [(Standard port types)] COM3 - ThinkPad Modem COM7 - Sierra Wireless AT Command Port (UMTS) [Sierra Wireless] COM5 - Sierra Wireless DM Port (UMTS) [Sierra Wireless] COM12 - Sierra Wireless CNS Port (UMTS) [Sierra Wireless] COM6 - Sierra Wireless Data Port (UMTS) [Sierra Wireless] COM4 - Sierra Wireless MC8775 HSDPA Modem

3) Differences (these shows some extra info between square brackets). See the COM1 and the Sierra Wireless ports.

4) The pure modems are correct (no port visible in the devicemanager desciption):

So the info is there, the list of devices is complete and it's pretty fast (much faster then my wmi approach).

I would hoewever prefer the description to (closely) match the one in the device manager. So a) move the part in square brackets (mfg/manufacturer) to a separate Manufacturer property in the PortDescription class and b) have the PortDescription's Description to include '(COMnn)' suffices for the serial ports (not the modem).

It would also be nice if you could add a enum for devices being a COM port or a modem so you can easily keep them apart.

wvdvegt commented 1 year ago

FYI: I have not tested with BlueTooth devices exposing a serial port. I remember having some but their where abouts are unkown.

jcurl commented 1 year ago

I was considering using the friendly name that had the port, or the description which did not have the port. I considered the additional COM port name superfluous (it's already present in the name), but I'll switch back to the friendly name so it's similar to device manager.

I like the idea of extending the port description object with the manufacturer name, I'll do that, and I'll extend the ToString() method to show the information, but the description containing only the data I received.

Could you tell me which framework you tested on? .NET Core (Standard 1.5), or .NET Framework? I ask as I don't have a way to know if the device manager code I wrote actually covers the modem case. I don't see a driver that I have which I can test on.

jcurl commented 1 year ago

For the last part:

It would also be nice if you could add a enum for devices being a COM port or a modem so you can easily keep them apart.

this is a serial port communications library and I feel differentiating the device types doesn't belong here. Ultimately, I'd like to dump the WMI code completely.

wvdvegt commented 1 year ago

Normally I show only the description to a user, so the comport in it isn't a dupe.

wvdvegt commented 1 year ago

I used the good old 4.x think 4.0 full.

I'll try to test it again with a netstd framework.

jcurl commented 1 year ago

Updated, you can test again if you wish.

wvdvegt commented 1 year ago

I just need to be able to differentiate between ports and modems so I can remove modems from the list. My guess the device class is enough.

jcurl commented 1 year ago

To help, could you send the output of my tool that prints all properties for your COM3? It should have a device of \Device\Serial0, or you could send the entire output to my email address if you'd like. Com0Com for example has a different Class GUID than normal ports, so this might not be enough, unless you know the Class GUID for modems.

jcurl commented 1 year ago

I turned on my old ThinkPad, it appears to have a modem also. You could use my new project RJCP.DLL.DeviceMgr and check if the class is a Modem. I still don't think it's a good idea to add this to my code, as there are more classes than just Modem and Ports. For example, Com0Com has a class of CNCPorts, and if someone looks for just Ports, they'll miss this, even though it will work.

But as said, I believe you can use my other library and just get the list and sort it out there.

wvdvegt commented 1 year ago

Your suggestion to use ToString() to return a description closely matching the devicemanager is a good idea.

What I need to disciminate between the device classes is a small modification

in WinNativeSerial.cs's QueryDevice add the following lines to the foreach (after assigning the port.Description):

port.Class = GetDeviceProperty(devInst, CfgMgr32.CM_DRP.CLASS) ?? string.Empty;

and add a:

public string Class { get; set; } property to the PortDescription class.

Results in the output of my test application:

[Ports]
COM1 - Communications Port (COM1)
COM7 - Sierra Wireless AT Command Port (UMTS) (COM7)
COM5 - Sierra Wireless DM Port (UMTS) (COM5)
COM12 - Sierra Wireless CNS Port (UMTS) (COM12)
COM6 - Sierra Wireless Data Port (UMTS) (COM6)

[Modem]
COM3 - ThinkPad Modem - Modem
COM4 - Sierra Wireless MC8775 HSDPA Modem - Modem

and on my development laptop:

[Ports]
COM3 - Intel(R) Active Management Technology - SOL (COM3) - Ports
COM6 - mbed Serial Port (COM6) - Ports

So i can easily discriminate between serial ports and modems.

jcurl commented 1 year ago

Hello, I did consider providing the class name, but decided it is not for this project. The SerialPortStream is for communicating with serial ports and getting a list, but getting the class is not well defined. A device driver can be a serial port, but must not necessarily be a Ports or a Modem class. For example, Com0Com is a CNCPorts class.

Also, this has no bearing on trying to extend this for Linux, as it has no such concept. So while it would be easy to do, I'd recommend you look into my other library and one query everything that is Windows specific.

The query itself only takes about 3ms all manner of extra functionality, now or in the future, can be had with my other library.

Thank you very mich for your support and help! Without your testing and input it would have been much harder.

I will prepare a new NuGet package for 2.4.1 soon.

wvdvegt commented 1 year ago

Glad to be of help.

As for the device classes, if you need a customer to access a Com0Com port between a multitude of other devices, the class is an easy pointer to narrow down the search/selection.

Nowadays my software uses the mainly description to match (because the mBed MCU happens to have a fixed one), so selection is automatic for customers if there is only one attached (else it's manual).

For older software using old Serial ports or USB to Serial converters the selection is more problematic and waking devices is not without risk (the reason for my qeustion to expose the class as well). In the past we had a UPS that switched off power if you sent it the letter 'A' (which is also common for modems and it's AT command).

On the other hand, your Linux remarks are valid as well (to keep the versions as similar as possible).

jcurl commented 1 year ago

I don't think relying on the class name is sufficient. If you want to look for a specific device, it would be better to query the device direct if it is USB and the vendor and device identifiers. That would be far more robust, and doesn't change due to a driver update.

jcurl commented 1 year ago

Released in 2.4.1, closing.