diff --git a/lib/bindings/win32.js b/lib/bindings/win32.js index f5214f4d0..955c3fafc 100644 --- a/lib/bindings/win32.js +++ b/lib/bindings/win32.js @@ -9,7 +9,7 @@ class WindowsBinding extends BaseBinding { return promisify(binding.list)().then(ports => { // Grab the serial number from the pnp id ports.forEach(port => { - if (port.pnpId) { + if (port.pnpId && !port.serialNumber) { const serialNumber = serialNumParser(port.pnpId); if (serialNumber) { port.serialNumber = serialNumber; diff --git a/src/serialport_win.cpp b/src/serialport_win.cpp index f2276bdaf..c620130be 100644 --- a/src/serialport_win.cpp +++ b/src/serialport_win.cpp @@ -6,11 +6,18 @@ #include #include #include +#include +#include #include #pragma comment(lib, "setupapi.lib") +#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0])) + #define MAX_BUFFER_SIZE 1000 +// As per https://msdn.microsoft.com/en-us/library/windows/desktop/ms724872(v=vs.85).aspx +#define MAX_REGISTRY_KEY_SIZE 255 + // Declare type of pointer to CancelIoEx function typedef BOOL (WINAPI *CancelIoExType)(HANDLE hFile, LPOVERLAPPED lpOverlapped); @@ -610,6 +617,162 @@ NAN_METHOD(List) { uv_queue_work(uv_default_loop(), req, EIO_List, (uv_after_work_cb)EIO_AfterList); } +// It's possible that the s/n is a construct and not the s/n of the parent USB +// composite device. This performs some convoluted registry lookups to fetch the USB s/n. +void getSerialNumber(const char *vid, + const char *pid, + const HDEVINFO hDevInfo, + SP_DEVINFO_DATA deviceInfoData, + const unsigned int maxSerialNumberLength, + char* serialNumber) { + _snprintf_s(serialNumber, maxSerialNumberLength, _TRUNCATE, ""); + if (vid == NULL || pid == NULL) { + return; + } + + DWORD dwSize; + WCHAR szWUuidBuffer[MAX_BUFFER_SIZE]; + WCHAR containerUuid[MAX_BUFFER_SIZE]; + + + // Fetch the "Container ID" for this device node. In USB context, this "Container + // ID" refers to the composite USB device, i.e. the USB device as a whole, not + // just one of its interfaces with a serial port driver attached. + + // From https://stackoverflow.com/questions/3438366/setupdigetdeviceproperty-usage-example: + // Because this is not compiled with UNICODE defined, the call to SetupDiGetDevicePropertyW + // has to be setup manually. + DEVPROPTYPE ulPropertyType; + typedef BOOL (WINAPI *FN_SetupDiGetDevicePropertyW)( + __in HDEVINFO DeviceInfoSet, + __in PSP_DEVINFO_DATA DeviceInfoData, + __in const DEVPROPKEY *PropertyKey, + __out DEVPROPTYPE *PropertyType, + __out_opt PBYTE PropertyBuffer, + __in DWORD PropertyBufferSize, + __out_opt PDWORD RequiredSize, + __in DWORD Flags); + + FN_SetupDiGetDevicePropertyW fn_SetupDiGetDevicePropertyW = (FN_SetupDiGetDevicePropertyW) + GetProcAddress(GetModuleHandle(TEXT("Setupapi.dll")), "SetupDiGetDevicePropertyW"); + + if (fn_SetupDiGetDevicePropertyW ( + hDevInfo, + &deviceInfoData, + &DEVPKEY_Device_ContainerId, + &ulPropertyType, + reinterpret_cast(szWUuidBuffer), + sizeof(szWUuidBuffer), + &dwSize, + 0)) { + szWUuidBuffer[dwSize] = '\0'; + + // Given the UUID bytes, build up a (widechar) string from it. There's some mangling + // going on. + StringFromGUID2((REFGUID)szWUuidBuffer, containerUuid, ARRAY_SIZE(containerUuid)); + } else { + // Container UUID could not be fetched, return empty serial number. + return; + } + + // NOTE: Devices might have a containerUuid like {00000000-0000-0000-FFFF-FFFFFFFFFFFF} + // This means they're non-removable, and are not handled (yet). + // Maybe they should inherit the s/n from somewhere else. + + // Sanitize input - for whatever reason, StringFromGUID2() returns a WCHAR* but + // the comparisons later need a plain old char*, in lowercase ASCII. + char wantedUuid[MAX_BUFFER_SIZE]; + _snprintf_s(wantedUuid, MAX_BUFFER_SIZE, _TRUNCATE, "%ws", containerUuid); + strlwr(wantedUuid); + + // Iterate through all the USB devices with the given VendorID/ProductID + + HKEY vendorProductHKey; + DWORD retCode; + char hkeyPath[MAX_BUFFER_SIZE]; + + _snprintf_s(hkeyPath, MAX_BUFFER_SIZE, _TRUNCATE, "SYSTEM\\CurrentControlSet\\Enum\\USB\\VID_%s&PID_%s", vid, pid); + + retCode = RegOpenKeyEx( + HKEY_LOCAL_MACHINE, + hkeyPath, + 0, + KEY_READ, + &vendorProductHKey); + + if (retCode == ERROR_SUCCESS) { + DWORD serialNumbersCount = 0; // number of subkeys + + // Fetch how many subkeys there are for this VendorID/ProductID pair. + // That's the number of devices for this VendorID/ProductID known to this machine. + + retCode = RegQueryInfoKey( + vendorProductHKey, // hkey handle + NULL, // buffer for class name + NULL, // size of class string + NULL, // reserved + &serialNumbersCount, // number of subkeys + NULL, // longest subkey size + NULL, // longest class string + NULL, // number of values for this key + NULL, // longest value name + NULL, // longest value data + NULL, // security descriptor + NULL); // last write time + + if (retCode == ERROR_SUCCESS && serialNumbersCount > 0) { + for (unsigned int i=0; i < serialNumbersCount; i++) { + // Each of the subkeys here is the serial number of a USB device with the + // given VendorId/ProductId. Now fetch the string for the S/N. + DWORD serialNumberLength = maxSerialNumberLength; + retCode = RegEnumKeyEx(vendorProductHKey, + i, + serialNumber, + &serialNumberLength, + NULL, + NULL, + NULL, + NULL); + + if (retCode == ERROR_SUCCESS) { + // Lookup info for VID_(vendorId)&PID_(productId)\(serialnumber) + + _snprintf_s(hkeyPath, MAX_BUFFER_SIZE, _TRUNCATE, + "SYSTEM\\CurrentControlSet\\Enum\\USB\\VID_%s&PID_%s\\%s", + vid, pid, serialNumber); + + HKEY deviceHKey; + + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, hkeyPath, 0, KEY_READ, &deviceHKey) == ERROR_SUCCESS) { + char readUuid[MAX_BUFFER_SIZE]; + DWORD readSize = sizeof(readUuid); + + // Query VID_(vendorId)&PID_(productId)\(serialnumber)\ContainerID + DWORD retCode = RegQueryValueEx(deviceHKey, "ContainerID", NULL, NULL, (LPBYTE)&readUuid, &readSize); + if (retCode == ERROR_SUCCESS) { + readUuid[readSize] = '\0'; + if (strcmp(wantedUuid, readUuid) == 0) { + // The ContainerID UUIDs match, return now that serialNumber has + // the right value. + RegCloseKey(deviceHKey); + RegCloseKey(vendorProductHKey); + return; + } + } + } + RegCloseKey(deviceHKey); + } + } + } + + /* In case we did not obtain the path, for whatever reason, we close the key and return an empty string. */ + RegCloseKey(vendorProductHKey); + } + + _snprintf_s(serialNumber, maxSerialNumberLength, _TRUNCATE, ""); + return; +} + void EIO_List(uv_work_t* req) { ListBaton* data = static_cast(req->data); @@ -619,13 +782,14 @@ void EIO_List(uv_work_t* req) { int memberIndex = 0; DWORD dwSize, dwPropertyRegDataType; - char szBuffer[400]; + char szBuffer[MAX_BUFFER_SIZE]; char *pnpId; char *vendorId; char *productId; char *name; char *manufacturer; char *locationId; + char serialNumber[MAX_REGISTRY_KEY_SIZE]; bool isCom; while (true) { pnpId = NULL; @@ -660,6 +824,8 @@ void EIO_List(uv_work_t* req) { productId = copySubstring(productId, 4); } + getSerialNumber(vendorId, productId, hDevInfo, deviceInfoData, MAX_REGISTRY_KEY_SIZE, serialNumber); + if (SetupDiGetDeviceRegistryProperty(hDevInfo, &deviceInfoData, SPDRP_LOCATION_INFORMATION, &dwPropertyRegDataType, reinterpret_cast(szBuffer), @@ -693,6 +859,7 @@ void EIO_List(uv_work_t* req) { if (productId) { resultItem->productId = productId; } + resultItem->serialNumber = serialNumber; if (locationId) { resultItem->locationId = locationId; }