Skip to content

Commit b6e7007

Browse files
Per Kristian SchankeIvanSanchez
authored andcommitted
feat(windows): Fetch USB serial number by lookups in win registry
Initial work to get serialnumbers from registry. Needs cleanup. Only run the win32 serialnumberparser if we can not find the serialnumber in the bindings. WIP: Ugly and half-working iteration through win registry for USB S/Ns Finish and cleanup of getSerialNumber() in win. It seems to work. Cleanup: make serialNumber an in/out parameter Cleanup: unused includes & constants, commented-out-printf()s. Add range checks all around rm trailing whitespace Make linter happier Make linter happier.
1 parent 270c2be commit b6e7007

2 files changed

Lines changed: 169 additions & 2 deletions

File tree

lib/bindings/win32.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class WindowsBinding extends BaseBinding {
99
return promisify(binding.list)().then(ports => {
1010
// Grab the serial number from the pnp id
1111
ports.forEach(port => {
12-
if (port.pnpId) {
12+
if (port.pnpId && !port.serialNumber) {
1313
const serialNumber = serialNumParser(port.pnpId);
1414
if (serialNumber) {
1515
port.serialNumber = serialNumber;

src/serialport_win.cpp

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,18 @@
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
1522
typedef 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+
613776
void 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

Comments
 (0)