Get USB devices serial number

by Pierre Bellisle

'Get USB device serial number by Pierre Bellisle 2011-10-25

#COMPILE EXE '#Win 8.04#
#DIM ALL
#INCLUDE "Win32Api.inc"

%USB_STRING_DESCRIPTOR_TYPE =   3
%USB_REQUEST_GET_DESCRIPTOR =   6
%MAXIMUM_USB_STRING_LENGTH  = 255
%DEVICECONNECTED            =   1

%FILE_DEVICE_UNKNOWN        =  34
%FILE_DEVICE_USB            = %FILE_DEVICE_UNKNOWN
%METHOD_BUFFERED            =   0
%FILE_ANY_ACCESS            =   0

%IOCTL_USB_GET_NODE_CONNECTION_NAME            = &H00220414
%IOCTL_GET_HCD_DRIVERKEY_NAME                  = &H00220424
%IOCTL_USB_GET_NODE_INFORMATION                = &H00220408
%IOCTL_USB_GET_ROOT_HUB_NAME                   = &H00220408
%IOCTL_USB_GET_NODE_CONNECTION_INFORMATION     = &H0022040C
%IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION = &H00220410

TYPE USB_DEVICE_DESCRIPTOR 'Packet.Data 1 - USB_CONFIGURATION_DESCRIPTOR_TYPE
 bLength            AS BYTE 'Specifies the length, in bytes, of this descriptor.
 bDescriptorType    AS BYTE 'Specifies the descriptor type. Must be set to USB_DEVICE_DESCRIPTOR_TYPE (1).
 bcdUSB             AS WORD 'Identifies the version of the USB specification that this descriptor structure complies with. This value is a binary-coded decimal number.
 bDeviceClass       AS BYTE 'Specifies the class code of the device as assigned by the USB specification group.
 bDeviceSubClass    AS BYTE 'Specifies the subclass code of the device as assigned by the USB specification group.
 bDeviceProtocol    AS BYTE 'Specifies the protocol code of the device as assigned by the USB specification group.
 bMaxPacketSize0    AS BYTE 'Specifies the maximum packet size, in bytes, for endpoint zero of the device. The value must be set to 8, 16, 32, or 64.
 idVendor           AS WORD 'Specifies the vendor identifier for the device as assigned by the USB specification committee.
 idProduct          AS WORD 'Specifies the product identifier. This value is assigned by the manufacturer and is device-specific.
 bcdDevice          AS WORD 'Identifies the version of the device. This value is a binary-coded decimal number.
 iManufacturer      AS BYTE 'Specifies a device-defined index of the string descriptor that provides a string containing the name of the manufacturer of this device.
 iProduct           AS BYTE 'Specifies a device-defined index of the string descriptor that provides a string that contains a description of the device.
 iSerialNumber      AS BYTE 'Specifies a device-defined index of the string descriptor that provides a string that contains a manufacturer-determined serial number for the device.
 bNumConfigurations AS BYTE 'Specifies the total number of possible configurations for the device.
END TYPE

TYPE USB_ENDPOINT_DESCRIPTOR 'Packet.Data 5 - USB_CONFIGURATION_DESCRIPTOR_TYPE
 bLength          AS BYTE 'Specifies the length, in bytes, of this descriptor.
 bDescriptorType  AS BYTE 'Specifies the descriptor type. Must be set to USB_ENDPOINT_DESCRIPTOR_TYPE.
 bEndpointAddress AS BYTE 'Specifies the USB-defined endpoint address. The four low-order bits specify the endpoint number. The high-order bit specifies the direction of data flow on this endpoint: 1 for in, 0 for out.
 bmAttributes     AS BYTE 'The two low-order bits specify the endpoint type, one of USB_ENDPOINT_TYPE_CONTROL, USB_ENDPOINT_TYPE_ISOCHRONOUS, USB_ENDPOINT_TYPE_BULK, or USB_ENDPOINT_TYPE_INTERRUPT.
 wMaxPacketSize   AS WORD 'Specifies the maximum packet size that can be sent from or to this endpoint.
 bInterval        AS BYTE 'For interrupt endpoints, bInterval contains the polling interval. For other types of endpoint, this value should be ignored. This value reflects the device's configuration in firmware. Drivers cannot change it.
END TYPE

TYPE USB_COMMON_DESCRIPTOR 'Packet.Data header - USB_CONFIGURATION_DESCRIPTOR_TYPE
 bLength         AS BYTE  'Lenght in bytes of the descriptor.
 bDescriptorType AS BYTE  'The descriptor type. 1 for USB_DEVICE_DESCRIPTOR, 2 for USB_CONFIGURATION_DESCRIPTOR, 4 for USB_INTERFACE_DESCRIPTOR, 5 for USB_ENDPOINT_DESCRIPTOR, 33 for HID_DESCRIPTOR.
END TYPE

TYPE SETUP_PACKET
 bmRequest AS BYTE 'The type of USB device request, standard, class, or vendor
 bRequest  AS BYTE '0x06 value indicates a request of GET_DESCRIPTOR.
 wValue    AS WORD 'Type of descriptor to retrieve in the high byte of wValue and the descriptor index in the low byte.
 wIndex    AS WORD 'The device-specific index of the descriptor that is to be retrieved.
 wLength   AS WORD 'The length of the data that is transferred during the second phase of the control transfer.
END TYPE

TYPE USB_DESCRIPTOR_REQUEST
 ConnectionIndex AS LONG                                'The port whose descriptors are retrieved.
 SETUP_PACKET                                           'See SETUP_PACKET type.
 DATA            AS STRING * %MAXIMUM_USB_STRING_LENGTH 'On output from the IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION I/O control request, this member contains the retrieved descriptors.
END TYPE

TYPE USB_HUB_DESCRIPTOR
 bDescriptorLength   AS BYTE        'The length, in bytes of the descriptor.
 bDescriptorType     AS BYTE        'The descriptor type. For hub descriptors, this value should be 0x29.
 bNumberOfPorts      AS BYTE        'The number of ports on the hub.
 wHubCharacteristics AS WORD        'The hub characteristics. For more information about this member, see Universal Serial Bus Specification.
 bPowerOnToPowerGood AS BYTE        'The time, in 2-millisecond intervals, that it takes the device to turn on completely. For more information about this member, see Universal Serial Bus Specification.
 bHubControlCurrent  AS BYTE        'The maximum current requirements, in milliamperes, of the controller component of the hub.
 bRemoveAndPowerMask AS STRING * 65 'Not currently implemented. Do not use this member. This member implements DeviceRemovable and PortPwrCtrlMask fields of the hub descriptor. For more information about these fields, see Universal Serial Bus Specification.
END TYPE

TYPE USB_HUB_NODE
 UsbHub      AS WORD 'Indicates that the device is a hub.
 UsbMIParent AS WORD 'Indicates that the device is a composite device with multiple interfaces.
END TYPE

TYPE USB_HUB_INFORMATION
 HubDescriptor   AS USB_HUB_DESCRIPTOR 'A USB_HUB_DESCRIPTOR structure that contains selected information from the hub descriptor.
 HubIsBusPowered AS LONG               'A Boolean value that indicates whether the hub is powered. If TRUE, the hub is powered. If FALSE, the hub is not powered.
END TYPE

TYPE USB_MI_PARENT_INFORMATION
 NumberOfInterfaces AS LONG 'The number of interfaces on the composite device.
END TYPE

UNION USB_NODE_INFORMATION_UNION
 HubInformation      AS USB_HUB_INFORMATION       'A USB_HUB_INFORMATION structure that contains information about a parent hub device.
 MiParentInformation AS USB_MI_PARENT_INFORMATION 'A USB_MI_PARENT_INFORMATION structure that contains information about a parent non-hub, composite device.
END UNION

TYPE USB_NODE_INFORMATION
 NodeType AS USB_HUB_NODE   'A USB_HUB_NODE enumerator that indicates whether the parent device is a hub or a non-hub composite device.
 USB_NODE_INFORMATION_UNION 'See USB_NODE_INFORMATION_UNION union
END TYPE

TYPE USB_HCD_DRIVERKEY_NAME
 ActualLength  AS DWORD                               'The length, in bytes, of the string in the DriverKeyName member.
 DriverKeyName AS STRING * %MAXIMUM_USB_STRING_LENGTH 'wChar array, a NULL-terminated Unicode driver key name for the USB host controller.
END TYPE

TYPE USB_NODE_CONNECTION_NAME
 ConnectionIndex AS LONG                                 'A value that is greater than or equal to 1 that specifies the number of the port to which the hub is attached.
 ActualLength    AS LONG                                 'The length, in bytes, of the attached hub's symbolic link.
 NAME            AS STRING * %MAXIMUM_USB_STRING_LENGTH  'A Unicode symbolic link for the downstream hub that is attached to the port that is indicated by ConnectionIndex.
END TYPE

TYPE USB_PIPE_INFO
 EndpointDescriptor AS USB_ENDPOINT_DESCRIPTOR 'See USB_ENDPOINT_DESCRIPTOR type
 ScheduleOffset     AS DWORD                   'Indicates the schedule offset assigned to the endpoint for this pipe.
END TYPE

TYPE USB_NODE_CONNECTION_INFORMATION
 ConnectionIndex           AS LONG                  'A value that is greater than or equal to 1 that specifies the number of the port.
 DeviceDescriptor          AS USB_DEVICE_DESCRIPTOR 'A USB_DEVICE_DESCRIPTOR structure that reports the USB device descriptor that is returned by the attached device during enumeration.
 CurrentConfigurationValue AS BYTE                  'Value used with SetConfiguration request to specify that current configuration of the device that is connected to the indicated port. See Universal Serial Bus Specification.
 LowSpeed                  AS BYTE                  'If TRUE, the port and its connected device are currently operating at a low speed.
 DeviceIsHub               AS BYTE                  'Value that indicates if the device that is attached to the port is a hub. If TRUE, the device that is attached to the port is a hub. If FALSE, the device is not a hub.
 DeviceAddress             AS WORD                  'The USB-assigned, bus-relative address of the device that is attached to the port.
 NumberOfOpenPipes         AS DWORD                 'The number of open USB pipes that are associated with the port.
 ConnectionStatus          AS DWORD                 'A USB_CONNECTION_STATUS-typed enumerator that indicates the connection status.
 PipeList(1 TO 32)         AS USB_PIPE_INFO         'An array of USB_PIPE_INFO structures that describes the open pipes that are associated with the port.
END TYPE

TYPE USB_LANGUAGE_ID
 bLength         AS BYTE 'Size of Descriptor in Bytes
 bDescriptorType AS BYTE 'Constant, string Descriptor (0x03)
 wLangId(0)      AS WORD 'First of an array of language id
END TYPE                                            'Pipe descriptions include the schedule offset of the pipe and the associated endpoint descriptor. This information can be used to calculate bandwidth usage.
'______________________________________________________________________________

FUNCTION regDosDevidesGet(BYREF sDosDevicesArray() AS STRING) AS LONG
 LOCAL Retval         AS LONG
 LOCAL hKey           AS DWORD
 LOCAL ValueLen       AS DWORD
 LOCAL DataLen        AS DWORD
 LOCAL dwRegType      AS DWORD
 LOCAL dwValueCount   AS DWORD
 LOCAL DosDeviceCount AS DWORD
 LOCAL zValueName     AS ASCIIZ * %MAX_PATH
 LOCAL sData          AS STRING * %MAX_PATH

 Retval = RegOpenKeyEx(%HKEY_LOCAL_MACHINE, "SYSTEM\MountedDevices", BYVAL %NULL, %KEY_READ, hKey)
 IF Retval = %ERROR_SUCCESS THEN
   dwValueCount = 0
   DosDeviceCount = 0
   DO
     ValueLen = %MAX_PATH
     DataLen = %MAX_PATH
     Retval = RegEnumValue(hKey, dwValueCount, zValueName, ValueLen, _
                           BYVAL %Null, dwRegType, sData, DataLen)
     IF Retval = %ERROR_NO_MORE_ITEMS THEN Retval = %ERROR_SUCCESS : EXIT DO
     IF LCASE$(LEFT$(zValueName, 12)) = "\dosdevices\" THEN
       IF DataLen > 12 THEN
         REDIM PRESERVE sDosDevicesArray(0 TO DosDeviceCount) AS STRING
         sDosDevicesArray(DosDeviceCount) = RIGHT$(zValueName, 2) & ACODE$(LEFT$(sData, DataLen)) 'If unicode
         INCR DosDeviceCount
       END IF
     END IF
     INCR dwValueCount
   LOOP WHILE Retval = %ERROR_SUCCESS
   RegCloseKey(hKey)
 END IF
 FUNCTION = DosDeviceCount

END FUNCTION
'______________________________________________________________________________

FUNCTION usbDeviceLanguageId(BYVAL hHub AS DWORD, BYVAL PortIndex AS LONG, BYREF LanguageIdArray() AS WORD)AS LONG
 LOCAL Request         AS USB_DESCRIPTOR_REQUEST
 LOCAL pUsbLanguageId  AS USB_LANGUAGE_ID POINTER
 LOCAL BytesReturned   AS DWORD
 LOCAL LanguageIdCount AS LONG
 LOCAL Success         AS LONG

 IF LanguageIdArray(1) = 0 THEN 'Get the languages ids
   Request.ConnectionIndex = PortIndex
   Request.bmRequest       = &H80
   Request.bRequest        = %USB_REQUEST_GET_DESCRIPTOR
   Request.wValue          = MAK(WORD, 0, %USB_STRING_DESCRIPTOR_TYPE) 'No Index here
   Request.wLength         = %MAXIMUM_USB_STRING_LENGTH '3 * 256 '4 'LanguageIdArray(1) size = 4, 1033
   Success = DeviceIoControl(hHub, %IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, _
                             BYVAL VARPTR(Request), SIZEOF(Request), _
                             BYVAL VARPTR(Request), SIZEOF(Request), BytesReturned, BYVAL %NULL)
   IF Success THEN
     pUsbLanguageId = VARPTR(Request.Data)
     IF @pUsbLanguageId.bDescriptorType = 3 THEN 'Constant, string Descriptor = 3
       LanguageIdCount = (@pUsbLanguageId.bLength \ 2) - 1
       DIM LanguageIdArrayFromIo(1 TO LanguageIdCount) AS WORD AT VARPTR(Request.Data) + 2
       REDIM LanguageIdArray(1 TO LanguageIdCount)
       COPYMEMORY(BYVAL VARPTR(LanguageIdArray(1)), BYVAL VARPTR(LanguageIdArrayFromIo(1)), LanguageIdCount * 2)

       'LOCAL sBuffer   AS STRING
       'LOCAL zLanguage AS ASCIIZ * %MAX_PATH
       'LOCAL Looper    AS LONG
       'FOR Looper = 1 TO LanguageIdCount
       '  sBuffer = sBuffer & "0x" & HEX$(LanguageIdArray(Looper), 4) & " (" & FORMAT$(LanguageIdArray(Looper)) & ") "
       '  GetLocaleInfo MAK(DWORD, LanguageIdArray(Looper), %SORT_DEFAULT), %LOCALE_SENGLANGUAGE, zLanguage, %MAX_PATH
       '  sBuffer = sBuffer & zLanguage & " ("
       '  GetLocaleInfo MAK(DWORD, LanguageIdArray(Looper), %SORT_DEFAULT), %LOCALE_SENGCOUNTRY, zLanguage, %MAX_PATH
       '  sBuffer = sBuffer & zLanguage &  "), "
       '  GetLocaleInfo MAK(DWORD, LanguageIdArray(Looper), %SORT_DEFAULT), %LOCALE_SLANGUAGE, zLanguage, %MAX_PATH
       '  sBuffer = sBuffer & zLanguage & $CRLF
       'NEXT
       'MSGBOX sBuffer
     END IF
   END IF
 END IF

END FUNCTION
'______________________________________________________________________________

FUNCTION usbDeviceString(BYVAL hHub AS DWORD, BYVAL PortIndex AS LONG, BYVAL Index AS BYTE)AS STRING
 LOCAL Request                 AS USB_DESCRIPTOR_REQUEST
 LOCAL pBuffer                 AS USB_COMMON_DESCRIPTOR POINTER
 LOCAL sBuffer                 AS STRING
 LOCAL BytesReturned           AS DWORD
 LOCAL StringLen               AS LONG
 LOCAL Success                 AS LONG
 DIM   LanguageIdArray(1 TO 1) AS WORD

 usbDeviceLanguageId(hHub, PortIndex, LanguageIdArray())

 Request.ConnectionIndex = PortIndex
 Request.bmRequest       = &H80
 Request.bRequest        = %USB_REQUEST_GET_DESCRIPTOR
 Request.wValue          = MAK(WORD, Index, %USB_STRING_DESCRIPTOR_TYPE)
 Request.wIndex          = LanguageIdArray(1) 'Use only the first one
 Request.wLength         = %MAXIMUM_USB_STRING_LENGTH

 Success = DeviceIoControl(hHub, %IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, _
                           BYVAL VARPTR(Request), SIZEOF(Request), _
                           BYVAL VARPTR(Request), SIZEOF(Request), BytesReturned, BYVAL %NULL)
 IF Success THEN
   pBuffer = VARPTR(Request.DATA)
   StringLen = @pBuffer.bLength
   sBuffer = ACODE$(MID$(Request.DATA, 3, StringLen - 2))
   sBuffer = RTRIM$(sBuffer, ANY $SPC & $NUL)
   IF sBuffer = "" THEN sBuffer = "None" 'Descriptor is blank
   FUNCTION = sBuffer
 ELSE
   FUNCTION = "None" 'No string descriptor returned
 END IF

END FUNCTION
'______________________________________________________________________________

FUNCTION usbDeviceInfo(BYVAL hHub AS DWORD, BYVAL PortIndex AS LONG, _
                       BYVAL pBuffer AS USB_DEVICE_DESCRIPTOR POINTER, BYREF sDosDevicesArray() AS STRING)AS STRING
 LOCAL sManufacturer   AS STRING
 LOCAL sProduct        AS STRING
 LOCAL sDiskSerial     AS STRING
 LOCAL sTry            AS STRING
 LOCAL sDrive          AS STRING
 LOCAL zVolume         AS ASCIIZ * 50
 LOCAL zVolumeSerial   AS ASCIIZ * 10
 LOCAL zFileSystem     AS ASCIIZ * 10
 LOCAL FileSystemFlags AS DWORD
 LOCAL VolumeSerial    AS DWORD
 LOCAL MaxLenFileName  AS LONG
 LOCAL Looper          AS LONG
 LOCAL CharPos         AS LONG

 sManufacturer = usbDeviceString(hHub, PortIndex, @pBuffer.iManufacturer)
 sProduct      = usbDeviceString(hHub, PortIndex, @pBuffer.iProduct)
 sDiskSerial   = usbDeviceString(hHub, PortIndex, @pBuffer.iSerialNumber)
 sDrive        = "None"

 FOR Looper = 0 TO UBOUND(sDosDevicesArray())
   zVolume       = "-"
   zFileSystem   = "-"
   zVolumeSerial = "-"

   sTry = LCASE$(sManufacturer)
   REPLACE $SPC WITH "_" IN sTry
   IF (sTry = "none") OR (sTry = "generic") OR (sTry = "?") THEN sTry = "&ven_"
   CharPos = INSTR(LCASE$(sDosDevicesArray(Looper)), sTry)
   IF CharPos THEN
     sTry = LCASE$(sProduct)
     REPLACE $SPC WITH "_" IN sTry
     IF (sTry = "none") OR (sTry = "mass_storage")  OR (sTry = "usb_mass_storage") THEN sTry = "&prod_"
     CharPos = INSTR(CharPos, LCASE$(sDosDevicesArray(Looper)), sTry)
     IF CharPos THEN
       sTry = LCASE$(sDiskSerial) & "&0"
       REPLACE $SPC WITH "_" IN sTry
       CharPos = INSTR(CharPos, LCASE$(sDosDevicesArray(Looper)), sTry)
       IF CharPos THEN
         sDrive = LEFT$(sDosDevicesArray(Looper), 2)
         GetVolumeInformation(BYVAL STRPTR(sDrive), zVolume, SIZEOF(zVolume), VolumeSerial, MaxLenFileName, _
                              FileSystemFlags, zFileSystem, SIZEOF(zFileSystem))
         zVolumeSerial = LEFT$(HEX$(VolumeSerial), 4) & "-" & RIGHT$(HEX$(VolumeSerial), 4)
         IF LEN(zVolume) = 0 THEN zVolume = "None"
         EXIT FOR
       END IF
     END IF
   END IF
 NEXT

 FUNCTION = "id Vendor    " & $TAB & "0x" & HEX$(@pBuffer.idVendor, 4)  & $CRLF & _
            "id Product   " & $TAB & "0x" & HEX$(@pBuffer.idProduct, 4) & $CRLF & _
            "Manufacturer " & $TAB & sManufacturer                      & $CRLF & _
            "Product      " & $TAB & sProduct                           & $CRLF & _
            "SerialNumber " & $TAB & sDiskSerial                        & $CRLF & _
            "Drive letter " & $TAB & sDrive                             & $CRLF & _
            "Volume       " & $TAB & zVolume                            & $CRLF & _
            "Volume Serial" & $TAB & zVolumeSerial                      & $CRLF & _
            "FileSystem   " & $TAB & zFileSystem                        & $CRLF & $CRLF

END FUNCTION
'______________________________________________________________________________

SUB usbPortEnum(BYVAL hHub AS DWORD, BYVAL PortCount AS BYTE, BYVAL HubDepth AS BYTE, _
                BYREF sBuffer AS STRING, BYREF sDosDevicesArray() AS STRING)
 LOCAL  ConnectionInformation AS USB_NODE_CONNECTION_INFORMATION
 LOCAL  NodeInformation       AS USB_NODE_INFORMATION
 LOCAL  NodeConnection        AS USB_NODE_CONNECTION_NAME
 LOCAL  SecurityAttr          AS SECURITY_ATTRIBUTES 'Needed for Win2000
 LOCAL  NodeConnectionName    AS ASCIIZ * %MAXIMUM_USB_STRING_LENGTH
 LOCAL  sDeviceInfo           AS STRING
 LOCAL  hNodeConnection       AS DWORD
 LOCAL  BytesReturned         AS DWORD
 LOCAL  Success               AS LONG
 LOCAL  PortIndex             AS LONG
 LOCAL  HubWanted             AS LONG

 SecurityAttr.nLength              = SIZEOF(SECURITY_ATTRIBUTES)
 SecurityAttr.lpSecurityDescriptor = 0
 SecurityAttr.bInheritHandle       = %FALSE

 FOR PortIndex = 1 TO PortCount 'Iterate each ports and see what is connected
   ConnectionInformation.ConnectionIndex = PortIndex

   Success = DeviceIoControl(hHub, %IOCTL_USB_GET_NODE_CONNECTION_INFORMATION, _
                             BYVAL VARPTR(ConnectionInformation), SIZEOF(ConnectionInformation), _
                             BYVAL VARPTR(ConnectionInformation), SIZEOF(ConnectionInformation), _
                             BytesReturned, BYVAL %NULL)
   IF Success THEN
     IF ConnectionInformation.ConnectionStatus = %DEVICECONNECTED THEN
       IF ConnectionInformation.DeviceIsHub THEN 'It's a HUB, we need to iterate his ports.
         NodeConnection.ConnectionIndex = PortIndex
         Success = DeviceIoControl(hHub, %IOCTL_USB_GET_NODE_CONNECTION_NAME, _
                                   BYVAL VARPTR(NodeConnection), SIZEOF(NodeConnection), _
                                   BYVAL VARPTR(NodeConnection), SIZEOF(NodeConnection), _
                                   BytesReturned, BYVAL %NULL)
         IF Success THEN
           NodeConnectionName = ACODE$(LEFT$(NodeConnection.NAME, NodeConnection.ActualLength))
           NodeConnectionName = "\\.\" & NodeConnectionName
           hNodeConnection = CreateFile(NodeConnectionName, %GENERIC_READ, %FILE_SHARE_READ, _
                                        SecurityAttr, %OPEN_EXISTING, 0, BYVAL %NULL)
           Success = DeviceIoControl(hNodeConnection, %IOCTL_USB_GET_NODE_INFORMATION, _
                                     BYVAL VARPTR(NodeInformation), SIZEOF(NodeInformation), _
                                     BYVAL VARPTR(NodeInformation), SIZEOF(NodeInformation), _
                                     BytesReturned, BYVAL %NULL)
           IF Success THEN
             HubWanted = %FALSE
             IF HubWanted THEN
               sBuffer = sBuffer & usbDeviceInfo(hHub, PortIndex, VARPTR(ConnectionInformation.DeviceDescriptor), _
                                                 BYREF sDosDevicesArray())
             ELSE
               usbDeviceInfo(hHub, PortIndex, VARPTR(ConnectionInformation.DeviceDescriptor), _
                             BYREF sDosDevicesArray())
             END IF
             'Recursive function call
             usbPortEnum(hNodeConnection, NodeInformation.HubInformation.HubDescriptor.bNumberOfPorts, _
                         HubDepth + 1, sBuffer, sDosDevicesArray())
           END IF
           CloseHandle(hNodeConnection)
         END IF
       ELSE
         sDeviceInfo = usbDeviceInfo(hHub, PortIndex, VARPTR(ConnectionInformation.DeviceDescriptor), _
                                     BYREF sDosDevicesArray())
         sBuffer = sBuffer & sDeviceInfo
       END IF
     END IF
   END IF
 NEXT

END SUB
'______________________________________________________________________________

SUB usbHostControllerInfo(BYVAL hHostController AS DWORD, BYREF sBuffer AS STRING, BYREF sDosDevicesArray()AS STRING)
 LOCAL SecurityAttr             AS SECURITY_ATTRIBUTES 'Needed for Win2000
 LOCAL NodeInformation          AS USB_NODE_INFORMATION
 LOCAL DriverKeyName            AS USB_HCD_DRIVERKEY_NAME
 LOCAL RootHubName              AS ASCIIZ * %MAXIMUM_USB_STRING_LENGTH
 LOCAL hRootHub                 AS DWORD
 LOCAL BytesReturned            AS LONG
 LOCAL Success                  AS LONG

 SecurityAttr.nLength              = SIZEOF(SECURITY_ATTRIBUTES)
 SecurityAttr.lpSecurityDescriptor = 0
 SecurityAttr.bInheritHandle       = %FALSE

 Success = DeviceIoControl(hHostController, %IOCTL_GET_HCD_DRIVERKEY_NAME, _
                           BYVAL VARPTR(DriverKeyName), SIZEOF(DriverKeyName), _
                           BYVAL VARPTR(DriverKeyName), SIZEOF(DriverKeyName), _
                           BytesReturned, BYVAL %NULL)
 IF Success THEN
   Success = DeviceIoControl(hHostController, %IOCTL_USB_GET_ROOT_HUB_NAME, _
                             BYVAL VARPTR(DriverKeyName), SIZEOF(DriverKeyName), _
                             BYVAL VARPTR(DriverKeyName), SIZEOF(DriverKeyName), _
                             BytesReturned, BYVAL %NULL)
   IF Success THEN
     RootHubName = ACODE$(LEFT$(DriverKeyName.DriverKeyName, DriverKeyName.ActualLength))
     RootHubName = "\\.\" & RootHubName
     hRootHub = CreateFile(RootHubName, %GENERIC_READ, %FILE_SHARE_READ, _
                           SecurityAttr, %OPEN_EXISTING, BYVAL %NULL, BYVAL %NULL)
     IF hRootHub <> %INVALID_HANDLE_VALUE THEN
       Success = DeviceIoControl(hRootHub, %IOCTL_USB_GET_NODE_INFORMATION, _
                                 BYVAL VARPTR(NodeInformation), SIZEOF(NodeInformation), _
                                 BYVAL VARPTR(NodeInformation), SIZEOF(NodeInformation), _
                                 BytesReturned, BYVAL %NULL)
       IF Success THEN
         usbPortEnum(hRootHub, NodeInformation.HubInformation.HubDescriptor.bNumberOfPorts, _
                     0, sBuffer, sDosDevicesArray())
         CloseHandle(hRootHub)
       END IF
     END IF
   END IF
 END IF

END SUB
'______________________________________________________________________________

FUNCTION UsbDeviceSerialNumberGet() AS STRING
 LOCAL SecurityAttr             AS SECURITY_ATTRIBUTES 'Needed for Win2000
 LOCAL HostControllerName       AS ASCIIZ * 12
 LOCAL sBuffer                  AS STRING
 DIM   sDosDevicesArray(0 TO 0) AS STRING
 LOCAL hHostController          AS DWORD
 LOCAL Looper                   AS LONG
 LOCAL DosDeviceCount           AS DWORD

 SecurityAttr.nLength              = SIZEOF(SECURITY_ATTRIBUTES)
 SecurityAttr.lpSecurityDescriptor = 0
 SecurityAttr.bInheritHandle       = %FALSE

 DosDeviceCount = regDosDevidesGet(sDosDevicesArray())

 FOR Looper = 0 TO 25
   HostControllerName = "\\.\HCD" & FORMAT$(Looper)
   hHostController    = CreateFile(HostControllerName, %GENERIC_READ, %FILE_SHARE_READ, _
                                   SecurityAttr, %OPEN_EXISTING, BYVAL %NULL, BYVAL %NULL)
   IF GetLastError = 0 THEN
     IF (hHostController <> %INVALID_HANDLE_VALUE) THEN
       usbHostControllerInfo(hHostController, sBuffer, sDosDevicesArray())
       CloseHandle(hHostController)
     END IF
   END IF
 NEXT

 FUNCTION = sBuffer

END FUNCTION
'______________________________________________________________________________

FUNCTION PBMAIN

 MessageBox(%HWND_DESKTOP, UsbDeviceSerialNumberGet, "USB serial number", 64)

END FUNCTION
'______________________________________________________________________________
'