├── .eslintrc ├── .github ├── dependabot.yml ├── stale.yml └── workflows │ └── main.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── Vagrantfile ├── code_of_conduct.md ├── lib ├── scan-codes.js └── virtualbox.js ├── package-lock.json ├── package.json └── test ├── guestproperty.spec.js ├── helpers └── logger.js └── integration ├── acpipowerbutton.js ├── acpisleepbutton.js ├── exec.js ├── export.js ├── getextradata.js ├── guestproperty-enumerate.js ├── guestproperty.js ├── isRunning.js ├── keyboardputscancode.js ├── list.js ├── modify.js ├── pause.js ├── poweroff.js ├── resume.js ├── savestate.js ├── setextradata.js ├── snapshot-delete.js ├── snapshot-list.js ├── snapshot-restore.js ├── snapshot-take.js ├── start.js └── stop.js /.eslintrc: -------------------------------------------------------------------------------- 1 | // { 2 | // // Settings 3 | // "esversion" : 6, 4 | // "passfail" : false, // Stop on first error. 5 | // "maxerr" : 100, // Maximum errors before stopping. 6 | 7 | // // Predefined globals whom JSHint will ignore. 8 | // "browser" : true, // Standard browser globals e.g. `window`, `document`. 9 | 10 | // "node" : true, 11 | // "rhino" : false, 12 | // "couch" : false, 13 | // "wsh" : false, // Windows Scripting Host. 14 | 15 | // "jquery" : false, 16 | // "prototypejs" : false, 17 | // "mootools" : false, 18 | // "dojo" : false, 19 | 20 | // "predef" : [ // Extra globals. 21 | // "require", 22 | // "define", 23 | // "notify", 24 | // "expect", 25 | // "it", 26 | // "afterEach", 27 | // "describe", 28 | // "jest" 29 | // ], 30 | 31 | // // Development. 32 | // "debug" : false, // Allow debugger statements e.g. browser breakpoints. 33 | // "devel" : false, // Allow development statements e.g. `console.log();`. 34 | 35 | // // EcmaScript 5. 36 | // "strict" : true, // Require `use strict` pragma in every file. 37 | // "globalstrict" : false, // Allow global "use strict" (also enables 'strict'). 38 | 39 | // // The Good Parts. 40 | // "asi" : false, // Tolerate Automatic Semicolon Insertion (no semicolons). 41 | // "laxbreak" : false, // Tolerate unsafe line breaks e.g. `return [\n] x` without semicolons. 42 | // "bitwise" : true, // Prohibit bitwise operators (&, |, ^, etc.). 43 | // "boss" : false, // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments. 44 | // "curly" : true, // Require {} for every new block or scope. 45 | // "eqeqeq" : false, // Require triple equals i.e. `===`. 46 | // "eqnull" : false, // Tolerate use of `== null`. 47 | // "evil" : true, // Tolerate use of `eval`. 48 | // "expr" : false, // Tolerate `ExpressionStatement` as Programs. 49 | // "forin" : false, // Tolerate `for in` loops without `hasOwnPrototype`. 50 | // "immed" : true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );` 51 | // "latedef" : false, // Prohibit variable use before definition. 52 | // "loopfunc" : true, // Allow functions to be defined within loops. 53 | // "noarg" : true, // Prohibit use of `arguments.caller` and `arguments.callee`. 54 | // "regexp" : true, // Prohibit `.` and `[^...]` in regular expressions. 55 | // "regexdash" : false, // Tolerate unescaped last dash i.e. `[-...]`. 56 | // "scripturl" : true, // Tolerate script-targeted URLs. 57 | // "shadow" : false, // Allows re-define variables later in code e.g. `var x=1; x=2;`. 58 | // "supernew" : true, // Tolerate `new function () { ... };` and `new Object;`. 59 | // "undef" : true, // Require all non-global variables be declared before they are used. 60 | 61 | // // Styling prefrences. 62 | // "newcap" : false, // Require capitalization of all constructor functions e.g. `new F()`. 63 | // "noempty" : false, // Prohibit use of empty blocks. 64 | // "nonew" : false, // Prohibit use of constructors for side-effects. 65 | // "nomen" : false, // Prohibit use of initial or trailing underbars in names. 66 | // "onevar" : false, // Allow only one `var` statement per function. 67 | // "plusplus" : false, // Prohibit use of `++` & `--`. 68 | // "sub" : true, // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`. 69 | // "trailing" : false, // Prohibit trailing whitespaces. 70 | // "white" : false // Check against strict whitespace and indentation rules. 71 | // } 72 | 73 | { 74 | "parserOptions": { 75 | "ecmaVersion": 2018, 76 | }, 77 | "env": { 78 | "browser": true, 79 | "node": true, 80 | "es6": true, 81 | }, 82 | "globals": { 83 | "require": true, 84 | "define": true, 85 | "notify": true, 86 | "expect": true, 87 | "it": true, 88 | "afterEach": true, 89 | "describe": true, 90 | "jest": true, 91 | }, 92 | "rules": { 93 | "no-debugger": "error", 94 | "no-console": "error", 95 | "strict": ["error", "global"], 96 | "no-bitwise": "error", 97 | "curly": "error", 98 | "eqeqeq": "off", 99 | "no-eval": "error", 100 | "no-undef": "error", 101 | "no-redeclare": "error", 102 | "no-caller": "error", 103 | "no-unused-vars": [ 104 | "error", 105 | { 106 | "argsIgnorePattern": "_", 107 | }, 108 | ], 109 | "no-empty": "off", 110 | "no-new": "off", 111 | "no-underscore-dangle": "off", 112 | "one-var": "off", 113 | "no-plusplus": "off", 114 | "no-sub": "off", 115 | "no-trailing-spaces": "off", 116 | "no-tabs": "off", 117 | }, 118 | } 119 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # .github/dependabot.yml 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: "npm" 6 | directory: "/" 7 | schedule: 8 | interval: "weekly" 9 | labels: 10 | - "dependencies" 11 | assignees: 12 | - "colonelpopcorn" 13 | reviewers: 14 | - "colonelpopcorn" 15 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 90 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 14 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: stale 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not received 14 | any attention in 90 days. It will be closed in 14 days if no further activity occurs. 15 | Thank you for your contribution! 16 | (I'm a bot.) 17 | # Comment to post when closing a stale issue. Set to `false` to disable 18 | closeComment: > 19 | This issue has been automatically closed due to inactivity. 20 | (I'm a bot.) 21 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # GitHub Actions (.github/workflows/main.yml) 2 | name: Node.js CI 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | - master 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | node-version: ["22", "21", "20", "19", "18"] 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v2 19 | - name: Setup Node.js 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | - name: Install project dependencies 24 | run: npm install 25 | - name: Run tests 26 | run: npm test 27 | - name: Run linting 28 | run: npm run lint 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ###################### 2 | # OS-generated files 3 | ###################### 4 | .DS_Store 5 | .DS_Store? 6 | ._* 7 | .Spotlight-V100 8 | .Trashes 9 | ehthumbs.db 10 | Thumbs.db 11 | 12 | ###################### 13 | # IDE & Tools files 14 | ###################### 15 | # IntelliJ IDEA, WebStorm, phpStorm 16 | .idea/ 17 | *.iml 18 | *.iws 19 | 20 | # Eclipse 21 | .classpath 22 | .project 23 | .settings/ 24 | 25 | # Sublime Text 26 | *.sublime-project 27 | *.sublime-workspace 28 | 29 | # vi buffers 30 | *~ 31 | *.swp 32 | *.swo 33 | 34 | # Emacs buffers 35 | \#* 36 | .\#* 37 | 38 | # TextMate 39 | *.tmproj 40 | tmtags 41 | 42 | # Build & testing tools 43 | node_modules 44 | .vagrant 45 | 46 | ###################### 47 | # Logs and databases 48 | ###################### 49 | *.log 50 | *.sql 51 | *.sqlite 52 | 53 | ###################### 54 | # Projects checked out to this root 55 | ###################### 56 | 57 | \.vscode/ 58 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # CONTRIBUTING 2 | 3 | Please familiarize yourself with the [Contributor Code of Conduct](https://github.com/Node-Virtualization/node-virtualbox/blob/master/code_of_conduct.md) 4 | 5 | ## Filing Issues 6 | 7 | - Please file issues using the issue templates. 8 | 9 | - Please follow the issue template exactly and provide as much meaningful detail as you can. 10 | 11 | ## Testing Your Work 12 | 13 | Please test your work thoroughly before submitting a PR. 14 | 15 | Understand that Node-Virtualbox is used on a wide variety of platforms; avoid code that applies only to one host operating system and make it as generic as possible. 16 | 17 | ## Submitting Pull Requests 18 | 19 | 1. Pull requests *must* be tied to an issue. If no issue exists, please file one (see above). 20 | 1. Before starting work, please claim the issue by commenting on it and letting _everyone_ know you'll be working on it. This helps prevent duplicate PRs. 21 | 1. Please try to let us know if you abandon an issue so we can remove the `in-progress` label. 22 | 1. Mention the issue number in a commit. 23 | 1. Fix any issues in the automated PR checks, or explain why they're not relevant. 24 | 25 | ## Code Quality Tools 26 | 27 | Checking tools are there as guidance. Use your judgement. 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Azer Koçulu, Michael Sanford 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-virtualbox 2 | 3 | ![NPM version](https://badge.fury.io/js/virtualbox.svg) 4 | [![Build Status](https://travis-ci.org/Node-Virtualization/node-virtualbox.svg?branch=master)](https://travis-ci.org/Node-Virtualization/node-virtualbox) 5 | [![DepShield Badge](https://depshield.sonatype.org/badges/Node-Virtualization/node-virtualbox/depshield.svg)](https://depshield.github.io) 6 | 7 | A JavaScript library to interact with [VirtualBox](https://www.virtualbox.org/) virtual machines. 8 | 9 | # Table of Contents 10 | 11 | - [Installation](#installation) 12 | - [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) 13 | - [Import a Machine](#import-a-machine) 14 | - [Export a Machine](#export-a-machine) 15 | - [Snapshot Manage](#snapshot-manage) 16 | - [Cloning a VM](#cloning-vms) 17 | - [Storage](#storage) - [Manage the IDE controller](#manage-the-ide-controller) - [Attach a disk image file](#attach-a-disk-image-file) 18 | - [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) 19 | - [Meta information about machine](#meta-information-about-machine) 20 | - [Putting it all together](#putting-it-all-together) 21 | - [Available Methods](#available-methods) 22 | - [Troubleshooting](#troubleshooting) 23 | - [More Examples](#more-examples) 24 | - [License (MIT)](#license) 25 | - [Contributing](#contributing) 26 | - [Testing](#testing) 27 | 28 | # Installation 29 | 30 | Obtain the package 31 | 32 | ```bash 33 | $ npm install virtualbox [--save] [-g] 34 | ``` 35 | 36 | and then use it 37 | 38 | ```javascript 39 | var virtualbox = require('virtualbox'); 40 | ``` 41 | 42 | The general formula for commands is: 43 | 44 | > virtualbox. **API command** ( "**registered vm name**", **[parameters]**, **callback** ); 45 | 46 | Available API commands are listed at the end of this document. 47 | 48 | # Controlling Power and State 49 | 50 | `node-virtualbox` provides convenience methods to command the guest machine's power state in the customary ways. 51 | 52 | ## Starting a cold machine: Two ways 53 | 54 | Virtual machines will _start headless by default_, but you can pass a boolean parameter to start them with a GUI: 55 | 56 | ```javascript 57 | virtualbox.start('machine_name', true, function start_callback(error) { 58 | if (error) throw error; 59 | console.log('Virtual Machine has started WITH A GUI!'); 60 | }); 61 | ``` 62 | 63 | So as not to break pre-0.1.0 implementations, the old method still works (which also defaults to headless): 64 | 65 | ```javascript 66 | virtualbox.start('machine_name', function start_callback(error) { 67 | if (error) throw error; 68 | console.log('Virtual Machine has started HEADLESS!'); 69 | }); 70 | ``` 71 | 72 | ## Stopping a machine 73 | 74 | **Note:** For historical reasons, `.stop` is an alias to `.savestate`. 75 | 76 | ```javascript 77 | virtualbox.stop('machine_name', function stop_callback(error) { 78 | if (error) throw error; 79 | console.log('Virtual Machine has been saved'); 80 | }); 81 | ``` 82 | 83 | To halt a machine completely, you can use `poweroff` or `acpipowerbutton`: 84 | 85 | ```javascript 86 | virtualbox.poweroff('machine_name', function poweroff_callback(error) { 87 | if (error) throw error; 88 | console.log('Virtual Machine has been powered off!'); 89 | }); 90 | ``` 91 | 92 | ```javascript 93 | virtualbox.acpipowerbutton('machine_name', function acpipower_callback(error) { 94 | if (error) throw error; 95 | console.log("Virtual Machine's ACPI power button was pressed."); 96 | }); 97 | ``` 98 | 99 | ## Pausing, Saving and Resuming a machine 100 | 101 | Noting the caveat above that `.stop` is actually an alias to `.savestate`... 102 | 103 | ```javascript 104 | virtualbox.pause('machine_name', function pause_callback(error) { 105 | if (error) throw error; 106 | console.log('Virtual Machine is now paused!'); 107 | }); 108 | ``` 109 | 110 | ```javascript 111 | virtualbox.savestate('machine_name', function save_callback(error) { 112 | if (error) throw error; 113 | console.log('Virtual Machine is now paused!'); 114 | }); 115 | ``` 116 | 117 | And, in the same family, `acpisleepbutton`: 118 | 119 | ```javascript 120 | virtualbox.acpisleepbutton('machine_name', function acpisleep_callback(error) { 121 | if (error) throw error; 122 | console.log("Virtual Machine's ACPI sleep button signal was sent."); 123 | }); 124 | ``` 125 | 126 | Note that you should probably _resume_ a machine which is in one of the above three states. 127 | 128 | ```javascript 129 | virtualbox.resume('machine_name', function resume_callback(error) { 130 | if (error) throw error; 131 | console.log('Virtual Machine is now paused!'); 132 | }); 133 | ``` 134 | 135 | And, of course, a reset button method: 136 | 137 | ```javascript 138 | virtualbox.reset('machine_name', function reset_callback(error) { 139 | if (error) throw error; 140 | console.log("Virtual Machine's reset button was pressed!"); 141 | }); 142 | ``` 143 | 144 | ## Import a machine 145 | 146 | You can import an OVA or OVF file with the `vmImport` method: 147 | 148 | ```javascript 149 | virtualbox.vmImport('ova_file_path', options, function import_callback(error) { 150 | if (error) throw error; 151 | console.log('Virtual Machine was imported!'); 152 | }); 153 | ``` 154 | 155 | The options object may contain optional parameters: 156 | * `vmname`: the name of the new VM. 157 | * `cpus`: the number of CPUs. 158 | * `memory`: the amount of memory in megabytes. 159 | 160 | ## Export a machine 161 | 162 | You can export with `vmExport` method: 163 | 164 | ```javascript 165 | virtualbox.vmExport('machine_name', 'output', function export_callback(error) { 166 | if (error) throw error; 167 | console.log('Virtual Machine was exported!'); 168 | }); 169 | ``` 170 | 171 | ## Snapshot Manage 172 | 173 | You can show snapshot list with `snapshotList` method: 174 | 175 | ```javascript 176 | virtualbox.snapshotList('machine_name', function ( 177 | error, 178 | snapshotList, 179 | currentSnapshotUUID 180 | ) { 181 | if (error) throw error; 182 | if (snapshotList) { 183 | console.log( 184 | JSON.stringify(snapshotList), 185 | JSON.stringify(currentSnapshotUUID) 186 | ); 187 | } 188 | }); 189 | ``` 190 | 191 | And, you can take a snapshot: 192 | 193 | ```javascript 194 | virtualbox.snapshotTake('machine_name', 'snapshot_name', function ( 195 | error, 196 | uuid 197 | ) { 198 | if (error) throw error; 199 | console.log('Snapshot has been taken!'); 200 | console.log('UUID: ', uuid); 201 | }); 202 | ``` 203 | 204 | Or, delete a snapshot: 205 | 206 | ```javascript 207 | virtualbox.snapshotDelete('machine_name', 'snapshot_name', function (error) { 208 | if (error) throw error; 209 | console.log('Snapshot has been deleted!'); 210 | }); 211 | ``` 212 | 213 | Or, restore a snapshot: 214 | 215 | ```javascript 216 | virtualbox.snapshotRestore('machine_name', 'snapshot_name', function (error) { 217 | if (error) throw error; 218 | console.log('Snapshot has been restored!'); 219 | }); 220 | ``` 221 | 222 | ## Cloning VMs 223 | 224 | Make a full clone (duplicate virtual hard drive) of a machine: 225 | 226 | ```javascript 227 | virtualbox.clone('source_machine_name', 'new_machine_name', function (error) { 228 | if (error) throw error; 229 | console.log('Done fully cloning the virtual machine!'); 230 | }); 231 | ``` 232 | 233 | Make a linked clone (interdependent-differentially stored virtual hard drive) of a machine: 234 | 235 | ```javascript 236 | virtualbox.snapshotTake('machine_name', 'snapshot_name', function ( 237 | error, 238 | uuid 239 | ) { 240 | if (error) throw error; 241 | console.log('Snapshot has been taken!'); 242 | console.log('UUID: ', uuid); 243 | virtualbox.clone( 244 | 'machine_name', 245 | 'new_machine_name', 246 | 'snapshot_name', 247 | function (error) { 248 | if (error) throw error; 249 | console.log('Done making a linked clone of the virtual machine!'); 250 | } 251 | ); 252 | }); 253 | ``` 254 | 255 | ## Storage 256 | 257 | ### Manage the IDE controller 258 | 259 | In case the VM doesn't have an IDE controller you can use the storagectl command to add one: 260 | 261 | ```javascript 262 | virtualbox.storage.addCtl( 263 | { 264 | vm: 'machine_name', 265 | perhiperal_name: 'IDE', //optional 266 | type: 'ide', //optional 267 | }, 268 | function () { 269 | console.log('Controller has been added!'); 270 | } 271 | ); 272 | ``` 273 | 274 | ### Attach a disk image file 275 | 276 | Mount an ISO file to the added controller: 277 | 278 | ```javascript 279 | virtualbox.storage.attach( 280 | { 281 | vm: 'machine_name', 282 | perhiperal_name: 'IDE', //optional 283 | port: '0', //optional 284 | device: '0', //optional 285 | type: 'dvddrive', //optional 286 | medium: 'X:Foldercontaining\the.iso', 287 | }, 288 | function () { 289 | console.log('Image has been mounted!'); 290 | } 291 | ); 292 | ``` 293 | 294 | The _medium_ parameter of the options object can be set to the **none** value to unmount. 295 | 296 | # Controlling the guest OS 297 | 298 | ## A note about security :warning: 299 | 300 | `node-virtualbox` is not opinionated: we believe that _you know best_ what _you_ need to do with _your_ virtual machine. Maybe that includes issuing `sudo rm -rf /` for some reason. 301 | 302 | To that end, the `virtualbox` APIs provided by this module _take absolutely no steps_ to prevent you shooting yourself in the foot. 303 | 304 | :warning: Therefore, if you accept user input and pass it to the virtual machine, you should take your own steps to filter input before it gets passed to `virtualbox`. 305 | 306 | For more details and discussion, see [issue #29](https://github.com/Node-Virtualization/node-virtualbox/issues/29). 307 | 308 | ## Running programs in the guest 309 | 310 | This method takes an options object with the name of the virtual machine, the path to the binary to be executed and any parameters to pass: 311 | 312 | ```javascript 313 | var options = { 314 | vm: 'machine_name', 315 | cmd: 'C:\\Program Files\\Internet Explorer\\iexplore.exe', 316 | params: 'https://google.com', 317 | }; 318 | 319 | virtualbox.exec(options, function exec_callback(error, stdout) { 320 | if (error) throw error; 321 | console.log('Started Internet Explorer...'); 322 | }); 323 | ``` 324 | 325 | ### Executing commands as Administrators on Windows guests 326 | 327 | Pass username and password information in an `options` object: 328 | 329 | ```javascript 330 | var options = { 331 | vm: 'machine_name', 332 | user: 'Administrator', 333 | password: '123456', 334 | cmd: 'C:\\Program Files\\Internet Explorer\\iexplore.exe', 335 | params: 'https://google.com', 336 | }; 337 | ``` 338 | 339 | ## Killing programs in the guest 340 | 341 | Tasks can be killed in the guest as well. In Windows guests this calls `taskkill.exe /im` and on Linux, BSD and OS X (Darwin) guests, it calls `sudo killall`: 342 | 343 | ```javascript 344 | virtualbox.kill( 345 | { 346 | vm: 'machine_name', 347 | cmd: 'iexplore.exe', 348 | }, 349 | function kill_callback(error) { 350 | if (error) throw error; 351 | console.log('Terminated Internet Explorer.'); 352 | } 353 | ); 354 | ``` 355 | 356 | ## Sending keystrokes to a virtual machine 357 | 358 | Keyboard scan code sequences can be piped directly to a virtual machine's console: 359 | 360 | ```javascript 361 | var SCAN_CODES = virtualbox.SCAN_CODES; 362 | var sequence = [ 363 | { key: 'SHIFT', type: 'make', code: SCAN_CODES['SHIFT'] }, 364 | { key: 'A', type: 'make', code: SCAN_CODES['A'] }, 365 | { key: 'SHIFT', type: 'break', code: SCAN_CODES.getBreakCode('SHIFT') }, 366 | { key: 'A', type: 'break', code: SCAN_CODES.getBreakCode('A') }, 367 | ]; 368 | 369 | virtualbox.keyboardputscancode( 370 | 'machine_name', 371 | sequence, 372 | function keyscan_callback(err) { 373 | if (error) throw error; 374 | console.log('Sent SHIFT A'); 375 | } 376 | ); 377 | ``` 378 | 379 | # Meta information about machine 380 | 381 | List all registered machines, returns an array: 382 | 383 | ```javascript 384 | virtualbox.list(function list_callback(machines, error) { 385 | if (error) throw error; 386 | // Act on machines 387 | }); 388 | ``` 389 | 390 | Obtaining a guest property by [key name](https://www.virtualbox.org/manual/ch04.html#guestadd-guestprops): 391 | 392 | ```javascript 393 | var options = { 394 | vm: 'machine_name', 395 | key: '/VirtualBox/GuestInfo/Net/0/V4/IP', 396 | }; 397 | 398 | virtualbox.guestproperty.get(options, function guestproperty_callback(machines, error) { 399 | if (error) throw error; 400 | // Act on machines 401 | }); 402 | ``` 403 | 404 | Obtaining an extra property by key name: 405 | 406 | ```javascript 407 | var options = { 408 | vm: 'machine_name', 409 | key: 'GUI/Fullscreen', 410 | }; 411 | 412 | virtualbox.extradata.get(options, function extradataget_callback(error, value) { 413 | if (error) throw error; 414 | console.log( 415 | 'Virtual Machine "%s" extra "%s" value is "%s"', 416 | options.vm, 417 | options.key, 418 | value 419 | ); 420 | }); 421 | ``` 422 | 423 | Writing an extra property by key name: 424 | 425 | ```javascript 426 | var options = { 427 | vm: 'machine_name', 428 | key: 'GUI/Fullscreen', 429 | value: 'true', 430 | }; 431 | 432 | virtualbox.extradata.set(options, function extradataset_callback(error) { 433 | if (error) throw error; 434 | console.log( 435 | 'Set Virtual Machine "%s" extra "%s" value to "%s"', 436 | options.vm, 437 | options.key, 438 | options.value 439 | ); 440 | }); 441 | ``` 442 | 443 | _Note: some properties are only available/effective if the Guest OS has the (https://www.virtualbox.org/manual/ch04.html)[Guest Additions] installed and running._ 444 | 445 | # Putting it all together 446 | 447 | ```javascript 448 | var virtualbox = require('virtualbox'); 449 | 450 | virtualbox.start('machine_name', function start_callback(error) { 451 | if (error) throw error; 452 | 453 | console.log('VM "w7" has been successfully started'); 454 | 455 | virtualbox.exec( 456 | { 457 | vm: 'machine_name', 458 | cmd: 'C:\\Program Files\\Internet Explorer\\iexplore.exe', 459 | params: 'http://google.com', 460 | }, 461 | function (error) { 462 | if (error) throw error; 463 | console.log('Running Internet Explorer...'); 464 | } 465 | ); 466 | }); 467 | ``` 468 | 469 | # Available Methods 470 | 471 | `virtualbox` 472 | 473 | - `.pause({vm:"machine_name"}, callback)` 474 | - `.reset({vm:"machine_name"}, callback)` 475 | - `.resume({vm:"machine_name"}, callback)` 476 | - `.start({vm:"machine_name"}, callback)` and `.start({vm:"machine_name"}, true, callback)` 477 | - `.stop({vm:"machine_name"}, callback)` 478 | - `.savestate({vm:"machine_name"}, callback)` 479 | - `.vmImport({input: "input"}, {options: "options"}, callback)` 480 | - `.vmExport({vm:"machine_name"}, {output: "output"}, callback)` 481 | - `.poweroff({vm:"machine_name"}, callback)` 482 | - `.acpisleepbutton({vm:"machine_name"}, callback)` 483 | - `.acpipowerbutton({vm:"machine_name"}, callback)` 484 | - `.guestproperty.get({vm:"machine_name", property: "propname"}, callback)` 485 | - `.exec(){vm: "machine_name", cmd: "C:\\Program Files\\Internet Explorer\\iexplore.exe", params: "http://google.com"}, callback)` 486 | - `.exec(){vm: "machine_name", user:"Administrator", password: "123456", cmd: "C:\\Program Files\\Internet Explorer\\iexplore.exe", params: "http://google.com"}, callback)` 487 | - `.keyboardputscancode("machine_name", [scan_codes], callback)` 488 | - `.kill({vm:"machine_name"}, callback)` 489 | - `.list(callback)` 490 | - `.isRunning({vm:"machine_name"}, callback)` 491 | - `.snapshotList({vm:"machine_name"}, callback)` 492 | - `.snapshotTake({vm:"machine_name"}, {vm:"snapshot_name"}, callback)` 493 | - `.snapshotDelete({vm:"machine_name"}, {vm:"snapshot_UUID"}, callback)` 494 | - `.snapshotRestore({vm:"machine_name"}, {vm:"snapshot_UUID"}, callback)` 495 | - `.clone({vm:"machine_name"}, {vm:"new_machine_name"}, callback)` 496 | - `.storage.addCtl({vm: "machine_name", perhiperal_name: "IDE", type: "ide"}, callback)` 497 | - `.storage.attach({vm: "machine_name", perhiperal_name: "IDE", port: "0", device: "0", type: "dvddrive", medium: "X:\Folder\containing\the.iso"}, callback)` 498 | - `.extradata.get({vm:"machine_name", key:"keyname"}, callback)` 499 | - `.extradata.set({vm:"machine_name", key:"keyname", value:"val"}, callback)` 500 | 501 | # Troubleshooting 502 | 503 | - Make sure that Guest account is enabled on the VM. 504 | - Make sure your linux guest can `sudo` with `NOPASSWD` (at least for now). 505 | - VMs start headlessly by default: if you're having trouble with executing a command, start the VM with GUI and observe the screen after executing same command. 506 | - To avoid having "Concurrent guest process limit is reached" error message, execute your commands as an administrator. 507 | - Don't forget that this whole thing is asynchronous, and depends on the return of `vboxmanage` _not_ the actual running state/runlevel of services within the guest. See 508 | 509 | # More Examples 510 | 511 | - [npm tests](https://github.com/Node-Virtualization/node-virtualbox/tree/master/test) 512 | 513 | # License 514 | 515 | [MIT](https://github.com/Node-Virtualization/node-virtualbox/blob/master/LICENSE) 516 | 517 | # Contributing 518 | 519 | Please do! 520 | 521 | - [File an issue](https://github.com/Node-Virtualization/node-virtualbox/issues) 522 | - [Fork](https://github.com/Node-Virtualization/node-virtualbox#fork-destination-box) and send a pull request. 523 | 524 | Please abide by the [Contributor Code of Conduct](https://github.com/Node-Virtualization/node-virtualbox/blob/master/code_of_conduct.md). 525 | 526 | ## Testing 527 | 528 | 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/.js`. Please be ready to provide test output upon opening a pull request. 529 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.box = "generic/alpine38" 3 | config.vm.provider "virtualbox" do |vbox| 4 | vbox.name = "node-virtualbox-test-machine" 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /code_of_conduct.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /lib/scan-codes.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var codes; 4 | 5 | codes = { 6 | ESCAPE: [0x01], 7 | NUMBER_1: [0x02], 8 | NUMBER_2: [0x03], 9 | NUMBER_3: [0x04], 10 | NUMBER_4: [0x05], 11 | NUMBER_5: [0x06], 12 | NUMBER_6: [0x07], 13 | NUMBER_7: [0x08], 14 | NUMBER_8: [0x09], 15 | NUMBER_9: [0x0a], 16 | NUMBER_0: [0x0b], 17 | MINUS: [0x0c], 18 | EQUAL: [0x0d], 19 | BACKSPACE: [0x0e], 20 | TAB: [0x0f], 21 | Q: [0x10], 22 | W: [0x11], 23 | E: [0x12], 24 | R: [0x13], 25 | T: [0x14], 26 | Y: [0x15], 27 | U: [0x16], 28 | I: [0x17], 29 | O: [0x18], 30 | P: [0x19], 31 | LEFTBRACKET: [0x1a], 32 | RIGHTBRACKET: [0x1b], 33 | ENTER: [0x1c], 34 | CTRL: [0x1d], 35 | A: [0x1e], 36 | S: [0x1f], 37 | D: [0x20], 38 | F: [0x21], 39 | G: [0x22], 40 | H: [0x23], 41 | J: [0x24], 42 | K: [0x25], 43 | L: [0x26], 44 | SEMICOLON: [0x27], 45 | QUOTE: [0x28], 46 | BACKQUOTE: [0x29], 47 | SHIFT: [0x2a], 48 | BACKSLASH: [0x2b], 49 | Z: [0x2c], 50 | X: [0x2d], 51 | C: [0x2e], 52 | V: [0x2f], 53 | B: [0x30], 54 | N: [0x31], 55 | M: [0x32], 56 | COMMA: [0x33], 57 | PERIOD: [0x34], 58 | SLASH: [0x35], 59 | R_SHIFT: [0x36], 60 | PRT_SC: [0x37], 61 | ALT: [0x38], 62 | SPACE: [0x39], 63 | CAPS_LOCK: [0x3a], 64 | F1: [0x3b], 65 | F2: [0x3c], 66 | F3: [0x3d], 67 | F4: [0x3e], 68 | F5: [0x3f], 69 | F6: [0x40], 70 | F7: [0x41], 71 | F8: [0x42], 72 | F9: [0x43], 73 | F10: [0x44], 74 | NUM_LOCK: [0x45], // May be [0x45, 0xC5], 75 | SCROLL_LOCK: [0x46], 76 | NUMPAD_7: [0x47], 77 | NUMPAD_8: [0x48], 78 | NUMPAD_9: [0x49], 79 | NUMPAD_SUBTRACT: [0x4a], 80 | NUMPAD_4: [0x4b], 81 | NUMPAD_5: [0x4c], 82 | NUMPAD_6: [0x4d], 83 | NUMPAD_ADD: [0x4e], 84 | NUMPAD_1: [0x4f], 85 | NUMPAD_2: [0x50], 86 | NUMPAD_3: [0x51], 87 | NUMPAD_0: [0x52], 88 | NUMPAD_DECIMAL: [0x53], 89 | F11: [0x57], 90 | F12: [0x58], 91 | // Same as other Enter key 92 | // 'NUMBER_Enter' : [0xE0, 0x1C], 93 | R_CTRL: [0xe0, 0x1d], 94 | NUMBER_DIVIDE: [0xe0, 0x35], 95 | // 'NUMBER_*' : [0xE0, 0x37], 96 | R_ALT: [0xe0, 0x38], 97 | HOME: [0xe0, 0x47], 98 | UP: [0xe0, 0x48], 99 | PAGE_UP: [0xe0, 0x49], 100 | LEFT: [0xe0, 0x4b], 101 | RIGHT: [0xe0, 0x4d], 102 | END: [0xe0, 0x4f], 103 | DOWN: [0xe0, 0x50], 104 | PAGE_DOWN: [0xe0, 0x51], 105 | INSERT: [0xe0, 0x52], 106 | DELETE: [0xe0, 0x53], 107 | WINDOW: [0xe0, 0x5b], 108 | R_WINDOW: [0xe0, 0x5c], 109 | MENU: [0xe0, 0x5d], 110 | PAUSE: [0xe1, 0x1d, 0x45, 0xe1, 0x9d, 0xc5], 111 | }; 112 | 113 | codes.getBreakCode = function (key) { 114 | var makeCode = codes[key]; 115 | if (makeCode === undefined) { 116 | throw new Error("Undefined key: " + key); 117 | } 118 | 119 | if (key === "PAUSE") { 120 | return []; 121 | } 122 | 123 | if (makeCode[0] === 0xe0) { 124 | return [0xe0, makeCode[1] + 0x80]; 125 | } else { 126 | return [makeCode[0] + 0x80]; 127 | } 128 | }; 129 | 130 | module.exports = codes; 131 | -------------------------------------------------------------------------------- /lib/virtualbox.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const execFile = require("child_process").execFile, 4 | log4js = require("log4js"), 5 | host_platform = process.platform, 6 | known_OS_types = { 7 | WINDOWS: "windows", 8 | MAC: "mac", 9 | LINUX: "linux", 10 | }, 11 | defaultExecutor = (bin, cmd) => { 12 | return new Promise((resolve, reject) => { 13 | if (!allowedBinaries.includes(bin)) { 14 | reject(new Error("Not an allowed binary")); 15 | } else { 16 | execFile(bin, cmd, function (err, stdout, stderr) { 17 | if ( 18 | !err && 19 | stderr && 20 | cmd.indexOf("pause") !== -1 && 21 | cmd.indexOf("savestate") !== -1 22 | ) { 23 | reject(new Error(stderr)); 24 | } 25 | 26 | resolve({ err, stdout, stderr }); 27 | }); 28 | } 29 | }); 30 | }, 31 | defaultLoggingConfig = { 32 | appenders: { 33 | out: { 34 | type: "stdout", 35 | layout: { 36 | type: "pattern", 37 | pattern: "%[[%d{yyyy-MM-dd hh:mm:ss.SSS}] [%p] %c - %]%m", 38 | }, 39 | }, 40 | }, 41 | categories: { default: { appenders: ["out"], level: "info" } }, 42 | }, 43 | defaultLoggerFn = () => { 44 | log4js.configure(defaultLoggingConfig); 45 | return log4js.getLogger("VirtualBox"); 46 | }, 47 | defaultLogger = defaultLoggerFn(), 48 | defaultvboxmanage = function (cmd, callback) { 49 | try { 50 | this._executor(this._vBoxManageBinary, cmd) 51 | .then(({ err, stdout, stderr }) => callback(err, stdout, stderr)) 52 | .catch(defaultErrorHandler); 53 | } catch (err) { 54 | this._logging.error(err); 55 | } 56 | }, 57 | defaultErrorHandler = (err) => { 58 | defaultLogger.error(err); 59 | }, 60 | allowedBinaries = ["VBoxControl"]; 61 | 62 | class Virtualbox { 63 | constructor(logging = defaultLogger, executor = defaultExecutor) { 64 | this._logging = logging; 65 | this._executor = executor; 66 | this._setVboxManageBinary(); 67 | allowedBinaries.push(this._vBoxManageBinary); 68 | this._logging.debug(allowedBinaries); 69 | this._detectVboxVersion(); 70 | this.storage = new VboxStorage( 71 | this._logging, 72 | this._executor, 73 | this._vBoxManageBinary 74 | ); 75 | this.guestproperty = new VboxGuestProperty( 76 | this._logging, 77 | this._executor, 78 | this._vBoxManageBinary 79 | ); 80 | this.extradata = new VboxExtraData( 81 | this._logging, 82 | this._executor, 83 | this._vBoxManageBinary 84 | ); 85 | this.vboxmanage = defaultvboxmanage; 86 | this.SCAN_CODES = require("./scan-codes"); 87 | } 88 | 89 | static create(logging, executor) { 90 | const logger = !!logging ? logging : defaultLogger; 91 | return new Virtualbox(logger, executor); 92 | } 93 | 94 | pause(vmname, callback) { 95 | this._logging.info('Pausing VM "%s"', vmname); 96 | this.vboxmanage(["controlvm", vmname, "pause"], function (error, _) { 97 | callback(error); 98 | }); 99 | } 100 | 101 | list(callback) { 102 | const parse_listdata = (raw_data) => { 103 | var _raw = raw_data.split(/\r?\n/g); 104 | var _data = {}; 105 | if (_raw.length > 0) { 106 | for (var _i = 0; _i < _raw.length; _i += 1) { 107 | var _line = _raw[_i]; 108 | if (_line === "") { 109 | continue; 110 | } 111 | // "centos6" {64ec13bb-5889-4352-aee9-0f1c2a17923d} 112 | var rePattern = /^"(.+)" \{(.+)\}$/; 113 | var arrMatches = _line.match(rePattern); 114 | // {'64ec13bb-5889-4352-aee9-0f1c2a17923d': 'centos6'} 115 | if (arrMatches && arrMatches.length === 3) { 116 | _data[arrMatches[2].toString()] = { 117 | name: arrMatches[1].toString(), 118 | }; 119 | } 120 | } 121 | } 122 | return _data; 123 | }; 124 | 125 | this._logging.info("Listing VMs"); 126 | this.vboxmanage(["list", "runningvms"], (_, stdout) => { 127 | var _runningvms = parse_listdata(stdout); 128 | this.vboxmanage(["list", "vms"], function (error, fullStdout) { 129 | var _all = parse_listdata(fullStdout); 130 | var _keys = Object.keys(_all); 131 | for (var _i = 0; _i < _keys.length; _i += 1) { 132 | var _key = _keys[_i]; 133 | if (_runningvms[_key]) { 134 | _all[_key].running = true; 135 | } else { 136 | _all[_key].running = false; 137 | } 138 | } 139 | callback(_all, error); 140 | }); 141 | }); 142 | } 143 | 144 | reset(vmname, callback) { 145 | this._logging.info('Resetting VM "%s"', vmname); 146 | this.vboxmanage(["controlvm", vmname, "reset"], function (error, _) { 147 | callback(error); 148 | }); 149 | } 150 | 151 | resume(vmname, callback) { 152 | this._logging.info('Resuming VM "%s"', vmname); 153 | this.vboxmanage(["controlvm", vmname, "resume"], function (error, _) { 154 | callback(error); 155 | }); 156 | } 157 | 158 | start(vmname, useGui, callback) { 159 | if (typeof useGui === "function") { 160 | callback = useGui; 161 | useGui = false; 162 | } 163 | var vmType = useGui ? "gui" : "headless"; 164 | 165 | this._logging.info('Starting VM "%s" with options: ', vmname, vmType); 166 | 167 | this.vboxmanage( 168 | ["-nologo", "startvm", vmname, "--type", vmType], 169 | function (error, _) { 170 | if (error && /VBOX_E_INVALID_OBJECT_STATE/.test(error.message)) { 171 | error = undefined; 172 | } 173 | callback(error); 174 | } 175 | ); 176 | } 177 | 178 | stop(vmname, callback) { 179 | this._logging.info('Stopping VM "%s"', vmname); 180 | this.vboxmanage(["controlvm", vmname, "savestate"], function (error, _) { 181 | callback(error); 182 | }); 183 | } 184 | 185 | savestate(vmname, callback) { 186 | this._logging.info('Saving State (alias to stop) VM "%s"', vmname); 187 | this.stop(vmname, callback); 188 | } 189 | 190 | vmExport(vmname, output, callback) { 191 | this._logging.info('Exporting VM "%s"', vmname); 192 | this.vboxmanage( 193 | ["export", vmname, "--output", output], 194 | function (error, _) { 195 | callback(error); 196 | } 197 | ); 198 | } 199 | 200 | vmImport(input, options, callback) { 201 | if (typeof options === "function") { 202 | callback = options; 203 | options = {}; 204 | } 205 | 206 | var cmd = ["import", "--vsys", "0"]; 207 | 208 | if (options.vmname !== undefined) { 209 | cmd.push("--vmname"); 210 | cmd.push(options.vmname); 211 | } 212 | 213 | if (options.cpus !== undefined) { 214 | cmd.push("--cpus"); 215 | cmd.push(options.cpus.toString()); 216 | } 217 | 218 | if (options.memory !== undefined) { 219 | cmd.push("--memory"); 220 | cmd.push(options.memory.toString()); 221 | } 222 | 223 | cmd.push(input); 224 | 225 | this._logging.info('Importing VM "%s"', input); 226 | this.vboxmanage(cmd, function (error, _) { 227 | callback(error); 228 | }); 229 | } 230 | 231 | poweroff(vmname, callback) { 232 | this._logging.info('Powering off VM "%s"', vmname); 233 | this.vboxmanage(["controlvm", vmname, "poweroff"], function (error, _) { 234 | callback(error); 235 | }); 236 | } 237 | 238 | acpipowerbutton(vmname, callback) { 239 | this._logging.info('ACPI power button VM "%s"', vmname); 240 | this.vboxmanage(["controlvm", vmname, "acpipowerbutton"], function (error) { 241 | callback(error); 242 | }); 243 | } 244 | 245 | acpisleepbutton(vmname, callback) { 246 | this._logging.info('ACPI sleep button VM "%s"', vmname); 247 | this.vboxmanage( 248 | ["controlvm", vmname, "acpisleepbutton"], 249 | function (error, _) { 250 | callback(error); 251 | } 252 | ); 253 | } 254 | 255 | modify(vname, properties, callback) { 256 | this._logging.info("Modifying VM %s", vname); 257 | var args = [vname]; 258 | 259 | for (var property in properties) { 260 | if (properties.hasOwnProperty(property)) { 261 | var value = properties[property]; 262 | args.push("--" + property); 263 | 264 | if (Array.isArray(value)) { 265 | Array.prototype.push.apply(args, value); 266 | } else { 267 | args.push(value.toString()); 268 | } 269 | } 270 | } 271 | 272 | this.vboxmanage(["modifyvm", ...args], function (error, _) { 273 | callback(error); 274 | }); 275 | } 276 | 277 | snapshotList(vmname, callback) { 278 | this._logging.info('Listing snapshots for VM "%s"', vmname); 279 | this.vboxmanage( 280 | ["snapshot", vmname, "list", "--machinereadable"], 281 | function (error, stdout) { 282 | if (error) { 283 | callback(error); 284 | return; 285 | } 286 | 287 | var s; 288 | var snapshots = []; 289 | var currentSnapshot; 290 | var lines = (stdout || "").split(require("os").EOL); 291 | 292 | lines.forEach(function (line) { 293 | line 294 | .trim() 295 | .replace( 296 | /^(CurrentSnapshotUUID|SnapshotName|SnapshotUUID).*\="(.*)"$/, 297 | function (l, k, v) { 298 | if (k === "CurrentSnapshotUUID") { 299 | currentSnapshot = v; 300 | } else if (k === "SnapshotName") { 301 | s = { 302 | name: v, 303 | }; 304 | snapshots.push(s); 305 | } else { 306 | s.uuid = v; 307 | } 308 | } 309 | ); 310 | }); 311 | 312 | callback(null, snapshots, currentSnapshot); 313 | } 314 | ); 315 | } 316 | 317 | snapshotTake( 318 | vmname, 319 | name, 320 | /*optional*/ description, 321 | /*optional*/ live, 322 | callback 323 | ) { 324 | this._logging.info('Taking snapshot for VM "%s"', vmname); 325 | 326 | if (typeof description === "function") { 327 | callback = description; 328 | description = undefined; 329 | } else if (typeof live === "function") { 330 | callback = live; 331 | live = false; 332 | } 333 | 334 | var cmd = ["snapshot", vmname, "take", name]; 335 | 336 | if (description) { 337 | cmd.push("--description"); 338 | cmd.push(description); 339 | } 340 | 341 | if (live === true) { 342 | cmd.push("--live"); 343 | } 344 | 345 | this.vboxmanage(cmd, function (error, stdout) { 346 | var uuid; 347 | stdout.trim().replace(/UUID\: ([a-f0-9\-]+)$/, function (l, u) { 348 | uuid = u; 349 | }); 350 | callback(error, uuid); 351 | }); 352 | } 353 | 354 | snapshotDelete(vmname, uuid, callback) { 355 | this._logging.info('Deleting snapshot "%s" for VM "%s"', uuid, vmname); 356 | this.vboxmanage(["snapshot", vmname, "delete", uuid], callback); 357 | } 358 | 359 | snapshotRestore(vmname, uuid, callback) { 360 | this._logging.info('Restoring snapshot "%s" for VM "%s"', uuid, vmname); 361 | this.vboxmanage(["snapshot", vmname, "restore", uuid], callback); 362 | } 363 | 364 | clone(vmname, clonevmname, /*optional*/ snapshot, callback) { 365 | this._logging.info('Cloning machine "%s" to "%s"', vmname, clonevmname); 366 | var cmd = ["clonevm", vmname, "--name", clonevmname, "--register"]; 367 | if (typeof snapshot === "function") { 368 | callback = snapshot; 369 | snapshot = undefined; 370 | } else { 371 | cmd.push("--options"); 372 | cmd.push("link"); 373 | cmd.push("--snapshot"); 374 | cmd.push(snapshot); 375 | } 376 | this.vboxmanage(cmd, callback); 377 | } 378 | 379 | isRunning(vmname, callback) { 380 | this.vboxmanage(["list", "runningvms"], (error, stdout) => { 381 | this._logging.info( 382 | 'Checking virtual machine "%s" is running or not', 383 | vmname 384 | ); 385 | if (stdout.indexOf(vmname) === -1) { 386 | callback(error, false); 387 | } else { 388 | callback(error, true); 389 | } 390 | }); 391 | } 392 | 393 | keyboardputscancode(vmname, codes, callback) { 394 | var codeStr = codes 395 | .map(function (code) { 396 | var s = code.toString(16); 397 | 398 | if (s.length === 1) { 399 | s = "0" + s; 400 | } 401 | return s; 402 | }) 403 | .join(" "); 404 | this._logging.info( 405 | 'Sending VM "%s" keyboard scan codes "%s"', 406 | vmname, 407 | codeStr 408 | ); 409 | this.vboxmanage( 410 | ["controlvm", vmname, "keyboardputscancode", codeStr], 411 | function (error, stdout) { 412 | callback(error, stdout); 413 | } 414 | ); 415 | } 416 | 417 | exec(options, callback) { 418 | var vm = options.vm || options.name || options.vmname || options.title, 419 | username = options.user || options.username || "Guest", 420 | password = options.pass || options.passwd || options.password, 421 | path = 422 | options.path || 423 | options.cmd || 424 | options.command || 425 | options.exec || 426 | options.execute || 427 | options.run, 428 | params = options.params || options.parameters || options.args; 429 | 430 | if (Array.isArray(params)) { 431 | params = params.join(" "); 432 | } 433 | 434 | if (params === undefined) { 435 | params = ""; 436 | } 437 | 438 | const getOSTypeCb = (os_type) => { 439 | var cmd = ["guestcontrol", vm]; 440 | var runcmd = this._vboxVersion > 5 ? ["run"] : ["execute", "--image"]; 441 | cmd = [...cmd, ...runcmd]; 442 | switch (os_type) { 443 | case known_OS_types.WINDOWS: 444 | path = path.replace(/\\/g, "\\\\"); 445 | cmd.push("cmd.exe", "--username", username); 446 | break; 447 | case known_OS_types.MAC: 448 | cmd.push("/usr/bin/open", "-a", "--username", username); 449 | break; 450 | case known_OS_types.LINUX: 451 | cmd.push("/bin/sh", "--username", username); 452 | break; 453 | default: 454 | break; 455 | } 456 | 457 | if (password) { 458 | cmd.push("--password", password); 459 | } 460 | cmd.push("--", "/c", path, params); 461 | 462 | this._logging.info( 463 | 'Executing command "vboxmanage %s" on VM "%s" detected OS type "%s"', 464 | cmd, 465 | vm, 466 | os_type 467 | ); 468 | 469 | this.vboxmanage(cmd, function (error, stdout) { 470 | callback(error, stdout); 471 | }); 472 | }; 473 | 474 | this.guestproperty.os(vm, getOSTypeCb); 475 | } 476 | 477 | kill(options, callback) { 478 | options = options || {}; 479 | var vm = options.vm || options.name || options.vmname || options.title, 480 | path = 481 | options.path || 482 | options.cmd || 483 | options.command || 484 | options.exec || 485 | options.execute || 486 | options.run, 487 | image_name = options.image_name || path; 488 | 489 | this.guestproperty.os(vm, (os_type) => { 490 | switch (os_type) { 491 | case known_OS_types.WINDOWS: 492 | this._executor( 493 | { 494 | vm: vm, 495 | user: options.user, 496 | password: options.password, 497 | path: "C:\\Windows\\System32\\taskkill.exe /im ", 498 | params: image_name, 499 | }, 500 | callback 501 | ); 502 | break; 503 | case known_OS_types.MAC: 504 | case known_OS_types.LINUX: 505 | this._executor( 506 | { 507 | vm: vm, 508 | user: options.user, 509 | password: options.password, 510 | path: "sudo killall ", 511 | params: image_name, 512 | }, 513 | callback 514 | ); 515 | break; 516 | } 517 | }); 518 | } 519 | 520 | _setVboxManageBinary() { 521 | this._logging.info(host_platform); 522 | if (/^win/.test(host_platform)) { 523 | // Path may not contain VBoxManage.exe but it provides this environment variable 524 | const vBoxInstallPath = 525 | process.env.VBOX_INSTALL_PATH || process.env.VBOX_MSI_INSTALL_PATH; 526 | this._vBoxManageBinary = `${vBoxInstallPath}VBoxManage.exe`; 527 | } else if (/^darwin/.test(host_platform) || /^linux/.test(host_platform)) { 528 | // Mac OS X and most Linux use the same binary name, in the path 529 | this._vBoxManageBinary = "vboxmanage"; 530 | } else { 531 | // Otherwise (e.g., SunOS) hope it's in the path 532 | this._vBoxManageBinary = "vboxmanage"; 533 | } 534 | } 535 | 536 | _detectVboxVersion() { 537 | this._executor(this._vBoxManageBinary, ["--version"]).then( 538 | ({ error, stdout }) => { 539 | if (error) { 540 | throw error; 541 | } else { 542 | this._vboxVersion = stdout.split(".")[0]; 543 | this._logging.info( 544 | "Virtualbox version detected as %s", 545 | this._vboxVersion 546 | ); 547 | } 548 | } 549 | ); 550 | } 551 | } 552 | 553 | class VboxStorage { 554 | constructor(logging, executor, vBoxManageBinary) { 555 | this._logging = logging; 556 | this._executor = executor; 557 | this._vBoxManageBinary = vBoxManageBinary; 558 | this.vboxmanage = defaultvboxmanage; 559 | } 560 | 561 | addCtl(options, callback) { 562 | var vm = options.vm || options.name || options.vmname || options.title, 563 | device_name = options.perhiperal_name || "IDE", 564 | type = options.type || "ide"; 565 | this._logging.info( 566 | 'Adding "%s" controller named "%s" to %s', 567 | type, 568 | device_name, 569 | vm 570 | ); 571 | var cmd = ["storagectl", vm, "--name", device_name, "--add", type]; 572 | this.vboxmanage(cmd, callback); 573 | } 574 | 575 | attach(options, callback) { 576 | var vm = options.vm || options.name || options.vmname || options.title, 577 | device_name = options.perhiperal_name || "IDE", 578 | port = options.port || "0", 579 | device = options.device || "0", 580 | type = options.type || "dvddrive", 581 | medium = options.medium; 582 | this._logging.info( 583 | 'Mounting "%s" to controller named "%s" on %s', 584 | medium, 585 | device_name, 586 | vm 587 | ); 588 | var cmd = [ 589 | "storageattach", 590 | vm, 591 | "--storagectl", 592 | device_name, 593 | "--port", 594 | port, 595 | "--device", 596 | device, 597 | "--type", 598 | type, 599 | "--medium", 600 | medium, 601 | ]; 602 | this.vboxmanage(cmd, callback); 603 | } 604 | } 605 | 606 | class VboxGuestProperty { 607 | constructor(logging, executor, vBoxManageBinary) { 608 | this._logging = logging; 609 | this._executor = executor; 610 | this._vBoxManageBinary = vBoxManageBinary; 611 | this.os_type = null; 612 | this.vboxmanage = defaultvboxmanage; 613 | } 614 | 615 | get(options, callback) { 616 | var vm = options.vm || options.name || options.vmname || options.title, 617 | key = options.key, 618 | defaultValue = options.defaultValue || options.value; 619 | 620 | this.os(vm, (_) => { 621 | this.vboxmanage( 622 | ["guestproperty", "get", vm, key], 623 | function (error, stdout) { 624 | var value; 625 | if (error) { 626 | throw error; 627 | } 628 | try { 629 | value = stdout.substr(stdout.indexOf(":") + 1).trim(); 630 | } catch (ex) { 631 | this._logging.error(ex); 632 | callback(defaultValue); 633 | } 634 | if (value === "No value set!") { 635 | value = defaultValue || undefined; 636 | } 637 | callback(value); 638 | } 639 | ); 640 | }); 641 | } 642 | 643 | os(vmname, callback) { 644 | if (this.os_type) { 645 | return callback(this.os_type); 646 | } 647 | 648 | try { 649 | this.vboxmanage( 650 | ["showvminfo", "--machinereadable", vmname], 651 | (error, stdout, _) => { 652 | if (error) { 653 | throw error; 654 | } 655 | 656 | // The ostype is matched against the ID attribute of 'vboxmanage list ostypes' 657 | if (stdout.indexOf('ostype="Windows') !== -1) { 658 | this.os_type = known_OS_types.WINDOWS; 659 | } else if ( 660 | ['ostype="MacOS', 'ostype="Mac OS machine'].includes(stdout) 661 | ) { 662 | this.os_type = known_OS_types.MAC; 663 | } else { 664 | this.os_type = known_OS_types.LINUX; 665 | } 666 | this._logging.debug("Detected guest OS as: " + this.os_type); 667 | callback(this.os_type); 668 | } 669 | ); 670 | } catch (e) { 671 | this._logging.error(e); 672 | this._logging.info("Could not showvminfo for %s", vmname); 673 | } 674 | } 675 | 676 | /** 677 | * Function to return an array of this object: 678 | * { 679 | * "key": "ResumeCounter", 680 | * "value": 0, 681 | * "namespace": "VMInfo", 682 | * "timestamp": 1596902741176896000, 683 | * "flags": ["TRANSIENT", "RDONLYGUEST"] 684 | * } 685 | * @param {String} vmname The name of the VM to enumerate guest properties on. 686 | * @param {Function} callback The callback to handle the output. 687 | * @returns {void} The output of stdout will be an array of properties objects. 688 | */ 689 | enumerate(vmname, callback) { 690 | this.vboxmanage( 691 | ["guestproperty", "enumerate", vmname], 692 | (err, stdout, _) => { 693 | if (err) { 694 | throw err; 695 | } 696 | const arrOfProps = stdout.split("\n"); 697 | const nameRegex = /(?<=Name: ).+?(?=\,)/; 698 | const valueRegex = /(?<=value: ).+?(?=\,)/; 699 | const timestampRegex = /(?<=timestamp: ).+?(?=\,)/; 700 | const flagsRegex = /(?<=flags: ).*/; 701 | 702 | const arrOfPropsParsed = []; 703 | arrOfProps 704 | .filter((prop) => !!prop) 705 | .forEach((prop) => { 706 | const nameMatch = prop.match(nameRegex).shift(), 707 | value = prop.match(valueRegex).shift(), 708 | timestamp = prop.match(timestampRegex).shift(), 709 | flags = prop 710 | .match(flagsRegex) 711 | .shift() 712 | .split(",") 713 | .map((flag) => flag.replace(" ", "")), 714 | nameMatchSplit = nameMatch 715 | .split("/") 716 | .filter((name) => name !== ""), 717 | key = nameMatchSplit[2], 718 | namespace = nameMatchSplit[1]; 719 | arrOfPropsParsed.push({ 720 | key, 721 | value, 722 | namespace, 723 | timestamp, 724 | flags, 725 | }); 726 | }); 727 | callback(err, arrOfPropsParsed); 728 | } 729 | ); 730 | } 731 | } 732 | 733 | class VboxExtraData { 734 | constructor(logging, executor, vBoxManageBinary) { 735 | this._logging = logging; 736 | this._executor = executor; 737 | this._vBoxManageBinary = vBoxManageBinary; 738 | this.vboxmanage = defaultvboxmanage; 739 | } 740 | 741 | get(options, callback) { 742 | var vm = options.vm || options.name || options.vmname || options.title, 743 | key = options.key, 744 | defaultValue = options.defaultValue || options.value; 745 | 746 | this.vboxmanage(["getextradata", vm, key], function (error, stdout) { 747 | var value; 748 | if (error) { 749 | callback(error); 750 | return; 751 | } 752 | try { 753 | value = stdout.substr(stdout.indexOf(":") + 1).trim(); 754 | } catch (ex) { 755 | this._logging.error(ex); 756 | callback(ex, defaultValue); 757 | } 758 | if (value === "No value set!") { 759 | value = defaultValue || undefined; 760 | } 761 | callback(null, value); 762 | }); 763 | } 764 | 765 | set(options, callback) { 766 | var vm = options.vm || options.name || options.vmname || options.title, 767 | key = options.key, 768 | value = options.defaultValue || options.value; 769 | 770 | var cmd = ["setextradata", vm, key, value]; 771 | this.vboxmanage(cmd, function (error, _) { 772 | callback(error); 773 | }); 774 | } 775 | } 776 | 777 | // module.exports = { 778 | // 'exec': vmExec, 779 | // 'kill': vmKill, 780 | // 'list': list, 781 | // 'pause': pause, 782 | // 'reset': reset, 783 | // 'resume': resume, 784 | // 'start': start, 785 | // 'stop': stop, 786 | // 'savestate': savestate, 787 | // 'import': vmImport, 788 | // 'export': vmExport, 789 | // 'poweroff': poweroff, 790 | // 'acpisleepbutton': acpisleepbutton, 791 | // 'acpipowerbutton': acpipowerbutton, 792 | // 'modify': modify, 793 | // 'guestproperty': guestproperty, 794 | // 'keyboardputscancode': keyboardputscancode, 795 | // 'snapshotList': snapshotList, 796 | // 'snapshotTake': snapshotTake, 797 | // 'snapshotDelete': snapshotDelete, 798 | // 'snapshotRestore': snapshotRestore, 799 | // 'isRunning': isRunning, 800 | // 'extradata': extradata, 801 | // 'clone': clone, 802 | // 'storage': storage, 803 | 804 | // 'SCAN_CODES': require('./scan-codes') 805 | // }; 806 | 807 | module.exports = new Virtualbox(); 808 | module.exports.create = Virtualbox.create; 809 | module.exports.Virtualbox = Virtualbox; 810 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "virtualbox", 3 | "version": "1.1.2", 4 | "description": "A library to interact with VirtualBox.", 5 | "author": "Azer Koculu ", 6 | "license": "MIT", 7 | "bugs": "https://github.com/Node-Virtualization/node-virtualbox/issues", 8 | "contributors": [ 9 | { 10 | "name": "Azer Koculu", 11 | "url": "http://azer.bike", 12 | "email": "azer@kodfabrik.com" 13 | }, 14 | { 15 | "name": "Michael Sanford", 16 | "url": "http://accidentalbeard.com" 17 | }, 18 | { 19 | "name": "Jonathan Ling", 20 | "url": "https://jonathanling.net" 21 | }, 22 | { 23 | "name": "Steffen Roegner", 24 | "url": "http://www.sroegner.org" 25 | }, 26 | { 27 | "name": "Jakub Lekstan", 28 | "url": "https://github.com/kuebk" 29 | }, 30 | { 31 | "name": "Christopher'chief' Najewicz", 32 | "url": "http://chiefy.github.io" 33 | }, 34 | { 35 | "name": "Cédric Belin", 36 | "url": "http://belin.io" 37 | }, 38 | { 39 | "name": "bschaepper", 40 | "url": "https://github.com/bschaepper" 41 | }, 42 | { 43 | "name": "2roy999", 44 | "url": "https://github.com/2roy999" 45 | }, 46 | { 47 | "name": "Felipe Miranda", 48 | "url": "https://github.com/felipemdrs" 49 | } 50 | ], 51 | "keywords": [ 52 | "virtualbox", 53 | "vboxmanage", 54 | "vboxheadless" 55 | ], 56 | "directories": { 57 | "lib": "./lib" 58 | }, 59 | "scripts": { 60 | "test": "jest ./test/*.spec.js", 61 | "lint": "eslint ./lib ./test" 62 | }, 63 | "main": "./lib/virtualbox", 64 | "repository": { 65 | "type": "git", 66 | "url": "https://github.com/Node-Virtualization/node-virtualbox.git" 67 | }, 68 | "devDependencies": { 69 | "async": "^3.2.5", 70 | "eslint": "^8.57.0", 71 | "jest": "^26.0.1", 72 | "jsdoc": "^4.0.2", 73 | "prettier": "^3.2.5" 74 | }, 75 | "engines": { 76 | "engine": "node >= 18" 77 | }, 78 | "dependencies": { 79 | "log4js": "^6.9.1" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /test/guestproperty.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { logger } = require('./helpers/logger'); 4 | const { create } = require('../lib/virtualbox'); 5 | 6 | describe('Virtualbox#guestproperty', () => { 7 | it('should not throw an error when getting a guest property with ostype of MacOS', (done) => { 8 | const executor = jest 9 | .fn() 10 | .mockReturnValueOnce( 11 | new Promise((resolve) => 12 | resolve({ err: null, stdout: 'somevalue', stderr: '' }) 13 | ) 14 | ) 15 | .mockReturnValueOnce( 16 | new Promise((resolve) => 17 | resolve({ err: null, stdout: 'ostype="MacOS', stderr: '' }) 18 | ) 19 | ) 20 | .mockReturnValueOnce( 21 | new Promise((resolve) => 22 | resolve({ 23 | err: null, 24 | stdout: 'somevalue', 25 | stderr: '', 26 | }) 27 | ) 28 | ); 29 | const virtualbox = create(logger, executor); 30 | virtualbox.guestproperty.get( 31 | { vm: 'testmachine', key: 'someProperty' }, 32 | function (value) { 33 | expect(value).toBeTruthy(); 34 | expect(virtualbox.guestproperty.os_type).toBe('mac'); 35 | done(); 36 | } 37 | ); 38 | }); 39 | 40 | it('should not throw an error when getting a guest property with ostype of Mac OS machine', (done) => { 41 | const executor = jest 42 | .fn() 43 | .mockReturnValueOnce( 44 | new Promise((resolve) => 45 | resolve({ 46 | err: null, 47 | stdout: 'somevalue', 48 | stderr: '', 49 | }) 50 | ) 51 | ) 52 | .mockReturnValueOnce( 53 | new Promise((resolve) => 54 | resolve({ 55 | err: null, 56 | stdout: 'ostype="Mac OS machine', 57 | stderr: '', 58 | }) 59 | ) 60 | ) 61 | .mockReturnValueOnce( 62 | new Promise((resolve) => 63 | resolve({ 64 | err: null, 65 | stdout: 'somevalue', 66 | stderr: '', 67 | }) 68 | ) 69 | ); 70 | const virtualbox = create(logger, executor); 71 | virtualbox.guestproperty.get( 72 | { vm: 'testmachine', key: 'someProperty' }, 73 | function (value) { 74 | expect(value).toBeTruthy(); 75 | expect(virtualbox.guestproperty.os_type).toBe('mac'); 76 | done(); 77 | } 78 | ); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /test/helpers/logger.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const log4js = require("log4js"), 3 | defaultLoggingConfig = { 4 | appenders: { 5 | out: { 6 | type: "stdout", 7 | layout: { 8 | type: "pattern", 9 | pattern: "%[[%d{yyyy-MM-dd hh:mm:ss.SSS}] [%p] %c - %]%m", 10 | }, 11 | }, 12 | }, 13 | categories: { default: { appenders: ["out"], level: "debug" } }, 14 | }; 15 | 16 | log4js.configure(defaultLoggingConfig); 17 | 18 | module.exports.logger = log4js.getLogger("VboxTestLogger"); 19 | -------------------------------------------------------------------------------- /test/integration/acpipowerbutton.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const virtualbox = require('../../lib/virtualbox'), 4 | vmName = 'node-virtualbox-test-machine'; 5 | 6 | virtualbox.acpipowerbutton(vmName, function (error) { 7 | if (error) { 8 | throw error; 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /test/integration/acpisleepbutton.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const virtualbox = require('../../lib/virtualbox'), 4 | vmName = 'node-virtualbox-test-machine'; 5 | 6 | virtualbox.acpisleepbutton(vmName, function (error) { 7 | if (error) { 8 | throw error; 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /test/integration/exec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const virtualbox = require("../../lib/virtualbox"), 4 | vm = "node-virtualbox-test-machine", 5 | user = "vagrant", 6 | pass = "vagrant", 7 | { logger } = require("../helpers/logger"), 8 | ostype = virtualbox.guestproperty.os(vm); 9 | let path; 10 | 11 | if (ostype === "windows") { 12 | path = "C:\\Program Files\\Internet Explorer\\iexplore.exe"; 13 | } else if (ostype === "mac") { 14 | path = "Safari.app"; 15 | } else { 16 | path = "ping"; 17 | } 18 | 19 | virtualbox.start(vm, function () { 20 | virtualbox.exec( 21 | { 22 | vm: vm, 23 | user: user, 24 | passwd: pass, 25 | path: path, 26 | params: ["http://google.com"], 27 | }, 28 | function (error, stdout) { 29 | if (error) { 30 | throw error; 31 | } 32 | logger.debug(stdout); 33 | } 34 | ); 35 | }); 36 | -------------------------------------------------------------------------------- /test/integration/export.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var virtualbox = require('../../lib/virtualbox'), 4 | vmName = 'node-virtualbox-test-machine'; 5 | 6 | virtualbox.export(vmName, 'test-export', function (error) { 7 | if (error) { 8 | throw error; 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /test/integration/getextradata.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var virtualbox = require("../../lib/virtualbox"), 4 | vmname = "node-virtualbox-test-machine", 5 | { logger } = require("../helpers/logger"), 6 | key = "some-data-key"; 7 | 8 | virtualbox.extradata.get({ vmname, key }, function (error, value) { 9 | if (error) { 10 | throw error; 11 | } 12 | 13 | logger.debug( 14 | 'Virtual Machine "%s" extra "%s" value is "%s"', 15 | vmname, 16 | key, 17 | value 18 | ); 19 | }); 20 | -------------------------------------------------------------------------------- /test/integration/guestproperty-enumerate.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const virtualbox = require("../../lib/virtualbox"), 4 | { logger } = require("../helpers/logger"), 5 | vm = "node-virtualbox-test-machine"; 6 | 7 | virtualbox.guestproperty.enumerate(vm, (err, arr) => { 8 | logger.debug(arr); 9 | }); 10 | -------------------------------------------------------------------------------- /test/integration/guestproperty.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const virtualbox = require("../../lib/virtualbox"), 4 | { logger } = require("../helpers/logger"), 5 | options = { 6 | vm: "node-virtualbox-test-machine", 7 | key: "/VirtualBox/GuestInfo/Net/0/V4/IP", 8 | }; 9 | 10 | virtualbox.guestproperty.get(options, (error, stdout, _) => { 11 | if (error) { 12 | throw error; 13 | } 14 | logger.debug(error, stdout, _); 15 | logger.debug(stdout); 16 | }); 17 | -------------------------------------------------------------------------------- /test/integration/isRunning.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var virtualbox = require("../../lib/virtualbox"), 4 | { logger } = require("../helpers/logger"), 5 | vm = "node-virtualbox-test-machine"; 6 | 7 | virtualbox.isRunning(vm, function (error, isRunning) { 8 | if (error) { 9 | throw error; 10 | } 11 | if (isRunning) { 12 | logger.log('Virtual Machine "%s" is Running', vm); 13 | } else { 14 | logger.log('Virtual Machine "%s" is Poweroff', vm); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /test/integration/keyboardputscancode.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var virtualbox = require("../../lib/virtualbox"), 4 | { logger } = require("../helpers/logger"), 5 | async = require("async"), 6 | args = process.argv.slice(2), 7 | vm = "node-virtualbox-test-machine", 8 | key = args.length > 1 && args[1], 9 | delay = 250, 10 | sequence; 11 | 12 | var SCAN_CODES = virtualbox.SCAN_CODES; 13 | 14 | var fns = []; 15 | 16 | /** 17 | * 18 | * Uncomment the following if you want to 19 | * test a particular key down/up (make/break) 20 | * sequence. 21 | * 22 | **/ 23 | 24 | // SHIFT + A Sequence 25 | sequence = [ 26 | { key: "SHIFT", type: "make", code: SCAN_CODES["SHIFT"] }, 27 | { key: "A", type: "make", code: SCAN_CODES["A"] }, 28 | { key: "SHIFT", type: "break", code: SCAN_CODES.getBreakCode("SHIFT") }, 29 | { key: "A", type: "break", code: SCAN_CODES.getBreakCode("A") }, 30 | ]; 31 | 32 | function onResponse(err) { 33 | if (err) { 34 | throw err; 35 | } 36 | } 37 | 38 | function generateFunc(key, type, code) { 39 | return function (cb) { 40 | setTimeout(function () { 41 | logger.info("Sending %s %s code", key, type); 42 | virtualbox.keyboardputscancode(vm, code, function (err) { 43 | onResponse(err); 44 | cb(); 45 | }); 46 | }, delay); 47 | }; 48 | } 49 | 50 | function addKeyFuncs(key) { 51 | var makeCode = SCAN_CODES[key]; 52 | var breakCode = SCAN_CODES.getBreakCode(key); 53 | 54 | if (makeCode && makeCode.length) { 55 | fns.push(generateFunc(key, "make", makeCode)); 56 | 57 | if (breakCode && breakCode.length) { 58 | fns.push(generateFunc(key, "break", breakCode)); 59 | } 60 | } 61 | } 62 | 63 | if (sequence) { 64 | fns = sequence.map(function (s) { 65 | return generateFunc(s.key, s.type, s.code); 66 | }); 67 | } else if (key) { 68 | addKeyFuncs(key); 69 | } else { 70 | for (var codeKey in SCAN_CODES) { 71 | if (codeKey === "getBreakCode") { 72 | continue; 73 | } 74 | addKeyFuncs(key); 75 | } 76 | } 77 | 78 | async.series(fns, function () { 79 | logger.info("Keyboard Put Scan Code Test Complete"); 80 | }); 81 | -------------------------------------------------------------------------------- /test/integration/list.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var virtualbox = require('../../lib/virtualbox'); 5 | 6 | virtualbox.list(function (list_data, error) { 7 | if (error) { 8 | throw error; 9 | } 10 | 11 | if (list_data) { 12 | virtualbox._logging.log(util.inspect(list_data)); 13 | //logger.log(list_data); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /test/integration/modify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var virtualbox = require('../../lib/virtualbox'), 4 | vmName = 'node-virtualbox-test-machine'; 5 | 6 | virtualbox.modify(vmName, { memory: 64 }, function (error) { 7 | if (error) { 8 | throw error; 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /test/integration/pause.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const virtualbox = require('../../lib/virtualbox'), 4 | vmName = 'node-virtualbox-test-machine'; 5 | 6 | virtualbox.pause(vmName, function (error) { 7 | if (error) { 8 | throw error; 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /test/integration/poweroff.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var virtualbox = require('../../lib/virtualbox'), 4 | vmName = 'node-virtualbox-test-machine'; 5 | 6 | virtualbox.poweroff(vmName, function (error) { 7 | if (error) { 8 | throw error; 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /test/integration/resume.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const virtualbox = require('../../lib/virtualbox'), 4 | vmName = 'node-virtualbox-test-machine'; 5 | 6 | virtualbox.resume(vmName, function (error) { 7 | if (error) { 8 | throw error; 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /test/integration/savestate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var virtualbox = require('../../lib/virtualbox'), 4 | vmName = 'node-virtualbox-test-machine'; 5 | 6 | virtualbox.savestate(vmName, function (error) { 7 | if (error) { 8 | throw error; 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /test/integration/setextradata.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var virtualbox = require('../../lib/virtualbox'), 4 | vmname = 'node-virtualbox-test-machine', 5 | key = 'some_value', 6 | value = 'kind of value'; 7 | 8 | virtualbox.extradata.set({ vmname, key, value }, function (error, result) { 9 | if (error) { 10 | throw error; 11 | } 12 | 13 | virtualbox._logging.log( 14 | 'Set Virtual Machine "%s" extra "%s" value to "%s"; result is "%s"', 15 | vmname, 16 | key, 17 | value, 18 | result 19 | ); 20 | }); 21 | -------------------------------------------------------------------------------- /test/integration/snapshot-delete.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var virtualbox = require('../../lib/virtualbox'), 4 | vm = 'node-virtualbox-test-machine', 5 | uuid = '44f96692-854f-4358-b246-d455bc403e16'; 6 | 7 | virtualbox.snapshotDelete(vm, uuid, function (error) { 8 | if (error) { 9 | throw error; 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /test/integration/snapshot-list.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var virtualbox = require('../../lib/virtualbox'), 4 | vm = 'node-virtualbox-test-machine'; 5 | 6 | virtualbox.snapshotList(vm, function ( 7 | error, 8 | snapshotList, 9 | currentSnapshotUUID 10 | ) { 11 | if (error) { 12 | throw error; 13 | } 14 | 15 | if (snapshotList) { 16 | virtualbox._logging.log( 17 | JSON.stringify(snapshotList), 18 | JSON.stringify(currentSnapshotUUID) 19 | ); 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /test/integration/snapshot-restore.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var virtualbox = require('../../lib/virtualbox'), 4 | vm = 'node-virtualbox-test-machine', 5 | uuid = '44f96692-854f-4358-b246-d455bc403e16'; 6 | 7 | virtualbox.snapshotRestore(vm, uuid, function (error) { 8 | if (error) { 9 | throw error; 10 | } 11 | 12 | virtualbox._logging.log('Virtual machine has been restored'); 13 | }); 14 | -------------------------------------------------------------------------------- /test/integration/snapshot-take.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var virtualbox = require('../../lib/virtualbox'), 4 | vmname = 'node-virtualbox-test-machine', 5 | exportName = 'test-export'; 6 | 7 | virtualbox.snapshotTake(vmname, exportName, function (error, uuid) { 8 | if (error) { 9 | throw error; 10 | } 11 | if (uuid) { 12 | virtualbox._logging.log('UUID: ' + uuid); 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /test/integration/start.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const virtualbox = require('../../lib/virtualbox'), 4 | vmName = 'node-virtualbox-test-machine'; 5 | 6 | virtualbox.start(vmName, function (error) { 7 | if (error) { 8 | throw error; 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /test/integration/stop.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var virtualbox = require('../../lib/virtualbox'), 4 | vmName = 'node-virtualbox-test-machine'; 5 | 6 | virtualbox.stop(vmName, function (error) { 7 | if (error) { 8 | throw error; 9 | } 10 | }); 11 | --------------------------------------------------------------------------------