/************************************************************************************************************ * registry.js - contains a wrapper for the REG command under Windows, which provides access to the registry * * @author Paul Bottin a/k/a FrEsC * */ /* imports */ var util = require('util') , path = require('path') , spawn = require('child_process').spawn /* set to console.log for debugging */ , log = function () {} /* registry hive ids */ , HKLM = 'HKLM' , HKCU = 'HKCU' , HKCR = 'HKCR' , HKU = 'HKU' , HKCC = 'HKCC' , HIVES = [ HKLM, HKCU, HKCR, HKU, HKCC ] /* registry value type ids */ , REG_SZ = 'REG_SZ' , REG_MULTI_SZ = 'REG_MULTI_SZ' , REG_EXPAND_SZ = 'REG_EXPAND_SZ' , REG_DWORD = 'REG_DWORD' , REG_QWORD = 'REG_QWORD' , REG_BINARY = 'REG_BINARY' , REG_NONE = 'REG_NONE' , REG_TYPES = [ REG_SZ, REG_MULTI_SZ, REG_EXPAND_SZ, REG_DWORD, REG_QWORD, REG_BINARY, REG_NONE ] /* default registry value name */ , DEFAULT_VALUE = '' /* general key pattern */ , KEY_PATTERN = /(\\[a-zA-Z0-9_\s]+)*/ /* key path pattern (as returned by REG-cli) */ , PATH_PATTERN = /^(HKEY_LOCAL_MACHINE|HKEY_CURRENT_USER|HKEY_CLASSES_ROOT|HKEY_USERS|HKEY_CURRENT_CONFIG)(.*)$/ /* registry item pattern */ , ITEM_PATTERN = /^(.*)\s(REG_SZ|REG_MULTI_SZ|REG_EXPAND_SZ|REG_DWORD|REG_QWORD|REG_BINARY|REG_NONE)\s+([^\s].*)$/ /** * Creates an Error object that contains the exit code of the REG.EXE process. * This contructor is private. Objects of this type are created internally and returned in the err parameters in case the REG.EXE process doesn't exit cleanly. * * @private * @class * * @param {string} message - the error message * @param {number} code - the process exit code * */ function ProcessUncleanExitError(message, code) { if (!(this instanceof ProcessUncleanExitError)) return new ProcessUncleanExitError(message, code); Error.captureStackTrace(this, ProcessUncleanExitError); /** * The error name. * @readonly * @member {string} ProcessUncleanExitError#name */ this.__defineGetter__('name', function () { return ProcessUncleanExitError.name; }); /** * The error message. * @readonly * @member {string} ProcessUncleanExitError#message */ this.__defineGetter__('message', function () { return message; }); /** * The process exit code. * @readonly * @member {number} ProcessUncleanExitError#code */ this.__defineGetter__('code', function () { return code; }); } util.inherits(ProcessUncleanExitError, Error); /* * Captures stdout/stderr for a child process */ function captureOutput(child) { // Use a mutable data structure so we can append as we get new data and have // the calling context see the new data var output = {'stdout': '', 'stderr': ''}; child.stdout.on('data', function(data) { output["stdout"] += data.toString(); }); child.stderr.on('data', function(data) { output["stderr"] += data.toString(); }); return output; } /* * Returns an error message containing the stdout/stderr of the child process */ function mkErrorMsg(registryCommand, code, output) { var stdout = output['stdout'].trim(); var stderr = output['stderr'].trim(); var msg = util.format("%s command exited with code %d:\n%s\n%s", registryCommand, code, stdout, stderr); return new ProcessUncleanExitError(msg, code); } /* * Converts x86/x64 to 32/64 */ function convertArchString(archString) { if (archString == 'x64') { return '64'; } else if (archString == 'x86') { return '32'; } else { throw new Error('illegal architecture: ' + archString + ' (use x86 or x64)'); } } /* * Adds correct architecture to reg args */ function pushArch(args, arch) { if (arch) { args.push('/reg:' + convertArchString(arch)); } } /* * Get the path to system's reg.exe. Useful when another reg.exe is added to the PATH * Implemented only for Windows */ function getRegExePath() { if (process.platform === 'win32') { return path.join(process.env.windir, 'system32', 'reg.exe'); } else { return "REG"; } } /** * Creates a single registry value record. * This contructor is private. Objects of this type are created internally and returned by methods of {@link Registry} objects. * * @private * @class * * @param {string} host - the hostname * @param {string} hive - the hive id * @param {string} key - the registry key * @param {string} name - the value name * @param {string} type - the value type * @param {string} value - the value * @param {string} arch - the hive architecture ('x86' or 'x64') * */ function RegistryItem (host, hive, key, name, type, value, arch) { if (!(this instanceof RegistryItem)) return new RegistryItem(host, hive, key, name, type, value, arch); /* private members */ var _host = host // hostname , _hive = hive // registry hive , _key = key // registry key , _name = name // property name , _type = type // property type , _value = value // property value , _arch = arch // hive architecture /* getters/setters */ /** * The hostname. * @readonly * @member {string} RegistryItem#host */ this.__defineGetter__('host', function () { return _host; }); /** * The hive id. * @readonly * @member {string} RegistryItem#hive */ this.__defineGetter__('hive', function () { return _hive; }); /** * The registry key. * @readonly * @member {string} RegistryItem#key */ this.__defineGetter__('key', function () { return _key; }); /** * The value name. * @readonly * @member {string} RegistryItem#name */ this.__defineGetter__('name', function () { return _name; }); /** * The value type. * @readonly * @member {string} RegistryItem#type */ this.__defineGetter__('type', function () { return _type; }); /** * The value. * @readonly * @member {string} RegistryItem#value */ this.__defineGetter__('value', function () { return _value; }); /** * The hive architecture. * @readonly * @member {string} RegistryItem#arch */ this.__defineGetter__('arch', function () { return _arch; }); } util.inherits(RegistryItem, Object); /** * Creates a registry object, which provides access to a single registry key. * Note: This class is returned by a call to ```require('winreg')```. * * @public * @class * * @param {object} options - the options * @param {string=} options.host - the hostname * @param {string=} options.hive - the hive id * @param {string=} options.key - the registry key * @param {string=} options.arch - the optional registry hive architecture ('x86' or 'x64'; only valid on Windows 64 Bit Operating Systems) * * @example * var Registry = require('winreg') * , autoStartCurrentUser = new Registry({ * hive: Registry.HKCU, * key: '\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' * }); * */ function Registry (options) { if (!(this instanceof Registry)) return new Registry(options); /* private members */ var _options = options || {} , _host = '' + (_options.host || '') // hostname , _hive = '' + (_options.hive || HKLM) // registry hive , _key = '' + (_options.key || '') // registry key , _arch = _options.arch || null // hive architecture /* getters/setters */ /** * The hostname. * @readonly * @member {string} Registry#host */ this.__defineGetter__('host', function () { return _host; }); /** * The hive id. * @readonly * @member {string} Registry#hive */ this.__defineGetter__('hive', function () { return _hive; }); /** * The registry key name. * @readonly * @member {string} Registry#key */ this.__defineGetter__('key', function () { return _key; }); /** * The full path to the registry key. * @readonly * @member {string} Registry#path */ this.__defineGetter__('path', function () { return (_host.length == 0 ? '' : '\\\\' + _host + '\\') + _hive + _key; }); /** * The registry hive architecture ('x86' or 'x64'). * @readonly * @member {string} Registry#arch */ this.__defineGetter__('arch', function () { return _arch; }); /** * Creates a new {@link Registry} instance that points to the parent registry key. * @readonly * @member {Registry} Registry#parent */ this.__defineGetter__('parent', function () { var i = _key.lastIndexOf('\\') return new Registry({ host: this.host, hive: this.hive, key: (i == -1)?'':_key.substring(0, i), arch: this.arch }); }); // validate options... if (HIVES.indexOf(_hive) == -1) throw new Error('illegal hive specified.'); if (!KEY_PATTERN.test(_key)) throw new Error('illegal key specified.'); if (_arch && _arch != 'x64' && _arch != 'x86') throw new Error('illegal architecture specified (use x86 or x64)'); } /** * Registry hive key HKEY_LOCAL_MACHINE. * Note: For writing to this hive your program has to run with admin privileges. * @type {string} */ Registry.HKLM = HKLM; /** * Registry hive key HKEY_CURRENT_USER. * @type {string} */ Registry.HKCU = HKCU; /** * Registry hive key HKEY_CLASSES_ROOT. * Note: For writing to this hive your program has to run with admin privileges. * @type {string} */ Registry.HKCR = HKCR; /** * Registry hive key HKEY_USERS. * Note: For writing to this hive your program has to run with admin privileges. * @type {string} */ Registry.HKU = HKU; /** * Registry hive key HKEY_CURRENT_CONFIG. * Note: For writing to this hive your program has to run with admin privileges. * @type {string} */ Registry.HKCC = HKCC; /** * Collection of available registry hive keys. * @type {array} */ Registry.HIVES = HIVES; /** * Registry value type STRING. * @type {string} */ Registry.REG_SZ = REG_SZ; /** * Registry value type MULTILINE_STRING. * @type {string} */ Registry.REG_MULTI_SZ = REG_MULTI_SZ; /** * Registry value type EXPANDABLE_STRING. * @type {string} */ Registry.REG_EXPAND_SZ = REG_EXPAND_SZ; /** * Registry value type DOUBLE_WORD. * @type {string} */ Registry.REG_DWORD = REG_DWORD; /** * Registry value type QUAD_WORD. * @type {string} */ Registry.REG_QWORD = REG_QWORD; /** * Registry value type BINARY. * @type {string} */ Registry.REG_BINARY = REG_BINARY; /** * Registry value type UNKNOWN. * @type {string} */ Registry.REG_NONE = REG_NONE; /** * Collection of available registry value types. * @type {array} */ Registry.REG_TYPES = REG_TYPES; /** * The name of the default value. May be used instead of the empty string literal for better readability. * @type {string} */ Registry.DEFAULT_VALUE = DEFAULT_VALUE; /** * Retrieve all values from this registry key. * @param {valuesCallback} cb - callback function * @param {ProcessUncleanExitError=} cb.err - error object or null if successful * @param {array=} cb.items - an array of {@link RegistryItem} objects * @returns {Registry} this registry key object */ Registry.prototype.values = function values (cb) { if (typeof cb !== 'function') throw new TypeError('must specify a callback'); var args = [ 'QUERY', this.path ]; pushArch(args, this.arch); var proc = spawn(getRegExePath(), args, { cwd: undefined, env: process.env, stdio: [ 'ignore', 'pipe', 'pipe' ] }) , buffer = '' , self = this , error = null // null means no error previously reported. var output = captureOutput(proc); proc.on('close', function (code) { if (error) { return; } else if (code !== 0) { log('process exited with code ' + code); cb(mkErrorMsg('QUERY', code, output), null); } else { var items = [] , result = [] , lines = buffer.split('\n') , lineNumber = 0 for (var i = 0, l = lines.length; i < l; i++) { var line = lines[i].trim(); if (line.length > 0) { log(line); if (lineNumber != 0) { items.push(line); } ++lineNumber; } } for (var i = 0, l = items.length; i < l; i++) { var match = ITEM_PATTERN.exec(items[i]) , name , type , value if (match) { name = match[1].trim(); type = match[2].trim(); value = match[3]; result.push(new RegistryItem(self.host, self.hive, self.key, name, type, value, self.arch)); } } cb(null, result); } }); proc.stdout.on('data', function (data) { buffer += data.toString(); }); proc.on('error', function(err) { error = err; cb(err); }); return this; }; /** * Retrieve all subkeys from this registry key. * @param {function (err, items)} cb - callback function * @param {ProcessUncleanExitError=} cb.err - error object or null if successful * @param {array=} cb.items - an array of {@link Registry} objects * @returns {Registry} this registry key object */ Registry.prototype.keys = function keys (cb) { if (typeof cb !== 'function') throw new TypeError('must specify a callback'); var args = [ 'QUERY', this.path ]; pushArch(args, this.arch); var proc = spawn(getRegExePath(), args, { cwd: undefined, env: process.env, stdio: [ 'ignore', 'pipe', 'pipe' ] }) , buffer = '' , self = this , error = null // null means no error previously reported. var output = captureOutput(proc); proc.on('close', function (code) { if (error) { return; } else if (code !== 0) { log('process exited with code ' + code); cb(mkErrorMsg('QUERY', code, output), null); } }); proc.stdout.on('data', function (data) { buffer += data.toString(); }); proc.stdout.on('end', function () { var items = [] , result = [] , lines = buffer.split('\n') for (var i = 0, l = lines.length; i < l; i++) { var line = lines[i].trim(); if (line.length > 0) { log(line); items.push(line); } } for (var i = 0, l = items.length; i < l; i++) { var match = PATH_PATTERN.exec(items[i]) , hive , key if (match) { hive = match[1]; key = match[2]; if (key && (key !== self.key)) { result.push(new Registry({ host: self.host, hive: self.hive, key: key, arch: self.arch })); } } } cb(null, result); }); proc.on('error', function(err) { error = err; cb(err); }); return this; }; /** * Gets a named value from this registry key. * @param {string} name - the value name, use {@link Registry.DEFAULT_VALUE} or an empty string for the default value * @param {function (err, item)} cb - callback function * @param {ProcessUncleanExitError=} cb.err - error object or null if successful * @param {RegistryItem=} cb.item - the retrieved registry item * @returns {Registry} this registry key object */ Registry.prototype.get = function get (name, cb) { if (typeof cb !== 'function') throw new TypeError('must specify a callback'); var args = ['QUERY', this.path]; if (name == '') args.push('/ve'); else args = args.concat(['/v', name]); pushArch(args, this.arch); var proc = spawn(getRegExePath(), args, { cwd: undefined, env: process.env, stdio: [ 'ignore', 'pipe', 'pipe' ] }) , buffer = '' , self = this , error = null // null means no error previously reported. var output = captureOutput(proc); proc.on('close', function (code) { if (error) { return; } else if (code !== 0) { log('process exited with code ' + code); cb(mkErrorMsg('QUERY', code, output), null); } else { var items = [] , result = null , lines = buffer.split('\n') , lineNumber = 0 for (var i = 0, l = lines.length; i < l; i++) { var line = lines[i].trim(); if (line.length > 0) { log(line); if (lineNumber != 0) { items.push(line); } ++lineNumber; } } //Get last item - so it works in XP where REG QUERY returns with a header var item = items[items.length-1] || '' , match = ITEM_PATTERN.exec(item) , name , type , value if (match) { name = match[1].trim(); type = match[2].trim(); value = match[3]; result = new RegistryItem(self.host, self.hive, self.key, name, type, value, self.arch); } cb(null, result); } }); proc.stdout.on('data', function (data) { buffer += data.toString(); }); proc.on('error', function(err) { error = err; cb(err); }); return this; }; /** * Sets a named value in this registry key, overwriting an already existing value. * @param {string} name - the value name, use {@link Registry.DEFAULT_VALUE} or an empty string for the default value * @param {string} type - the value type * @param {string} value - the value * @param {function (err)} cb - callback function * @param {ProcessUncleanExitError=} cb.err - error object or null if successful * @returns {Registry} this registry key object */ Registry.prototype.set = function set (name, type, value, cb) { if (typeof cb !== 'function') throw new TypeError('must specify a callback'); if (REG_TYPES.indexOf(type) == -1) throw Error('illegal type specified.'); var args = ['ADD', this.path]; if (name == '') args.push('/ve'); else args = args.concat(['/v', name]); args = args.concat(['/t', type, '/d', value, '/f']); pushArch(args, this.arch); var proc = spawn(getRegExePath(), args, { cwd: undefined, env: process.env, stdio: [ 'ignore', 'pipe', 'pipe' ] }) , error = null // null means no error previously reported. var output = captureOutput(proc); proc.on('close', function (code) { if(error) { return; } else if (code !== 0) { log('process exited with code ' + code); cb(mkErrorMsg('ADD', code, output, null)); } else { cb(null); } }); proc.stdout.on('data', function (data) { // simply discard output log(''+data); }); proc.on('error', function(err) { error = err; cb(err); }); return this; }; /** * Remove a named value from this registry key. If name is empty, sets the default value of this key. * Note: This key must be already existing. * @param {string} name - the value name, use {@link Registry.DEFAULT_VALUE} or an empty string for the default value * @param {function (err)} cb - callback function * @param {ProcessUncleanExitError=} cb.err - error object or null if successful * @returns {Registry} this registry key object */ Registry.prototype.remove = function remove (name, cb) { if (typeof cb !== 'function') throw new TypeError('must specify a callback'); var args = name ? ['DELETE', this.path, '/f', '/v', name] : ['DELETE', this.path, '/f', '/ve']; pushArch(args, this.arch); var proc = spawn(getRegExePath(), args, { cwd: undefined, env: process.env, stdio: [ 'ignore', 'pipe', 'pipe' ] }) , error = null // null means no error previously reported. var output = captureOutput(proc); proc.on('close', function (code) { if(error) { return; } else if (code !== 0) { log('process exited with code ' + code); cb(mkErrorMsg('DELETE', code, output), null); } else { cb(null); } }); proc.stdout.on('data', function (data) { // simply discard output log(''+data); }); proc.on('error', function(err) { error = err; cb(err); }); return this; }; /** * Remove all subkeys and values (including the default value) from this registry key. * @param {function (err)} cb - callback function * @param {ProcessUncleanExitError=} cb.err - error object or null if successful * @returns {Registry} this registry key object */ Registry.prototype.clear = function clear (cb) { if (typeof cb !== 'function') throw new TypeError('must specify a callback'); var args = ['DELETE', this.path, '/f', '/va']; pushArch(args, this.arch); var proc = spawn(getRegExePath(), args, { cwd: undefined, env: process.env, stdio: [ 'ignore', 'pipe', 'pipe' ] }) , error = null // null means no error previously reported. var output = captureOutput(proc); proc.on('close', function (code) { if(error) { return; } else if (code !== 0) { log('process exited with code ' + code); cb(mkErrorMsg("DELETE", code, output), null); } else { cb(null); } }); proc.stdout.on('data', function (data) { // simply discard output log(''+data); }); proc.on('error', function(err) { error = err; cb(err); }); return this; }; /** * Alias for the clear method to keep it backward compatible. * @method * @deprecated Use {@link Registry#clear} or {@link Registry#destroy} in favour of this method. * @param {function (err)} cb - callback function * @param {ProcessUncleanExitError=} cb.err - error object or null if successful * @returns {Registry} this registry key object */ Registry.prototype.erase = Registry.prototype.clear; /** * Delete this key and all subkeys from the registry. * @param {function (err)} cb - callback function * @param {ProcessUncleanExitError=} cb.err - error object or null if successful * @returns {Registry} this registry key object */ Registry.prototype.destroy = function destroy (cb) { if (typeof cb !== 'function') throw new TypeError('must specify a callback'); var args = ['DELETE', this.path, '/f']; pushArch(args, this.arch); var proc = spawn(getRegExePath(), args, { cwd: undefined, env: process.env, stdio: [ 'ignore', 'pipe', 'pipe' ] }) , error = null // null means no error previously reported. var output = captureOutput(proc); proc.on('close', function (code) { if (error) { return; } else if (code !== 0) { log('process exited with code ' + code); cb(mkErrorMsg('DELETE', code, output), null); } else { cb(null); } }); proc.stdout.on('data', function (data) { // simply discard output log(''+data); }); proc.on('error', function(err) { error = err; cb(err); }); return this; }; /** * Create this registry key. Note that this is a no-op if the key already exists. * @param {function (err)} cb - callback function * @param {ProcessUncleanExitError=} cb.err - error object or null if successful * @returns {Registry} this registry key object */ Registry.prototype.create = function create (cb) { if (typeof cb !== 'function') throw new TypeError('must specify a callback'); var args = ['ADD', this.path, '/f']; pushArch(args, this.arch); var proc = spawn(getRegExePath(), args, { cwd: undefined, env: process.env, stdio: [ 'ignore', 'pipe', 'pipe' ] }) , error = null // null means no error previously reported. var output = captureOutput(proc); proc.on('close', function (code) { if (error) { return; } else if (code !== 0) { log('process exited with code ' + code); cb(mkErrorMsg('ADD', code, output), null); } else { cb(null); } }); proc.stdout.on('data', function (data) { // simply discard output log(''+data); }); proc.on('error', function(err) { error = err; cb(err); }); return this; }; /** * Checks if this key already exists. * @param {function (err, exists)} cb - callback function * @param {ProcessUncleanExitError=} cb.err - error object or null if successful * @param {boolean=} cb.exists - true if a registry key with this name already exists * @returns {Registry} this registry key object */ Registry.prototype.keyExists = function keyExists (cb) { this.values(function (err, items) { if (err) { // process should return with code 1 if key not found if (err.code == 1) { return cb(null, false); } // other error return cb(err); } cb(null, true); }); return this; }; /** * Checks if a value with the given name already exists within this key. * @param {string} name - the value name, use {@link Registry.DEFAULT_VALUE} or an empty string for the default value * @param {function (err, exists)} cb - callback function * @param {ProcessUncleanExitError=} cb.err - error object or null if successful * @param {boolean=} cb.exists - true if a value with the given name was found in this key * @returns {Registry} this registry key object */ Registry.prototype.valueExists = function valueExists (name, cb) { this.get(name, function (err, item) { if (err) { // process should return with code 1 if value not found if (err.code == 1) { return cb(null, false); } // other error return cb(err); } cb(null, true); }); return this; }; module.exports = Registry;