├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── release.yml ├── .gitignore ├── .npmignore ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── TESTS.md ├── bin ├── elevate │ ├── elevate.cmd │ └── elevate.vbs ├── sudowin │ ├── LICENSE.txt │ ├── Sudowin.Common.dll │ ├── sudo.exe │ └── sudo.exe.config └── winsw │ ├── winsw.exe │ └── winsw.exe.config ├── docs ├── assets │ ├── css │ │ ├── border-radius.htc │ │ ├── guide.css │ │ └── main.css │ └── images │ │ ├── favicon.ico │ │ └── windows8.png ├── eventlog.png ├── guides.json ├── guides │ ├── nodeWindowsService │ │ └── README.md │ └── nodewindowstroubleshooting │ │ └── README.md └── service.png ├── example ├── helloworld.js ├── install.js ├── package.json └── uninstall.js ├── lib ├── binaries.js ├── cmd.js ├── daemon.js ├── eventlog.js ├── node-windows.js ├── winsw.js └── wrapper.js ├── package-lock.json └── package.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto 3 | 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [coreybutler] 4 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Tag, Release, & Publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | deploy: 10 | name: Deploy 11 | runs-on: ubuntu-latest 12 | steps: 13 | # Checkout updated source code 14 | - uses: actions/checkout@v2 15 | 16 | # If the version has changed, create a new git tag for it. 17 | - name: Tag 18 | id: autotagger 19 | uses: butlerlogic/action-autotag@master 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | 23 | # The remaining steps all depend on whether or not 24 | # a new tag was created. There is no need to release/publish 25 | # updates until the code base is in a releaseable state. 26 | 27 | # If the new version/tag is a pre-release (i.e. 1.0.0-beta.1), create 28 | # an environment variable indicating it is a prerelease. 29 | - name: Pre-release 30 | if: steps.autotagger.outputs.tagname != '' 31 | run: | 32 | if [[ "${{ steps.autotagger.output.version }}" == *"-"* ]]; then echo "::set-env IS_PRERELEASE=true";else echo "::set-env IS_PRERELEASE=''";fi 33 | 34 | # Create a github release 35 | # This will create a snapshot of the module, 36 | # available in the "Releases" section on Github. 37 | - name: Release 38 | id: create_release 39 | if: steps.autotagger.outputs.tagname != '' 40 | uses: actions/create-release@v1.0.0 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | with: 44 | tag_name: ${{ steps.autotagger.outputs.tagname }} 45 | release_name: ${{ steps.autotagger.outputs.tagname }} 46 | body: ${{ steps.autotagger.outputs.tagmessage }} 47 | draft: false 48 | prerelease: env.IS_PRERELEASE != '' 49 | 50 | # Use this action to publish a single module to npm. 51 | - name: Publish 52 | id: publish_npm 53 | if: steps.autotagger.outputs.tagname != '' 54 | uses: author/action-publish@master 55 | env: 56 | REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} 57 | 58 | - name: Rollback Release 59 | if: failure() && steps.create_release.outputs.id != '' 60 | uses: author/action-rollback@stable 61 | with: 62 | tag: ${{ steps.autotagger.outputs.tagname }} 63 | env: 64 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .project 3 | .settings/ 4 | .classpath 5 | .DS_Store/ 6 | npm-debug.log 7 | node_modules/ 8 | example/daemon 9 | example/node_modules 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | support 2 | test 3 | docs 4 | Gruntfile.js 5 | example 6 | docs 7 | .* 8 | *.md 9 | *.sock 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | **If you have a question, please use the node-windows tag on StackOverflow instead, available** [here](http://stackoverflow.com/questions/tagged/node-windows). 2 | 3 | Please make sure you review the README, especially the updates, before submitting anything. 4 | 5 | **Contributions must be cross-platform compatible...** node-windows/mac/linux share a common API. 6 | 7 | --- 8 | 9 | ## NGN 10 | 11 | node-windows/mac/linux were built to support [NGN](http://ngn.js.org). NGN is a Node.js-based microservice platform. 12 | In accordance to the Microservice/POA/SOA approach, it uses multiple independent but connected processes to support apps/systems. 13 | These processes are controlled with the node-* modules. 14 | 15 | It has been in the works for almost 3yrs and is slated for a mid-late 2017 release. The node-* series 16 | will progress in accordance to milestone accomplishments with NGN (which should be plentiful soon). 17 | 18 | --- 19 | 20 | # THANKS 21 | 22 | I sincerely appreciate your contributions and feedback. 23 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*global module:false*/ 2 | module.exports = function(grunt) { 3 | 4 | var cfg = {}; 5 | 6 | // Project configuration. 7 | grunt.initConfig({ 8 | pkg: '', 9 | meta: { 10 | banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + 11 | '<%= grunt.template.today("yyyy-mm-dd") %>\n' + 12 | '<%= pkg.homepage ? "* " + pkg.homepage + "\n" : "" %>' + 13 | '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + 14 | ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */' 15 | }, 16 | jsduck: { 17 | main: { 18 | // source paths with your code 19 | src: [ 20 | './lib' 21 | ], 22 | 23 | // docs output dir 24 | dest: '../node-daemon-docs/manual', 25 | 26 | // extra options 27 | options: { 28 | 'title': 'node-windows', 29 | //'welcome': 'src/assets/html/welcome.html', 30 | 'head-html': '', 31 | //'categories': 'src/categories.json', 32 | 'guides': 'docs/guides.json', 33 | 'color': true, 34 | 'builtin-classes': true, 35 | 'warnings': ['-req_after_opt'], 36 | 'external': ['XMLHttpRequest'] 37 | } 38 | } 39 | }, 40 | clean: { 41 | docs: ["../node-daemon-docs/manual"], 42 | }, 43 | copy: { 44 | jsduckassets: { 45 | files: [ 46 | {expand: true, cwd: './docs/assets/css/', src:['*.*'], dest: '../node-daemon-docs/manual/resources/css/'}, 47 | {expand: true, cwd: './docs/assets/images/', src:['*.*'], dest: '../node-daemon-docs/manual/resources/images/'}, 48 | {expand: true, cwd: './docs/assets/images/', src:['tabs.png'], dest: '../node-daemon-docs/manual/resources/images/'} 49 | ] 50 | } 51 | } 52 | }); 53 | 54 | grunt.loadNpmTasks('grunt-contrib-copy'); 55 | grunt.loadNpmTasks('grunt-jsduck'); 56 | grunt.loadNpmTasks('grunt-contrib-clean'); 57 | 58 | // Default task. 59 | grunt.registerTask('default', ['clean:docs','jsduck','copy:jsduckassets']); 60 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | NSSM and sudowin are the copyrights of their respective owners. No license ships with NSSM, 2 | but it can be found at http://nssm.cc. sudowin is a BSD license. 3 | 4 | All other scripts are Copyright (c) Corey Butler under an MIT license. 5 | 6 | --- 7 | 8 | (The MIT License) 9 | 10 | Copyright (c) 2012 Corey Butler <corey@coreybutler.com> 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining 13 | a copy of this software and associated documentation files (the 14 | 'Software'), to deal in the Software without restriction, including 15 | without limitation the rights to use, copy, modify, merge, publish, 16 | distribute, sublicense, and/or sell copies of the Software, and to 17 | permit persons to whom the Software is furnished to do so, subject to 18 | the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 26 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 27 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 28 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 29 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-windows 2 | 3 | ![NPM version](https://img.shields.io/npm/v/node-windows?label=node-windows&logo=npm&style=for-the-badge) 4 | ![NGN Dependencies](https://img.shields.io/librariesio/release/npm/node-windows?style=for-the-badge) 5 | 6 | This library can be used to install/start/stop/uninstall Node scripts as Windows background services for **production** environments. This is not a tool for developing applications, it is a tool for releasing them. This tool generates an executable that will run your app with whichever version of Node.js is installed on the computer. 7 | 8 | See [node-mac](http://github.com/coreybutler/node-mac) and [node-linux](http://github.com/coreybutler/node-linux) if you need to support those operating systems. 9 | 10 | [Tweet me (@goldglovecb)](http://twitter.com/goldglovecb) if you need me. 11 | 12 | ## Sponsors 13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 27 | 28 | 29 | 33 | 34 |
24 | 25 |   26 |
30 |
31 | Can't sponsor?
Consider nominating @coreybutler for a Github star. 32 |
35 |
36 |
37 | 38 | ## Overview 39 | 40 | The following features are available in node-windows: 41 | 42 | - **Service Management**: Run Node.js scripts as native Windows services. Includes monitoring. 43 | - **Event Logging**: Create logs in the Event log. 44 | - **Commands**: 45 | - _Elevated Permissions_: Run a command with elevated privileges (may prompt user for acceptance) 46 | - _Sudo_: Run an `exec` command as a sudoer. 47 | - _Identify Administrative Privileges_: Determines whether the current user has administrative privileges. 48 | - _List Tasks_: A method to list running windows tasks/services. 49 | - _Kill Task_: A method to kill a specific windows service/task (by PID). 50 | 51 | ## Installation 52 | 53 | The recommended way to install node-windows is with npm, using the global flag: 54 | 55 | `npm install -g node-windows` 56 | 57 | Then, in your project root, run: 58 | 59 | `npm link node-windows` 60 | 61 | However; it is possible to use node-windows without the global flag (i.e. install directly into the project root). 62 | More details regarding why this is not the recommended approach are available throughout this Readme. 63 | 64 | ## NO NATIVE MODULES 65 | 66 | Using native node modules on Windows can suck. Most native modules are not distributed in a binary format. 67 | Instead, these modules rely on `npm` to build the project, utilizing [node-gyp](https://github.com/TooTallNate/node-gyp). 68 | This means developers need to have Visual Studio (and potentially other software) installed on the system, 69 | just to install a native module. This is portable, but painful... mostly because Visual Studio 70 | itself is over 2GB. 71 | 72 | **node-windows does not use native modules.** There are some binary/exe utilities, but everything 73 | needed to run more complex tasks is packaged and distributed in a readily usable format. So, no need for 74 | Visual Studio... at least not for this module. 75 | 76 | --- 77 | 78 | # Windows Services 79 | 80 | node-windows has a utility to run Node.js scripts as Windows services. Please note that like all 81 | Windows services, creating one requires administrative privileges. To create a service with 82 | node-windows, prepare a script like: 83 | 84 | ```js 85 | var Service = require('node-windows').Service; 86 | 87 | // Create a new service object 88 | var svc = new Service({ 89 | name:'Hello World', 90 | description: 'The nodejs.org example web server.', 91 | script: 'C:\\path\\to\\helloworld.js', 92 | nodeOptions: [ 93 | '--harmony', 94 | '--max_old_space_size=4096' 95 | ] 96 | //, workingDirectory: '...' 97 | //, allowServiceLogon: true 98 | }); 99 | 100 | // Listen for the "install" event, which indicates the 101 | // process is available as a service. 102 | svc.on('install',function(){ 103 | svc.start(); 104 | }); 105 | 106 | svc.install(); 107 | ``` 108 | 109 | The code above creates a new `Service` object, providing a pretty name and description. 110 | The `script` attribute identifies the Node.js script that should run as a service. Upon running 111 | this, the script will be visible from the Windows Services utility. 112 | 113 | ![Windows Service](https://raw.github.com/coreybutler/node-windows/master/docs/service.png) 114 | 115 | The `Service` object emits the following events: 116 | 117 | - _install_ - Fired when the script is installed as a service. 118 | - _alreadyinstalled_ - Fired if the script is already known to be a service. 119 | - _invalidinstallation_ - Fired if an installation is detected but missing required files. 120 | - _uninstall_ - Fired when an uninstallation is complete. 121 | - _alreadyuninstalled_ - Fired when an uninstall is requested and no installation exists. 122 | - _start_ - Fired when the new service is started. 123 | - _stop_ - Fired when the service is stopped. 124 | - _error_ - Fired in some instances when an error occurs. 125 | 126 | In the example above, the script listens for the `install` event. Since this event 127 | is fired when a service installation is complete, it is safe to start the service. 128 | 129 | Services created by node-windows are similar to most other services running on Windows. 130 | They can be started/stopped from the windows service utility, via `NET START` or `NET STOP` commands, 131 | or even managed using the sc 132 | utility. 133 | 134 | ### Command-line Options 135 | 136 | It may be desired to specify command-line switches to your script. You can do this by setting the `scriptOptions` within the service config: 137 | 138 | ```js 139 | var svc = new Service({ 140 | name:'Hello World', 141 | description: 'The nodejs.org example web server.', 142 | script: 'C:\\path\\to\\helloworld.js', 143 | scriptOptions: '-c C:\\path\\to\\somewhere\\special -i' 144 | }); 145 | ``` 146 | 147 | 148 | ### Environment Variables 149 | 150 | Sometimes you may want to provide a service with static data, passed in on creation of the service. You can do this by setting environment variables in the service config, as shown below: 151 | 152 | ```js 153 | var svc = new Service({ 154 | name:'Hello World', 155 | description: 'The nodejs.org example web server.', 156 | script: 'C:\\path\\to\\helloworld.js', 157 | env: { 158 | name: "HOME", 159 | value: process.env["USERPROFILE"] // service is now able to access the user who created its' home directory 160 | } 161 | }); 162 | ``` 163 | You can also supply an array to set multiple environment variables: 164 | 165 | ```js 166 | var svc = new Service({ 167 | name:'Hello World', 168 | description: 'The nodejs.org example web server.', 169 | script: 'C:\\path\\to\\helloworld.js', 170 | env: [{ 171 | name: "HOME", 172 | value: process.env["USERPROFILE"] // service is now able to access the user who created its' home directory 173 | }, 174 | { 175 | name: "TEMP", 176 | value: path.join(process.env["USERPROFILE"],"/temp") // use a temp directory in user's home directory 177 | }] 178 | }); 179 | ``` 180 | 181 | 182 | ### Node Executable Path 183 | 184 | There are times when you may want to specify a specific `node` executable to use to run your script. You can do this by setting the `execPath` in the service config, as shown below: 185 | 186 | ```js 187 | var svc = new Service({ 188 | name:'Hello World', 189 | description: 'The nodejs.org example web server.', 190 | script: 'C:\\path\\to\\helloworld.js', 191 | execPath: 'C:\\path\\to\\specific\\node.exe' 192 | }); 193 | ``` 194 | 195 | 196 | ### User Account Attributes 197 | 198 | If you need to specify a specific user or particular credentials to manage a service, the following 199 | attributes may be helpful. 200 | 201 | The `user` attribute is an object with three keys: `domain`,`account`, and `password`. 202 | This can be used to identify which user the service library should use to perform system commands. 203 | By default, the domain is set to the local computer name, but it can be overridden with an Active Directory 204 | or LDAP domain. For example: 205 | 206 | **app.js** 207 | ```js 208 | var Service = require('node-windows').Service; 209 | 210 | // Create a new service object 211 | var svc = new Service({ 212 | name:'Hello World', 213 | script: require('path').join(__dirname,'helloworld.js'), 214 | //, allowServiceLogon: true 215 | }); 216 | 217 | svc.logOnAs.domain = 'mydomain.local'; 218 | svc.logOnAs.account = 'username'; 219 | svc.logOnAs.password = 'password'; 220 | ... 221 | ``` 222 | 223 | Both the account and password must be explicitly defined if you want the service module to 224 | run commands as a specific user. By default, it will run using the user account that launched 225 | the process (i.e. who launched `node app.js`). 226 | 227 | If you want to instruct winsw to allow service account logins, specify `allowServiceLogon: true`. This is disabled by default since some users have experienced issues running this without service logons. 228 | 229 | The other attribute is `sudo`. This attribute has a single property called `password`. By supplying 230 | this, the service module will attempt to run commands using the user account that launched the 231 | process and the password for that account. This should only be used for accounts with administrative 232 | privileges. 233 | 234 | **app.js** 235 | ```js 236 | var Service = require('node-windows').Service; 237 | 238 | // Create a new service object 239 | var svc = new Service({ 240 | name:'Hello World', 241 | script: require('path').join(__dirname,'helloworld.js') 242 | }); 243 | 244 | svc.sudo.password = 'password'; 245 | ... 246 | ``` 247 | 248 | ### Depending on other services 249 | 250 | The service can also be made dependant on other Windows services. 251 | 252 | ```js 253 | var svc = new Service({ 254 | name:'Hello World', 255 | description: 'The nodejs.org example web server.', 256 | script: 'C:\\path\\to\\helloworld.js', 257 | dependsOn: ["serviceA"] 258 | }); 259 | ``` 260 | 261 | ### Cleaning Up: Uninstall a Service 262 | 263 | Uninstalling a previously created service is syntactically similar to installation. 264 | 265 | ```js 266 | var Service = require('node-windows').Service; 267 | 268 | // Create a new service object 269 | var svc = new Service({ 270 | name:'Hello World', 271 | script: require('path').join(__dirname,'helloworld.js') 272 | }); 273 | 274 | // Listen for the "uninstall" event so we know when it's done. 275 | svc.on('uninstall',function(){ 276 | console.log('Uninstall complete.'); 277 | console.log('The service exists: ',svc.exists); 278 | }); 279 | 280 | // Uninstall the service. 281 | svc.uninstall(); 282 | ``` 283 | 284 | The uninstall process only removes process-specific files. **It does NOT delete your Node.js script!** 285 | 286 | ### What Makes node-windows Services Unique? 287 | 288 | Lots of things! 289 | 290 | **Long Running Processes & Monitoring:** 291 | 292 | The built-in service recovery for Windows services is fairly limited and cannot easily be configured 293 | from code. Therefore, node-windows creates a wrapper around the Node.js script. This wrapper 294 | is responsible for restarting a failed service in an intelligent and configurable manner. For example, 295 | if your script crashes due to an unknown error, node-windows will attempt to restart it. By default, 296 | this occurs every second. However; if the script has a fatal flaw that makes it crash repeatedly, 297 | it adds unnecessary overhead to the system. node-windows handles this by increasing the time interval 298 | between restarts and capping the maximum number of restarts. 299 | 300 | **Smarter Restarts That Won't Pummel Your Server:** 301 | 302 | Using the default settings, node-windows adds 25% to the wait interval each time it needs to restart 303 | the script. With the default setting (1 second), the first restart attempt occurs after one second. 304 | The second occurs after 1.25 seconds. The third after 1.56 seconds (1.25 increased by 25%) and so on. 305 | Both the initial wait time and the growth rate are configuration options that can be passed to a new 306 | `Service`. For example: 307 | 308 | ```js 309 | var svc = new Service({ 310 | name:'Hello World', 311 | description: 'The nodejs.org example web server.', 312 | script: 'C:\\path\\to\\helloworld.js', 313 | wait: 2, 314 | grow: .5 315 | }); 316 | ``` 317 | 318 | In this example, the wait period will start at 2 seconds and increase by 50%. So, the second attempt 319 | would be 3 seconds later while the fourth would be 4.5 seconds later. 320 | 321 | **Don't DOS Yourself!** 322 | 323 | Repetitive recycling could potentially go on forever with a bad script. To handle these situations, node-windows 324 | supports two kinds of caps. Using `maxRetries` will cap the maximum number of restart attempts. By 325 | default, this is unlimited. Setting it to 3 would tell the process to no longer restart a process 326 | after it has failed 3 times. Another option is `maxRestarts`, which caps the number of restarts attempted 327 | within 60 seconds. For example, if this is set to 3 (the default) and the process crashes/restarts repeatedly, 328 | node-windows will cease restart attempts after the 3rd cycle in a 60 second window. Both of these 329 | configuration options can be set, just like `wait` or `grow`. 330 | 331 | Finally, an attribute called `abortOnError` can be set to `true` if you want your script to **not** restart 332 | at all when it exits with an error. 333 | 334 | ### How Services Are Made 335 | 336 | node-windows uses the [winsw](https://github.com/kohsuke/winsw) utility to create a unique `.exe` 337 | for each Node.js script deployed as a service. A directory called `daemon` is created and populated 338 | with `myappname.exe` and `myappname.xml`. The XML file is a configuration for the executable. Additionally, 339 | `winsw` will create some logs for itself in this directory (which are viewable in the Event log). 340 | 341 | The `myappname.exe` file launches the node-windows wrapper, which is responsible for monitoring and managing 342 | the script. Since this file is a part of node-windows, moving the node-windows directory could result in 343 | the `.exe` file not being able to find the Node.js script. However; this should not be a problem if 344 | node-windows is installed globally, per the recommended installation instructions. 345 | 346 | All of these daemon-specific files are created in a subdirectory called `daemon`, which is created in the 347 | same directory where the Node.js script is saved. Uninstalling a service will remove these files. 348 | 349 | _Event Logging_ 350 | 351 | Services created with node-windows have two event logs that can be viewed through the Windows Event Viewer. 352 | A log source named `myappname.exe` provides basic logging for the executable file. It can be used to see 353 | when the entire service starts/stops or has errors. A second log, named after your service name (i.e. My App Name), 354 | is used by the node-windows monitor. It is possible to write to this log from the Node.js script using 355 | the node-windows Event Logging. 356 | 357 | --- 358 | 359 | # Event Logging 360 | 361 | New as of `v0.1.0` is a _non-C++_ based event logging utility. This utility can write to the event log, 362 | making your logs visible from the Event Viewer. It uses [eventcreate](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/eventcreate) under the hood. 363 | 364 | To create a logger: 365 | 366 | ```js 367 | var EventLogger = require('node-windows').EventLogger; 368 | 369 | var log = new EventLogger('Hello World'); 370 | 371 | log.info('Basic information.'); 372 | log.warn('Watch out!'); 373 | log.error('Something went wrong.'); 374 | ``` 375 | 376 | Looks similar to: 377 | 378 | ![Event Logging in node-windows](https://raw.github.com/coreybutler/node-windows/master/docs/eventlog.png) 379 | 380 | Some lesser-used options are also available through node-windows event logging. 381 | 382 | ```js 383 | log.auditSuccess('AUser Login Success'); 384 | log.auditFailure('AUser Login Failure'); 385 | ``` 386 | 387 | Each log type (info, warn, error, auditSuccess, and auditFailure) method optionally accepts two additional 388 | arguments, including a _code_ and _callback_. By default, the event code is `1000` if not otherwise specified. 389 | To provide a custom event code with a log message and write that message to the console, the following code could 390 | be used: 391 | 392 | > **Notice:** It appears [eventcreate](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/eventcreate) only supports custom ID's <=1000. 393 | 394 | ```js 395 | log.info('Something different happened!', 700, function(){ 396 | console.log('Something different happened!'); 397 | }); 398 | ``` 399 | 400 | By default, event logs are all part of the `APPLICATION` scope. However; it is also possible to use the `SYSTEM` log. 401 | To do this, a configuration object must be passed to the new log: 402 | 403 | ```js 404 | var EventLogger = require('node-windows').EventLogger; 405 | var log = new EventLogger({ 406 | source: 'My Event Log', 407 | eventLog: 'SYSTEM' 408 | }); 409 | ``` 410 | 411 | Warning event logs that are produced by the wrapper can be suppressed by disabling it when creating the service. 412 | Warning logs are enabled by default. 413 | 414 | ```js 415 | var svc = new Service({ 416 | name:'Hello World', 417 | description: 'The nodejs.org example web server.', 418 | disableWarningLogs: true, 419 | }); 420 | ``` 421 | 422 | --- 423 | 424 | # Commands 425 | 426 | node-windows ships with several commands to simplify tasks on MS Windows. 427 | 428 | ## elevate 429 | 430 | Elevate is similar to `sudo` on Linux/Mac. It attempts to elevate the privileges of the 431 | current user to a local administrator. Using this does not require a password, but it 432 | does require that the current user have administrative privileges. Without these 433 | privileges, the command will fail with a `access denied` error. 434 | 435 | On systems with UAC enabled, this may prompt the user for permission to proceed: 436 | 437 | ![UAC Prompt](http://upload.wikimedia.org/wikipedia/en/5/51/Windows_7_UAC.png) 438 | 439 | **Syntax**: 440 | 441 | `elevate(cmd[,options,callback])` 442 | 443 | - _cmd_: The command to execute with elevated privileges. This can be any string that would be typed at the command line. 444 | - _options_ (optional): Any options that will be passed to `require('child_process').exec(cmd,,callback)`. 445 | - _callback_ (optional): The callback function passed to `require('child_process').exec(cmd,options,)`. 446 | 447 | ## sudo 448 | 449 | Sudo acts similarly to `sudo` on Linux/Mac. Unlike _elevate_, it requires a password, but it 450 | will not prompt the user for permission to proceed. Like _elevate_, this 451 | _still requires administrative privileges_ for the user, otherwise the command will fail. 452 | The primary difference between this and _elevate()_ is the prompt. 453 | 454 | **Syntax**: 455 | 456 | `sudo(cmd,password[,options,callback])` 457 | 458 | - _cmd_: The command to execute with elevated privileges. This can be any string that would be typed at the command line. 459 | - _password_: The password of the user 460 | - _options_ (optional): Any options that will be passed to `require('child_process').exec(cmd,,callback)`. 461 | - _callback_ (optional): The callback function passed to `require('child_process').exec(cmd,options,)`. 462 | 463 | ## isAdminUser 464 | 465 | This asynchronous command determines whether the current user has administrative privileges. 466 | It passes a boolean value to the callback, returning `true` if the user is an administrator 467 | or `false` if it is not. 468 | 469 | **Example** 470 | 471 | ```js 472 | var wincmd = require('node-windows'); 473 | 474 | wincmd.isAdminUser(function(isAdmin){ 475 | if (isAdmin) { 476 | console.log('The user has administrative privileges.'); 477 | } else { 478 | console.log('NOT AN ADMIN'); 479 | } 480 | }); 481 | ``` 482 | 483 | ## list 484 | 485 | The list method queries the operating system for a list of running processes. 486 | 487 | ```js 488 | var wincmd = require('node-windows'); 489 | 490 | wincmd.list(function(svc){ 491 | console.log(svc); 492 | },true); 493 | ``` 494 | 495 | This returns an array of running processes. Supplying the optional `true` 496 | argument in the above example provides a list with verbose output. The output is 497 | specific to the version of the operating system. Here is an example of verbose 498 | output on a Windows 8 computer. 499 | 500 | ```js 501 | [{ 502 | ImageName: 'cmd.exe', 503 | PID: '12440', 504 | SessionName: 'Console', 505 | 'Session#': '1', 506 | MemUsage: '1,736 K', 507 | Status: 'Unknown', 508 | UserName: 'Machine\\Corey', 509 | CPUTime: '0:00:00', 510 | WindowTitle: 'N/A' 511 | },{ 512 | ImageName: 'tasklist.exe', 513 | PID: '1652', 514 | SessionName: 'Console', 515 | 'Session#': '1', 516 | MemUsage: '8,456 K', 517 | Status: 'Unknown', 518 | UserName: 'Machine\\Corey', 519 | CPUTime: '0:00:00', 520 | WindowTitle: 'N/A' 521 | }] 522 | ``` 523 | 524 | The regular (non-verbose) output typically provides the `ImageName`,`PID`,`SessionName`, 525 | `Session#`, `MemUsage`, and `CPUTime`. 526 | 527 | ## kill 528 | 529 | This method will kill a process by `PID`. 530 | 531 | 532 | ```js 533 | var wincmd = require('node-windows'); 534 | 535 | wincmd.kill(12345,function(){ 536 | console.log('Process Killed'); 537 | }); 538 | ``` 539 | 540 | In this example, process ID `12345` would be killed. It is important to note that the 541 | user account executing this node script may require administrative privileges. 542 | 543 | # Troubleshooting 544 | 545 | If you're experiencing issues with the examples, please review the `TESTS.md` file. 546 | 547 | If you are encountering the _invalidinstallation_ event, take a look at the `daemon` 548 | directory that is created during the installation to make sure the `.exe` and `.xml` 549 | files are there. In some circumstances, primarily during _un_installation, it is 550 | possbile for the process to temporarily lock a log file, which prevents Windows 551 | from removing it. In this scenario, simply run the uninstall again. In most cases this 552 | will fix the issue. If not, manually remove the `daemon` directory before running the 553 | installation again. 554 | 555 | # Thank You 556 | 557 | There have been many contributors who have done everything from committing features to helping pick up slack while I've been swamped. I'm incredibly appreciative for the help. 558 | 559 | Special thanks to @arthurblake whose modifications have FINALLY been added. Thanks to @hockeytim11, who helped compile and update a bunch of outstanding issues and started bringing support to the other node-* libraries. 560 | 561 | # Licenses 562 | 563 | winsw and sudowin are the copyrights of their respective owners. winsw 564 | is distributed under an MIT license. sudowin is distributed under a BSD license. 565 | 566 | All other scripts are Copyright (c) Corey Butler under an MIT license. 567 | 568 | (The MIT License) 569 | 570 | Copyright (c) 2013 Corey Butler 571 | 572 | Permission is hereby granted, free of charge, to any person obtaining 573 | a copy of this software and associated documentation files (the 574 | 'Software'), to deal in the Software without restriction, including 575 | without limitation the rights to use, copy, modify, merge, publish, 576 | distribute, sublicense, and/or sell copies of the Software, and to 577 | permit persons to whom the Software is furnished to do so, subject to 578 | the following conditions: 579 | 580 | The above copyright notice and this permission notice shall be 581 | included in all copies or substantial portions of the Software. 582 | 583 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 584 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 585 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 586 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 587 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 588 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 589 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 590 | -------------------------------------------------------------------------------- /TESTS.md: -------------------------------------------------------------------------------- 1 | # Node-Windows Tests 2 | 3 | node-windows uses several command line scripts to execute processes and bypass the need 4 | for native modules. Additionally, node-windows attempts to handle account permissions, but 5 | tests will still fail if they are not run with the appropriate account credentials. Therefore, 6 | it is hard to decipher whether an issue stems from a user account, credentials, elevated permmissions, 7 | or the code itself. 8 | 9 | In a _best effort_ fashion, the examples are rudimentary. They have been tested and are known 10 | to work. If you experience problems with the module, it is best to try running the examples first to 11 | verify basic functionality. 12 | 13 | ## Testing Services 14 | 15 | node-windows ships with a service/daemon management tool. It is important to note that installing 16 | and uninstalling processes, which use `winsw.exe`, can take some time to complete. In particular, 17 | there is a problem if you attempt to uninstall a process before it has completed installation. 18 | Windows locks a log file while installing the process, which cannot be removed until the installation 19 | is complete. -------------------------------------------------------------------------------- /bin/elevate/elevate.cmd: -------------------------------------------------------------------------------- 1 | @setlocal 2 | @echo off 3 | set CMD=%* 4 | set APP=%1 5 | start wscript //nologo "%~dpn0.vbs" %* -------------------------------------------------------------------------------- /bin/elevate/elevate.vbs: -------------------------------------------------------------------------------- 1 | Set Shell = CreateObject("Shell.Application") 2 | Set WShell = WScript.CreateObject("WScript.Shell") 3 | Set ProcEnv = WShell.Environment("PROCESS") 4 | 5 | cmd = ProcEnv("CMD") 6 | app = ProcEnv("APP") 7 | args= Right(cmd,(Len(cmd)-Len(app))) 8 | 9 | If (WScript.Arguments.Count >= 1) Then 10 | Shell.ShellExecute app, args, "", "runas", 0 11 | Else 12 | WScript.Quit 13 | End If -------------------------------------------------------------------------------- /bin/sudowin/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2005-2008, Schley Andrew Kutz 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | * Neither the name of l o s t c r e a t i o n s nor the names of its 13 | contributors may be used to endorse or promote products derived from this 14 | software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 23 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /bin/sudowin/Sudowin.Common.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreybutler/node-windows/54ac1e382f1cf56bc7278672672aba1342c96c01/bin/sudowin/Sudowin.Common.dll -------------------------------------------------------------------------------- /bin/sudowin/sudo.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreybutler/node-windows/54ac1e382f1cf56bc7278672672aba1342c96c01/bin/sudowin/sudo.exe -------------------------------------------------------------------------------- /bin/sudowin/sudo.exe.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 32 | 33 | 34 | 35 | 36 | 37 | 41 | 42 | 43 | 48 | 49 | 50 | 51 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /bin/winsw/winsw.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreybutler/node-windows/54ac1e382f1cf56bc7278672672aba1342c96c01/bin/winsw/winsw.exe -------------------------------------------------------------------------------- /bin/winsw/winsw.exe.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/assets/css/border-radius.htc: -------------------------------------------------------------------------------- 1 | --Do not remove this if you are using-- 2 | Original Author: Remiz Rahnas 3 | Original Author URL: http://www.htmlremix.com 4 | Published date: 2008/09/24 5 | 6 | Changes by Nick Fetchak: 7 | - IE8 standards mode compatibility 8 | - VML elements now positioned behind original box rather than inside of it - should be less prone to breakage 9 | Published date : 2009/11/18 10 | 11 | 12 | 13 | 143 | 144 | -------------------------------------------------------------------------------- /docs/assets/css/guide.css: -------------------------------------------------------------------------------- 1 | .console { 2 | -moz-border-radius: 20px; 3 | -webkit-border-radius: 20px; 4 | -khtml-border-radius: 20px; 5 | behavior: url(/assets/css/border-radius.htc); 6 | border-radius: 20px; 7 | position: relative; 8 | zoom: 1; 9 | background-color: #000000; 10 | color: #eeeeee; 11 | font-family: Monaco, Consolas, Arial, Helvetica, Sans-Serif; 12 | border: 1px solid #888888; 13 | border-top: 16px solid #888888; 14 | } 15 | -------------------------------------------------------------------------------- /docs/assets/css/main.css: -------------------------------------------------------------------------------- 1 | #header-content { 2 | background: url(../images/windows8.png) 0 0 no-repeat !important; 3 | } 4 | #north-region { 5 | background-color: #0050EF !important; 6 | background-image: none !important; 7 | } 8 | #content hr { 9 | height: 0; 10 | border: 0px; 11 | border-top: 1px solid #EFEFEF; 12 | margin: 15px 0 15px 0; 13 | } 14 | #content li { 15 | margin: 3px 0 0px 25px; 16 | list-style-type: square !important; 17 | } 18 | #content ol li { 19 | list-style-type:upper-roman; 20 | } 21 | #content h1 { 22 | margin: 0 0 8px 0; 23 | } 24 | -------------------------------------------------------------------------------- /docs/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreybutler/node-windows/54ac1e382f1cf56bc7278672672aba1342c96c01/docs/assets/images/favicon.ico -------------------------------------------------------------------------------- /docs/assets/images/windows8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreybutler/node-windows/54ac1e382f1cf56bc7278672672aba1342c96c01/docs/assets/images/windows8.png -------------------------------------------------------------------------------- /docs/eventlog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreybutler/node-windows/54ac1e382f1cf56bc7278672672aba1342c96c01/docs/eventlog.png -------------------------------------------------------------------------------- /docs/guides.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "title": "Node Windows", 3 | "items": [{ 4 | "name": "nodeWindowsService", 5 | "title": "Services", 6 | "description": "Create Windows (background) services from your node.js scripts." 7 | },{ 8 | "name": "nodewindowstroubleshooting", 9 | "title": "Troubleshooting", 10 | "description": "Having problems? Check here for answers." 11 | }] 12 | }] -------------------------------------------------------------------------------- /docs/guides/nodeWindowsService/README.md: -------------------------------------------------------------------------------- 1 | # Windows Services 2 | 3 | node-windows has a utility to run Node.js scripts as Windows services. Please note that like all 4 | Windows services, creating one requires administrative privileges. To create a service with 5 | node-windows, prepare a script like: 6 | 7 | var Service = require('node-windows').Service; 8 | 9 | // Create a new service object 10 | var svc = new Service({ 11 | name:'Hello World', 12 | description: 'The nodejs.org example web server.', 13 | script: 'C:\\path\\to\\helloworld.js') 14 | }); 15 | 16 | // Listen for the "install" event, which indicates the 17 | // process is available as a service. 18 | svc.on('install',function(){ 19 | svc.start(); 20 | }); 21 | 22 | svc.install(); 23 | 24 | The code above creates a new `Service` object, providing a pretty name and description. 25 | The `script` attribute identifies the Node.js script that should run as a service. Upon running 26 | this, the script will be visible from the Windows Services utility. 27 | 28 | ![Windows Service](https://raw.github.com/coreybutler/node-windows/master/docs/service.png) 29 | 30 | The `Service` object emits the following events: 31 | 32 | - _install_ - Fired when the script is installed as a service. 33 | - _alreadyinstalled_ - Fired if the script is already known to be a service. 34 | - _invalidinstallation_ - Fired if an installation is detected but missing required files. 35 | - _uninstall_ - Fired when an uninstallation is complete. 36 | - _start_ - Fired when the new service is started. 37 | - _stop_ - Fired when the service is stopped. 38 | - _error_ - Fired in some instances when an error occurs. 39 | 40 | In the example above, the script listens for the `install` event. Since this event 41 | is fired when a service installation is complete, it is safe to start the service. 42 | 43 | Services created by node-windows are similar to most other services running on Windows. 44 | They can be started/stopped from the windows service utility, via `NET START` or `NET STOP` commands, 45 | or even managed using the sc 46 | utility. 47 | 48 | ## Environment Variables 49 | 50 | Sometimes you may want to provide a service with static data, passed in on creation of the service. You can do this by setting environment variables in the service config, as shown below: 51 | 52 | var svc = new Service({ 53 | name:'Hello World', 54 | description: 'The nodejs.org example web server.', 55 | script: 'C:\\path\\to\\helloworld.js', 56 | env: { 57 | name: "HOME", 58 | value: process.env["USERPROFILE"] // service is now able to access the user who created its' home directory 59 | } 60 | }); 61 | 62 | You can also supply an array to set multiple environment variables: 63 | 64 | var svc = new Service({ 65 | name:'Hello World', 66 | description: 'The nodejs.org example web server.', 67 | script: 'C:\\path\\to\\helloworld.js', 68 | env: [{ 69 | name: "HOME", 70 | value: process.env["USERPROFILE"] // service is now able to access the user who created its' home directory 71 | }, 72 | { 73 | name: "TEMP", 74 | value: path.join(process.env["USERPROFILE"],"/temp") // use a temp directory in user's home directory 75 | }] 76 | }); 77 | 78 | ## User Account Attributes 79 | 80 | If you need to specify a specific user or particular credentials to manage a service, the following 81 | attributes may be helpful. 82 | 83 | The `user` attribute is an object with three keys: `domain`,`account`, and `password`. 84 | This can be used to identify which user the service library should use to perform system commands. 85 | By default, the domain is set to the local computer name, but it can be overridden with an Active Directory 86 | or LDAP domain. For example: 87 | 88 | **app.js** 89 | 90 | var Service = require('node-windows').Service; 91 | 92 | // Create a new service object 93 | var svc = new Service({ 94 | name:'Hello World', 95 | script: require('path').join(__dirname,'helloworld.js') 96 | }); 97 | 98 | svc.user.domain = 'mydomain.local'; 99 | svc.user.account = 'username'; 100 | svc.user.password = 'password'; 101 | ... 102 | 103 | Both the account and password must be explicitly defined if you want the service module to 104 | run commands as a specific user. By default, it will run using the user account that launched 105 | the process (i.e. who launched `node app.js`). 106 | 107 | The other attribute is `sudo`. This attribute has a single property called `password`. By supplying 108 | this, the service module will attempt to run commands using the user account that launched the 109 | process and the password for that account. This should only be used for accounts with administrative 110 | privileges. 111 | 112 | **app.js** 113 | var Service = require('node-windows').Service; 114 | 115 | // Create a new service object 116 | var svc = new Service({ 117 | name:'Hello World', 118 | script: require('path').join(__dirname,'helloworld.js') 119 | }); 120 | 121 | svc.sudo.password = 'password'; 122 | ... 123 | 124 | ## Cleaning Up: Uninstall a Service 125 | 126 | Uninstalling a previously created service is syntactically similar to installation. 127 | 128 | var Service = require('node-windows').Service; 129 | 130 | // Create a new service object 131 | var svc = new Service({ 132 | name:'Hello World', 133 | script: require('path').join(__dirname,'helloworld.js') 134 | }); 135 | 136 | // Listen for the "uninstall" event so we know when it's done. 137 | svc.on('uninstall',function(){ 138 | console.log('Uninstall complete.'); 139 | console.log('The service exists: ',svc.exists); 140 | }); 141 | 142 | // Uninstall the service. 143 | svc.uninstall(); 144 | 145 | The uninstall process only removes process-specific files. **It does NOT delete your Node.js script!** 146 | 147 | ## What Makes node-windows Services Unique? 148 | 149 | Lots of things! 150 | 151 | **Long Running Processes & Monitoring:** 152 | 153 | The built-in service recovery for Windows services is fairly limited and cannot easily be configured 154 | from code. Therefore, node-windows creates a wrapper around the Node.js script. This wrapper 155 | is responsible for restarting a failed service in an intelligent and configurable manner. For example, 156 | if your script crashes due to an unknown error, node-windows will attempt to restart it. By default, 157 | this occurs every second. However; if the script has a fatal flaw that makes it crash repeatedly, 158 | it adds unnecessary overhead to the system. node-windows handles this by increasing the time interval 159 | between restarts and capping the maximum number of restarts. 160 | 161 | **Smarter Restarts That Won't Pummel Your Server:** 162 | 163 | Using the default settings, node-windows adds 25% to the wait interval each time it needs to restart 164 | the script. With the default setting (1 second), the first restart attempt occurs after one second. 165 | The second occurs after 1.25 seconds. The third after 1.56 seconds (1.25 increased by 25%) and so on. 166 | Both the initial wait time and the growth rate are configuration options that can be passed to a new 167 | `Service`. For example: 168 | 169 | var svc = new Service({ 170 | name:'Hello World', 171 | description: 'The nodejs.org example web server.', 172 | script: 'C:\\path\\to\\helloworld.js'), 173 | wait: 2, 174 | grow: .5 175 | }); 176 | 177 | In this example, the wait period will start at 2 seconds and increase by 50%. So, the second attempt 178 | would be 3 seconds later while the fourth would be 4.5 seconds later. 179 | 180 | **Don't DOS Yourself!** 181 | 182 | Repetitive recycling could potentially go on forever with a bad script. To handle these situations, node-windows 183 | supports two kinds of caps. Using `maxRetries` will cap the maximum number of restart attempts. By 184 | default, this is unlimited. Setting it to 3 would tell the process to no longer restart a process 185 | after it has failed 3 times. Another option is `maxRestarts`, which caps the number of restarts attempted 186 | within 60 seconds. For example, if this is set to 3 (the default) and the process crashes/restarts repeatedly, 187 | node-windows will cease restart attempts after the 3rd cycle in a 60 second window. Both of these 188 | configuration options can be set, just like `wait` or `grow`. 189 | 190 | Finally, an attribute called `abortOnError` can be set to `true` if you want your script to **not** restart 191 | at all when it exits with an error. 192 | 193 | ## How Services Are Made 194 | 195 | node-windows uses the [winsw](https://github.com/kohsuke/winsw) utility to create a unique `.exe` 196 | for each Node.js script deployed as a service. A directory called `daemon` is created and populated 197 | with `myappname.exe` and `myappname.xml`. The XML file is a configuration for the executable. Additionally, 198 | `winsw` will create some logs for itself in this directory (which are viewable in the Event log). 199 | 200 | The `myappname.exe` file launches the node-windows wrapper, which is responsible for monitoring and managing 201 | the script. Since this file is a part of node-windows, moving the node-windows directory could result in 202 | the `.exe` file not being able to find the Node.js script. However; this should not be a problem if 203 | node-windows is installed globally, per the recommended installation instructions. 204 | 205 | All of these daemon-specific files are created in a subdirectory called `daemon`, which is created in the 206 | same directory where the Node.js script is saved. Uninstalling a service will remove these files. 207 | 208 | _Event Logging_ 209 | 210 | Services created with node-windows have two event logs that can be viewed through the Windows Event Viewer. 211 | A log source named `myappname.exe` provides basic logging for the executable file. It can be used to see 212 | when the entire service starts/stops or has errors. A second log, named after your service name (i.e. My App Name), 213 | is used by the node-windows monitor. It is possible to write to this log from the Node.js script using 214 | the node-windows Event Logging. 215 | 216 | --- 217 | 218 | # Event Logging 219 | 220 | New as of `v0.1.0` is a _non-C++_ based event logging utility. This utility can write to the event log, 221 | making your logs visible from the Event Viewer. 222 | 223 | To create a logger: 224 | 225 | var EventLogger = require('node-windows').EventLogger; 226 | 227 | var log = new EventLogger('Hello World'); 228 | 229 | log.info('Basic information.'); 230 | log.warn('Watch out!'); 231 | log.error('Something went wrong.'); 232 | 233 | Looks similar to: 234 | 235 | ![Event Logging in node-windows](https://raw.github.com/coreybutler/node-windows/master/docs/eventlog.png) 236 | 237 | Some lesser-used options are also available through node-windows event logging. 238 | 239 | log.auditSuccess('AUser Login Success'); 240 | log.auditFailure('AUser Login Failure'); 241 | 242 | Each log type (info, warn, error, auditSuccess, and auditFailure) method optionally accepts two additional 243 | arguments, including a _code_ and _callback_. By default, the event code is `1000` if not otherwise specified. 244 | To provide a custom event code with a log message and write that message to the console, the following code could 245 | be used: 246 | 247 | log.info('Something different happened!', 1002, function(){ 248 | console.log('Something different happened!'); 249 | }); 250 | 251 | By default, event logs are all part of the `APPLICATION` scope. However; it is also possible to use the `SYSTEM` log. 252 | To do this, a configuration object must be passed to the new log: 253 | 254 | var EventLogger = require('node-windows').EventLogger; 255 | var log = new EventLogger({ 256 | source: 'My Event Log', 257 | eventLog: 'SYSTEM' 258 | }); 259 | 260 | # Licenses 261 | 262 | winsw and sudowin are the copyrights of their respective owners. winsw 263 | is distributed under an MIT license. sudowin is distributed under a BSD license. 264 | 265 | All other scripts are Copyright (c) Corey Butler under an MIT license. 266 | 267 | (The MIT License) 268 | 269 | Copyright (c) 2013 Corey Butler 270 | 271 | Permission is hereby granted, free of charge, to any person obtaining 272 | a copy of this software and associated documentation files (the 273 | 'Software'), to deal in the Software without restriction, including 274 | without limitation the rights to use, copy, modify, merge, publish, 275 | distribute, sublicense, and/or sell copies of the Software, and to 276 | permit persons to whom the Software is furnished to do so, subject to 277 | the following conditions: 278 | 279 | The above copyright notice and this permission notice shall be 280 | included in all copies or substantial portions of the Software. 281 | 282 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 283 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 284 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 285 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 286 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 287 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 288 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 289 | -------------------------------------------------------------------------------- /docs/guides/nodewindowstroubleshooting/README.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting 2 | 3 | If you are encountering the _invalidinstallation_ event, take a look at the `daemon` 4 | directory that is created during the installation to make sure the `.exe` and `.xml` 5 | files are there. In some circumstances, primarily during _un_installation, it is 6 | possbile for the process to temporarily lock a log file, which prevents Windows 7 | from removing it. In this scenario, simply run the uninstall again. In most cases this 8 | will fix the issue. If not, manually remove the `daemon` directory before running the 9 | installation again. 10 | 11 | ## Node-Windows Tests 12 | 13 | node-windows uses several command line scripts to execute processes and bypass the need 14 | for native modules. Additionally, node-windows attempts to handle account permissions, but 15 | tests will still fail if they are not run with the appropriate account credentials. Therefore, 16 | it is hard to decipher whether an issue stems from a user account, credentials, elevated permmissions, 17 | or the code itself. 18 | 19 | In a _best effort_ fashion, the examples are rudimentary. They have been tested and are known 20 | to work. If you experience problems with the module, it is best to try running the examples first to 21 | verify basic functionality. 22 | 23 | ## Testing Services 24 | 25 | node-windows ships with a service/daemon management tool. It is important to note that installing 26 | and uninstalling processes, which use `winsw.exe`, can take some time to complete. In particular, 27 | there is a problem if you attempt to uninstall a process before it has completed installation. 28 | Windows locks a log file while installing the process, which cannot be removed until the installation 29 | is complete. 30 | 31 | ## Other Issue? 32 | 33 | If you are having other issues, please post them in the [bug tracker](https://github.com/coreybutler/node-windows/issues). 34 | -------------------------------------------------------------------------------- /docs/service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coreybutler/node-windows/54ac1e382f1cf56bc7278672672aba1342c96c01/docs/service.png -------------------------------------------------------------------------------- /example/helloworld.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | 3 | var server = http.createServer(function (req, res) { 4 | res.writeHead(200, {'Content-Type': 'text/plain'}); 5 | //res.end(JSON.stringify(process.env)); 6 | res.end('Hello World\n'); 7 | }); 8 | 9 | server.listen(3000, '127.0.0.1'); 10 | console.log('Server running at http://127.0.0.1:3000/'); 11 | 12 | // Force the process to close after 15 seconds 13 | /*setTimeout(function(){ 14 | process.exit(); 15 | },15000); 16 | */ -------------------------------------------------------------------------------- /example/install.js: -------------------------------------------------------------------------------- 1 | // var Service = require('../lib/node-windows.js').Service; 2 | var Service = require('node-windows').Service; 3 | var dir = require('path').join(process.cwd(), 'helloworld.js') 4 | 5 | // Create a new service object 6 | var svc = new Service({ 7 | name:'Hello World', 8 | description: 'The nodejs.org example web server.', 9 | script: dir, 10 | env:{ 11 | name: "NODE_ENV", 12 | value: "production" 13 | } 14 | }); 15 | 16 | // Listen for the "install" event, which indicates the 17 | // process is available as a service. 18 | svc.on('install',function(){ 19 | svc.start(); 20 | }); 21 | 22 | // Just in case this file is run twice. 23 | svc.on('alreadyinstalled',function(){ 24 | console.log('This service is already installed.'); 25 | }); 26 | 27 | // Listen for the "start" event and let us know when the 28 | // process has actually started working. 29 | svc.on('start',function(){ 30 | console.log(svc.name+' started!\nVisit http://127.0.0.1:3000 to see it in action.'); 31 | }); 32 | 33 | // Install the script as a service. 34 | console.log("Installing to", dir) 35 | svc.install(); 36 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "node-windows": "^1.0.0-beta.7" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /example/uninstall.js: -------------------------------------------------------------------------------- 1 | var Service = require('node-windows').Service; 2 | 3 | // Create a new service object 4 | var svc = new Service({ 5 | name:'Hello World', 6 | script: require('path').join(process.cwd(),'helloworld.js') 7 | }); 8 | 9 | // Listen for the "uninstall" event so we know when it's done. 10 | svc.on('uninstall',function(){ 11 | console.log('Uninstall complete.'); 12 | console.log('The service exists: ',svc.exists); 13 | }); 14 | 15 | // Uninstall the service. 16 | svc.uninstall(); -------------------------------------------------------------------------------- /lib/binaries.js: -------------------------------------------------------------------------------- 1 | var path = require('path'), 2 | bin = path.join(__dirname, '..', 'bin'), 3 | exec = require('child_process').exec; 4 | 5 | var params = function (options, callback) { 6 | callback = callback || function () { }; 7 | options = options || {}; 8 | if (typeof options === 'function') { 9 | callback = options; 10 | options = {}; 11 | } 12 | if (typeof options !== 'object') { 13 | throw 'Invalid options parameter.'; 14 | } 15 | return { options: options, callback: callback }; 16 | } 17 | 18 | module.exports = { 19 | /** 20 | * @method elevate 21 | * @member nodewindows 22 | * Elevate is similar to `sudo` on Linux/Mac. It attempts to elevate the privileges of the 23 | * current user to a local administrator. Using this does not require a password, but it 24 | * does require that the current user have administrative privileges. Without these 25 | * privileges, the command will fail with a `access denied` error. 26 | * 27 | * On systems with UAC enabled, this may prompt the user for permission to proceed: 28 | * 29 | * ![UAC Prompt](http://upload.wikimedia.org/wikipedia/en/5/51/Windows_7_UAC.png) 30 | * 31 | * **Syntax**: 32 | * 33 | * `elevate(cmd[,options,callback])` 34 | * 35 | * @param {String} cmd 36 | * The command to execute with elevated privileges. This can be any string that would be typed at the command line. 37 | * @param {Object} [options] 38 | * Any options that will be passed to `require('child_process').exec(cmd,,callback)`. 39 | * @param {Function} callback 40 | * The callback function passed to `require('child_process').exec(cmd,options,)`. 41 | */ 42 | elevate: function (cmd, options, callback) { 43 | var p = params(options, callback); 44 | exec('"' + path.join(bin, 'elevate', 'elevate.cmd') + '" ' + cmd, p.options, p.callback); 45 | }, 46 | 47 | /** 48 | * @method sudo 49 | * @member nodewindows 50 | * Sudo acts similarly to `sudo` on Linux/Mac. Unlike _elevate_, it requires a password, but it 51 | * will not prompt the user for permission to proceed. Like _elevate_, this 52 | * _still requires administrative privileges_ for the user, otherwise the command will fail. 53 | * The primary difference between this and _elevate()_ is the prompt. 54 | * 55 | * **Syntax**: 56 | * 57 | * `sudo(cmd,password[,options,callback])` 58 | * 59 | * @param {String} cmd 60 | * The command to execute with elevated privileges. This can be any string that would be typed at the command line. 61 | * @param {String} password 62 | * The password of the user 63 | * @param {Object} [options] 64 | * Any options that will be passed to `require('child_process').exec(cmd,,callback)`. 65 | * @param {Function} [callback] 66 | * The callback function passed to `require('child_process').exec(cmd,options,)`. 67 | */ 68 | sudo: function (cmd, password, options, callback) { 69 | password = password || ''; 70 | if (typeof password !== 'string') { 71 | callback = options; 72 | options = password; 73 | password = ''; 74 | } 75 | var p = params(options, callback); 76 | exec(path.join(bin, 'sudowin', 'sudo.exe') + ' ' + (password !== '' ? '-p ' + password : '') + cmd, p.options, p.callback); 77 | } 78 | } -------------------------------------------------------------------------------- /lib/cmd.js: -------------------------------------------------------------------------------- 1 | var exec = require('child_process').exec, 2 | bin = require('./binaries'); 3 | 4 | module.exports = { 5 | 6 | /** 7 | * @method isAdminUser 8 | * @member nodewindows 9 | * This asynchronous command determines whether the current user has administrative privileges. 10 | * It passes a boolean value to the callback, returning `true` if the user is an administrator 11 | * or `false` if it is not. 12 | * @param {Function} callback 13 | * @param {Boolean} callback.isAdmin 14 | * Receives true/false as an argument to the callback. 15 | */ 16 | isAdminUser: function (callback) { 17 | exec('NET SESSION', function (err, so, se) { 18 | if (se.length !== 0) { 19 | bin.elevate('NET SESSION', function (_err, _so, _se) { 20 | callback(_se.length === 0); 21 | }); 22 | } else { 23 | callback(true); 24 | } 25 | }); 26 | }, 27 | 28 | /** 29 | * @method kill 30 | * @member nodewindows 31 | * Kill a specific process 32 | * @param {Number} PID 33 | * Process ID 34 | * @param {Boolean} [force=false] 35 | * Force close the process. 36 | * @param {Function} [callback] 37 | */ 38 | kill: function (pid, force, callback) { 39 | if (!pid) { 40 | throw new Error('PID is required for the kill operation.'); 41 | } 42 | 43 | if (typeof isNaN(pid)) { 44 | throw new Error('PID must be a number.') 45 | } 46 | 47 | callback = callback || function () { }; 48 | if (typeof force == 'function') { 49 | callback = force; 50 | force = false; 51 | } 52 | exec("taskkill /PID " + pid + (force == true ? ' /f' : ''), callback); 53 | }, 54 | 55 | /** 56 | * @method list 57 | * @member nodewindows 58 | * List the processes running on the server. 59 | * @param {Function} callback 60 | * Receives the process object as the only callback argument 61 | * @param {Boolean} [verbose=false] 62 | */ 63 | list: function (callback, verbose) { 64 | verbose = typeof verbose == 'boolean' ? verbose : false; 65 | exec('tasklist /FO CSV' + (verbose == true ? ' /V' : ''), function (err, stdout, stderr) { 66 | var p = stdout.split('\r\n'); 67 | var proc = []; 68 | var head = null; 69 | while (p.length > 1) { 70 | var rec = p.shift(); 71 | rec = rec.replace(/\"\,/gi, '";').replace(/\"|\'/gi, '').split(';'); 72 | if (head == null) { 73 | head = rec; 74 | for (var i = 0; i < head.length; i++) { 75 | head[i] = head[i].replace(/ /gi, ''); 76 | } 77 | } else { 78 | var tmp = {}; 79 | for (var i = 0; i < rec.length; i++) { 80 | tmp[head[i]] = rec[i].replace(/\"|\'/gi, ''); 81 | } 82 | proc.push(tmp); 83 | } 84 | } 85 | callback(proc); 86 | }); 87 | } 88 | 89 | }; -------------------------------------------------------------------------------- /lib/daemon.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class nodewindows.Service 3 | * This utility can be used to manage node.js scripts as Windows services. 4 | * 5 | * **Please note that like all Windows services, creating one requires administrative privileges**. 6 | * 7 | * To create a service with node-windows, prepare a script like: 8 | * 9 | * var Service = require('node-windows').Service; 10 | * 11 | * // Create a new service object 12 | * var svc = new Service({ 13 | * name:'Hello World', 14 | * description: 'The nodejs.org example web server.', 15 | * script: 'C:\\path\\to\\helloworld.js') 16 | * }); 17 | * 18 | * // Listen for the "install" event, which indicates the 19 | * // process is available as a service. 20 | * svc.on('install',function(){ 21 | * svc.start(); 22 | * }); 23 | * 24 | * svc.install(); 25 | * 26 | * The code above creates a new `Service` object, providing a pretty name and description. 27 | * The `script` attribute identifies the Node.js script that should run as a service. Upon running 28 | * this, the script will be visible from the Windows Services utility. 29 | * 30 | * ![Windows Service](https://raw.github.com/coreybutler/node-windows/master/docs/service.png) 31 | */ 32 | var exec = require('child_process').exec, 33 | path = require('path'), 34 | fs = require('fs'), 35 | PermError = 'Permission Denied. Requires administrative privileges.', 36 | wincmd = require('./binaries'), 37 | Logger = require('./eventlog'), 38 | daemonDir = 'daemon', 39 | wrapper = path.resolve(path.join(__dirname, './wrapper.js')); 40 | 41 | // BEGIN SUPER AWFUL HACK TO GET AROUND WINSW.EXE ISSUE! REPLACE ASAP!!!! 42 | // winsw.exe immediately responds with nothing, indicating success, even though 43 | // it continues processing with the "install" method. 44 | var sleep = function (period) { 45 | var st = new Date().getTime(); 46 | while (new Date().getTime() <= st + (period * 1000)) { } 47 | return; 48 | }; 49 | 50 | // The daemon class 51 | var daemon = function (config) { 52 | 53 | /** 54 | * @cfg {Array|Object} [env] 55 | * An optional array or object used to pass environment variables to the node.js script. 56 | * You can do this by setting environment variables in the service config, as shown below: 57 | * 58 | * var svc = new Service({ 59 | * name:'Hello World', 60 | * description: 'The nodejs.org example web server.', 61 | * script: 'C:\\path\\to\\helloworld.js', 62 | * env: { 63 | * name: "NODE_ENV", 64 | * value: "production" 65 | * } 66 | * }); 67 | * 68 | * You can also supply an array to set multiple environment variables: 69 | * 70 | * var svc = new Service({ 71 | * name:'Hello World', 72 | * description: 'The nodejs.org example web server.', 73 | * script: 'C:\\path\\to\\helloworld.js', 74 | * env: [{ 75 | * name: "HOME", 76 | * value: process.env["USERPROFILE"] // Access the user home directory 77 | * },{ 78 | * name: "NODE_ENV", 79 | * value: "production" 80 | * }] 81 | * }); 82 | */ 83 | Object.defineProperties(this, { 84 | _name: { 85 | enumerable: false, 86 | writable: true, 87 | configurable: false, 88 | value: config.name || null 89 | }, 90 | 91 | _eventlog: { 92 | enumerable: false, 93 | writable: true, 94 | configurable: false, 95 | value: null 96 | }, 97 | 98 | _xml: { 99 | enumerable: false, 100 | get: function () { 101 | var wrapperArgs = [ 102 | '--file', this.script, 103 | '--scriptoptions=' + this.scriptOptions, 104 | '--log', this.name + ' ' + 'wrapper', 105 | '--grow', this.grow, 106 | '--wait', this.wait, 107 | '--maxrestarts', this.maxRestarts, 108 | '--abortonerror', (this.abortOnError == true ? 'y' : 'n'), 109 | '--stopparentfirst', this.stopparentfirst, 110 | '--disablewarninglogs', (this.disableWarningLogs==true?'y':'n'), 111 | ]; 112 | 113 | if (this.maxRetries !== null) { 114 | wrapperArgs.push('--maxretries'); 115 | wrapperArgs.push(this.maxRetries); 116 | } 117 | 118 | return require('./winsw').generateXml({ 119 | name: this.name, 120 | id: this._exe, 121 | nodeOptions: this.nodeOptions, 122 | script: wrapper, 123 | scriptOptions: this.scriptOptions, 124 | wrapperArgs: wrapperArgs, 125 | description: this.description, 126 | logpath: this.logpath, 127 | env: config.env, 128 | execPath: this.execPath, 129 | logOnAs: this.logOnAs, 130 | workingdirectory: this.workingdirectory, 131 | stopparentfirst: this.stopparentfirst, 132 | stoptimeout: this.stoptimeout, 133 | logmode: this.logmode, 134 | logging: config.logging, 135 | allowServiceLogon: config.allowServiceLogon, 136 | dependsOn: this.dependsOn, 137 | disableWarningLogs: this.disableWarningLogs 138 | }); 139 | } 140 | }, 141 | 142 | _exe: { 143 | enumerable: false, 144 | get: function () { 145 | return this.id + '.exe'; 146 | } 147 | }, 148 | 149 | /** 150 | * @cfg {Number} [maxRetries=null] 151 | * The maximum number of restart attempts to make before the service is considered non-responsive/faulty. 152 | * Ignored by default. 153 | */ 154 | maxRetries: { 155 | enumerable: true, 156 | writable: false, 157 | configurable: false, 158 | value: config.hasOwnProperty('maxRetries') ? config.maxRetries : null 159 | }, 160 | 161 | /** 162 | * @cfg {Boolean} [stopparentfirst=false] 163 | * Allow the service to shutdown cleanly. 164 | */ 165 | stopparentfirst: { 166 | enumerable: true, 167 | writable: false, 168 | configurable: false, 169 | value: config.stopparentfirst 170 | }, 171 | 172 | /** 173 | * @cfg {Number} [stoptimeout=30] 174 | * How long to wait in seconds before force killing the application. 175 | * This only takes effect when stopparentfirst is enabled. 176 | */ 177 | stoptimeout: { 178 | enumerable: true, 179 | writable: false, 180 | configurable: false, 181 | value: config.hasOwnProperty('stoptimeout') ? config.stoptimeout : 30 182 | }, 183 | 184 | /** 185 | * @cfg {string} [nodeOptions='--harmony'] 186 | * Options to be passed to the node process. 187 | */ 188 | nodeOptions: { 189 | enumerable: true, 190 | writable: false, 191 | configurable: false, 192 | value: config.nodeOptions || '--harmony' 193 | }, 194 | 195 | /** 196 | * @cfg {string} [scriptOptions=''] 197 | * Options to be passed to the script. 198 | */ 199 | scriptOptions: { 200 | enumerable: true, 201 | writable: false, 202 | configurable: false, 203 | value: config.scriptOptions || '' 204 | }, 205 | 206 | /** 207 | * @cfg {Number} [maxRestarts=3] 208 | * The maximum number of restarts within a 60 second period before haulting the process. 209 | * This cannot be _disabled_, but it can be rendered ineffective by setting a value of `0`. 210 | */ 211 | maxRestarts: { 212 | enumerable: true, 213 | writable: false, 214 | configurable: false, 215 | value: config.hasOwnProperty('maxRestarts') ? config.maxRestarts : 3 216 | }, 217 | 218 | /** 219 | * @cfg {Boolean} [abortOnError=false] 220 | * Setting this to `true` will force the process to exit if it encounters an error that stops the node.js script from running. 221 | * This does not mean the process will stop if the script throws an error. It will only abort if the 222 | * script throws an error causing the process to exit (i.e. `process.exit(1)`). 223 | */ 224 | abortOnError: { 225 | enumerable: true, 226 | writable: false, 227 | configurable: false, 228 | value: config.abortOnError instanceof Boolean ? config.abortOnError : false 229 | }, 230 | 231 | /** 232 | * @cfg {Number} [wait=1] 233 | * The initial number of seconds to wait before attempting a restart (after the script stops). 234 | */ 235 | wait: { 236 | enumerable: true, 237 | writable: false, 238 | configurable: false, 239 | value: !isNaN(config.wait) ? config.wait : 1 240 | }, 241 | 242 | /** 243 | * @cfg {Number} [grow=.25] 244 | * A number between 0-1 representing the percentage growth rate for the #wait interval. 245 | * Setting this to anything other than `0` allows the process to increase it's wait period 246 | * on every restart attempt. If a process dies fatally, this will prevent the server from 247 | * restarting the process too rapidly (and too strenuously). 248 | */ 249 | grow: { 250 | enumerable: true, 251 | writable: false, 252 | configurable: false, 253 | value: !isNaN(config.grow) ? config.grow : .25 254 | }, 255 | 256 | _directory: { 257 | enumerable: false, 258 | writable: true, 259 | configurable: false, 260 | value: config.script !== null ? path.dirname(config.script) : null 261 | }, 262 | 263 | /** 264 | * Resolves the directory where the script is saved. 265 | */ 266 | directory: { 267 | enumerable: false, 268 | writable: false, 269 | configurable: false, 270 | value: function (dir) { 271 | if (this.script == null || this.name == null) { 272 | throw Error('Script and Name are required but were not provided.'); 273 | } 274 | if (dir) { 275 | this._directory = path.resolve(dir); 276 | } 277 | return path.resolve(path.join(this._directory, daemonDir)); 278 | } 279 | }, 280 | 281 | /** 282 | * @property {String} root 283 | * The root directory where the process files are stored. 284 | */ 285 | root: { 286 | enumerable: true, 287 | get: function () { return this.directory(); } 288 | }, 289 | 290 | // Generates the primary logging utility 291 | log: { 292 | enumerable: false, 293 | get: function () { 294 | if (this._eventlog !== null) 295 | return this._eventlog; 296 | if (this.name == null) 297 | throw 'No name was specified for the service'; 298 | this._eventlog = new Logger(this.name + ' Monitor'); 299 | return this._eventlog; 300 | } 301 | }, 302 | 303 | // The path where log files should be stored 304 | logpath: { 305 | enumerable: true, 306 | writable: false, 307 | configurable: false, 308 | value: config.logpath || null 309 | }, 310 | 311 | // The log mode. Options are the same as winsw#generateXml 312 | logmode: { 313 | enumerable: true, 314 | writable: false, 315 | configurable: false, 316 | value: config.logmode || 'rotate' 317 | }, 318 | 319 | // The name of the process 320 | name: { 321 | enumerable: false, 322 | get: function () { return this._name; }, 323 | set: function (value) { this._name = value; } 324 | }, 325 | 326 | // The ID for the process 327 | id: { 328 | enumerable: true, 329 | get: function () { 330 | return this.name.replace(/[^\w]/gi, '').toLowerCase(); 331 | } 332 | }, 333 | 334 | // Description of the service 335 | description: { 336 | enumerable: true, 337 | writable: false, 338 | configurable: false, 339 | value: config.description || '' 340 | }, 341 | 342 | /** 343 | * @property {Object} [user] 344 | * If you need to specify a specific user or particular credentials to manage a service, the following 345 | * attributes may be helpful. 346 | * 347 | * The `user` attribute is an object with three keys: `domain`,`account`, and `password`. 348 | * This can be used to identify which user the service library should use to perform system commands. 349 | * By default, the domain is set to the local computer name, but it can be overridden with an Active Directory 350 | * or LDAP domain. For example: 351 | * 352 | * **app.js** 353 | * 354 | * var Service = require('node-windows').Service; 355 | * 356 | * // Create a new service object 357 | * var svc = new Service({ 358 | * name:'Hello World', 359 | * script: require('path').join(__dirname,'helloworld.js') 360 | * }); 361 | * 362 | * svc.user.domain = 'mydomain.local'; 363 | * svc.user.account = 'username'; 364 | * svc.user.password = 'password'; 365 | * ... 366 | * 367 | * Both the account and password must be explicitly defined if you want the service module to 368 | * run commands as a specific user. By default, it will run using the user account that launched 369 | * the process (i.e. who launched `node app.js`). 370 | */ 371 | user: { 372 | enumerable: false, 373 | writable: true, 374 | configurable: false, 375 | value: { 376 | account: null, 377 | password: null, 378 | domain: process.env.COMPUTERNAME 379 | } 380 | }, 381 | /** 382 | * @property {Object} [logOnAs] 383 | * If you need to specify a specific user or particular credentials for the service log on as once installed, the following 384 | * attributes may be helpful. 385 | * 386 | * The `logOnAs` attribute is an object with four keys: `domain`,`account`, `password`, and `mungeCredentialsAfterInstall`. 387 | * This can be used to identify which user the service should run as once installed. 388 | * 389 | * If no account and password is specified, the logOnAs property is not used and the service will run as the "Local System" account. 390 | * If account and password is specified, but domain is not specified then the domain is set to the local computer name, but it can be overridden with an Active Directory 391 | * or LDAP domain. For example: 392 | * 393 | * **app.js** 394 | * 395 | * var Service = require('node-windows').Service; 396 | * 397 | * // Create a new service object 398 | * var svc = new Service({ 399 | * name:'Hello World', 400 | * script: require('path').join(__dirname,'helloworld.js') 401 | * }); 402 | * 403 | * svc.logOnAs.domain = 'mydomain.local'; 404 | * svc.logOnAs.account = 'username'; 405 | * svc.logOnAs.password = 'password'; 406 | * ... 407 | * 408 | * Both the account and password must be explicitly defined if you want the service to log on as that user, 409 | * otherwise the Local System account will be used. 410 | */ 411 | logOnAs: { 412 | enumerable: false, 413 | writable: true, 414 | configurable: false, 415 | value: { 416 | account: null, 417 | password: null, 418 | domain: process.env.COMPUTERNAME, 419 | mungeCredentialsAfterInstall: true 420 | } 421 | }, 422 | 423 | /** 424 | * @property {String} [workingdirectory] 425 | * The full path to the working directory that the service process 426 | * should launch from. If this is omitted, it will default to the 427 | * current processes working directory. 428 | */ 429 | workingdirectory: { 430 | enumerable: false, 431 | writable: true, 432 | configurable: false, 433 | value: config.hasOwnProperty('workingDirectory') ? config.workingDirectory : process.cwd() 434 | }, 435 | 436 | // Optionally provide a sudo password. 437 | sudo: { 438 | enumerable: false, 439 | writable: true, 440 | configurable: false, 441 | value: { 442 | password: null 443 | } 444 | }, 445 | 446 | /** 447 | * @cfg {String} script 448 | * The absolute path of the script to launch as a service. 449 | * @required 450 | */ 451 | script: { 452 | enumerable: true, 453 | writable: true, 454 | configurable: false, 455 | value: config.script !== undefined ? require('path').resolve(config.script) : null 456 | }, 457 | 458 | /** 459 | * @cfg {String} execPath 460 | * The absolute path to the executable that will launch the script. 461 | * If omitted process.execPath is used. 462 | */ 463 | execPath: { 464 | enumerable: true, 465 | writable: true, 466 | configurable: false, 467 | value: config.execPath !== undefined ? require('path').resolve(config.execPath) : null 468 | }, 469 | 470 | /** 471 | * @cfg {Boolean} [disableWarningLogs=false] 472 | * Setting this to `true` will prevent warning logs from being sent to the event log. 473 | * This is useful if you want to suppress warnings to clear up the event logs. 474 | */ 475 | disableWarningLogs: { 476 | enumerable: true, 477 | writable: false, 478 | configurable: false, 479 | value: typeof(config.disableWarningLogs) == 'boolean' ? config.disableWarningLogs : false 480 | }, 481 | 482 | /** 483 | * @property {Array} [dependsOn] 484 | * List of service names on which this service will be dependant on 485 | */ 486 | dependsOn: { 487 | enumerable: false, 488 | writable: true, 489 | configurable: false, 490 | value: config.hasOwnProperty('dependsOn') ? config.dependsOn : [] 491 | }, 492 | 493 | /** 494 | * @method install 495 | * Install the script as a process. 496 | * @param {String} [dir=root of script] 497 | * The directory where the process files will be saved. Defaults to #script path. 498 | * @param {Function} [callback] 499 | * The callback to fire when the installation completes. 500 | */ 501 | /** 502 | * @event install 503 | * Fired when the installation process is complete. 504 | */ 505 | /** 506 | * @event alreadyinstalled 507 | * Fired if the script is already known to be a service. 508 | */ 509 | /** 510 | * @event invalidinstallation 511 | * Fired if an installation is detected but missing required files. 512 | */ 513 | /** 514 | * @event error 515 | * Fired in some instances when an error occurs. 516 | */ 517 | install: { 518 | enumerable: true, 519 | writable: false, 520 | configurable: false, 521 | value: function (dir) { 522 | if (this.script == null || this.name == null) { 523 | throw Error('Script and Name are required but were not provided.'); 524 | } 525 | 526 | if (this.exists) { 527 | var missing = false; 528 | if (!fs.existsSync(path.join(this.root, this._exe))) { 529 | this.log.warn('The main executable is missing or cannot be found (' + path.join(this.root, this._exe) + ')'); 530 | missing = true; 531 | } 532 | if (!fs.existsSync(path.join(this.root, this.id + '.xml'))) { 533 | this.log.warn('The primary configuration file is missing or cannot be found (' + path.join(this.root, this.id + '.xml') + ')'); 534 | missing = true; 535 | } 536 | if (missing.length > 0) { 537 | this.emit('invalidinstallation'); 538 | return; 539 | } 540 | this.log.warn('The process cannot be installed again because it already exists.'); 541 | this.emit('alreadyinstalled'); 542 | return; 543 | } 544 | 545 | var winsw = require('./winsw'), me = this; 546 | 547 | if (typeof dir === 'function') { 548 | callback = dir; 549 | dir = null; 550 | } 551 | dir = this.directory(dir); 552 | 553 | // If the output directory does not exist, create it. 554 | fs.exists(dir, function (exists) { 555 | if (!exists) { 556 | fs.mkdirSync(dir); 557 | } 558 | // Write the configuration file 559 | fs.writeFile(path.resolve(dir, me.id + '.xml'), me._xml, function () { 560 | // Write the exe file 561 | winsw.createExe(me.id, dir, function () { 562 | me.execute('"' + path.resolve(dir, me._exe) + '" install', function () { 563 | sleep(2); 564 | me.emit('install'); 565 | }); 566 | }); 567 | }); 568 | }); 569 | } 570 | }, 571 | 572 | /** 573 | * @method uninstall 574 | * Uninstall the service. 575 | * @param {Number} [waitTime] 576 | * Seconds to wait until winsw.exe finish processing the uninstall command. 577 | * 578 | * var Service = require('node-windows').Service; 579 | * 580 | * // Create a new service object 581 | * var svc = new Service({ 582 | * name:'Hello World', 583 | * script: require('path').join(__dirname,'helloworld.js') 584 | * }); 585 | * 586 | * // Listen for the "uninstall" event so we know when it's done. 587 | * svc.on('uninstall',function(){ 588 | * console.log('Uninstall complete.'); 589 | * console.log('The service exists: ',svc.exists); 590 | * }); 591 | * 592 | * // Uninstall the service. 593 | * svc.uninstall(); 594 | */ 595 | /** 596 | * @event uninstall 597 | * Fired when the uninstall is complete. 598 | */ 599 | /** 600 | * @event alreadyuninstalled 601 | * Fired if the script is unknown as a service. 602 | */ 603 | uninstall: { 604 | enumerable: true, 605 | writable: false, 606 | value: function (waitTime) { 607 | var me = this; 608 | waitTime = waitTime || 2; // The same wait time as in the install method 609 | 610 | if (!this.exists) { 611 | console.log('Uninstall was skipped because process does not exist or could not be found.'); 612 | this.emit('alreadyuninstalled'); 613 | return; 614 | } 615 | 616 | var uninstaller = function () { 617 | // Uninstall the process 618 | me.execute('"' + path.resolve(me.root, me._exe) + '" uninstall', function (error, stdout, stderr) { 619 | if (error) { 620 | me.checkPermError(error); 621 | } else if (stderr.trim().length > 0) { 622 | console.log('Error: ', stderr); 623 | } else { 624 | sleep(waitTime); // Wait for uninstall to fully finish 625 | 626 | var rm = function (file) { 627 | if (fs.existsSync(path.join(me.root, file))) { 628 | fs.unlinkSync(path.join(me.root, file)); 629 | } 630 | }; 631 | 632 | // Remove the daemon files individually to prevent security warnings. 633 | rm(me.id + '.xml'); 634 | 635 | // Remove known wrappers 636 | rm(me.id + '.wrapper.log'); 637 | rm(me.id + '.out.log'); 638 | rm(me.id + '.err.log'); 639 | 640 | // Remove the executable and executable .NET runtime config file 641 | rm(me.id + '.exe'); 642 | rm(me.id + '.exe.config'); 643 | 644 | // Remove all other files 645 | var _other_files = fs.readdirSync(me.root).filter(function (file) { 646 | const regex = /^.+\.((wrapper|out|err)\.log)|(exe|xml)$/g 647 | return !regex.exec(file) 648 | }) 649 | _other_files.forEach(function (f) { 650 | rm(f) 651 | }); 652 | 653 | // Remove the dir iff it's empty 654 | if (fs.readdirSync(me.root).length === 0) { 655 | if (me.root !== path.dirname(me.script)) { 656 | fs.rmdir(me.root, function () { 657 | sleep(1); 658 | me.emit('uninstall'); 659 | }); 660 | } else { 661 | me.emit('uninstall'); 662 | } 663 | } 664 | else { 665 | me.emit('uninstall'); 666 | } 667 | } 668 | }); 669 | }; 670 | 671 | this.once('stop', uninstaller); 672 | this.once('alreadystopped', uninstaller); 673 | this.stop(); 674 | } 675 | }, 676 | 677 | /** 678 | * @method start 679 | * Start an existing method. 680 | */ 681 | /** 682 | * @event start 683 | * Fired when the event has started. 684 | */ 685 | start: { 686 | enumerable: true, 687 | writable: false, 688 | configurable: false, 689 | value: function () { 690 | var me = this; 691 | 692 | if (this.name == null) { 693 | throw "A name for the service is required."; 694 | } 695 | 696 | if (!this.exists) { 697 | throw Error('The service "' + this.name + '" does not exist or could not be found.'); 698 | } 699 | 700 | this.execute('NET START "' + me._exe + '"', function (err, stdout, stderr) { 701 | if (err) { 702 | if (err.code == 2) { 703 | if (err.message.indexOf('already been started') >= 0 && err.message.indexOf('service name is invalid') < 0) { 704 | me.log.warn('An attempt to start the service failed because the service is already running. The process should be stopped before starting, or the restart method should be used.'); 705 | me.emit('error', err); 706 | return; 707 | } else if (err.message.indexOf('service name is invalid') < 0) { 708 | me.checkPermError(err); 709 | console.log(err); 710 | me.emit('error', err); 711 | return; 712 | } 713 | } else { 714 | me.log.error(err.toString()); 715 | } 716 | } else { 717 | me.emit('start'); 718 | } 719 | }) 720 | } 721 | }, 722 | 723 | /** 724 | * @method stop 725 | * Stop the service. 726 | */ 727 | /** 728 | * @event stop 729 | * Fired when the service is stopped. 730 | */ 731 | stop: { 732 | enumerable: true, 733 | writable: false, 734 | value: function () { 735 | var me = this; 736 | 737 | me.execute('NET STOP "' + me._exe + '"', function (err, stdout, stderr) { 738 | if (err) { 739 | if (err.code == 2) { 740 | me.log.warn('An attempt to stop the service failed because the service is/was not running.'); 741 | me.emit('alreadystopped'); 742 | } else { 743 | me.checkPermError(err); 744 | } 745 | } else { 746 | me.log.info(stdout); 747 | //sleep(10); // Wait for stop to complete. 748 | me.emit('stop'); 749 | } 750 | }); 751 | } 752 | }, 753 | 754 | /** 755 | * @method restart 756 | * Restart an existing service 757 | */ 758 | restart: { 759 | enumerable: true, 760 | writable: false, 761 | value: function (callback) { 762 | var me = this; 763 | this.once('stop', me.start); 764 | this.stop(); 765 | } 766 | }, 767 | 768 | /** 769 | * @property {Boolean} exists 770 | * Determine whether the service exists. 771 | */ 772 | exists: { 773 | enumerable: true, 774 | get: function () { 775 | if (this.script == null || this.name == null) { 776 | throw Error('Script and name are required but were not specified.'); 777 | } 778 | return fs.existsSync(path.join(this.directory(), this.id + '.exe')) && fs.existsSync(path.join(this.directory(), this.id + '.xml')); 779 | } 780 | }, 781 | 782 | // Execute commands with elevated privileges. 783 | execute: { 784 | enumerable: false, 785 | writable: false, 786 | configurable: false, 787 | value: function (cmd, options, callback) { 788 | var me = this; 789 | callback = callback || function () { }; 790 | options = options || {}; 791 | 792 | wincmd.isAdminUser(function (isAdmin) { 793 | if (isAdmin) { 794 | if (typeof options === 'function') { 795 | callback = options; 796 | options = {}; 797 | } 798 | if (me.user.account !== null && me.user.password !== null) { 799 | _cmd = "runas /profile /user:" + me.user.domain + "\\" + me.user.account + " " + cmd; 800 | exec(cmd, options, callback); 801 | } else if (me.sudo.password !== null) { 802 | // If the user is not an admin, but a sudo password is provided for admin, 803 | // attempt to launch using sudo. 804 | wincmd.sudo(cmd, me.sudo.password || '', options, callback); 805 | } else { 806 | wincmd.elevate(cmd, options, callback) 807 | } 808 | } else { 809 | console.log(PermError); 810 | throw PermError; 811 | } 812 | }); 813 | } 814 | }, 815 | 816 | // Check for permission errors 817 | checkPermError: { 818 | enumerable: false, 819 | writable: false, 820 | configurable: false, 821 | value: function (error) { 822 | if (error.message.indexOf('Administrator access') >= 0 || error.message.indexOf('Access is denied') >= 0) { 823 | try { this.log.error(PermError); } catch (e) { console.log(PermError); } 824 | } else { 825 | try { this.log.error(error.toString()); } catch (e) { console.log(error.toString()); } 826 | } 827 | process.exit(1); 828 | } 829 | } 830 | }); 831 | }; 832 | 833 | var util = require('util'), 834 | EventEmitter = require('events').EventEmitter; 835 | 836 | // Inherit Events 837 | util.inherits(daemon, EventEmitter); 838 | 839 | // Export functionality for the module. 840 | module.exports = daemon; -------------------------------------------------------------------------------- /lib/eventlog.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class nodewindows.EventLogger 3 | * @since 0.1.0 4 | * Use a _non-C++_ based event logging utility with your Windows code. 5 | * This utility can write to the event log, making your logs visible from the Event Viewer. 6 | * 7 | * To create a logger: 8 | * 9 | * var EventLogger = require('node-windows').EventLogger; 10 | * 11 | * var log = new EventLogger('Hello World'); 12 | * 13 | * log.info('Basic information.'); 14 | * log.warn('Watch out!'); 15 | * log.error('Something went wrong.'); 16 | * 17 | * Looks similar to: 18 | * 19 | * ![Event Logging in node-windows](https://raw.github.com/coreybutler/node-windows/master/docs/eventlog.png) 20 | * 21 | * Some lesser-used options are also available through node-windows event logging. 22 | * 23 | * log.auditSuccess('AUser Login Success'); 24 | * log.auditFailure('AUser Login Failure'); 25 | * 26 | * Each log type (info, warn, error, auditSuccess, and auditFailure) method optionally accepts two additional 27 | * arguments, including a _code_ and _callback_. By default, the event code is `1000` if not otherwise specified. 28 | * To provide a custom event code with a log message and write that message to the console, the following code could 29 | * be used: 30 | * 31 | * log.info('Something different happened!', 1002, function(){ 32 | * console.log('Something different happened!'); 33 | * }); 34 | * 35 | * By default, event logs are all part of the `APPLICATION` scope. However; it is also possible to use the `SYSTEM` log. 36 | * To do this, a configuration object must be passed to the new log: 37 | * 38 | * var EventLogger = require('node-windows').EventLogger; 39 | * var log = new EventLogger({ 40 | * source: 'My Event Log', 41 | * eventLog: 'SYSTEM' 42 | * }); 43 | */ 44 | var wincmd = require('./binaries'), 45 | exec = require("child_process").exec, 46 | eventlogs = ['APPLICATION', 'SYSTEM'], 47 | validtypes = ['ERROR', 'WARNING', 'INFORMATION', 'SUCCESSAUDIT', 'FAILUREAUDIT']; 48 | 49 | // Write a message to the log. This will create the log if it doesn't exist. 50 | var write = function (log, src, type, msg, id, callback) { 51 | var cmd; 52 | 53 | if (msg == null) { return }; 54 | if (msg.trim().length == 0) { return }; 55 | 56 | msg = msg.replace(/\r\n|\n\r|\r|\n/g, "\f") 57 | 58 | log = log || 'APPLICATION'; 59 | log = eventlogs.indexOf(log.toUpperCase()) >= 0 ? log : 'APPLICATION'; 60 | type = (type || 'INFORMATION').trim().toUpperCase(); 61 | type = (validtypes.indexOf(type.trim().toUpperCase()) >= 0 ? type : 'INFORMATION').trim().toUpperCase(); 62 | id = typeof id == 'number' ? (id > 0 ? id : 1000) : 1000; 63 | src = (src || 'Unknown Application').trim(); 64 | 65 | cmd = "eventcreate /L " + log + " /T " + type + " /SO \"" + src + "\" /D \"" + msg + "\" /ID " + id; 66 | 67 | exec(cmd, function (err) { 68 | if (err && err.message.indexOf("Access is Denied")) { 69 | wincmd.elevate(cmd, callback); 70 | } else if (callback) { 71 | callback(err); 72 | } 73 | }); 74 | }; 75 | 76 | // Basic functionality 77 | var logger = function (config) { 78 | 79 | config = config || {}; 80 | 81 | if (typeof config == 'string') { 82 | config = { 83 | source: config 84 | }; 85 | } 86 | 87 | // Common attributes 88 | Object.defineProperties(this, { 89 | 90 | /** 91 | * @cfg {String} [name=Node.js] 92 | * The source of the log information. This is commonly the title of an application 93 | * or the Node.js script name (i.e. MyApp). 94 | */ 95 | source: { 96 | enumerable: true, 97 | writable: true, 98 | configurable: false, 99 | value: config.source || 'Node.js' 100 | }, 101 | 102 | _logname: { 103 | enumerable: false, 104 | writable: true, 105 | configurable: false, 106 | value: config.eventLog || 'APPLICATION' 107 | }, 108 | 109 | /** 110 | * @cfg {String} [eventLog=APPLICATION] 111 | * The event log where messages should be written. This is either `APPLICATION` or `SYSTEM`. 112 | */ 113 | eventLog: { 114 | enumerable: true, 115 | get: function () { 116 | return this._logname.toUpperCase(); 117 | }, 118 | set: function (value) { 119 | if (value) { 120 | this._logname = eventlogs.indexOf(value.toUpperCase()) >= 0 ? value.toUpperCase() : 'APPLICATION'; 121 | } 122 | } 123 | }, 124 | 125 | /** 126 | * @method info 127 | * Log an informational message. 128 | * @param {String} message 129 | * The content of the log message. 130 | * @param {Number} [code=1000] 131 | * The event code to assign to the message. 132 | * @param {Function} [callback] 133 | * An optional callback to run when the message is logged. 134 | */ 135 | info: { 136 | enumerable: true, 137 | writable: true, 138 | configurable: false, 139 | value: function (message, code, callback) { 140 | write(this.eventLog, this.source, 'INFORMATION', message, code, callback); 141 | } 142 | }, 143 | 144 | information: { 145 | enumerable: false, 146 | get: function () { 147 | return this.info; 148 | } 149 | }, 150 | 151 | /** 152 | * @method error 153 | * Log an error message. 154 | * @param {String} message 155 | * The content of the log message. 156 | * @param {Number} [code=1000] 157 | * The event code to assign to the message. 158 | * @param {Function} [callback] 159 | * An optional callback to run when the message is logged. 160 | */ 161 | error: { 162 | enumerable: true, 163 | writable: true, 164 | configurable: false, 165 | value: function (message, code, callback) { 166 | write(this.eventLog, this.source, 'ERROR', message, code, callback); 167 | } 168 | }, 169 | 170 | /** 171 | * @method warn 172 | * Log a warning message. 173 | * @param {String} message 174 | * The content of the log message. 175 | * @param {Number} [code=1000] 176 | * The event code to assign to the message. 177 | * @param {Function} [callback] 178 | * An optional callback to run when the message is logged. 179 | */ 180 | warn: { 181 | enumerable: true, 182 | writable: true, 183 | configurable: false, 184 | value: function (message, code, callback) { 185 | if (config.disablewarninglogs === 'y') { 186 | return; 187 | } 188 | write(this.eventLog, this.source, 'WARNING', message, code, callback); 189 | } 190 | }, 191 | 192 | warning: { 193 | enumerable: false, 194 | get: function () { return this.warn; } 195 | }, 196 | 197 | /** 198 | * @method auditSuccess 199 | * Log an audit success message. 200 | * @param {String} message 201 | * The content of the log message. 202 | * @param {Number} [code=1000] 203 | * The event code to assign to the message. 204 | * @param {Function} [callback] 205 | * An optional callback to run when the message is logged. 206 | */ 207 | auditSuccess: { 208 | enumerable: true, 209 | writable: true, 210 | configurable: false, 211 | value: function (message, code, callback) { 212 | write(this.eventLog, this.source, 'SUCCESSAUDIT', message, code, callback); 213 | } 214 | }, 215 | 216 | /** 217 | * @method auditFailure 218 | * Log an audit failure message. 219 | * @param {String} message 220 | * The content of the log message. 221 | * @param {Number} [code=1000] 222 | * The event code to assign to the message. 223 | * @param {Function} [callback] 224 | * An optional callback to run when the message is logged. 225 | */ 226 | auditFailure: { 227 | enumerable: true, 228 | writable: true, 229 | configurable: false, 230 | value: function (message, code, callback) { 231 | write(this.eventLog, this.source, 'FAILUREAUDIT', message, code, callback); 232 | } 233 | } 234 | }); 235 | }; 236 | 237 | module.exports = logger; -------------------------------------------------------------------------------- /lib/node-windows.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class nodewindows 3 | * This is a standalone module, originally designed for internal use in [NGN](http://github.com/thinkfirst/NGN). 4 | * However; it is capable of providing the same features for Node.JS scripts 5 | * independently of NGN. 6 | * 7 | * ### Getting node-windows 8 | * 9 | * `npm install -g node-windows` 10 | * 11 | * ### Using node-windows 12 | * 13 | * `var nw = require('node-windows');` 14 | * 15 | * @singleton 16 | * @author Corey Butler 17 | */ 18 | if (require('os').platform().indexOf('win32') < 0) { 19 | throw 'node-windows is only supported on Windows.'; 20 | } 21 | 22 | // Add binary invokers 23 | module.exports = require('./binaries'); 24 | 25 | // Add command line shortcuts 26 | var commands = require('./cmd'); 27 | for (var item in commands) { 28 | module.exports[item] = commands[item]; 29 | } 30 | 31 | // Add daemon management capabilities 32 | module.exports.Service = require('./daemon'); 33 | module.exports.EventLogger = require('./eventlog'); -------------------------------------------------------------------------------- /lib/winsw.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | /** 4 | * @method generateXml 5 | * Generate the XML for the winsw configuration file. 6 | * @param {Object} config 7 | * The config object must have the following attributes: 8 | * 9 | * - *id* This is is how the service is identified. Alphanumeric, no spaces. 10 | * - *name* The descriptive name of the service. 11 | * - *script* The absolute path of the node.js server script. i.e. in this case 12 | * it's the wrapper script, not the user's server script. 13 | * 14 | * Optional attributes include 15 | * 16 | * - *description* The description that shows up in the service manager. 17 | * - *nodeOptions* Array or space separated string of node options (e.g. '--harmony') 18 | * - *wrapperArgs* additional arguments to pass to wrapper script to control restarts, etc. 19 | * - *logmode* Valid values include `rotate` (default), `reset` (clear log), `roll` (move to .old), and `append`. 20 | * - *logging* Supersedes *logmode*. Object of form `{mode: 'append'}`, 21 | * `{mode: 'reset'}`, `{mode: 'none'}`, `{mode: 'roll-by-time', pattern: 'yyyMMdd'}`, 22 | * or `{mode: 'roll-by-size', sizeThreshold: 10240, keepFiles: 8}` (all attributes optional, 23 | * example shows defaults). See [winsw docs](https://github.com/kohsuke/winsw/tree/winsw-1.17#logging). 24 | * - *logpath* The absolute path to the directory where logs should be stored. Defaults to the current directory. 25 | * - *dependencies* A comma delimited list or array of process dependencies. 26 | * - *env* A key/value object or array of key/value objects containing 27 | * environment variables to pass to the process. The object might look like `{name:'HOME',value:'c:\Windows'}`. 28 | * - *logOnAs* A key/value object that contains the service logon credentials. 29 | * The object might look like `{account:'user', password:'pwd', domain:'MYDOMAIN'} 30 | * If this is not included or does not have all 3 members set then it is not used. 31 | * - *workingdirectory* optional working directory that service should run in. 32 | * If this is not included, the current working directory of the install process 33 | * is used. 34 | */ 35 | generateXml: function(config) 36 | { 37 | var xml; 38 | 39 | // add multiple "tag" items to the xml 40 | // if input is an array, add each element of the array, if it's a string, 41 | // split it around splitter and add each one 42 | // if input is null or undefined do nothing 43 | function multi(tag, input, splitter) 44 | { 45 | // do nothing if no input 46 | if (input===undefined || input===null) { 47 | return; 48 | } 49 | 50 | if (!(input instanceof Array)) { 51 | input = input.split(splitter||','); 52 | } 53 | input.forEach(function(val) { 54 | var ele={}; 55 | ele[tag]=String(val).trim(); 56 | xml.push(ele); 57 | }); 58 | } 59 | 60 | // Make sure required configuration items are present 61 | if (!config || !config.id || !config.name || !config.script) 62 | { 63 | throw "WINSW must be configured with a minimum of id, name and script"; 64 | } 65 | 66 | // create json template of xml 67 | // only the portion of the xml inside the top level 'service' tag 68 | xml = [ 69 | {id: config.id}, 70 | {name: config.name}, 71 | {description: config.description||''}, 72 | {executable: config.execPath || process.execPath} 73 | ]; 74 | 75 | multi('argument',config.nodeOptions, ' '); 76 | xml.push({argument:config.script.trim()}); 77 | console.log({loc: 'winsw.js ~line 77', xml, config}) 78 | multi('argument',config.wrapperArgs,' '); 79 | 80 | // Optionally add logging values, defaulting to logmode 81 | if (config.logging) { 82 | var logcontent = [{_attr: {mode: (config.logging.mode || 'append')}}]; 83 | if (config.logging.mode === 'roll-by-time') { 84 | logcontent.push({pattern: (config.logging.pattern || 'yyyMMdd')}); 85 | } 86 | if (config.logging.mode === 'roll-by-size') { 87 | logcontent.push({sizeThreshold: (config.logging.sizeThreshold || 10240)}); 88 | logcontent.push({keepFiles: (config.logging.keepFiles || 8)}); 89 | } 90 | xml.push({log: logcontent}); 91 | } 92 | else { 93 | xml.push({logmode: config.logmode||'rotate'}); 94 | } 95 | 96 | // Optionally add log path 97 | if (config.logpath) { 98 | xml.push({logpath : config.logpath}); 99 | } 100 | 101 | // Optionally add stopparentprocessfirst 102 | if (config.stopparentfirst) { 103 | xml.push({stopparentprocessfirst: config.stopparentfirst}); 104 | } 105 | 106 | // Optionally set the stoptimeout 107 | if (config.stoptimeout) { 108 | xml.push({stoptimeout: config.stoptimeout + 'sec'}); 109 | } 110 | 111 | // Optionally add service dependencies 112 | multi('depend',config.dependencies); 113 | 114 | // Optionally add environment values 115 | if (config.env) { 116 | config.env = (config.env instanceof Array == true) ? 117 | config.env : [config.env]; 118 | config.env.forEach(function(env){ 119 | xml.push({env: {_attr: {name:env.name, value:env.value}}}); 120 | }); 121 | } 122 | 123 | // optionally set the service logon credentials 124 | if (config.logOnAs) { 125 | var serviceaccount = [ 126 | { domain: config.logOnAs.domain || 'NT AUTHORITY' }, 127 | { user: config.logOnAs.account || 'LocalSystem' }, 128 | { password: config.logOnAs.password || '' } 129 | ] 130 | 131 | if (config.allowServiceLogon) { 132 | serviceaccount.push({ allowservicelogon: 'true' }) 133 | } 134 | 135 | xml.push({ 136 | serviceaccount: serviceaccount 137 | }); 138 | } 139 | 140 | if(config.dependsOn.length > 0) { 141 | for (var i = 0; i < config.dependsOn.length; i++) { 142 | xml.push({ depend: config.dependsOn[i] }); 143 | } 144 | } 145 | 146 | // if no working directory specified, use current working directory 147 | // that this process was launched with 148 | xml.push({workingdirectory: config.workingdirectory || process.cwd()}); 149 | 150 | // indent resultant xml with tabs, and use windows newlines for extra readability 151 | return require('xml')({service:xml}, {indent: '\t'}).replace(/\n/g,'\r\n'); 152 | }, 153 | 154 | /** 155 | * Copy install version of winsw.exe to specific renamed version according to 156 | * the service id. Also copy .exe.config file that allows it to run under 157 | * .NET 4+ runtime on newer versions of windows. 158 | * (see https://github.com/kohsuke/winsw#net-runtime-40) 159 | * 160 | * @method createExe 161 | * Create the executable 162 | * @param {String} name 163 | * The alphanumeric string (spaces are stripped) of the `.exe` file. For example, `My App` generates `myapp.exe` 164 | * @param {String} [dir=cwd] 165 | * The output directory where the executable will be saved. 166 | * @param {Function} [callback] 167 | * The callback to fire upon completion. 168 | */ 169 | createExe: function(name,dir,callback) { 170 | var fs = require('fs'), p = require('path'); 171 | 172 | if (typeof dir === 'function') { 173 | callback = dir; 174 | dir = null; 175 | } 176 | 177 | dir = dir || process.cwd(); 178 | 179 | var exeOrigin = p.join(__dirname,'..','bin','winsw','winsw.exe'), 180 | cfgOrigin = p.join(__dirname,'..','bin','winsw','winsw.exe.config'), 181 | exeDest = p.join(dir,name.replace(/[^\w]/gi,'').toLowerCase()+'.exe'), 182 | cfgDest = p.join(dir,name.replace(/[^\w]/gi,'').toLowerCase()+'.exe.config'), 183 | exeData = fs.readFileSync(exeOrigin,{encoding:'binary'}), 184 | cfgData = fs.readFileSync(cfgOrigin,{encoding:'binary'}); 185 | 186 | fs.writeFileSync(exeDest,exeData,{encoding:'binary'}); 187 | fs.writeFileSync(cfgDest,cfgData,{encoding:'binary'}); 188 | callback && callback(); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /lib/wrapper.js: -------------------------------------------------------------------------------- 1 | const { errorMonitor } = require('events'); 2 | 3 | // Handle input parameters 4 | var Logger = require('./eventlog'), 5 | optimist = require('yargs'), 6 | net = require('net'), 7 | max = 60, 8 | p = require('path'), 9 | fs = require('fs'), 10 | argv = optimist 11 | .demand('file') 12 | .alias('f', 'file') 13 | .describe('file', 'The absolute path of the script to be run as a process.') 14 | .check(function (argv) { 15 | var exists = fs.existsSync(p.resolve(argv.f)) 16 | if (!exists) { 17 | throw new Error(argv.f + ' does not exist or cannot be found.') 18 | } 19 | return true 20 | }) 21 | .describe('scriptoptions', 'The options to be sent to the script.') 22 | .alias('d', 'cwd') 23 | .describe('cwd', 'The absolute path of the current working directory of the script to be run as a process.') 24 | // .check(function(argv){ 25 | // require('fs').existsSync(p.resolve(argv.d),function(exists){ 26 | // return exists; 27 | // }); 28 | // }) 29 | .demand('log') 30 | .alias('l', 'log') 31 | .describe('log', 'The descriptive name of the log for the process') 32 | .default('eventlog', 'APPLICATION') 33 | .alias('e', 'eventlog') 34 | .describe('eventlog', 'The event log container. This must be APPLICATION or SYSTEM.') 35 | .default('maxretries', -1) 36 | .alias('m', 'maxretries') 37 | .describe('maxretries', 'The maximim number of times the process will be auto-restarted.') 38 | .default('maxrestarts', 5) 39 | .alias('r', 'maxrestarts') 40 | .describe('maxrestarts', 'The maximim number of times the process should be restarted within a ' + max + ' second period shutting down.') 41 | .default('wait', 1) 42 | .alias('w', 'wait') 43 | .describe('wait', 'The number of seconds between each restart attempt.') 44 | // .check(function (argv) { 45 | // return argv.w >= 0; 46 | // }) 47 | .default('grow', .25) 48 | .alias('g', 'grow') 49 | .describe('grow', 'A percentage growth rate at which the wait time is increased.') 50 | // .check(function (argv) { 51 | // return (argv.g >= 0 && argv.g <= 1); 52 | // }) 53 | .default('abortonerror', 'no') 54 | .alias('a', 'abortonerror') 55 | .describe('abortonerror', 'Do not attempt to restart the process if it fails with an error,') 56 | .check(function (argv) { 57 | return ['y', 'n', 'yes', 'no'].indexOf(argv.a.trim().toLowerCase()) >= 0; 58 | }) 59 | .default('stopparentfirst', 'no') 60 | .alias('s', 'stopparentfirst') 61 | .describe('stopparentfirst', 'Allow the script to exit using a shutdown message.') 62 | .check(function (argv) { 63 | return ['y', 'n', 'yes', 'no'].indexOf(argv.a.trim().toLowerCase()) >= 0; 64 | }) 65 | .default('disablewarninglogs','no') 66 | .alias('dw','disablewarninglogs') 67 | .describe('disablewarninglogs','Prevent warning logs from being written to the event log.') 68 | .check(function(argv){ 69 | return ['y','n','yes','no'].indexOf(argv.a.trim().toLowerCase()) >= 0; 70 | }) 71 | .parse(), 72 | log = new Logger({ 73 | source: argv.l, 74 | ...(argv.e ? {eventlog:argv.e} : {}), 75 | disablewarninglogs: argv.dw, 76 | }), 77 | fork = require('child_process').fork, 78 | script = p.resolve(argv.f), 79 | wait = argv.w * 1000, 80 | grow = argv.g + 1, 81 | attempts = 0, 82 | startTime = null, 83 | starts = 0, 84 | child = null 85 | forcekill = false; 86 | 87 | if (argv.d) { 88 | if (!require('fs').existsSync(p.resolve(argv.d))) { 89 | console.warn(argv.d + ' not found.'); 90 | argv.d = process.cwd(); 91 | } 92 | argv.d = p.resolve(argv.d); 93 | } 94 | 95 | if (typeof argv.m === 'string') { 96 | argv.m = parseInt(argv.m); 97 | } 98 | 99 | // Set the absolute path of the file 100 | argv.f = p.resolve(argv.f); 101 | 102 | // Hack to force the wrapper process to stay open by launching a ghost socket server 103 | var server = net.createServer().listen(); 104 | 105 | server.on('error', function (err) { 106 | launch('warn', err.message); 107 | server = net.createServer().listen(); 108 | }); 109 | 110 | /** 111 | * @method monitor 112 | * Monitor the process to make sure it is running 113 | */ 114 | var monitor = function () { 115 | if (!child || !child.pid) { 116 | 117 | // If the number of periodic starts exceeds the max, kill the process 118 | if (starts >= argv.r) { 119 | if (new Date().getTime() - (max * 1000) <= startTime.getTime()) { 120 | log.error('Too many restarts within the last ' + max + ' seconds. Please check the script.'); 121 | process.exit(); 122 | } 123 | } 124 | 125 | setTimeout(function () { 126 | wait = wait * grow; 127 | attempts += 1; 128 | if (attempts > argv.m && argv.m >= 0) { 129 | log.error('Too many restarts. ' + argv.f + ' will not be restarted because the maximum number of total restarts has been exceeded.'); 130 | process.exit(); 131 | } else { 132 | launch('warn', 'Restarted ' + wait + ' msecs after unexpected exit; attempts = ' + attempts); 133 | } 134 | }, wait); 135 | } else { 136 | // reset attempts and wait time 137 | attempts = 0; 138 | wait = argv.w * 1000; 139 | } 140 | }; 141 | 142 | 143 | /** 144 | * @method launch 145 | * A method to start a process. 146 | * logLevel - optional logging level (must be the name of a function the the Logger object) 147 | * msg - optional msg to log 148 | */ 149 | var launch = function (logLevel, msg) { 150 | if (forcekill) { 151 | log.info("Process killed"); 152 | return; 153 | } 154 | 155 | //log.info('Starting '+argv.f); 156 | if (logLevel && msg) { 157 | log[logLevel](msg); 158 | } 159 | 160 | // Set the start time if it's null 161 | if (startTime == null) { 162 | startTime = startTime || new Date(); 163 | setTimeout(function () { 164 | startTime = null; 165 | starts = 0; 166 | }, (max * 1000) + 1); 167 | } 168 | starts += 1; 169 | 170 | // Fork the child process 171 | var opts = { env: process.env }; 172 | var args = []; 173 | if (argv.d) opts.cwd = argv.d; 174 | if (argv.s) opts.detached = true; 175 | if (argv.scriptoptions) args = argv.scriptoptions.split(' '); 176 | child = fork(script, args, opts); 177 | 178 | // When the child dies, attempt to restart based on configuration 179 | child.on('exit', function (code) { 180 | log.warn(argv.f + ' stopped running.'); 181 | 182 | // If an error is thrown and the process is configured to exit, then kill the parent. 183 | if (code !== 0 && argv.a == "yes") { 184 | log.error(argv.f + ' exited with error code ' + code); 185 | process.exit(); 186 | //server.unref(); 187 | } else if (forcekill) { 188 | process.exit(); 189 | } 190 | 191 | child = null; 192 | // Monitor the process 193 | monitor(); 194 | }); 195 | }; 196 | 197 | var killkid = function () { 198 | forcekill = true; 199 | if (child) { 200 | if (argv.s) { 201 | child.send('shutdown'); 202 | } else { 203 | child.kill(); 204 | } 205 | } else { 206 | log.warn('Attempted to kill an unrecognized process.') 207 | } 208 | } 209 | 210 | process.on('exit', killkid); 211 | process.on("SIGINT", killkid); 212 | process.on("SIGTERM", killkid); 213 | 214 | process.on('uncaughtException', function (err) { 215 | launch('warn', err.message); 216 | }); 217 | 218 | // Launch the process 219 | launch('info', 'Starting ' + argv.f); -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-windows", 3 | "version": "1.0.0-beta.8", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "node-windows", 9 | "version": "1.0.0-beta.8", 10 | "license": "MIT", 11 | "dependencies": { 12 | "xml": "1.0.1", 13 | "yargs": "^17.5.1" 14 | } 15 | }, 16 | "node_modules/ansi-regex": { 17 | "version": "5.0.1", 18 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 19 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 20 | "engines": { 21 | "node": ">=8" 22 | } 23 | }, 24 | "node_modules/ansi-styles": { 25 | "version": "4.3.0", 26 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 27 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 28 | "dependencies": { 29 | "color-convert": "^2.0.1" 30 | }, 31 | "engines": { 32 | "node": ">=8" 33 | }, 34 | "funding": { 35 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 36 | } 37 | }, 38 | "node_modules/cliui": { 39 | "version": "7.0.4", 40 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", 41 | "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", 42 | "dependencies": { 43 | "string-width": "^4.2.0", 44 | "strip-ansi": "^6.0.0", 45 | "wrap-ansi": "^7.0.0" 46 | } 47 | }, 48 | "node_modules/color-convert": { 49 | "version": "2.0.1", 50 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 51 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 52 | "dependencies": { 53 | "color-name": "~1.1.4" 54 | }, 55 | "engines": { 56 | "node": ">=7.0.0" 57 | } 58 | }, 59 | "node_modules/color-name": { 60 | "version": "1.1.4", 61 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 62 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 63 | }, 64 | "node_modules/emoji-regex": { 65 | "version": "8.0.0", 66 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 67 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" 68 | }, 69 | "node_modules/escalade": { 70 | "version": "3.1.1", 71 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 72 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", 73 | "engines": { 74 | "node": ">=6" 75 | } 76 | }, 77 | "node_modules/get-caller-file": { 78 | "version": "2.0.5", 79 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 80 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 81 | "engines": { 82 | "node": "6.* || 8.* || >= 10.*" 83 | } 84 | }, 85 | "node_modules/is-fullwidth-code-point": { 86 | "version": "3.0.0", 87 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 88 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 89 | "engines": { 90 | "node": ">=8" 91 | } 92 | }, 93 | "node_modules/require-directory": { 94 | "version": "2.1.1", 95 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 96 | "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", 97 | "engines": { 98 | "node": ">=0.10.0" 99 | } 100 | }, 101 | "node_modules/string-width": { 102 | "version": "4.2.3", 103 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 104 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 105 | "dependencies": { 106 | "emoji-regex": "^8.0.0", 107 | "is-fullwidth-code-point": "^3.0.0", 108 | "strip-ansi": "^6.0.1" 109 | }, 110 | "engines": { 111 | "node": ">=8" 112 | } 113 | }, 114 | "node_modules/strip-ansi": { 115 | "version": "6.0.1", 116 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 117 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 118 | "dependencies": { 119 | "ansi-regex": "^5.0.1" 120 | }, 121 | "engines": { 122 | "node": ">=8" 123 | } 124 | }, 125 | "node_modules/wrap-ansi": { 126 | "version": "7.0.0", 127 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 128 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 129 | "dependencies": { 130 | "ansi-styles": "^4.0.0", 131 | "string-width": "^4.1.0", 132 | "strip-ansi": "^6.0.0" 133 | }, 134 | "engines": { 135 | "node": ">=10" 136 | }, 137 | "funding": { 138 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 139 | } 140 | }, 141 | "node_modules/xml": { 142 | "version": "1.0.1", 143 | "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", 144 | "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=" 145 | }, 146 | "node_modules/y18n": { 147 | "version": "5.0.8", 148 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 149 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", 150 | "engines": { 151 | "node": ">=10" 152 | } 153 | }, 154 | "node_modules/yargs": { 155 | "version": "17.5.1", 156 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", 157 | "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", 158 | "dependencies": { 159 | "cliui": "^7.0.2", 160 | "escalade": "^3.1.1", 161 | "get-caller-file": "^2.0.5", 162 | "require-directory": "^2.1.1", 163 | "string-width": "^4.2.3", 164 | "y18n": "^5.0.5", 165 | "yargs-parser": "^21.0.0" 166 | }, 167 | "engines": { 168 | "node": ">=12" 169 | } 170 | }, 171 | "node_modules/yargs-parser": { 172 | "version": "21.1.1", 173 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", 174 | "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", 175 | "engines": { 176 | "node": ">=12" 177 | } 178 | } 179 | }, 180 | "dependencies": { 181 | "ansi-regex": { 182 | "version": "5.0.1", 183 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 184 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" 185 | }, 186 | "ansi-styles": { 187 | "version": "4.3.0", 188 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 189 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 190 | "requires": { 191 | "color-convert": "^2.0.1" 192 | } 193 | }, 194 | "cliui": { 195 | "version": "7.0.4", 196 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", 197 | "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", 198 | "requires": { 199 | "string-width": "^4.2.0", 200 | "strip-ansi": "^6.0.0", 201 | "wrap-ansi": "^7.0.0" 202 | } 203 | }, 204 | "color-convert": { 205 | "version": "2.0.1", 206 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 207 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 208 | "requires": { 209 | "color-name": "~1.1.4" 210 | } 211 | }, 212 | "color-name": { 213 | "version": "1.1.4", 214 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 215 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 216 | }, 217 | "emoji-regex": { 218 | "version": "8.0.0", 219 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 220 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" 221 | }, 222 | "escalade": { 223 | "version": "3.1.1", 224 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 225 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" 226 | }, 227 | "get-caller-file": { 228 | "version": "2.0.5", 229 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 230 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" 231 | }, 232 | "is-fullwidth-code-point": { 233 | "version": "3.0.0", 234 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 235 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" 236 | }, 237 | "require-directory": { 238 | "version": "2.1.1", 239 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 240 | "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" 241 | }, 242 | "string-width": { 243 | "version": "4.2.3", 244 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 245 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 246 | "requires": { 247 | "emoji-regex": "^8.0.0", 248 | "is-fullwidth-code-point": "^3.0.0", 249 | "strip-ansi": "^6.0.1" 250 | } 251 | }, 252 | "strip-ansi": { 253 | "version": "6.0.1", 254 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 255 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 256 | "requires": { 257 | "ansi-regex": "^5.0.1" 258 | } 259 | }, 260 | "wrap-ansi": { 261 | "version": "7.0.0", 262 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 263 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 264 | "requires": { 265 | "ansi-styles": "^4.0.0", 266 | "string-width": "^4.1.0", 267 | "strip-ansi": "^6.0.0" 268 | } 269 | }, 270 | "xml": { 271 | "version": "1.0.1", 272 | "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", 273 | "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=" 274 | }, 275 | "y18n": { 276 | "version": "5.0.8", 277 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 278 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" 279 | }, 280 | "yargs": { 281 | "version": "17.5.1", 282 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", 283 | "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", 284 | "requires": { 285 | "cliui": "^7.0.2", 286 | "escalade": "^3.1.1", 287 | "get-caller-file": "^2.0.5", 288 | "require-directory": "^2.1.1", 289 | "string-width": "^4.2.3", 290 | "y18n": "^5.0.5", 291 | "yargs-parser": "^21.0.0" 292 | } 293 | }, 294 | "yargs-parser": { 295 | "version": "21.1.1", 296 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", 297 | "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" 298 | } 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-windows", 3 | "version": "1.0.0-beta.8", 4 | "description": "Support for Windows services, event logging, UAC, and several helper methods for interacting with the OS.", 5 | "keywords": [ 6 | "ngn", 7 | "windows", 8 | "service", 9 | "daemon", 10 | "logging", 11 | "event", 12 | "event logging", 13 | "elevate", 14 | "sudo", 15 | "task" 16 | ], 17 | "author": "Corey Butler ", 18 | "main": "lib/node-windows.js", 19 | "preferGlobal": true, 20 | "dependencies": { 21 | "xml": "1.0.1", 22 | "yargs": "^17.5.1" 23 | }, 24 | "readmeFilename": "README.md", 25 | "scripts": {}, 26 | "repository": { 27 | "type": "git", 28 | "url": "git://github.com/coreybutler/node-windows.git" 29 | }, 30 | "license": "MIT", 31 | "engine": "node >= 0.10.10" 32 | } 33 | --------------------------------------------------------------------------------