Recent

Author Topic: [SOLVED] USB connected device- How to access 'Bus Reported Device Description'  (Read 1524 times)

Username

  • New Member
  • *
  • Posts: 22
Hi,

OS is Windows 10 64 bit, but preferably need general Windows info.
FPC version 3.3.2
Laz Ver 2.2.0

I have a device connected to USB Port, I need to find it, ie, determine which com port it is connected to. The only field I can see in the Windows Device Manager ComPorts list that easily identifies my device is the 'Bus Reported Device Description' Field in the details tab.
 
I have searched and so far struck out on just how to do this. I downloaded Jurrasic Pork's Utilwmi and played with that a bit, example works fine but it looks like it is not straightforward or maybe impossible to get at this field with that. I'm not well versed in this stuff, and most of it is out of my league, I fear.

I would really appreciate any help on this. TIA
« Last Edit: June 27, 2022, 09:48:21 pm by Username »

jamie

  • Hero Member
  • *****
  • Posts: 4746
If you would like to and handy at reading some C code here is the link to the source code for USBView from MS.

https://github.com/Microsoft/Windows-driver-samples/tree/master/usb/usbview

Beware that you may need some create some procedure calls to DLL's etc.
The only true wisdom is knowing you know nothing

Username

  • New Member
  • *
  • Posts: 22
If you would like to and handy at reading some C code here is the link to the source code for USBView from MS.

https://github.com/Microsoft/Windows-driver-samples/tree/master/usb/usbview

Beware that you may need some create some procedure calls to DLL's etc.


Thanks Jamie,
I looked through it and I think I'm probably not able to sort out the parts I need and translate in any reasonable time period (Like say, the rest of my life...) but I appreciate your suggestion. However that, for me is like drinking from a firehose. :)

Thausand

  • Sr. Member
  • ****
  • Posts: 291
If usbview too much then may be can help:

- Powershell wmi (that may be find you device ?) read: https://devblogs.microsoft.com/powershell/displaying-usb-devices-using-wmi/
- .net/vb (use windows API call so can translate to pascal), read: https://stackoverflow.com/questions/26732291/how-to-get-bus-reported-device-description-using-c-sharp

Username

  • New Member
  • *
  • Posts: 22
If usbview too much then may be can help:

- Powershell wmi (that may be find you device ?) read: https://devblogs.microsoft.com/powershell/displaying-usb-devices-using-wmi/
- .net/vb (use windows API call so can translate to pascal), read: https://stackoverflow.com/questions/26732291/how-to-get-bus-reported-device-description-using-c-sharp

Thanks Thausand,
The VB code in the second link looks promising. I'm surprised that doing this is such a complex task. Having the VB code in one piece should make it easier to put something together. I appreciate the links. I came across the Powershell post at stackoverflow earlier. I'm not familiar with powershell at all so I passed on that one.

Thausand

  • Sr. Member
  • ****
  • Posts: 291
I'm not familiar with powershell at all so I passed on that one.
Powershell is for test alone  :)

1) It standard windows so open powershell and type command. If see you device then utiliwmi can also make it see. Powershell command need translate to utilwmi.
2)  page also show vba script and use two wmi exe command for show device, so translate for utilwmi more easy.

I think you description is found in second vba exe command.

The command list PNP id that is unique identify. If you know you device PNP id then you can may be recognize USB-device that you search ?

2 cent  :)

Quote
The VB code in the second link looks promising.
Yes, there description is read explicit and that what you look for can may be identify you device.

Quote
I'm surprised that doing this is such a complex task.
All things windows is complex (for me it is) :D

Jurassic Pork

  • Hero Member
  • *****
  • Posts: 1132
Hello,
I have a device connected to USB Port, I need to find it, ie, determine which com port it is connected to

I have searched and so far struck out on just how to do this. I downloaded Jurrasic Pork's Utilwmi and played with that a bit, example works fine but it looks like it is not straightforward or maybe impossible to get at this field with that.
with WMI you can try to use the class Win32_PnPEntity with a Filter on PnpClass with value Ports :
Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button5Click(Sender: TObject);
  2. var
  3.   WMIResult         : TFPObjectList;
  4.   i                 : Integer;
  5.   PropNamesIndex    : Integer;
  6.   PropNames         : Array[0..3] of String =
  7.                       ( 'Manufacturer','Caption','Name','Description');
  8.   PropName       : String;
  9.   PropValue      : String;
  10. begin
  11.     WMIResult := GetWMIInfo('Win32_PnPEntity', PropNames,
  12.                  'Where PnpClass = ''Ports'' ');
  13.   for i := 0 to Pred(WMIResult.Count) do
  14.   begin
  15.     Memo1.Append('================================================');
  16.     for PropNamesIndex := Low(PropNames) to High(PropNames) do
  17.     begin
  18.       PropName :=   TStringList(WMIResult[i]).Names[PropNamesIndex];
  19.       PropValue :=   TStringList(WMIResult[i]).ValueFromIndex[PropNamesIndex];
  20.       Memo1.Append(PropName + ' : ' +
  21.               PropValue);
  22.     end;
  23.   end;
  24.   // Clean up
  25.   WMIResult.Free;          
   

see Attachment fot Result  on my computer  Windows 10 Lazarus 2.0.12   and  with  two ports USB Serial Port   connected .

if it doesn't work :
1 - Remove the filter to see all the pnp_entities
2 - Try another filter ex :
Code: Pascal  [Select][+][-]
  1. 'Where Name LIKE ''%(COM%'' ');      

Friendly, J.P
« Last Edit: June 26, 2022, 07:41:48 am by Jurassic Pork »
Jurassic computer : Sinclair ZX81 - Zilog Z80A à 3,25 MHz - RAM 1 Ko - ROM 8 Ko

DonAlfredo

  • Hero Member
  • *****
  • Posts: 1561
Here is another one. A bit the same, a bit different.

Code: Pascal  [Select][+][-]
  1. procedure EnumerateCOMPorts(ComList: TStrings);
  2. const
  3.   GUID_DEVINTERFACE_COMPORT:TGUID='{86E0D1E0-8089-11D0-9CE4-08003E301F73}';
  4. var
  5.   cbRequired          : DWORD;
  6.   hdev                : HDEVINFO;
  7.   idev                : Integer;
  8.   did                 : TSPDeviceInterfaceData;
  9.   pdidd               : PSPDeviceInterfaceDetailData;
  10.   s                   : string;
  11.   PropertyBuffer      : array[0..255] of Char;
  12.   DeviceInfoData      : TSPDevInfoData;
  13.   PropertyRegDataType : DWORD;
  14.   RequiredSize        : DWORD;
  15.   Key                 : HKEY;
  16.   PortName            : string;
  17.   RegType,Count       : DWORD;
  18. begin
  19.   // enumerate the com ports
  20.   LoadSetupApi;
  21.   hdev :=  SetupDiGetClassDevs(@GUID_DEVINTERFACE_COMPORT, nil, 0,  DIGCF_PRESENT OR DIGCF_DEVICEINTERFACE);
  22.   if (INVALID_HANDLE_VALUE<> THandle(hdev)) then
  23.   begin
  24.     try
  25.       idev:=0;
  26.       ZeroMemory(@did, SizeOf(did));
  27.       did.cbSize := SizeOf(did);
  28.       repeat
  29.         if (SetupDiEnumDeviceInterfaces(hdev, nil, GUID_DEVINTERFACE_COMPORT, idev, did)) then
  30.         begin
  31.            cbRequired := 0;
  32.            SetupDiGetDeviceInterfaceDetail(hdev, @did, nil, 0, cbRequired, nil);
  33.            if (ERROR_INSUFFICIENT_BUFFER= GetLastError()) then
  34.            begin
  35.              pdidd:=AllocMem(cbRequired);
  36.              try
  37.                pdidd^.cbSize := SizeOf(TSPDeviceInterfaceDetailData);
  38.                DeviceInfoData.cbSize:= SizeOf(DeviceInfoData);
  39.                RequiredSize:=0;
  40.                if (SetupDiGetDeviceInterfaceDetail(hdev, @did, pdidd, cbRequired, RequiredSize, @DeviceInfoData)) then
  41.                begin
  42.  
  43.                  PropertyRegDataType:=0;
  44.                  RequiredSize:=0;
  45.                  s:='';
  46.  
  47.                  {
  48.                  if SetupDiGetDeviceRegistryProperty(hdev, DeviceInfoData, SPDRP_FRIENDLYNAME, PropertyRegDataType,  PBYTE(@PropertyBuffer[0]), SizeOf(PropertyBuffer), RequiredSize) then
  49.                  begin
  50.                    s:=s+PropertyBuffer;
  51.                  end;
  52.                  s:=s+DefaultFormatSettings.ListSeparator;
  53.  
  54.                  if SetupDiGetDeviceRegistryProperty(hdev, DeviceInfoData, SPDRP_DEVICEDESC, PropertyRegDataType,  PBYTE(@PropertyBuffer[0]), SizeOf(PropertyBuffer), RequiredSize) then
  55.                  begin
  56.                    s:=s+PropertyBuffer;
  57.                  end;
  58.                  s:=s+DefaultFormatSettings.ListSeparator;
  59.  
  60.                  if SetupDiGetDeviceRegistryProperty(hdev, DeviceInfoData, SPDRP_MFG, PropertyRegDataType,  PBYTE(@PropertyBuffer[0]), SizeOf(PropertyBuffer), RequiredSize) then
  61.                  begin
  62.                    s:=s+PropertyBuffer;
  63.                  end;
  64.                  s:=s+DefaultFormatSettings.ListSeparator;
  65.                  }
  66.  
  67.                  Key := SetupDiOpenDevRegKey(hdev, DeviceInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_QUERY_VALUE);
  68.                  if (Key<>0) then
  69.                  try
  70.                    SetLength({%H-}PortName, MAX_PATH);
  71.                    Count := Length(PortName);
  72.                    Windows.RegQueryValueEx(Key, 'PortName', nil, @RegType, PByte(PChar(PortName)), @Count);
  73.                    if (Count>0) AND (RegType=REG_SZ) then
  74.                    begin
  75.                      SetLength(PortName, Count - 1);
  76.                      s:=s+PortName;
  77.                    end;
  78.                  finally
  79.                    Windows.RegCloseKey(Key);
  80.                  end;
  81.                  s:=s+DefaultFormatSettings.ListSeparator;
  82.  
  83.                  if Length(s)>0 then
  84.                  begin
  85.                    SetLength(s,Length(s)-1);
  86.                    ComList.Append(s);
  87.                  end;
  88.                 end
  89.                 else
  90.                 begin
  91.                   RaiseLastOSError;
  92.                 end;
  93.               finally
  94.                 FreeMem(pdidd);
  95.               end;
  96.            end;
  97.         end
  98.         else
  99.         begin
  100.           break;
  101.         end;
  102.         inc(idev);
  103.       until false;
  104.     finally
  105.       SetupDiDestroyDeviceInfoList(hdev);
  106.     end;
  107.   end;
  108.   UnloadSetupApi;
  109. end;
  110.  

bobby100

  • Full Member
  • ***
  • Posts: 102
    • Malzilla
https://gitlab.com/bobby100 - my Lazarus components and units
https://sourceforge.net/u/boban_spasic/profile/ - my open source apps

https://malzilla.org/ - remainder at my previous life as a web security expert

Username

  • New Member
  • *
  • Posts: 22
Wow! Thanks to everyone who so kindly replied to this post. There's a lot to look at up there!  ;D

So nice to have such a helpful community here, and Lazarus is just absolutely incredible. Kudos to all who have dedicated so much work on it.

Again thanks Jamie, Thausand, JP, DonAlfredo, Bobby100. I will mark this solved when I have something that works for me!

Username

  • New Member
  • *
  • Posts: 22
I'm marking this SOLVED even thought I never did figure out how to get the Bus Reported Device Description. Even so, I was able to accomplish what I needed to do using JP's code plus some I added. (Thanks @Jurassic Pork !)

The PNPDeviceID field (and the DeviceID and several others) are adequate to identify the device and are easy to access. So I used that plus I got the Com Port from the Caption Field. It's also in the Name field and several others as well.

Here's the code that works, along with a screen shot of the results. The highlighted lines are what I added to JP's code for this specific task.

Code: Pascal  [Select][+][-]
  1. procedure TForm1.ButtonJPClick(Sender: TObject);
  2. var
  3.   //*******************************
  4.   // My Vars:
  5.   PropValues        : Array[0..4] of String;
  6.   SubFields         : TStringArray;
  7.   VendorID_Part     : String;
  8.   ProductID_Part    : String;
  9.   Instance_Part     : String;
  10.   ComPort_Part      : String;
  11.   //*******************************
  12.   WMIResult         : TFPObjectList;
  13.   i                 : Integer;
  14.   PropNamesIndex    : Integer;
  15.   PropNames         : Array[0..4] of String =
  16.                     ( 'Manufacturer','Caption','Name','Description','PnPDeviceID');
  17.   PropName       : String;
  18.   PropValue      : String;
  19. begin
  20.     WMIResult := GetWMIInfo('Win32_PnPEntity', PropNames,
  21.                  'Where PnpClass = ''Ports'' ');
  22.   for i := 0 to Pred(WMIResult.Count) do
  23.   begin
  24.     Memo1.Append('================================================');
  25.     for PropNamesIndex := Low(PropNames) to High(PropNames) do
  26.     begin
  27.       PropName :=   TStringList(WMIResult[i]).Names[PropNamesIndex];
  28.       PropValue :=   TStringList(WMIResult[i]).ValueFromIndex[PropNamesIndex];
  29.       Memo1.Append(PropName + ' : ' +
  30.               PropValue);
  31.       //*********************************************************************
  32.       PropValues[PropNamesIndex]:=PropValue;
  33.       if leftStr(PropValue,21)='USB\VID_2044&PID_1020' then
  34.       begin
  35.         SubFields :=  PropValue.Split('\&');
  36.         VendorID_Part := SubFields[1];
  37.         ProductID_Part := subFields[2];
  38.         SubFields :=  PropValue.Split('\');
  39.         Instance_Part := SubFields[2];
  40.         SubFields := PropValues[1].Split('()');
  41.         ComPort_Part := SubFields[1];
  42.         ShowMessage('Found the com port, it is '+ComPort_Part+
  43.         #13#10+'VendorID = '+VendorID_Part+#10#13+
  44.         'ProductID = '+ProductID_Part+#10#13+
  45.         'Instance = '+Instance_Part+#13#10+'ComPort = '+Comport_Part);
  46.         SerialPortDriver1.Active  :=  False;
  47.         SerialPortDriver1.ComPort :=  ComPort_Part;
  48.         SerialPortDriver1.Active  :=  True;
  49.       end;
  50.       //*********************************************************************
  51.     end;
  52.   end;
  53.   // Clean up
  54.   WMIResult.Free;
  55. end;

bobby100

  • Full Member
  • ***
  • Posts: 102
    • Malzilla
Are you working on identifying one specific device or on identifying every kind of COM port devices?

The manufacturer in your code is the driver manufacturer. It will be "Microsoft" for 99% of the drivers that are delivered with Windows or through Windows Update.
There is also a 'friendly name' for devices, as seen in the Device Manager. The device manufacturer can mostly be seen in the 'friendly name' (google for 'USB friendly name' to get more info about the concept. On Windows, all the devices have 'friendly names')

Just test your code along with COM Port notifier from my link, and see the differences.
https://gitlab.com/bobby100 - my Lazarus components and units
https://sourceforge.net/u/boban_spasic/profile/ - my open source apps

https://malzilla.org/ - remainder at my previous life as a web security expert

Username

  • New Member
  • *
  • Posts: 22
@bobby100,
Thanks again for your suggestions. I did look at your link, and if I ever need that type of functionality I'll certainly consider it. However it's not what I needed for this project.

Are you working on identifying one specific device or on identifying every kind of COM port devices?
The code is only for this specific device. It's a programmable controller, and I'm writing a windows program using Laz and PascalSCADA to program it. The code I pasted isn't the finished code. The manufacturer stuff is there just left-over from JP's example code, and I'll remove that later. I left it in just for illustration in case someone might find the code useful as a little additional functionality for JP's code. The only thing I really want to do here is determine which port the controller is plugged into, programmatically, (and transparently) as regards the user's experience, and only when the program actually runs.
Quote
The manufacturer in your code is the driver manufacturer. It will be "Microsoft" for 99% of the drivers that are delivered with Windows or through Windows Update.
Yes, I realize that. Microsoft is not the manufacturer.
Quote
There is also a 'friendly name' for devices, as seen in the Device Manager. The device manufacturer can mostly be seen in the 'friendly name' (google for 'USB friendly name' to get more info about the concept. On Windows, all the devices have 'friendly names')
This Vendor didn't use the friendly name field apparently. It just has 'USB Serial Device (COM8)' which I assume is default Windows behavior.
Quote
Just test your code along with COM Port notifier from my link, and see the differences.
I looked at your link. It seems interesting but it's just not what I needed for this instance. I need just this one particular device, and I only need to know what port it's on, so this is fine as is. Plus, I can see all the fields using Windows Device Manager if I want to see them outside of a program. And, I did check all of them. The only one that actually has the actual Name of the device is Bus Reported Device Description. But the PnPDeviceID is just as good for ID'ing the unit, and apparently FAR easier to discover programmatically.

Thanks, Dave

avra

  • Hero Member
  • *****
  • Posts: 2367
    • Additional info
It's a programmable controller, and I'm writing a windows program using Laz and PascalSCADA to program it.

If you know that your users will always use some specific USB<>RS485 or similar adapter to connect to PLC, then you might query WMI to ask for manufacturer info of all serial ports and search for a match. Or you might ask the user to choose only the first time and then compare and match that info on all later attempts.

But there might be a better way if you only have one PLC, and that is to get a list of all COM ports, try to open them one by one, and for those that can be opened try to send manually a specific message that only PLC should be able to understand and reply (like MODBUS query - but do it before Pascal SCADA communication driver initialization), and see if an expected answer shows up.

That way you could identify COM port of a PLC without user's assistance, whatever serial adapter is used.
« Last Edit: June 28, 2022, 08:41:02 am by avra »
ct2laz - Conversion between Lazarus and CodeTyphon
bithelpers - Bit manipulation for standard types
pasettimino - Siemens S7 PLC lib

MarkMLl

  • Hero Member
  • *****
  • Posts: 5119
If you know that your users will always use some specific USB<>RS485 or similar adapter to connect to PLC, then you might query WMI to ask for manufacturer info of all serial ports and search for a match. Or you might ask the user to choose only the first time and then compare and match that info on all later attempts.

That's unworkable though if there are multiple devices in different roles.

I think OP's asking about Windows, but if I can throw in a Linux perspective for completeness: any device which is plugged in and recognised by a kernel driver creates various things in the /sys tree, but the precise layout is driver-specific i.e. the relative positioning of device name etc. for (say) an FTDI serial adapter might not be the same as those for e.g. a CH340.

If the device isn't recognised by the kernel then the best one can do is use the libusb API.

In the case of genuine FTDI serial adapters, each is supplied uniquely serialised in EEPROM and if necessary this can be overwritten with e.g. the serial number of the kit it's interfacing with. Don't expect this to work with rip-off copies, of which there are several variants.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

 

TinyPortal © 2005-2018