virtualbox
Advanced tools
Comparing version 1.0.2 to 1.1.0
@@ -1,438 +0,587 @@ | ||
"use strict"; | ||
'use strict'; | ||
// @todo use a promise library instead of so many callbacks | ||
const execFile = require('child_process').execFile, | ||
log4js = require('log4js'), | ||
host_platform = process.platform, | ||
logging = require('./logging'), | ||
known_OS_types = { | ||
known_OS_types = { | ||
WINDOWS: 'windows', | ||
MAC: 'mac', | ||
LINUX: 'linux' | ||
}; | ||
LINUX: 'linux', | ||
}, | ||
defaultExecutor = (bin, cmd) => { | ||
return new Promise((resolve, reject) => { | ||
if (!allowedBinaries.includes(bin)) { | ||
reject(new Error('Not an allowed binary')); | ||
} else { | ||
execFile(bin, cmd, function (err, stdout, stderr) { | ||
if ( | ||
!err && | ||
stderr && | ||
cmd.indexOf('pause') !== -1 && | ||
cmd.indexOf('savestate') !== -1 | ||
) { | ||
reject(new Error(stderr)); | ||
} | ||
let vBoxManageBinary, | ||
vbox_version; | ||
resolve({ err, stdout, stderr }); | ||
}); | ||
} | ||
}); | ||
}, | ||
defaultLoggingConfig = { | ||
appenders: { | ||
out: { | ||
type: 'stdout', | ||
layout: { | ||
type: 'pattern', | ||
pattern: '%[[%d{yyyy-MM-dd hh:mm:ss.SSS}] [%p] %c - %]%m', | ||
}, | ||
}, | ||
}, | ||
categories: { default: { appenders: ['out'], level: 'info' } }, | ||
}, | ||
defaultLoggerFn = () => { | ||
log4js.configure(defaultLoggingConfig); | ||
return log4js.getLogger('VirtualBox'); | ||
}, | ||
defaultLogger = defaultLoggerFn(), | ||
defaultvboxmanage = function (cmd, callback) { | ||
try { | ||
this._executor(this._vBoxManageBinary, cmd) | ||
.then(({ err, stdout, stderr }) => callback(err, stdout, stderr)) | ||
.catch(defaultErrorHandler); | ||
} catch (err) { | ||
this._logging.error(err); | ||
} | ||
}, | ||
defaultErrorHandler = (err) => { | ||
console.error(err); | ||
}, | ||
allowedBinaries = ['VBoxControl']; | ||
// Host operating system | ||
if (/^win/.test(host_platform)) { | ||
class Virtualbox { | ||
constructor(logging = defaultLogger, executor = defaultExecutor) { | ||
this._logging = logging; | ||
this._executor = executor; | ||
this._setVboxManageBinary(); | ||
allowedBinaries.push(this._vBoxManageBinary); | ||
this._logging.debug(allowedBinaries); | ||
this._detectVboxVersion(); | ||
this.storage = new VboxStorage( | ||
this._logging, | ||
this._executor, | ||
this._vBoxManageBinary | ||
); | ||
this.guestproperty = new VboxGuestProperty( | ||
this._logging, | ||
this._executor, | ||
this._vBoxManageBinary | ||
); | ||
this.extradata = new VboxExtraData( | ||
this._logging, | ||
this._executor, | ||
this._vBoxManageBinary | ||
); | ||
this.vboxmanage = defaultvboxmanage; | ||
this.SCAN_CODES = require('./scan-codes'); | ||
} | ||
// Path may not contain VBoxManage.exe but it provides this environment variable | ||
var vBoxInstallPath = process.env.VBOX_INSTALL_PATH || process.env.VBOX_MSI_INSTALL_PATH; | ||
vBoxManageBinary = '"' + vBoxInstallPath + '\\VBoxManage.exe' + '" '; | ||
static create(logging, executor) { | ||
const logger = !!logging ? logging : defaultLogger; | ||
return new Virtualbox(logger, executor); | ||
} | ||
} else if (/^darwin/.test(host_platform) || /^linux/.test(host_platform)) { | ||
pause(vmname, callback) { | ||
this._logging.info('Pausing VM "%s"', vmname); | ||
this.vboxmanage(['controlvm', vmname, 'pause'], function (error, _) { | ||
callback(error); | ||
}); | ||
} | ||
// Mac OS X and most Linux use the same binary name, in the path | ||
vBoxManageBinary = 'vboxmanage '; | ||
list(callback) { | ||
const parse_listdata = (raw_data) => { | ||
var _raw = raw_data.split(/\r?\n/g); | ||
var _data = {}; | ||
if (_raw.length > 0) { | ||
for (var _i = 0; _i < _raw.length; _i += 1) { | ||
var _line = _raw[_i]; | ||
if (_line === '') { | ||
continue; | ||
} | ||
// "centos6" {64ec13bb-5889-4352-aee9-0f1c2a17923d} | ||
var rePattern = /^"(.+)" \{(.+)\}$/; | ||
var arrMatches = _line.match(rePattern); | ||
// {'64ec13bb-5889-4352-aee9-0f1c2a17923d': 'centos6'} | ||
if (arrMatches && arrMatches.length === 3) { | ||
_data[arrMatches[2].toString()] = { | ||
name: arrMatches[1].toString(), | ||
}; | ||
} | ||
} | ||
} | ||
return _data; | ||
}; | ||
} else { | ||
this._logging.info('Listing VMs'); | ||
this.vboxmanage(['list', 'runningvms'], (_, stdout) => { | ||
var _runningvms = parse_listdata(stdout); | ||
this.vboxmanage(['list', 'vms'], function (error, fullStdout) { | ||
var _all = parse_listdata(fullStdout); | ||
var _keys = Object.keys(_all); | ||
for (var _i = 0; _i < _keys.length; _i += 1) { | ||
var _key = _keys[_i]; | ||
if (_runningvms[_key]) { | ||
_all[_key].running = true; | ||
} else { | ||
_all[_key].running = false; | ||
} | ||
} | ||
callback(_all, error); | ||
}); | ||
}); | ||
} | ||
// Otherwise (e.g., SunOS) hope it's in the path | ||
vBoxManageBinary = 'vboxmanage '; | ||
reset(vmname, callback) { | ||
this._logging.info('Resetting VM "%s"', vmname); | ||
this.vboxmanage(['controlvm', vmname, 'reset'], function (error, _) { | ||
callback(error); | ||
}); | ||
} | ||
} | ||
const allowedBinaries = ["VBoxControl", vBoxManageBinary]; | ||
execFile(vBoxManageBinary, ["--version"], function(error, stdout, stderr) { | ||
// e.g., "4.3.38r106717" or "5.0.20r106931" | ||
vbox_version = stdout.split(".")[0]; | ||
logging.info("Virtualbox version detected as %s", vbox_version); | ||
}); | ||
function command(bin, cmd, callback) { | ||
if(!allowedBinaries.includes(bin)) { | ||
throw new Error("Not an allowed binary"); | ||
resume(vmname, callback) { | ||
this._logging.info('Resuming VM "%s"', vmname); | ||
this.vboxmanage(['controlvm', vmname, 'resume'], function (error, _) { | ||
callback(error); | ||
}); | ||
} | ||
execFile(bin, cmd, function(err, stdout, stderr) { | ||
if (!err && stderr && cmd.indexOf("pause") !== -1 && cmd.indexOf("savestate") !== -1) { | ||
err = new Error(stderr); | ||
start(vmname, useGui, callback) { | ||
if (typeof useGui === 'function') { | ||
callback = useGui; | ||
useGui = false; | ||
} | ||
var vmType = useGui ? 'gui' : 'headless'; | ||
callback(err, stdout); | ||
}); | ||
} | ||
this._logging.info('Starting VM "%s" with options: ', vmname, vmType); | ||
function vboxcontrol(cmd, callback) { | ||
command("VBoxControl", cmd, callback); | ||
} | ||
function vboxmanage(cmd, callback) { | ||
command(vBoxManageBinary, cmd, callback); | ||
} | ||
function pause(vmname, callback) { | ||
logging.info('Pausing VM "%s"', vmname); | ||
vboxmanage(["controlvm", vmname, "pause"], function(error, stdout) { | ||
callback(error); | ||
}); | ||
} | ||
function list(callback) { | ||
logging.info('Listing VMs'); | ||
vboxmanage(['list', 'runningvms'], function(error, stdout) { | ||
var _list = {}; | ||
var _runningvms = parse_listdata(stdout); | ||
vboxmanage(["list", "vms"], function(error, fullStdout) { | ||
var _all = parse_listdata(fullStdout); | ||
var _keys = Object.keys(_all); | ||
for (var _i = 0; _i < _keys.length; _i += 1) { | ||
var _key = _keys[_i]; | ||
if (_runningvms[_key]) { | ||
_all[_key].running = true; | ||
} else { | ||
_all[_key].running = false; | ||
} | ||
this.vboxmanage(['-nologo', 'startvm', vmname, '--type', vmType], function ( | ||
error, | ||
_ | ||
) { | ||
if (error && /VBOX_E_INVALID_OBJECT_STATE/.test(error.message)) { | ||
error = undefined; | ||
} | ||
callback(_all, error); | ||
callback(error); | ||
}); | ||
}); | ||
} | ||
} | ||
function parse_listdata(raw_data) { | ||
var _raw = raw_data.split(/\r?\n/g); | ||
var _data = {}; | ||
if (_raw.length > 0) { | ||
for (var _i = 0; _i < _raw.length; _i += 1) { | ||
var _line = _raw[_i]; | ||
if (_line === '') { | ||
continue; | ||
} | ||
// "centos6" {64ec13bb-5889-4352-aee9-0f1c2a17923d} | ||
var rePattern = /^"(.+)" \{(.+)\}$/; | ||
var arrMatches = _line.match(rePattern); | ||
// {'64ec13bb-5889-4352-aee9-0f1c2a17923d': 'centos6'} | ||
if (arrMatches && arrMatches.length === 3) { | ||
_data[arrMatches[2].toString()] = { | ||
name: arrMatches[1].toString() | ||
}; | ||
} | ||
} | ||
stop(vmname, callback) { | ||
this._logging.info('Stopping VM "%s"', vmname); | ||
this.vboxmanage(['controlvm', vmname, 'savestate'], function (error, _) { | ||
callback(error); | ||
}); | ||
} | ||
return _data; | ||
} | ||
function reset(vmname, callback) { | ||
logging.info('Resetting VM "%s"', vmname); | ||
vboxmanage(["controlvm", vmname, "reset"], function(error, stdout) { | ||
callback(error); | ||
}); | ||
} | ||
savestate(vmname, callback) { | ||
this._logging.info('Saving State (alias to stop) VM "%s"', vmname); | ||
this.stop(vmname, callback); | ||
} | ||
function resume(vmname, callback) { | ||
logging.info('Resuming VM "%s"', vmname); | ||
vboxmanage(["controlvm", vmname, "resume"], function(error, stdout) { | ||
callback(error); | ||
}); | ||
} | ||
vmExport(vmname, output, callback) { | ||
this._logging.info('Exporting VM "%s"', vmname); | ||
this.vboxmanage(['export', vmname, '--output', output], function ( | ||
error, | ||
_ | ||
) { | ||
callback(error); | ||
}); | ||
} | ||
function start(vmname, useGui, callback) { | ||
if ((typeof useGui) === 'function') { | ||
callback = useGui; | ||
useGui = false; | ||
poweroff(vmname, callback) { | ||
this._logging.info('Powering off VM "%s"', vmname); | ||
this.vboxmanage(['controlvm', vmname, 'poweroff'], function (error, _) { | ||
callback(error); | ||
}); | ||
} | ||
var vmType = (useGui ? "gui" : "headless"); | ||
logging.info('Starting VM "%s" with options: ', vmname, vmType); | ||
acpipowerbutton(vmname, callback) { | ||
this._logging.info('ACPI power button VM "%s"', vmname); | ||
this.vboxmanage(['controlvm', vmname, 'acpipowerbutton'], function (error) { | ||
callback(error); | ||
}); | ||
} | ||
vboxmanage(["-nologo", "startvm", vmname, "--type", vmType], function(error, stdout) { | ||
if (error && /VBOX_E_INVALID_OBJECT_STATE/.test(error.message)) { | ||
error = undefined; | ||
} | ||
callback(error); | ||
}); | ||
} | ||
acpisleepbutton(vmname, callback) { | ||
this._logging.info('ACPI sleep button VM "%s"', vmname); | ||
this.vboxmanage(['controlvm', vmname, 'acpisleepbutton'], function ( | ||
error, | ||
_ | ||
) { | ||
callback(error); | ||
}); | ||
} | ||
function stop(vmname, callback) { | ||
logging.info('Stopping VM "%s"', vmname); | ||
vboxmanage(["controlvm", vmname, "savestate"], function(error, stdout) { | ||
callback(error); | ||
}); | ||
} | ||
modify(vname, properties, callback) { | ||
this._logging.info('Modifying VM %s', vname); | ||
var args = [vname]; | ||
function savestate(vmname, callback) { | ||
logging.info('Saving State (alias to stop) VM "%s"', vmname); | ||
stop(vmname, callback); | ||
} | ||
for (var property in properties) { | ||
if (properties.hasOwnProperty(property)) { | ||
var value = properties[property]; | ||
args.push('--' + property); | ||
function vmExport(vmname, output, callback) { | ||
logging.info('Exporting VM "%s"', vmname); | ||
vboxmanage(['export', vmname, '--output', output], function(error, stdout) { | ||
callback(error); | ||
}); | ||
} | ||
if (Array.isArray(value)) { | ||
Array.prototype.push.apply(args, value); | ||
} else { | ||
args.push(value.toString()); | ||
} | ||
} | ||
} | ||
function poweroff(vmname, callback) { | ||
logging.info('Powering off VM "%s"', vmname); | ||
vboxmanage(['controlvm', vmname, 'poweroff'], function(error, stdout) { | ||
callback(error); | ||
}); | ||
} | ||
this.vboxmanage(['modifyvm', ...args], function (error, _) { | ||
callback(error); | ||
}); | ||
} | ||
function acpipowerbutton(vmname, callback) { | ||
logging.info('ACPI power button VM "%s"', vmname); | ||
vboxmanage(['controlvm', vmname, 'acpipowerbutton'], function(error, stdout) { | ||
callback(error); | ||
}); | ||
} | ||
snapshotList(vmname, callback) { | ||
this._logging.info('Listing snapshots for VM "%s"', vmname); | ||
this.vboxmanage( | ||
['snapshot', vmname, 'list', '--machinereadable'], | ||
function (error, stdout) { | ||
if (error) { | ||
callback(error); | ||
return; | ||
} | ||
function acpisleepbutton(vmname, callback) { | ||
logging.info('ACPI sleep button VM "%s"', vmname); | ||
vboxmanage(['controlvm', vmname, 'acpisleepbutton'], function(error, stdout) { | ||
callback(error); | ||
}); | ||
} | ||
var s; | ||
var snapshots = []; | ||
var currentSnapshot; | ||
var lines = (stdout || '').split(require('os').EOL); | ||
function modify(vname, properties, callback) { | ||
logging.info('Modifying VM %s', vname); | ||
var args = [vname]; | ||
lines.forEach(function (line) { | ||
line | ||
.trim() | ||
.replace( | ||
/^(CurrentSnapshotUUID|SnapshotName|SnapshotUUID).*\="(.*)"$/, | ||
function (l, k, v) { | ||
if (k === 'CurrentSnapshotUUID') { | ||
currentSnapshot = v; | ||
} else if (k === 'SnapshotName') { | ||
s = { | ||
name: v, | ||
}; | ||
snapshots.push(s); | ||
} else { | ||
s.uuid = v; | ||
} | ||
} | ||
); | ||
}); | ||
for (var property in properties) { | ||
if (properties.hasOwnProperty(property)) { | ||
var value = properties[property]; | ||
args.push('--' + property); | ||
if (Array.isArray(value)) { | ||
Array.prototype.push.apply(args, value); | ||
callback(null, snapshots, currentSnapshot); | ||
} | ||
else { | ||
args.push(value.toString()); | ||
} | ||
} | ||
); | ||
} | ||
vboxmanage(['modifyvm', ...args], function (error, stdout) { | ||
callback(error); | ||
}); | ||
} | ||
snapshotTake( | ||
vmname, | ||
name, | ||
/*optional*/ description, | ||
/*optional*/ live, | ||
callback | ||
) { | ||
this._logging.info('Taking snapshot for VM "%s"', vmname); | ||
function snapshotList(vmname, callback) { | ||
logging.info('Listing snapshots for VM "%s"', vmname); | ||
vboxmanage(['snapshot', vmname, 'list', '--machinereadable'], function(error, stdout) { | ||
if (typeof description === 'function') { | ||
callback = description; | ||
description = undefined; | ||
} else if (typeof live === 'function') { | ||
callback = live; | ||
live = false; | ||
} | ||
if (error) { | ||
callback(error); | ||
return; | ||
var cmd = ['snapshot', vmname, 'take', name]; | ||
if (description) { | ||
cmd.push('--description'); | ||
cmd.push(description); | ||
} | ||
var s; | ||
var snapshots = []; | ||
var currentSnapshot; | ||
var lines = (stdout || '').split(require('os').EOL); | ||
if (live === true) { | ||
cmd.push('--live'); | ||
} | ||
lines.forEach(function(line) { | ||
line.trim().replace(/^(CurrentSnapshotUUID|SnapshotName|SnapshotUUID).*\="(.*)"$/, function(l, k, v) { | ||
if (k === 'CurrentSnapshotUUID') { | ||
currentSnapshot = v; | ||
} | ||
else if (k === 'SnapshotName') { | ||
s = { | ||
'name': v | ||
}; | ||
snapshots.push(s); | ||
} | ||
else { | ||
s.uuid = v; | ||
} | ||
this.vboxmanage(cmd, function (error, stdout) { | ||
var uuid; | ||
stdout.trim().replace(/UUID\: ([a-f0-9\-]+)$/, function (l, u) { | ||
uuid = u; | ||
}); | ||
callback(error, uuid); | ||
}); | ||
} | ||
callback(null, snapshots, currentSnapshot); | ||
}); | ||
} | ||
function snapshotTake(vmname, name, /*optional*/ description, /*optional*/ live, callback) { | ||
logging.info('Taking snapshot for VM "%s"', vmname); | ||
if(typeof description === 'function') { | ||
callback = description; | ||
description = undefined; | ||
snapshotDelete(vmname, uuid, callback) { | ||
this._logging.info('Deleting snapshot "%s" for VM "%s"', uuid, vmname); | ||
this.vboxmanage(['snapshot', vmname, 'delete', uuid], callback); | ||
} | ||
else if(typeof live === 'function') { | ||
callback = live; | ||
live = false; | ||
} | ||
var cmd = ['snapshot', vmname, 'take', name]; | ||
if(description) { | ||
cmd.push('--description'); | ||
cmd.push(description); | ||
snapshotRestore(vmname, uuid, callback) { | ||
this._logging.info('Restoring snapshot "%s" for VM "%s"', uuid, vmname); | ||
this.vboxmanage(['snapshot', vmname, 'restore', uuid], callback); | ||
} | ||
if(live === true) { | ||
cmd.push('--live'); | ||
clone(vmname, clonevmname, /*optional*/ snapshot, callback) { | ||
this._logging.info('Cloning machine "%s" to "%s"', vmname, clonevmname); | ||
var cmd = ['clonevm', vmname, '--name', clonevmname, '--register']; | ||
if (typeof snapshot === 'function') { | ||
callback = snapshot; | ||
snapshot = undefined; | ||
} else { | ||
cmd.push('--options'); | ||
cmd.push('link'); | ||
cmd.push('--snapshot'); | ||
cmd.push(snapshot); | ||
} | ||
this.vboxmanage(cmd, callback); | ||
} | ||
vboxmanage(cmd, function(error, stdout) { | ||
var uuid; | ||
stdout.trim().replace(/UUID\: ([a-f0-9\-]+)$/, function(l, u) { | ||
uuid = u; | ||
isRunning(vmname, callback) { | ||
this.vboxmanage(['list', 'runningvms'], (error, stdout) => { | ||
this._logging.info( | ||
'Checking virtual machine "%s" is running or not', | ||
vmname | ||
); | ||
if (stdout.indexOf(vmname) === -1) { | ||
callback(error, false); | ||
} else { | ||
callback(error, true); | ||
} | ||
}); | ||
callback(error, uuid); | ||
}); | ||
} | ||
} | ||
function snapshotDelete(vmname, uuid, callback) { | ||
logging.info('Deleting snapshot "%s" for VM "%s"', uuid, vmname); | ||
vboxmanage(['snapshot', vmname, 'delete', uuid], callback); | ||
} | ||
keyboardputscancode(vmname, codes, callback) { | ||
var codeStr = codes | ||
.map(function (code) { | ||
var s = code.toString(16); | ||
function snapshotRestore(vmname, uuid, callback) { | ||
logging.info('Restoring snapshot "%s" for VM "%s"', uuid, vmname); | ||
vboxmanage(['snapshot', vmname, 'restore', uuid], callback); | ||
} | ||
function clone(vmname, clonevmname, /*optional*/ snapshot, callback) { | ||
logging.info('Cloning machine "%s" to "%s"', vmname, clonevmname); | ||
var cmd = ['clonevm', vmname, '--name', clonevmname, "--register"]; | ||
if(typeof snapshot === 'function') { | ||
callback = snapshot; | ||
snapshot = undefined; | ||
}else{ | ||
cmd.push("--options"); | ||
cmd.push("link"); | ||
cmd.push("--snapshot"); | ||
cmd.push(snapshot); | ||
if (s.length === 1) { | ||
s = '0' + s; | ||
} | ||
return s; | ||
}) | ||
.join(' '); | ||
this._logging.info( | ||
'Sending VM "%s" keyboard scan codes "%s"', | ||
vmname, | ||
codeStr | ||
); | ||
this.vboxmanage( | ||
['controlvm', vmname, 'keyboardputscancode', codeStr], | ||
function (error, stdout) { | ||
callback(error, stdout); | ||
} | ||
); | ||
} | ||
vboxmanage(cmd, callback); | ||
} | ||
var storage = { | ||
addCtl: function (options, callback) { | ||
exec(options, callback) { | ||
var vm = options.vm || options.name || options.vmname || options.title, | ||
device_name = options.perhiperal_name || "IDE", | ||
type = options.type || "ide"; | ||
logging.info('Adding "%s" controller named "%s" to %s', type, device_name, vm); | ||
var cmd = ['storagectl', vm, '--name', device_name, "--add", type]; | ||
vboxmanage(cmd, callback); | ||
}, | ||
attach: function (options, callback) { | ||
var vm = options.vm || options.name || options.vmname || options.title, | ||
device_name = options.perhiperal_name || "IDE", | ||
port = options.port || "0", | ||
device = options.device || "0", | ||
type = options.type || "dvddrive", | ||
medium = options.medium; | ||
logging.info('Mounting "%s" to controller named "%s" on %s', medium, device_name, vm); | ||
var cmd = ['storageattach', vm, '--storagectl', device_name, "--port", port, "--device", device, "--type", type, "--medium", medium]; | ||
vboxmanage(cmd, callback); | ||
} | ||
}; | ||
username = options.user || options.username || 'Guest', | ||
password = options.pass || options.passwd || options.password, | ||
path = | ||
options.path || | ||
options.cmd || | ||
options.command || | ||
options.exec || | ||
options.execute || | ||
options.run, | ||
cmd, | ||
params = options.params || options.parameters || options.args; | ||
function isRunning(vmname, callback) { | ||
vboxmanage(['list', 'runningvms'], function (error, stdout) { | ||
logging.info('Checking virtual machine "%s" is running or not', vmname); | ||
if (stdout.indexOf(vmname) === -1) { | ||
callback(error, false); | ||
} else { | ||
callback(error, true); | ||
if (Array.isArray(params)) { | ||
params = params.join(' '); | ||
} | ||
}); | ||
} | ||
function keyboardputscancode(vmname, codes, callback) { | ||
var codeStr = codes.map(function(code) { | ||
var s = code.toString(16); | ||
if (s.length === 1) { | ||
s = '0' + s; | ||
if (params === undefined) { | ||
params = ''; | ||
} | ||
return s; | ||
}).join(' '); | ||
logging.info('Sending VM "%s" keyboard scan codes "%s"', vmname, codeStr); | ||
vboxmanage(['controlvm', vmname, 'keyboardputscancode', codeStr], function(error, stdout) { | ||
callback(error, stdout); | ||
}); | ||
} | ||
function vmExec(options, callback) { | ||
var vm = options.vm || options.name || options.vmname || options.title, | ||
username = options.user || options.username || 'Guest', | ||
password = options.pass || options.passwd || options.password, | ||
path = options.path || options.cmd || options.command || options.exec || options.execute || options.run, | ||
cmd, | ||
params = options.params || options.parameters || options.args; | ||
const getOSTypeCb = (os_type) => { | ||
var cmd = ['guestcontrol', vm]; | ||
var runcmd = this._vboxVersion > 5 ? ['run'] : ['execute', '--image']; | ||
cmd = [...cmd, ...runcmd]; | ||
switch (os_type) { | ||
case known_OS_types.WINDOWS: | ||
path = path.replace(/\\/g, '\\\\'); | ||
cmd.push('cmd.exe', '--username', username); | ||
break; | ||
case known_OS_types.MAC: | ||
cmd.push('/usr/bin/open', '-a', '--username', username); | ||
break; | ||
case known_OS_types.LINUX: | ||
cmd.push('/bin/sh', '--username', username); | ||
break; | ||
default: | ||
break; | ||
} | ||
if (Array.isArray(params)) { | ||
params = params.join(" "); | ||
} | ||
if (password) { | ||
cmd.push('--password', password); | ||
} | ||
cmd.push('--', '/c', path, params); | ||
if (params === undefined) { | ||
params = ""; | ||
this._logging.info( | ||
'Executing command "vboxmanage %s" on VM "%s" detected OS type "%s"', | ||
cmd, | ||
vm, | ||
os_type | ||
); | ||
this.vboxmanage(cmd, function (error, stdout) { | ||
callback(error, stdout); | ||
}); | ||
}; | ||
this.guestproperty.os(vm, getOSTypeCb); | ||
} | ||
guestproperty.os(vm, getOSTypeCb); | ||
kill(options, callback) { | ||
options = options || {}; | ||
var vm = options.vm || options.name || options.vmname || options.title, | ||
path = | ||
options.path || | ||
options.cmd || | ||
options.command || | ||
options.exec || | ||
options.execute || | ||
options.run, | ||
image_name = options.image_name || path; | ||
function getOSTypeCb(os_type) { | ||
var cmd = ['guestcontrol', vm]; | ||
var runcmd = vbox_version === 5 ? ['run'] : ['execute', '--image']; | ||
cmd.push(runcmd); | ||
switch (os_type) { | ||
case known_OS_types.WINDOWS: | ||
path = path.replace(/\\/g, '\\\\'); | ||
cmd.push('cmd.exe', '--username', username); | ||
break; | ||
case known_OS_types.MAC: | ||
cmd.push('/usr/bin/open', '-a', '--username', username); | ||
break; | ||
case known_OS_types.LINUX: | ||
cmd.push('/bin/sh', '--username', username); | ||
break; | ||
default: | ||
break; | ||
} | ||
this.guestproperty.os(vm, (os_type) => { | ||
switch (os_type) { | ||
case known_OS_types.WINDOWS: | ||
this._executor( | ||
{ | ||
vm: vm, | ||
user: options.user, | ||
password: options.password, | ||
path: 'C:\\Windows\\System32\\taskkill.exe /im ', | ||
params: image_name, | ||
}, | ||
callback | ||
); | ||
break; | ||
case known_OS_types.MAC: | ||
case known_OS_types.LINUX: | ||
this._executor( | ||
{ | ||
vm: vm, | ||
user: options.user, | ||
password: options.password, | ||
path: 'sudo killall ', | ||
params: image_name, | ||
}, | ||
callback | ||
); | ||
break; | ||
} | ||
}); | ||
} | ||
if (password) { | ||
cmd.push('--password', password); | ||
_setVboxManageBinary() { | ||
this._logging.info(host_platform); | ||
if (/^win/.test(host_platform)) { | ||
// Path may not contain VBoxManage.exe but it provides this environment variable | ||
var vBoxInstallPath = | ||
process.env.VBOX_INSTALL_PATH || process.env.VBOX_MSI_INSTALL_PATH; | ||
this._vBoxManageBinary = | ||
'"' + vBoxInstallPath + '\\VBoxManage.exe' + '" '; | ||
} else if (/^darwin/.test(host_platform) || /^linux/.test(host_platform)) { | ||
// Mac OS X and most Linux use the same binary name, in the path | ||
this._vBoxManageBinary = 'vboxmanage'; | ||
} else { | ||
// Otherwise (e.g., SunOS) hope it's in the path | ||
this._vBoxManageBinary = 'vboxmanage'; | ||
} | ||
cmd.push('--', '/c', path, params); | ||
} | ||
logging.info('Executing command "vboxmanage %s" on VM "%s" detected OS type "%s"', cmd, vm, os_type); | ||
vboxmanage(cmd, function(error, stdout) { | ||
callback(error, stdout); | ||
}); | ||
_detectVboxVersion() { | ||
this._executor(this._vBoxManageBinary, ['--version']).then( | ||
({ error, stdout }) => { | ||
if (error) { | ||
throw error; | ||
} else { | ||
this._vboxVersion = stdout.split('.')[0]; | ||
this._logging.info( | ||
'Virtualbox version detected as %s', | ||
this._vboxVersion | ||
); | ||
} | ||
} | ||
); | ||
} | ||
} | ||
function vmKill(options, callback) { | ||
options = options || {}; | ||
var vm = options.vm || options.name || options.vmname || options.title, | ||
path = options.path || options.cmd || options.command || options.exec || options.execute || options.run, | ||
image_name = options.image_name || path; | ||
class VboxStorage { | ||
constructor(logging, executor, vBoxManageBinary) { | ||
this._logging = logging; | ||
this._executor = executor; | ||
this._vBoxManageBinary = vBoxManageBinary; | ||
this.vboxmanage = defaultvboxmanage; | ||
} | ||
guestproperty.os(vm, function(os_type) { | ||
switch (os_type) { | ||
case known_OS_types.WINDOWS: | ||
vmExec({ | ||
vm: vm, | ||
user: options.user, | ||
password: options.password, | ||
path: 'C:\\Windows\\System32\\taskkill.exe /im ', | ||
params: image_name | ||
}, callback); | ||
break; | ||
case known_OS_types.MAC: | ||
case known_OS_types.LINUX: | ||
vmExec({ | ||
vm: vm, | ||
user: options.user, | ||
password: options.password, | ||
path: 'sudo killall ', | ||
params: image_name | ||
}, callback); | ||
break; | ||
} | ||
}); | ||
addCtl(options, callback) { | ||
var vm = options.vm || options.name || options.vmname || options.title, | ||
device_name = options.perhiperal_name || 'IDE', | ||
type = options.type || 'ide'; | ||
this._logging.info( | ||
'Adding "%s" controller named "%s" to %s', | ||
type, | ||
device_name, | ||
vm | ||
); | ||
var cmd = ['storagectl', vm, '--name', device_name, '--add', type]; | ||
this.vboxmanage(cmd, callback); | ||
} | ||
attach(options, callback) { | ||
var vm = options.vm || options.name || options.vmname || options.title, | ||
device_name = options.perhiperal_name || 'IDE', | ||
port = options.port || '0', | ||
device = options.device || '0', | ||
type = options.type || 'dvddrive', | ||
medium = options.medium; | ||
this._logging.info( | ||
'Mounting "%s" to controller named "%s" on %s', | ||
medium, | ||
device_name, | ||
vm | ||
); | ||
var cmd = [ | ||
'storageattach', | ||
vm, | ||
'--storagectl', | ||
device_name, | ||
'--port', | ||
port, | ||
'--device', | ||
device, | ||
'--type', | ||
type, | ||
'--medium', | ||
medium, | ||
]; | ||
this.vboxmanage(cmd, callback); | ||
} | ||
} | ||
var guestproperty = { | ||
get: function(options, callback) { | ||
class VboxGuestProperty { | ||
constructor(logging, executor, vBoxManageBinary) { | ||
this._logging = logging; | ||
this._executor = executor; | ||
this._vBoxManageBinary = vBoxManageBinary; | ||
this.os_type = null; | ||
this.vboxmanage = defaultvboxmanage; | ||
} | ||
get(options, callback) { | ||
var vm = options.vm || options.name || options.vmname || options.title, | ||
@@ -442,6 +591,7 @@ key = options.key, | ||
guestproperty.os(vm, getOSTypeCallback); | ||
function getOSTypeCallback(os_type) { | ||
vboxmanage(['guestproperty', 'get', vm, key], function(error, stdout) { | ||
this.os(vm, (os_type) => { | ||
this.vboxmanage(['guestproperty', 'get', vm, key], function ( | ||
error, | ||
stdout | ||
) { | ||
if (error) { | ||
@@ -456,42 +606,104 @@ throw error; | ||
}); | ||
}); | ||
} | ||
os(vmname, callback) { | ||
if (this.os_type) { | ||
return callback(this.os_type); | ||
} | ||
}, | ||
try { | ||
this.vboxmanage( | ||
['showvminfo', '--machinereadable', vmname], | ||
(error, stdout, _) => { | ||
if (error) { | ||
throw error; | ||
} | ||
os_type: null, // cached | ||
// The ostype is matched against the ID attribute of 'vboxmanage list ostypes' | ||
if (stdout.indexOf('ostype="Windows') !== -1) { | ||
this.os_type = known_OS_types.WINDOWS; | ||
} else if ( | ||
['ostype="MacOS', 'ostype="Mac OS machine'].includes(stdout) | ||
) { | ||
this.os_type = known_OS_types.MAC; | ||
} else { | ||
this.os_type = known_OS_types.LINUX; | ||
} | ||
this._logging.debug('Detected guest OS as: ' + this.os_type); | ||
callback(this.os_type); | ||
} | ||
); | ||
} catch (e) { | ||
this._logging.error(e); | ||
this._logging.info('Could not showvminfo for %s', vmname); | ||
} | ||
} | ||
os: function(vmname, callback) { | ||
function getOSTypeCallback(error, stdout, stderr) { | ||
if (error) { | ||
throw error; | ||
} | ||
/** | ||
* Function to return an array of this object: | ||
* { | ||
* "key": "ResumeCounter", | ||
* "value": 0, | ||
* "namespace": "VMInfo", | ||
* "timestamp": 1596902741176896000, | ||
* "flags": ["TRANSIENT", "RDONLYGUEST"] | ||
* } | ||
* @param {String} vmname The name of the VM to enumerate guest properties on. | ||
* @param {Function} callback The callback to handle the output. | ||
* @returns {void} The output of stdout will be an array of properties objects. | ||
*/ | ||
enumerate(vmname, callback) { | ||
this.vboxmanage( | ||
['guestproperty', 'enumerate', vmname], | ||
(err, stdout, _) => { | ||
if (err) { | ||
throw err; | ||
} | ||
const arrOfProps = stdout.split('\n'); | ||
const nameRegex = /(?<=Name: ).+?(?=\,)/; | ||
const valueRegex = /(?<=value: ).+?(?=\,)/; | ||
const timestampRegex = /(?<=timestamp: ).+?(?=\,)/; | ||
const flagsRegex = /(?<=flags: ).*/; | ||
// The ostype is matched against the ID attribute of 'vboxmanage list ostypes' | ||
if (stdout.indexOf('ostype="Windows') !== -1) { | ||
guestproperty.os_type = known_OS_types.WINDOWS; | ||
} else if (['ostype="MacOS', 'ostype="Mac OS machine'].includes(stdout)) { | ||
guestproperty.os_type = known_OS_types.MAC; | ||
} else { | ||
guestproperty.os_type = known_OS_types.LINUX; | ||
const arrOfPropsParsed = []; | ||
arrOfProps | ||
.filter((prop) => !!prop) | ||
.forEach((prop) => { | ||
const nameMatch = prop.match(nameRegex).shift(), | ||
value = prop.match(valueRegex).shift(), | ||
timestamp = prop.match(timestampRegex).shift(), | ||
flags = prop | ||
.match(flagsRegex) | ||
.shift() | ||
.split(',') | ||
.map((flag) => flag.replace(' ', '')), | ||
nameMatchSplit = nameMatch | ||
.split('/') | ||
.filter((name) => name !== ''), | ||
key = nameMatchSplit[2], | ||
namespace = nameMatchSplit[1]; | ||
arrOfPropsParsed.push({ | ||
key, | ||
value, | ||
namespace, | ||
timestamp, | ||
flags, | ||
}); | ||
}); | ||
callback(err, arrOfPropsParsed); | ||
} | ||
logging.debug('Detected guest OS as: ' + guestproperty.os_type); | ||
callback(guestproperty.os_type); | ||
} | ||
); | ||
} | ||
} | ||
if (guestproperty.os_type) { | ||
return callback(guestproperty.os_type); | ||
} | ||
try { | ||
vboxmanage(['showvminfo', '--machinereadable', vmname], getOSTypeCallback); | ||
} catch (e) { | ||
logging.error(e); | ||
logging.info('Could not showvminfo for %s', vmname); | ||
} | ||
class VboxExtraData { | ||
constructor(logging, executor, vBoxManageBinary) { | ||
this._logging = logging; | ||
this._executor = executor; | ||
this._vBoxManageBinary = vBoxManageBinary; | ||
this.vboxmanage = defaultvboxmanage; | ||
} | ||
}; | ||
var extradata = { | ||
get: function(options, callback) { | ||
get(options, callback) { | ||
var vm = options.vm || options.name || options.vmname || options.title, | ||
@@ -501,3 +713,3 @@ key = options.key, | ||
vboxmanage(['getextradata', vm, key], function(error, stdout) { | ||
this.vboxmanage(['getextradata', vm, key], function (error, stdout) { | ||
if (error) { | ||
@@ -513,5 +725,5 @@ callback(error); | ||
}); | ||
}, | ||
} | ||
set: function(options, callback) { | ||
set(options, callback) { | ||
var vm = options.vm || options.name || options.vmname || options.title, | ||
@@ -521,36 +733,40 @@ key = options.key, | ||
var cmd = 'setextradata "' + vm + '" "' + key + '" "' + value + '"'; | ||
vboxmanage(cmd, function(error, stdout) { | ||
callback(error); | ||
var cmd = ['setextradata', vm, key, value]; | ||
this.vboxmanage(cmd, function (error, stdout) { | ||
callback(error); | ||
}); | ||
} | ||
}; | ||
} | ||
module.exports = { | ||
'exec': vmExec, | ||
'kill': vmKill, | ||
'list': list, | ||
'pause': pause, | ||
'reset': reset, | ||
'resume': resume, | ||
'start': start, | ||
'stop': stop, | ||
'savestate': savestate, | ||
'export': vmExport, | ||
'poweroff': poweroff, | ||
'acpisleepbutton': acpisleepbutton, | ||
'acpipowerbutton': acpipowerbutton, | ||
'modify': modify, | ||
'guestproperty': guestproperty, | ||
'keyboardputscancode': keyboardputscancode, | ||
'snapshotList': snapshotList, | ||
'snapshotTake': snapshotTake, | ||
'snapshotDelete': snapshotDelete, | ||
'snapshotRestore': snapshotRestore, | ||
'isRunning': isRunning, | ||
'extradata': extradata, | ||
'clone': clone, | ||
'storage': storage, | ||
// module.exports = { | ||
// 'exec': vmExec, | ||
// 'kill': vmKill, | ||
// 'list': list, | ||
// 'pause': pause, | ||
// 'reset': reset, | ||
// 'resume': resume, | ||
// 'start': start, | ||
// 'stop': stop, | ||
// 'savestate': savestate, | ||
// 'export': vmExport, | ||
// 'poweroff': poweroff, | ||
// 'acpisleepbutton': acpisleepbutton, | ||
// 'acpipowerbutton': acpipowerbutton, | ||
// 'modify': modify, | ||
// 'guestproperty': guestproperty, | ||
// 'keyboardputscancode': keyboardputscancode, | ||
// 'snapshotList': snapshotList, | ||
// 'snapshotTake': snapshotTake, | ||
// 'snapshotDelete': snapshotDelete, | ||
// 'snapshotRestore': snapshotRestore, | ||
// 'isRunning': isRunning, | ||
// 'extradata': extradata, | ||
// 'clone': clone, | ||
// 'storage': storage, | ||
'SCAN_CODES': require('./scan-codes') | ||
}; | ||
// 'SCAN_CODES': require('./scan-codes') | ||
// }; | ||
module.exports = new Virtualbox(); | ||
module.exports.create = Virtualbox.create; | ||
module.exports.Virtualbox = Virtualbox; |
{ | ||
"name": "virtualbox", | ||
"version": "1.0.2", | ||
"version": "1.1.0", | ||
"description": "A library to interact with VirtualBox.", | ||
@@ -74,3 +74,3 @@ "author": "Azer Koculu <azer@kodfabrik.com>", | ||
"jest": "^26.0.1", | ||
"jshint": "^2.10.2" | ||
"jshint": "^2.12.0" | ||
}, | ||
@@ -77,0 +77,0 @@ "engines": { |
277
README.md
@@ -12,19 +12,10 @@ # node-virtualbox | ||
# Table of Contents | ||
- [Installation](#installation) | ||
- [Controlling Power and State](#controlling-power-and-state) | ||
- [Starting a cold machine: Two ways](#starting-a-cold-machine-two-ways) | ||
- [Stopping a machine](#stopping-a-machine) | ||
- [Pausing, Saving and Resuming a machine](#pausing-saving-and-resuming-a-machine) | ||
- [Controlling Power and State](#controlling-power-and-state) - [Starting a cold machine: Two ways](#starting-a-cold-machine-two-ways) - [Stopping a machine](#stopping-a-machine) - [Pausing, Saving and Resuming a machine](#pausing-saving-and-resuming-a-machine) | ||
- [Export a Machine](#export-a-machine) | ||
- [Snapshot Manage](#snapshot-manage) | ||
- [Cloning a VM](#cloning-vms) | ||
- [Storage](#storage) | ||
- [Manage the IDE controller](#manage-the-ide-controller) | ||
- [Attach a disk image file](#attach-a-disk-image-file) | ||
- [Controlling the guest OS](#controlling-the-guest-os) | ||
- [A note about security :warning:](#a-note-about-security) | ||
- [Running programs in the guest](#running-programs-in-the-guest) | ||
- [Executing commands as Administrators on Windows guests](#executing-commands-as-administrators-on-windows-guests) | ||
- [Killing programs in the guest](#killing-programs-in-the-guest) | ||
- [Sending keystrokes to a virtual machine](#sending-keystrokes-to-a-virtual-machine) | ||
- [Storage](#storage) - [Manage the IDE controller](#manage-the-ide-controller) - [Attach a disk image file](#attach-a-disk-image-file) | ||
- [Controlling the guest OS](#controlling-the-guest-os) - [A note about security :warning:](#a-note-about-security) - [Running programs in the guest](#running-programs-in-the-guest) - [Executing commands as Administrators on Windows guests](#executing-commands-as-administrators-on-windows-guests) - [Killing programs in the guest](#killing-programs-in-the-guest) - [Sending keystrokes to a virtual machine](#sending-keystrokes-to-a-virtual-machine) | ||
- [Meta information about machine](#meta-information-about-machine) | ||
@@ -37,2 +28,3 @@ - [Putting it all together](#putting-it-all-together) | ||
- [Contributing](#contributing) | ||
- [Testing](#testing) | ||
@@ -68,5 +60,5 @@ # Installation | ||
```javascript | ||
virtualbox.start("machine_name", true, function start_callback(error) { | ||
virtualbox.start('machine_name', true, function start_callback(error) { | ||
if (error) throw error; | ||
console.log("Virtual Machine has started WITH A GUI!"); | ||
console.log('Virtual Machine has started WITH A GUI!'); | ||
}); | ||
@@ -78,5 +70,5 @@ ``` | ||
```javascript | ||
virtualbox.start("machine_name", function start_callback(error) { | ||
virtualbox.start('machine_name', function start_callback(error) { | ||
if (error) throw error; | ||
console.log("Virtual Machine has started HEADLESS!"); | ||
console.log('Virtual Machine has started HEADLESS!'); | ||
}); | ||
@@ -90,5 +82,5 @@ ``` | ||
```javascript | ||
virtualbox.stop("machine_name", function stop_callback(error) { | ||
virtualbox.stop('machine_name', function stop_callback(error) { | ||
if (error) throw error; | ||
console.log("Virtual Machine has been saved"); | ||
console.log('Virtual Machine has been saved'); | ||
}); | ||
@@ -100,5 +92,5 @@ ``` | ||
```javascript | ||
virtualbox.poweroff("machine_name", function poweroff_callback(error) { | ||
virtualbox.poweroff('machine_name', function poweroff_callback(error) { | ||
if (error) throw error; | ||
console.log("Virtual Machine has been powered off!"); | ||
console.log('Virtual Machine has been powered off!'); | ||
}); | ||
@@ -108,3 +100,3 @@ ``` | ||
```javascript | ||
virtualbox.acpipowerbutton("machine_name", function acpipower_callback(error) { | ||
virtualbox.acpipowerbutton('machine_name', function acpipower_callback(error) { | ||
if (error) throw error; | ||
@@ -120,5 +112,5 @@ console.log("Virtual Machine's ACPI power button was pressed."); | ||
```javascript | ||
virtualbox.pause("machine_name", function pause_callback(error) { | ||
virtualbox.pause('machine_name', function pause_callback(error) { | ||
if (error) throw error; | ||
console.log("Virtual Machine is now paused!"); | ||
console.log('Virtual Machine is now paused!'); | ||
}); | ||
@@ -128,5 +120,5 @@ ``` | ||
```javascript | ||
virtualbox.savestate("machine_name", function save_callback(error) { | ||
virtualbox.savestate('machine_name', function save_callback(error) { | ||
if (error) throw error; | ||
console.log("Virtual Machine is now paused!"); | ||
console.log('Virtual Machine is now paused!'); | ||
}); | ||
@@ -138,3 +130,3 @@ ``` | ||
```javascript | ||
virtualbox.acpisleepbutton("machine_name", function acpisleep_callback(error) { | ||
virtualbox.acpisleepbutton('machine_name', function acpisleep_callback(error) { | ||
if (error) throw error; | ||
@@ -148,5 +140,5 @@ console.log("Virtual Machine's ACPI sleep button signal was sent."); | ||
```javascript | ||
virtualbox.resume("machine_name", function resume_callback(error) { | ||
virtualbox.resume('machine_name', function resume_callback(error) { | ||
if (error) throw error; | ||
console.log("Virtual Machine is now paused!"); | ||
console.log('Virtual Machine is now paused!'); | ||
}); | ||
@@ -158,3 +150,3 @@ ``` | ||
```javascript | ||
virtualbox.reset("machine_name", function reset_callback(error) { | ||
virtualbox.reset('machine_name', function reset_callback(error) { | ||
if (error) throw error; | ||
@@ -165,3 +157,2 @@ console.log("Virtual Machine's reset button was pressed!"); | ||
## Export a machine | ||
@@ -172,5 +163,5 @@ | ||
```javascript | ||
virtualbox.export("machine_name", "output", function export_callback(error) { | ||
if(error) throw error; | ||
console.log("Virtual Machine was exported!"); | ||
virtualbox.export('machine_name', 'output', function export_callback(error) { | ||
if (error) throw error; | ||
console.log('Virtual Machine was exported!'); | ||
}); | ||
@@ -184,6 +175,13 @@ ``` | ||
```javascript | ||
virtualbox.snapshotList("machine_name", function(error, snapshotList, currentSnapshotUUID) { | ||
if(error) throw error; | ||
if(snapshotList) { | ||
console.log(JSON.stringify(snapshotList), JSON.stringify(currentSnapshotUUID)); | ||
virtualbox.snapshotList('machine_name', function ( | ||
error, | ||
snapshotList, | ||
currentSnapshotUUID | ||
) { | ||
if (error) throw error; | ||
if (snapshotList) { | ||
console.log( | ||
JSON.stringify(snapshotList), | ||
JSON.stringify(currentSnapshotUUID) | ||
); | ||
} | ||
@@ -196,6 +194,9 @@ }); | ||
```javascript | ||
virtualbox.snapshotTake("machine_name", "snapshot_name", function(error, uuid) { | ||
virtualbox.snapshotTake('machine_name', 'snapshot_name', function ( | ||
error, | ||
uuid | ||
) { | ||
if (error) throw error; | ||
console.log('Snapshot has been taken!'); | ||
console.log('UUID: ', uuid); | ||
console.log('Snapshot has been taken!'); | ||
console.log('UUID: ', uuid); | ||
}); | ||
@@ -207,5 +208,5 @@ ``` | ||
```javascript | ||
virtualbox.snapshotDelete("machine_name", "snapshot_name", function(error) { | ||
virtualbox.snapshotDelete('machine_name', 'snapshot_name', function (error) { | ||
if (error) throw error; | ||
console.log('Snapshot has been deleted!'); | ||
console.log('Snapshot has been deleted!'); | ||
}); | ||
@@ -217,5 +218,5 @@ ``` | ||
```javascript | ||
virtualbox.snapshotRestore("machine_name", "snapshot_name", function(error) { | ||
virtualbox.snapshotRestore('machine_name', 'snapshot_name', function (error) { | ||
if (error) throw error; | ||
console.log('Snapshot has been restored!'); | ||
console.log('Snapshot has been restored!'); | ||
}); | ||
@@ -229,5 +230,5 @@ ``` | ||
```javascript | ||
virtualbox.clone("source_machine_name", "new_machine_name", function(error) { | ||
virtualbox.clone('source_machine_name', 'new_machine_name', function (error) { | ||
if (error) throw error; | ||
console.log('Done fully cloning the virtual machine!'); | ||
console.log('Done fully cloning the virtual machine!'); | ||
}); | ||
@@ -239,10 +240,18 @@ ``` | ||
```javascript | ||
virtualbox.snapshotTake("machine_name", "snapshot_name", function(error, uuid) { | ||
virtualbox.snapshotTake('machine_name', 'snapshot_name', function ( | ||
error, | ||
uuid | ||
) { | ||
if (error) throw error; | ||
console.log('Snapshot has been taken!'); | ||
console.log('UUID: ', uuid); | ||
virtualbox.clone("machine_name", "new_machine_name", "snapshot_name", function(error) { | ||
if (error) throw error; | ||
console.log('Done making a linked clone of the virtual machine!'); | ||
}); | ||
console.log('Snapshot has been taken!'); | ||
console.log('UUID: ', uuid); | ||
virtualbox.clone( | ||
'machine_name', | ||
'new_machine_name', | ||
'snapshot_name', | ||
function (error) { | ||
if (error) throw error; | ||
console.log('Done making a linked clone of the virtual machine!'); | ||
} | ||
); | ||
}); | ||
@@ -258,9 +267,12 @@ ``` | ||
```javascript | ||
virtualbox.storage.addCtl({ | ||
vm: "machine_name", | ||
perhiperal_name: "IDE", //optional | ||
type: "ide" //optional | ||
}, function(){ | ||
console.log('Controller has been added!'); | ||
}) | ||
virtualbox.storage.addCtl( | ||
{ | ||
vm: 'machine_name', | ||
perhiperal_name: 'IDE', //optional | ||
type: 'ide', //optional | ||
}, | ||
function () { | ||
console.log('Controller has been added!'); | ||
} | ||
); | ||
``` | ||
@@ -273,12 +285,15 @@ | ||
```javascript | ||
virtualbox.storage.attach({ | ||
vm: "machine_name", | ||
perhiperal_name: "IDE", //optional | ||
port: "0", //optional | ||
device: "0", //optional | ||
type: "dvddrive", //optional | ||
medium: "X:\Folder\containing\the.iso" | ||
}, function(){ | ||
console.log('Image has been mounted!'); | ||
}) | ||
virtualbox.storage.attach( | ||
{ | ||
vm: 'machine_name', | ||
perhiperal_name: 'IDE', //optional | ||
port: '0', //optional | ||
device: '0', //optional | ||
type: 'dvddrive', //optional | ||
medium: 'X:Foldercontaining\the.iso', | ||
}, | ||
function () { | ||
console.log('Image has been mounted!'); | ||
} | ||
); | ||
``` | ||
@@ -306,10 +321,10 @@ | ||
var options = { | ||
vm: "machine_name", | ||
cmd: "C:\\Program Files\\Internet Explorer\\iexplore.exe", | ||
params: "https://google.com" | ||
} | ||
vm: 'machine_name', | ||
cmd: 'C:\\Program Files\\Internet Explorer\\iexplore.exe', | ||
params: 'https://google.com', | ||
}; | ||
virtualbox.exec(options, function exec_callback(error, stdout) { | ||
if (error) throw error; | ||
console.log('Started Internet Explorer...'); | ||
if (error) throw error; | ||
console.log('Started Internet Explorer...'); | ||
}); | ||
@@ -324,7 +339,7 @@ ``` | ||
var options = { | ||
vm: "machine_name", | ||
user:"Administrator", | ||
password: "123456", | ||
cmd: "C:\\Program Files\\Internet Explorer\\iexplore.exe", | ||
params: "https://google.com" | ||
vm: 'machine_name', | ||
user: 'Administrator', | ||
password: '123456', | ||
cmd: 'C:\\Program Files\\Internet Explorer\\iexplore.exe', | ||
params: 'https://google.com', | ||
}; | ||
@@ -338,9 +353,12 @@ ``` | ||
```javascript | ||
virtualbox.kill({ | ||
vm: "machine_name", | ||
cmd: "iexplore.exe" | ||
}, function kill_callback(error) { | ||
virtualbox.kill( | ||
{ | ||
vm: 'machine_name', | ||
cmd: 'iexplore.exe', | ||
}, | ||
function kill_callback(error) { | ||
if (error) throw error; | ||
console.log('Terminated Internet Explorer.'); | ||
}); | ||
} | ||
); | ||
``` | ||
@@ -355,12 +373,16 @@ | ||
var sequence = [ | ||
{ key: 'SHIFT', type: 'make', code: SCAN_CODES['SHIFT']}, | ||
{ key: 'A', type: 'make', code: SCAN_CODES['A']}, | ||
{ key: 'SHIFT', type: 'break', code: SCAN_CODES.getBreakCode('SHIFT')}, | ||
{ key: 'A', type: 'break', code: SCAN_CODES.getBreakCode('A')} | ||
{ key: 'SHIFT', type: 'make', code: SCAN_CODES['SHIFT'] }, | ||
{ key: 'A', type: 'make', code: SCAN_CODES['A'] }, | ||
{ key: 'SHIFT', type: 'break', code: SCAN_CODES.getBreakCode('SHIFT') }, | ||
{ key: 'A', type: 'break', code: SCAN_CODES.getBreakCode('A') }, | ||
]; | ||
virtualbox.keyboardputscancode("machine_name", sequence, function keyscan_callback(err) { | ||
virtualbox.keyboardputscancode( | ||
'machine_name', | ||
sequence, | ||
function keyscan_callback(err) { | ||
if (error) throw error; | ||
console.log('Sent SHIFT A'); | ||
}); | ||
} | ||
); | ||
``` | ||
@@ -383,5 +405,5 @@ | ||
var options = { | ||
vm: "machine_name", | ||
key: "/VirtualBox/GuestInfo/Net/0/V4/IP" | ||
} | ||
vm: 'machine_name', | ||
key: '/VirtualBox/GuestInfo/Net/0/V4/IP', | ||
}; | ||
@@ -398,9 +420,14 @@ virtualbox.guestproperty.get(function guestproperty_callback(machines, error) { | ||
var options = { | ||
vm: "machine_name", | ||
key: "GUI/Fullscreen" | ||
} | ||
vm: 'machine_name', | ||
key: 'GUI/Fullscreen', | ||
}; | ||
virtualbox.extradata.get(options, function extradataget_callback(error, value) { | ||
if (error) throw error; | ||
console.log('Virtual Machine "%s" extra "%s" value is "%s"', options.vm, options.key, value); | ||
console.log( | ||
'Virtual Machine "%s" extra "%s" value is "%s"', | ||
options.vm, | ||
options.key, | ||
value | ||
); | ||
}); | ||
@@ -413,10 +440,15 @@ ``` | ||
var options = { | ||
vm: "machine_name", | ||
key: "GUI/Fullscreen", | ||
value: "true" | ||
} | ||
vm: 'machine_name', | ||
key: 'GUI/Fullscreen', | ||
value: 'true', | ||
}; | ||
virtualbox.extradata.set(options, function extradataset_callback(error) { | ||
if (error) throw error; | ||
console.log('Set Virtual Machine "%s" extra "%s" value to "%s"', options.vm, options.key, options.value); | ||
console.log( | ||
'Set Virtual Machine "%s" extra "%s" value to "%s"', | ||
options.vm, | ||
options.key, | ||
options.value | ||
); | ||
}); | ||
@@ -432,19 +464,18 @@ ``` | ||
virtualbox.start("machine_name", function start_callback(error) { | ||
virtualbox.start('machine_name', function start_callback(error) { | ||
if (error) throw error; | ||
if (error) throw error; | ||
console.log('VM "w7" has been successfully started'); | ||
console.log('VM "w7" has been successfully started'); | ||
virtualbox.exec({ | ||
vm: "machine_name", | ||
cmd: "C:\\Program Files\\Internet Explorer\\iexplore.exe", | ||
params: "http://google.com" | ||
}, function (error) { | ||
if (error) throw error; | ||
console.log('Running Internet Explorer...'); | ||
}); | ||
virtualbox.exec( | ||
{ | ||
vm: 'machine_name', | ||
cmd: 'C:\\Program Files\\Internet Explorer\\iexplore.exe', | ||
params: 'http://google.com', | ||
}, | ||
function (error) { | ||
if (error) throw error; | ||
console.log('Running Internet Explorer...'); | ||
} | ||
); | ||
}); | ||
@@ -475,3 +506,3 @@ ``` | ||
- `.snapshotList({vm:"machine_name"}, callback)` | ||
- `.snapshotTake({vm:"machine_name"}, {vm:"snapshot_name"}, callback)` | ||
- `.snapshotTake({vm:"machine_name"}, {vm:"snapshot_name"}, callback)` | ||
- `.snapshotDelete({vm:"machine_name"}, {vm:"snapshot_UUID"}, callback)` | ||
@@ -509,1 +540,5 @@ - `.snapshotRestore({vm:"machine_name"}, {vm:"snapshot_UUID"}, callback)` | ||
Please abide by the [Contributor Code of Conduct](https://github.com/Node-Virtualization/node-virtualbox/blob/master/code_of_conduct.md). | ||
## Testing | ||
We currently do not have a complete unit testing suite. However, example scripts and a Vagrantfile are provided. Test your changes by writing a new script and/or running through all the test scripts to make sure they behave as expected. To do this [install vagrant](https://www.vagrantup.com/docs/installation) and run `vagrant up` in this repository's root directory. Then run the example scripts by using node: `node test/integration/<script-name>.js`. Please be ready to provide test output upon opening a pull request. |
'use strict'; | ||
const virtualbox = require('../lib/virtualbox'), | ||
args = process.argv.slice(2); | ||
const { logger } = require('./helpers/logger'); | ||
const { create } = require('../lib/virtualbox'); | ||
jest.mock('child_process'); | ||
describe('Virtualbox#guestproperty', () => { | ||
afterEach(() => { | ||
virtualbox.guestproperty.os_type = null; | ||
}); | ||
it('should not throw an error when getting a guest property with ostype of MacOS', (done) => { | ||
const { execFile } = require('child_process'); | ||
execFile | ||
.mockImplementationOnce((_, __, callback) => { | ||
callback(null, 'ostype="MacOS', ''); | ||
}) | ||
.mockImplementationOnce((_, __, callback) => { | ||
callback(null, 'somevalue', ''); | ||
}); | ||
const executor = jest | ||
.fn() | ||
.mockReturnValueOnce( | ||
new Promise((resolve) => | ||
resolve({ err: null, stdout: 'somevalue', stderr: '' }) | ||
) | ||
) | ||
.mockReturnValueOnce( | ||
new Promise((resolve) => | ||
resolve({ err: null, stdout: 'ostype="MacOS', stderr: '' }) | ||
) | ||
) | ||
.mockReturnValueOnce( | ||
new Promise((resolve) => | ||
resolve({ | ||
err: null, | ||
stdout: 'somevalue', | ||
stderr: '', | ||
}) | ||
) | ||
); | ||
const virtualbox = create(logger, executor); | ||
virtualbox.guestproperty.get( | ||
@@ -32,10 +41,32 @@ { vm: 'testmachine', key: 'someProperty' }, | ||
it('should not throw an error when getting a guest property with ostype of Mac OS machine', (done) => { | ||
const { execFile } = require('child_process'); | ||
execFile | ||
.mockImplementationOnce((_, __, callback) => { | ||
callback(null, 'ostype="Mac OS machine', ''); | ||
}) | ||
.mockImplementationOnce((_, __, callback) => { | ||
callback(null, 'somevalue', ''); | ||
}); | ||
const executor = jest | ||
.fn() | ||
.mockReturnValueOnce( | ||
new Promise((resolve) => | ||
resolve({ | ||
err: null, | ||
stdout: 'somevalue', | ||
stderr: '', | ||
}) | ||
) | ||
) | ||
.mockReturnValueOnce( | ||
new Promise((resolve) => | ||
resolve({ | ||
err: null, | ||
stdout: 'ostype="Mac OS machine', | ||
stderr: '', | ||
}) | ||
) | ||
) | ||
.mockReturnValueOnce( | ||
new Promise((resolve) => | ||
resolve({ | ||
err: null, | ||
stdout: 'somevalue', | ||
stderr: '', | ||
}) | ||
) | ||
); | ||
const virtualbox = create(logger, executor); | ||
virtualbox.guestproperty.get( | ||
@@ -42,0 +73,0 @@ { vm: 'testmachine', key: 'someProperty' }, |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
61406
34
1220
513
2