UNPKG

native-reg

Version:

Native in-process low-level windows registry access.

394 lines (343 loc) 11.6 kB
const assert = require('assert'); const types = require('util').types || { // approximate polyfill for Node.js < 10 isExternal(x: any): boolean { return Object.prototype.toString.call(x) === '[object Object]'; } }; const isWindows = process.platform === 'win32'; const native = isWindows && require('node-gyp-build')(__dirname + "/.."); // from winreg.h export enum HKEY { CLASSES_ROOT = 0x80000000, CURRENT_USER = 0x80000001, LOCAL_MACHINE = 0x80000002, USERS = 0x80000003, PERFORMANCE_DATA = 0x80000004, PERFORMANCE_TEXT = 0x80000050, PERFORMANCE_NLSTEXT = 0x80000060, CURRENT_CONFIG = 0x80000005, DYN_DATA = 0x80000006, CURRENT_USER_LOCAL_SETTINGS = 0x80000007, } export enum CreateKeyOptions { NON_VOLATILE = 0, VOLATILE = 1, CREATE_LINK = 2, BACKUP_RESTORE = 4, } export enum OpenKeyOptions { OPEN_LINK = 8, } // from https://docs.microsoft.com/en-nz/windows/desktop/SysInfo/registry-key-security-and-access-rights export enum Access { // Specific rights QUERY_VALUE = 0x0001, SET_VALUE = 0x0002, CREATE_SUB_KEY = 0x0004, ENUMERATE_SUB_KEYS = 0x0008, NOTIFY = 0x0010, CREATE_LINK = 0x0020, // WOW64. See https://docs.microsoft.com/en-nz/windows/desktop/WinProg64/accessing-an-alternate-registry-view WOW64_64KEY = 0x0100, WOW64_32KEY = 0x0200, // Standard access rights DELETE = 0x00010000, // Generic rights. READ = 0x2_0019, WRITE = 0x2_0006, EXECUTE = READ, ALL_ACCESS = 0xF_003F, } // from winnt.h export enum ValueType { NONE = 0, // No value type SZ = 1, // Unicode nul terminated string EXPAND_SZ = 2, // Unicode nul terminated string // (with environment variable references) BINARY = 3, // Free form binary DWORD = 4, // 32-bit number DWORD_LITTLE_ENDIAN = 4, // 32-bit number (same as REG_DWORD) DWORD_BIG_ENDIAN = 5, // 32-bit number LINK = 6, // Symbolic Link (unicode) MULTI_SZ = 7, // Multiple Unicode strings RESOURCE_LIST = 8, // Resource list in the resource map FULL_RESOURCE_DESCRIPTOR = 9, // Resource list in the hardware description RESOURCE_REQUIREMENTS_LIST = 10, QWORD = 11, // 64-bit number QWORD_LITTLE_ENDIAN = 11, // 64-bit number (same as REG_QWORD) } // From RegGetValue docs export enum GetValueFlags { RT_ANY = 0xffff, RT_REG_NONE = 0x0001, RT_REG_SZ = 0x0002, RT_REG_EXPAND_SZ = 0x0004, RT_REG_BINARY = 0x0008, RT_REG_DWORD = 0x0010, RT_REG_MULTI_SZ = 0x0020, RT_REG_QWORD = 0x0040, RT_DWORD = RT_REG_DWORD | RT_REG_BINARY, RT_QWORD = RT_REG_QWORD | RT_REG_BINARY, NO_EXPAND = 0x10000000, // ZEROONFAILURE = 0x20000000, // doesn't make sense here SUBKEY_WOW6464KEY = 0x00010000, SUBKEY_WOW6432KEY = 0x00020000, } export const HKCR = HKEY.CLASSES_ROOT; export const HKCU = HKEY.CURRENT_USER; export const HKLM = HKEY.LOCAL_MACHINE; export const HKU = HKEY.USERS; export type Value = Buffer & { type: ValueType }; export function isHKEY(hkey: any): hkey is HKEY { assert(isWindows); return ( hkey instanceof native.HKEY || typeof hkey === "number" && hkey !== 0 && hkey === (hkey >>> 0) || // checks value is a positive uint32 types.isExternal(hkey) ); } // Raw APIs export function createKey( hkey: HKEY, subKey: string, access: Access, options: CreateKeyOptions = 0, ): HKEY { assert(isWindows); assert(isHKEY(hkey)); assert(typeof subKey === 'string'); assert(typeof options === 'number'); assert(typeof access === 'number'); return native.createKey(hkey, subKey, options, access); } export function openKey( hkey: HKEY, subKey: string, access: Access, options: OpenKeyOptions = 0, ): HKEY | null { assert(isWindows); assert(isHKEY(hkey)); assert(typeof subKey === 'string'); assert(typeof options === 'number'); assert(typeof access === 'number'); return native.openKey(hkey, subKey, options, access); } export function openCurrentUser(access: Access = Access.ALL_ACCESS): HKEY { assert(isWindows); assert(typeof access === 'number'); return native.openCurrentUser(access); } export function loadAppKey(file: string, access: Access): HKEY | null { assert(isWindows); assert(typeof file === 'string'); assert(typeof access === 'number'); return native.loadAppKey(file, access); } export function enumKeyNames(hkey: HKEY): string[] { assert(isWindows); assert(isHKEY(hkey)); return native.enumKeyNames(hkey); } export function enumValueNames(hkey: HKEY): string[] { assert(isWindows); assert(isHKEY(hkey)); return native.enumValueNames(hkey); } export function queryValueRaw(hkey: HKEY, valueName: string | null): Value | null { assert(isWindows); assert(isHKEY(hkey)); assert(typeof valueName === 'string' || valueName === null); return native.queryValue(hkey, valueName || ""); } export function getValueRaw( hkey: HKEY, subKey: string | null, valueName: string | null, flags: GetValueFlags = 0, ): Value | null { assert(isWindows); assert(isHKEY(hkey)); assert(typeof subKey === 'string' || subKey === null); assert(typeof valueName === 'string' || valueName === null); assert(typeof flags === 'number'); if ((flags & GetValueFlags.RT_ANY) === 0) { flags |= GetValueFlags.RT_ANY; } return native.getValue(hkey, subKey || "", valueName || "", flags); } export function setValueRaw( hkey: HKEY, valueName: string | null, valueType: ValueType, data: Buffer, ): void { assert(isWindows); assert(isHKEY(hkey)); assert(typeof valueName === 'string' || valueName === null); assert(typeof valueType === 'number'); assert(Buffer.isBuffer(data)); native.setValue(hkey, valueName || "", valueType, data); } export function renameKey(hkey: HKEY, subKey: string | null, newSubKey: string): void { assert(isWindows); assert(isHKEY(hkey)); assert(typeof subKey === 'string' || subKey === null); assert(typeof newSubKey === 'string'); return native.renameKey(hkey, subKey || "", newSubKey); } export function copyTree(hkeySrc: HKEY, subKey: string | null, hkeyDest: HKEY): void { assert(isWindows); assert(isHKEY(hkeySrc)); assert(typeof subKey === 'string' || subKey === null); assert(isHKEY(hkeyDest)); return native.copyTree(hkeySrc, subKey || "", hkeyDest); } export function deleteKey(hkey: HKEY, subKey: string): boolean { assert(isWindows); assert(isHKEY(hkey)); assert(typeof subKey === 'string'); return native.deleteKey(hkey, subKey); } export function deleteTree(hkey: HKEY, subKey: string | null): boolean { assert(isWindows); assert(isHKEY(hkey)); assert(typeof subKey === 'string' || subKey === null); return native.deleteTree(hkey, subKey || ""); } export function deleteKeyValue( hkey: HKEY, subKey: string, valueName: string, ): boolean { assert(isWindows); assert(isHKEY(hkey)); assert(typeof subKey === 'string'); assert(typeof valueName === 'string'); return native.deleteKeyValue(hkey, subKey, valueName); } export function deleteValue(hkey: HKEY, valueName: string | null): boolean { assert(isWindows); assert(isHKEY(hkey)); assert(typeof valueName === 'string' || valueName === null); return native.deleteValue(hkey, valueName || ""); } export function closeKey(hkey: HKEY | null | undefined): void { assert(isWindows); if (hkey == null) return; // nicely handle uninitialized assert(isHKEY(hkey)); native.closeKey(hkey); } // Format helpers export type ParsedValue = number | bigint | string | string[] | Buffer; export function parseValue(value: Value | null): ParsedValue | null { if (value === null) { return null; } switch (value.type) { default: return assert.fail(`Unhandled reg value type: ${value.type}`); case ValueType.SZ: case ValueType.EXPAND_SZ: return parseString(value); case ValueType.BINARY: return value; case ValueType.DWORD_LITTLE_ENDIAN: return value.readUInt32LE(0); case ValueType.DWORD_BIG_ENDIAN: return value.readUInt32BE(0); case ValueType.MULTI_SZ: return parseMultiString(value); case ValueType.QWORD_LITTLE_ENDIAN: return value.readBigUInt64LE(0); } } function trimUcs2Null(value: Buffer) { // https://docs.microsoft.com/en-us/windows/desktop/api/Winreg/nf-winreg-regqueryvalueexw // Remarks: "The string may not have been stored with the proper terminating null characters" if (value.length >= 2 && value[value.length - 2] === 0 && value[value.length - 1] === 0) { value = value.slice(0, -2); } return value; } export function parseString(value: Buffer): string { value = trimUcs2Null(value); return value.toString('ucs-2'); } export function parseMultiString(value: Buffer) { value = trimUcs2Null(value); value = trimUcs2Null(value); return value.toString('ucs-2').split('\0'); } export function formatString(value: string): Buffer { return Buffer.from(value + '\0', 'ucs-2') } export function formatMultiString(values: string[]): Buffer { return Buffer.from(values.join('\0') + '\0\0', 'ucs-2') } export function formatDWORD(value: number): Buffer { const data = Buffer.alloc(4); data.writeUInt32LE(value, 0); return data; } export function formatQWORD(value: number | bigint): Buffer { const data = Buffer.alloc(8); if (typeof value === "bigint") { data.writeBigUInt64LE(value, 0); } else { data.writeUInt32LE(value, 0); // bit operations immediately cast to a 32-bit value data.writeUInt32LE(value / 0x1_0000_0000, 4); } return data; } // Formatted APIs export function setValueSZ( hkey: HKEY, valueName: string | null, value: string, ): void { setValueRaw(hkey, valueName, ValueType.SZ, formatString(value)); } export function setValueEXPAND_SZ( hkey: HKEY, valueName: string | null, value: string, ): void { setValueRaw(hkey, valueName, ValueType.EXPAND_SZ, formatString(value)); } export function setValueMULTI_SZ( hkey: HKEY, valueName: string | null, value: string[], ): void { setValueRaw(hkey, valueName, ValueType.MULTI_SZ, formatMultiString(value)); } export function setValueDWORD( hkey: HKEY, valueName: string | null, value: number, ): void { setValueRaw(hkey, valueName, ValueType.DWORD, formatDWORD(value)); } export function setValueQWORD( hkey: HKEY, valueName: string | null, value: number | bigint, ): void { setValueRaw(hkey, valueName, ValueType.QWORD, formatQWORD(value)); } export function getValue( hkey: HKEY, subKey: string | null, valueName: string | null, flags: GetValueFlags = 0, ): ParsedValue | null { return parseValue(getValueRaw(hkey, subKey, valueName, flags)); } export function queryValue(hkey: HKEY, valueName: string | null): ParsedValue | null { return parseValue(queryValueRaw(hkey, valueName)); }