66#include < string.h>
77#include < windows.h>
88#include < Setupapi.h>
9+ #include < initguid.h>
10+ #include < devpkey.h>
911#include < devguid.h>
1012#pragma comment(lib, "setupapi.lib")
1113
14+ #define ARRAY_SIZE (arr ) (sizeof (arr)/sizeof (arr[0 ]))
15+
1216#define MAX_BUFFER_SIZE 1000
1317
18+ // As per https://msdn.microsoft.com/en-us/library/windows/desktop/ms724872(v=vs.85).aspx
19+ #define MAX_REGISTRY_KEY_SIZE 255
20+
1421// Declare type of pointer to CancelIoEx function
1522typedef BOOL (WINAPI *CancelIoExType)(HANDLE hFile, LPOVERLAPPED lpOverlapped);
1623
@@ -610,6 +617,162 @@ NAN_METHOD(List) {
610617 uv_queue_work (uv_default_loop (), req, EIO_List, (uv_after_work_cb)EIO_AfterList);
611618}
612619
620+ // It's possible that the s/n is a construct and not the s/n of the parent USB
621+ // composite device. This performs some convoluted registry lookups to fetch the USB s/n.
622+ void getSerialNumber (const char *vid,
623+ const char *pid,
624+ const HDEVINFO hDevInfo,
625+ SP_DEVINFO_DATA deviceInfoData,
626+ const unsigned int maxSerialNumberLength,
627+ char * serialNumber) {
628+ _snprintf_s (serialNumber, maxSerialNumberLength, _TRUNCATE, " " );
629+ if (vid == NULL || pid == NULL ) {
630+ return ;
631+ }
632+
633+ DWORD dwSize;
634+ WCHAR szWUuidBuffer[MAX_BUFFER_SIZE];
635+ WCHAR containerUuid[MAX_BUFFER_SIZE];
636+
637+
638+ // Fetch the "Container ID" for this device node. In USB context, this "Container
639+ // ID" refers to the composite USB device, i.e. the USB device as a whole, not
640+ // just one of its interfaces with a serial port driver attached.
641+
642+ // From https://stackoverflow.com/questions/3438366/setupdigetdeviceproperty-usage-example:
643+ // Because this is not compiled with UNICODE defined, the call to SetupDiGetDevicePropertyW
644+ // has to be setup manually.
645+ DEVPROPTYPE ulPropertyType;
646+ typedef BOOL (WINAPI *FN_SetupDiGetDevicePropertyW)(
647+ __in HDEVINFO DeviceInfoSet,
648+ __in PSP_DEVINFO_DATA DeviceInfoData,
649+ __in const DEVPROPKEY *PropertyKey,
650+ __out DEVPROPTYPE *PropertyType,
651+ __out_opt PBYTE PropertyBuffer,
652+ __in DWORD PropertyBufferSize,
653+ __out_opt PDWORD RequiredSize,
654+ __in DWORD Flags);
655+
656+ FN_SetupDiGetDevicePropertyW fn_SetupDiGetDevicePropertyW = (FN_SetupDiGetDevicePropertyW)
657+ GetProcAddress (GetModuleHandle (TEXT (" Setupapi.dll" )), " SetupDiGetDevicePropertyW" );
658+
659+ if (fn_SetupDiGetDevicePropertyW (
660+ hDevInfo,
661+ &deviceInfoData,
662+ &DEVPKEY_Device_ContainerId,
663+ &ulPropertyType,
664+ reinterpret_cast <BYTE*>(szWUuidBuffer),
665+ sizeof (szWUuidBuffer),
666+ &dwSize,
667+ 0 )) {
668+ szWUuidBuffer[dwSize] = ' \0 ' ;
669+
670+ // Given the UUID bytes, build up a (widechar) string from it. There's some mangling
671+ // going on.
672+ StringFromGUID2 ((REFGUID)szWUuidBuffer, containerUuid, ARRAY_SIZE (containerUuid));
673+ } else {
674+ // Container UUID could not be fetched, return empty serial number.
675+ return ;
676+ }
677+
678+ // NOTE: Devices might have a containerUuid like {00000000-0000-0000-FFFF-FFFFFFFFFFFF}
679+ // This means they're non-removable, and are not handled (yet).
680+ // Maybe they should inherit the s/n from somewhere else.
681+
682+ // Sanitize input - for whatever reason, StringFromGUID2() returns a WCHAR* but
683+ // the comparisons later need a plain old char*, in lowercase ASCII.
684+ char wantedUuid[MAX_BUFFER_SIZE];
685+ _snprintf_s (wantedUuid, MAX_BUFFER_SIZE, _TRUNCATE, " %ws" , containerUuid);
686+ strlwr (wantedUuid);
687+
688+ // Iterate through all the USB devices with the given VendorID/ProductID
689+
690+ HKEY vendorProductHKey;
691+ DWORD retCode;
692+ char hkeyPath[MAX_BUFFER_SIZE];
693+
694+ _snprintf_s (hkeyPath, MAX_BUFFER_SIZE, _TRUNCATE, " SYSTEM\\ CurrentControlSet\\ Enum\\ USB\\ VID_%s&PID_%s" , vid, pid);
695+
696+ retCode = RegOpenKeyEx (
697+ HKEY_LOCAL_MACHINE,
698+ hkeyPath,
699+ 0 ,
700+ KEY_READ,
701+ &vendorProductHKey);
702+
703+ if (retCode == ERROR_SUCCESS) {
704+ DWORD serialNumbersCount = 0 ; // number of subkeys
705+
706+ // Fetch how many subkeys there are for this VendorID/ProductID pair.
707+ // That's the number of devices for this VendorID/ProductID known to this machine.
708+
709+ retCode = RegQueryInfoKey (
710+ vendorProductHKey, // hkey handle
711+ NULL , // buffer for class name
712+ NULL , // size of class string
713+ NULL , // reserved
714+ &serialNumbersCount, // number of subkeys
715+ NULL , // longest subkey size
716+ NULL , // longest class string
717+ NULL , // number of values for this key
718+ NULL , // longest value name
719+ NULL , // longest value data
720+ NULL , // security descriptor
721+ NULL ); // last write time
722+
723+ if (retCode == ERROR_SUCCESS && serialNumbersCount > 0 ) {
724+ for (unsigned int i=0 ; i < serialNumbersCount; i++) {
725+ // Each of the subkeys here is the serial number of a USB device with the
726+ // given VendorId/ProductId. Now fetch the string for the S/N.
727+ DWORD serialNumberLength = maxSerialNumberLength;
728+ retCode = RegEnumKeyEx (vendorProductHKey,
729+ i,
730+ serialNumber,
731+ &serialNumberLength,
732+ NULL ,
733+ NULL ,
734+ NULL ,
735+ NULL );
736+
737+ if (retCode == ERROR_SUCCESS) {
738+ // Lookup info for VID_(vendorId)&PID_(productId)\(serialnumber)
739+
740+ _snprintf_s (hkeyPath, MAX_BUFFER_SIZE, _TRUNCATE,
741+ " SYSTEM\\ CurrentControlSet\\ Enum\\ USB\\ VID_%s&PID_%s\\ %s" ,
742+ vid, pid, serialNumber);
743+
744+ HKEY deviceHKey;
745+
746+ if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, hkeyPath, 0 , KEY_READ, &deviceHKey) == ERROR_SUCCESS) {
747+ char readUuid[MAX_BUFFER_SIZE];
748+ DWORD readSize = sizeof (readUuid);
749+
750+ // Query VID_(vendorId)&PID_(productId)\(serialnumber)\ContainerID
751+ DWORD retCode = RegQueryValueEx (deviceHKey, " ContainerID" , NULL , NULL , (LPBYTE)&readUuid, &readSize);
752+ if (retCode == ERROR_SUCCESS) {
753+ readUuid[readSize] = ' \0 ' ;
754+ if (strcmp (wantedUuid, readUuid) == 0 ) {
755+ // The ContainerID UUIDs match, return now that serialNumber has
756+ // the right value.
757+ RegCloseKey (deviceHKey);
758+ RegCloseKey (vendorProductHKey);
759+ return ;
760+ }
761+ }
762+ }
763+ RegCloseKey (deviceHKey);
764+ }
765+ }
766+ }
767+
768+ /* In case we did not obtain the path, for whatever reason, we close the key and return an empty string. */
769+ RegCloseKey (vendorProductHKey);
770+ }
771+
772+ _snprintf_s (serialNumber, maxSerialNumberLength, _TRUNCATE, " " );
773+ return ;
774+ }
775+
613776void EIO_List (uv_work_t * req) {
614777 ListBaton* data = static_cast <ListBaton*>(req->data );
615778
@@ -619,13 +782,14 @@ void EIO_List(uv_work_t* req) {
619782
620783 int memberIndex = 0 ;
621784 DWORD dwSize, dwPropertyRegDataType;
622- char szBuffer[400 ];
785+ char szBuffer[MAX_BUFFER_SIZE ];
623786 char *pnpId;
624787 char *vendorId;
625788 char *productId;
626789 char *name;
627790 char *manufacturer;
628791 char *locationId;
792+ char serialNumber[MAX_REGISTRY_KEY_SIZE];
629793 bool isCom;
630794 while (true ) {
631795 pnpId = NULL ;
@@ -660,6 +824,8 @@ void EIO_List(uv_work_t* req) {
660824 productId = copySubstring (productId, 4 );
661825 }
662826
827+ getSerialNumber (vendorId, productId, hDevInfo, deviceInfoData, MAX_REGISTRY_KEY_SIZE, serialNumber);
828+
663829 if (SetupDiGetDeviceRegistryProperty (hDevInfo, &deviceInfoData,
664830 SPDRP_LOCATION_INFORMATION, &dwPropertyRegDataType,
665831 reinterpret_cast <BYTE*>(szBuffer),
@@ -693,6 +859,7 @@ void EIO_List(uv_work_t* req) {
693859 if (productId) {
694860 resultItem->productId = productId;
695861 }
862+ resultItem->serialNumber = serialNumber;
696863 if (locationId) {
697864 resultItem->locationId = locationId;
698865 }
0 commit comments