Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Query registry for USB serial numbers in win #1483

Merged
merged 1 commit into from
Feb 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

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