Skip to content

Commit

Permalink
feat(windows): Fetch USB serial number by lookups in win registry (se…
Browse files Browse the repository at this point in the history
…rialport#1483)

Serial numbers in win32 from USB devices cannot be readily derived from the PnP ID.
This adds some logic to iterate through the win32 registry, as per the research in serialport#1459.
  • Loading branch information
Per Kristian Schanke authored and IvanSanchez committed Feb 28, 2018
1 parent 270c2be commit 04ee499
Show file tree
Hide file tree
Showing 2 changed files with 169 additions and 2 deletions.
2 changes: 1 addition & 1 deletion lib/bindings/win32.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
169 changes: 168 additions & 1 deletion src/serialport_win.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,18 @@
#include <string.h>
#include <windows.h>
#include <Setupapi.h>
#include <initguid.h>
#include <devpkey.h>
#include <devguid.h>
#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);

Expand Down Expand Up @@ -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<BYTE*>(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<ListBaton*>(req->data);

Expand All @@ -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;
Expand Down Expand Up @@ -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<BYTE*>(szBuffer),
Expand Down Expand Up @@ -693,6 +859,7 @@ void EIO_List(uv_work_t* req) {
if (productId) {
resultItem->productId = productId;
}
resultItem->serialNumber = serialNumber;
if (locationId) {
resultItem->locationId = locationId;
}
Expand Down

0 comments on commit 04ee499

Please sign in to comment.