├── .editorconfig ├── .gitattributes ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .npmrc ├── csv-headers.js ├── index.js ├── license ├── package.json ├── readme.md ├── test ├── global.test.js ├── promise.test.js └── stream.test.js └── transform.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.yml] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | - push 4 | - pull_request 5 | jobs: 6 | test: 7 | name: Node.js ${{ matrix.node-version }} 8 | runs-on: windows-latest 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | node-version: 13 | - 16 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: actions/setup-node@v2 17 | with: 18 | node-version: ${{ matrix.node-version }} 19 | - run: npm install 20 | - run: npm test 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /csv-headers.js: -------------------------------------------------------------------------------- 1 | const csvHeaders = { 2 | default: [ 3 | 'imageName', 4 | 'pid', 5 | 'sessionName', 6 | 'sessionNumber', 7 | 'memUsage', 8 | ], 9 | defaultVerbose: [ 10 | 'imageName', 11 | 'pid', 12 | 'sessionName', 13 | 'sessionNumber', 14 | 'memUsage', 15 | 'status', 16 | 'username', 17 | 'cpuTime', 18 | 'windowTitle', 19 | ], 20 | apps: [ 21 | 'imageName', 22 | 'pid', 23 | 'memUsage', 24 | 'packageName', 25 | ], 26 | appsVerbose: [ 27 | 'imageName', 28 | 'pid', 29 | 'sessionName', 30 | 'sessionNumber', 31 | 'memUsage', 32 | 'status', 33 | 'username', 34 | 'cpuTime', 35 | 'windowTitle', 36 | 'packageName', 37 | ], 38 | modules: [ 39 | 'imageName', 40 | 'pid', 41 | 'modules', 42 | ], 43 | services: [ 44 | 'imageName', 45 | 'pid', 46 | 'services', 47 | ], 48 | }; 49 | 50 | export default csvHeaders; 51 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import childProcess from 'node:child_process'; 2 | import {promisify} from 'node:util'; 3 | import {pipeline} from 'node:stream'; 4 | import process from 'node:process'; 5 | import csv from 'csv'; 6 | import csvHeaders from './csv-headers.js'; 7 | import transform from './transform.js'; 8 | 9 | const execFile = promisify(childProcess.execFile); 10 | const parse = promisify(csv.parse); 11 | 12 | function main(options = {}) { 13 | if (process.platform !== 'win32') { 14 | throw new Error('Windows only'); 15 | } 16 | 17 | if (options.verbose === true && (options.services === true || options.modules !== undefined)) { 18 | throw new Error('Verbose option is invalid when Services or Modules option is set'); 19 | } 20 | 21 | if (options.modules !== undefined && options.services === true) { 22 | throw new Error('The Services and Modules options can\'t be used together'); 23 | } 24 | 25 | // Check if system, username and password is specified together 26 | const remoteParameters = [options.system, options.username, options.password]; 27 | let isRemote; 28 | if (remoteParameters.every(value => value === undefined)) { 29 | // All params are undefined 30 | isRemote = false; 31 | } else if (remoteParameters.includes(undefined)) { 32 | // Some, but not all of the params are undefined 33 | throw new Error('The System, Username and Password options must be specified together'); 34 | } else { 35 | isRemote = true; 36 | } 37 | 38 | // Check for unsupported filters on remote machines 39 | if (Array.isArray(options.filter) && isRemote) { 40 | for (const filter of options.filter) { 41 | const parameter = filter.split(' ')[0].toLowerCase(); 42 | if (parameter === 'windowtitle' || parameter === 'status') { 43 | throw new Error('Windowtitle and Status parameters for filtering are not supported when querying remote machines'); 44 | } 45 | } 46 | } 47 | 48 | // Populate args 49 | const args = ['/nh', '/fo', 'csv']; 50 | 51 | if (options.verbose) { 52 | args.push('/v'); 53 | } 54 | 55 | if (options.apps) { 56 | args.push('/apps'); 57 | } 58 | 59 | if (options.modules !== undefined) { 60 | args.push('/m'); 61 | if (options.modules.length > 0) { 62 | args.push(options.modules); 63 | } 64 | } 65 | 66 | if (options.services) { 67 | args.push('/svc'); 68 | } 69 | 70 | if (isRemote) { 71 | args.push( 72 | '/s', options.system, 73 | '/u', options.username, 74 | '/p', options.password, 75 | ); 76 | } 77 | 78 | if (Array.isArray(options.filter)) { 79 | for (const filter of options.filter) { 80 | args.push('/fi', filter); 81 | } 82 | } 83 | 84 | let currentHeader; 85 | if (options.apps) { 86 | currentHeader = 'apps'; 87 | } else if (options.modules !== undefined) { 88 | currentHeader = 'modules'; 89 | } else if (options.services) { 90 | currentHeader = 'services'; 91 | } else { 92 | currentHeader = 'default'; 93 | } 94 | 95 | if (options.verbose) { 96 | currentHeader += 'Verbose'; 97 | } 98 | 99 | const columns = csvHeaders[currentHeader]; 100 | const currentTransform = transform.transforms[currentHeader]; 101 | return {args, columns, currentTransform}; 102 | } 103 | 104 | export async function tasklist(options = {}) { 105 | const {args, columns, currentTransform} = main(options); 106 | const {stdout} = await execFile('tasklist.exe', args); 107 | if (!stdout.startsWith('"')) { 108 | return []; 109 | } 110 | 111 | const records = await parse(stdout, {columns}); 112 | records.map(task => currentTransform(task)); 113 | return records; 114 | } 115 | 116 | export function tasklistStream(options = {}) { 117 | const {args, columns, currentTransform} = main(options); 118 | const checkEmptyStream = new transform.ReportEmpty().getTransform(); 119 | const processOutput = childProcess.spawn('tasklist.exe', args).stdout; 120 | 121 | // Ignore errors originating from stream end 122 | const resultStream = pipeline(processOutput, checkEmptyStream, csv.parse({columns}), transform.makeTransform(currentTransform), error => error); 123 | resultStream.on('error', error => error); 124 | return resultStream; 125 | } 126 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Sindre Sorhus (https://sindresorhus.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tasklist", 3 | "version": "5.0.0", 4 | "description": "Wrapper for the Windows `tasklist` command. Returns a list of apps and services with their Process ID (PID) for all tasks running on either a local or a remote computer.", 5 | "license": "MIT", 6 | "repository": "sindresorhus/tasklist", 7 | "funding": "https://github.com/sponsors/sindresorhus", 8 | "author": { 9 | "name": "Sindre Sorhus", 10 | "email": "sindresorhus@gmail.com", 11 | "url": "https://sindresorhus.com" 12 | }, 13 | "type": "module", 14 | "exports": "./index.js", 15 | "engines": { 16 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 17 | }, 18 | "scripts": { 19 | "test": "xo && ava" 20 | }, 21 | "files": [ 22 | "index.js", 23 | "csv-headers.js", 24 | "index.d.ts", 25 | "transform.js" 26 | ], 27 | "keywords": [ 28 | "tasklist", 29 | "task", 30 | "list", 31 | "windows", 32 | "win", 33 | "win32", 34 | "pid", 35 | "process", 36 | "processes", 37 | "services" 38 | ], 39 | "dependencies": { 40 | "csv": "^5.5.0", 41 | "sec": "^2.0.0" 42 | }, 43 | "devDependencies": { 44 | "@types/node": "^16.6.0", 45 | "ava": "^3.15.0", 46 | "get-stream": "^6.0.1", 47 | "xo": "^0.44.0" 48 | }, 49 | "ava": { 50 | "files": [ 51 | "test/*.test.js" 52 | ] 53 | }, 54 | "xo": { 55 | "ignores": [ 56 | "*.ts" 57 | ] 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # tasklist 2 | 3 | > Wrapper for the Windows [`tasklist`](https://technet.microsoft.com/en-us/library/bb491010.aspx) command. Returns a list of apps and services with their Process ID (PID) for all tasks running on either a local or a remote computer. 4 | 5 | Cleans up and normalizes the data. 6 | 7 | ## Install 8 | 9 | ``` 10 | $ npm install tasklist 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```js 16 | import {tasklist} from 'tasklist'; 17 | 18 | console.log(await tasklist()); 19 | /* 20 | [ 21 | { 22 | imageName: 'taskhostex.exe', 23 | pid: 1820, 24 | sessionName: 'Console', 25 | sessionNumber: 1, 26 | memUsage: 4415488 27 | }, 28 | … 29 | ] 30 | */ 31 | ``` 32 | 33 | ## API 34 | 35 | See the [`tasklist` docs](https://technet.microsoft.com/en-us/library/bb491010.aspx) for more. 36 | 37 | ### tasklist(options?) 38 | 39 | Returns a `Promise` that contains the normalized results of the command output. 40 | 41 | Examples for `options` below will use this interface, but you can check `tasklist.stream` below for usage of the stream interface. 42 | 43 | ### tasklistStream(options?) 44 | 45 | Returns a `stream.Readable` that returns the resulting lines, normalized, one-by-one. 46 | 47 | Options are the same as the Promise interface. 48 | 49 | ```js 50 | import {tasklistStream} from 'tasklist'; 51 | 52 | tasklistStream({verbose: true}).pipe(process.stdout); 53 | /* 54 | { 55 | imageName: 'taskhostex.exe', 56 | pid: 1820, 57 | sessionName: 'Console', 58 | sessionNumber: 1, 59 | memUsage: 4415488, 60 | status: 'Running', 61 | username: 'SINDRESORHU3930\\sindre' 62 | cpuTime: 0, 63 | windowTitle: 'Task Host Window' 64 | } 65 | … 66 | */ 67 | ``` 68 | 69 | #### options 70 | 71 | Type: `object` 72 | 73 | **Warning** 74 | - The `system`, `username`, `password` options must be specified together. 75 | - The `modules` and `services` options can't be specified if verbose is set to `true`. 76 | - The `modules` and `services` options can't be specified at the same time. 77 | - When `system`, `username`, `password` options are specified, the filter option can't have `windowtitle` and `status` as the parameter. 78 | 79 | ##### verbose 80 | 81 | Type: `boolean`\ 82 | Default: `false` 83 | 84 | Return verbose results. 85 | 86 | Without the `verbose` and `apps` option, `tasklist` returns tasks with the following properties: 87 | 88 | - `imageName` (Type: `string`) 89 | - `pid` (Type: `number`) 90 | - `sessionName` (Type: `string`) 91 | - `sessionNumber` (Type: `number`) 92 | - `memUsage` in bytes (Type: `number`) 93 | 94 | With the `verbose` option set to `true` but the `apps` option still set to `false`, it additionally returns the following properties: 95 | 96 | - `status` (Type: `string`): One of `Running`, `Suspended`, `Not Responding`, or `Unknown` 97 | - `username` (Type: `string`) 98 | - `cpuTime` in seconds (Type: `number`) 99 | - `windowTitle` (Type: `string`) 100 | 101 | **Note:** It's not guaranteed that the `username` and `windowTitle` properties are returned with proper values. If they are *not available*, `'N/A'` may be returned on English systems. In contrast, `'Nicht zutreffend'` may be returned on German systems, for example. 102 | 103 | **Verbose example:** 104 | 105 | ```js 106 | import {tasklist} from 'tasklist'; 107 | 108 | console.log(await tasklist({verbose: true})); 109 | /* 110 | [ 111 | { 112 | imageName: 'taskhostex.exe', 113 | pid: 1820, 114 | sessionName: 'Console', 115 | sessionNumber: 1, 116 | memUsage: 4415488, 117 | status: 'Running', 118 | username: 'SINDRESORHU3930\\sindre' 119 | cpuTime: 0, 120 | windowTitle: 'Task Host Window' 121 | }, 122 | … 123 | ] 124 | */ 125 | ``` 126 | 127 | **Warning:** Using the `verbose` option may have a considerable performance impact (See: [#6](https://github.com/sindresorhus/tasklist/issues/6)). 128 | 129 | ##### system 130 | 131 | Type: `string` 132 | 133 | Name or IP address of a remote computer (don't use backslashes). The default is the local computer. 134 | 135 | ##### username 136 | 137 | Type: `string`\ 138 | Example: `'SINDRESORHU3930\\sindre'` 139 | 140 | User specified by `User` or `Domain\User`. The default is the permissions of the current logged on user on the computer issuing the command. 141 | 142 | ##### password 143 | 144 | Type: `string` 145 | 146 | Password of the user account for the specified `username`. 147 | 148 | ##### filter 149 | 150 | Type: `string[]` 151 | 152 | Specify the types of processes to include or exclude. [More info.](https://technet.microsoft.com/en-us/library/bb491010.aspx) 153 | 154 | ##### apps 155 | 156 | Type: `boolean` 157 | 158 | Displays store apps. 159 | Without the `verbose` option, the command returns the following data: 160 | - `imageName` (Type: `string`) 161 | - `pid` (Type: `number`) 162 | - `memUsage` in bytes (Type: `number`) 163 | - `packageName` (Type: `string`) 164 | 165 | ```js 166 | import {tasklist} from 'tasklist'; 167 | 168 | console.log(await tasklist({apps: true})); 169 | /* 170 | [ 171 | { 172 | imageName: 'SearchUI.exe (CortanaUI)', 173 | pid: 1820, 174 | memUsage: 4415488, 175 | packageName: 'Microsoft.Windows.Cortana' 176 | }, 177 | … 178 | ] 179 | */ 180 | ``` 181 | 182 | With the `verbose` option set to `true`, the command additionally returns the following data: 183 | - `sessionName` (Type: `string`) 184 | - `sessionNumber` (Type: `number`) 185 | - `status` (Type: `string`): One of `Running`, `Suspended`, `Not Responding`, or `Unknown` 186 | - `username` (Type: `string`) 187 | - `cpuTime` in seconds (Type: `number`) 188 | - `windowTitle` (Type: `string`) 189 | 190 | **Note:** It's not guaranteed that the `username` and `windowTitle` properties are returned with proper values. If they are *not available*, `'N/A'` may be returned on English systems. In contrast, `'Nicht zutreffend'` may be returned on German systems, for example. 191 | 192 | **Verbose example:** 193 | 194 | ```js 195 | import {tasklist} from 'tasklist'; 196 | 197 | console.log(await tasklist({apps: true, verbose: true})); 198 | /* 199 | [ 200 | { 201 | imageName: 'SearchUI.exe (CortanaUI)', 202 | pid: 1820, 203 | sessionName: 'Console', 204 | sessionNumber: 1, 205 | memUsage: 4415488, 206 | status: 'Running', 207 | username: 'SINDRESORHU3930\\sindre' 208 | cpuTime: 0, 209 | windowTitle: 'N/A', 210 | packageName: 'Microsoft.Windows.Cortana' 211 | }, 212 | … 213 | ] 214 | */ 215 | ``` 216 | 217 | ##### modules 218 | 219 | Type: `string` 220 | 221 | List all tasks using the given DLL module name. If an empty string is given, it will list all tasks with the used DLL modules. 222 | 223 | **Note:** You can't use the `verbose` option with this option set. 224 | 225 | ```js 226 | import {tasklist} from 'tasklist'; 227 | 228 | console.log(await tasklist({modules: 'wmiutils.dll'})); 229 | /* 230 | [{ 231 | imageName: 'chrome.exe', 232 | pid: 1820, 233 | modules: ['wmiutils.dll'] 234 | }, …] 235 | */ 236 | ``` 237 | 238 | ##### services 239 | 240 | Type: `boolean` 241 | 242 | Displays services hosted in each process. 243 | **Note:** You can't use the `verbose` option with this option set. 244 | 245 | ```js 246 | import {tasklist} from 'tasklist'; 247 | 248 | console.log(await tasklist({services: true})); 249 | /* 250 | [{ 251 | imageName: 'lsass.exe', 252 | pid: 856, 253 | services: ['KeyIso', 'SamSs', 'VaultSvc'] 254 | }, …] 255 | */ 256 | ``` 257 | 258 | ## Related 259 | 260 | - [taskkill](https://github.com/sindresorhus/taskkill) - Wrapper for the Windows `taskkill` command 261 | 262 | ## Maintainers 263 | 264 | - [Sindre Sorhus](https://sindresorhus.com) 265 | - [Mark Tiedemann](https://marksweb.site) 266 | -------------------------------------------------------------------------------- /test/global.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import {tasklist} from '../index.js'; 3 | 4 | test('reject windowtitle and status parameter filter for remote machine', async t => { 5 | await t.throwsAsync( 6 | tasklist({ 7 | system: 'test', 8 | username: 'test', 9 | password: 'test', 10 | filter: ['Windowtitle eq asd'], 11 | }), 12 | ); 13 | }); 14 | 15 | test('reject /svc with /m flag', async t => { 16 | await t.throwsAsync( 17 | tasklist({ 18 | services: true, 19 | modules: '', 20 | }), 21 | ); 22 | }); 23 | 24 | test('reject verbose with /svc flag', async t => { 25 | await t.throwsAsync( 26 | tasklist({ 27 | verbose: true, 28 | services: true, 29 | }), 30 | ); 31 | }); 32 | 33 | test('reject verbose with /m flag', async t => { 34 | await t.throwsAsync( 35 | tasklist({ 36 | verbose: true, 37 | modules: '', 38 | }), 39 | ); 40 | }); 41 | 42 | test('reject system without username and password', async t => { 43 | await t.throwsAsync( 44 | tasklist({ 45 | system: '192.168.1.1', 46 | }), 47 | ); 48 | }); 49 | -------------------------------------------------------------------------------- /test/promise.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import {tasklist} from '../index.js'; 3 | 4 | const hasDefaultTaskProps = (t, task) => { 5 | t.is(typeof task.imageName, 'string'); 6 | t.is(typeof task.pid, 'number'); 7 | t.is(typeof task.sessionName, 'string'); 8 | t.is(typeof task.sessionNumber, 'number'); 9 | t.is(typeof task.memUsage, 'number'); 10 | }; 11 | 12 | const hasNonVerboseTaskProps = (t, task) => { 13 | t.is(task.status, undefined); 14 | t.is(task.username, undefined); 15 | t.is(task.cpuTime, undefined); 16 | t.is(task.windowTitle, undefined); 17 | }; 18 | 19 | const hasVerboseTaskProps = (t, task) => { 20 | t.is(typeof task.status, 'string'); 21 | t.is(typeof task.username, 'string'); 22 | t.is(typeof task.cpuTime, 'number'); 23 | t.is(typeof task.windowTitle, 'string'); 24 | }; 25 | 26 | const hasAppsProps = (t, task) => { 27 | t.is(typeof task.imageName, 'string'); 28 | t.is(typeof task.pid, 'number'); 29 | t.is(typeof task.memUsage, 'number'); 30 | t.is(typeof task.packageName, 'string'); 31 | }; 32 | 33 | const hasNonVerboseAppsProps = (t, task) => { 34 | t.is(task.status, undefined); 35 | t.is(task.username, undefined); 36 | t.is(task.cpuTime, undefined); 37 | t.is(task.windowTitle, undefined); 38 | t.is(task.sessionName, undefined); 39 | t.is(task.sessionNumber, undefined); 40 | }; 41 | 42 | const hasVerboseAppsProps = (t, task) => { 43 | t.is(typeof task.status, 'string'); 44 | t.is(typeof task.username, 'string'); 45 | t.is(typeof task.cpuTime, 'number'); 46 | t.is(typeof task.windowTitle, 'string'); 47 | t.is(typeof task.sessionName, 'string'); 48 | t.is(typeof task.sessionNumber, 'number'); 49 | }; 50 | 51 | const hasModulesProps = (t, task) => { 52 | t.is(typeof task.imageName, 'string'); 53 | t.is(typeof task.pid, 'number'); 54 | t.true(Array.isArray(task.modules)); 55 | }; 56 | 57 | const hasServicesProps = (t, task) => { 58 | t.is(typeof task.imageName, 'string'); 59 | t.is(typeof task.pid, 'number'); 60 | t.true(Array.isArray(task.services)); 61 | }; 62 | 63 | const macro = async (t, options) => { 64 | const tasks = await tasklist(options); 65 | t.true(tasks.length > 0); 66 | 67 | for (const task of tasks) { 68 | hasDefaultTaskProps(t, task); 69 | 70 | if (options.verbose) { 71 | hasVerboseTaskProps(t, task); 72 | } else { 73 | hasNonVerboseTaskProps(t, task); 74 | } 75 | } 76 | }; 77 | 78 | const appsMacro = async (t, options) => { 79 | const tasks = await tasklist(options); 80 | if (tasks.length === 0) { 81 | // TravisCI doesn't seem to have any apps so this test fails 82 | t.pass('Test passing with empty result, probably running inside TravisCI'); 83 | } else { 84 | for (const task of tasks) { 85 | hasAppsProps(t, task); 86 | 87 | if (options.verbose) { 88 | hasVerboseAppsProps(t, task); 89 | } else { 90 | hasNonVerboseAppsProps(t, task); 91 | } 92 | } 93 | } 94 | }; 95 | 96 | test('default', macro, {}); 97 | test('verbose option', macro, {verbose: true}); 98 | test('filter option', macro, {filter: ['sessionname eq console', 'username ne F4k3U53RN4M3']}); 99 | 100 | test('apps', appsMacro, {apps: true}); 101 | test('apps with verbose', appsMacro, {apps: true, verbose: true}); 102 | 103 | test('modules', async t => { 104 | const tasks = await tasklist({modules: ''}); 105 | t.true(tasks.length > 0); 106 | 107 | for (const task of tasks) { 108 | hasModulesProps(t, task); 109 | } 110 | }); 111 | 112 | test('services', async t => { 113 | const tasks = await tasklist({services: true}); 114 | t.true(tasks.length > 0); 115 | 116 | for (const task of tasks) { 117 | hasServicesProps(t, task); 118 | } 119 | }); 120 | 121 | test('test handle no matching tasks gracefully', async t => { 122 | const tasks = await tasklist({ 123 | filter: ['imagename eq does-not-exist'], 124 | }); 125 | t.is(tasks.length, 0); 126 | }); 127 | -------------------------------------------------------------------------------- /test/stream.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import getStream from 'get-stream'; 3 | import {tasklistStream} from '../index.js'; 4 | 5 | const hasDefaultTaskProps = (t, task) => { 6 | t.is(typeof task.imageName, 'string'); 7 | t.is(typeof task.pid, 'number'); 8 | t.is(typeof task.sessionName, 'string'); 9 | t.is(typeof task.sessionNumber, 'number'); 10 | t.is(typeof task.memUsage, 'number'); 11 | }; 12 | 13 | const hasNonVerboseTaskProps = (t, task) => { 14 | t.is(task.status, undefined); 15 | t.is(task.username, undefined); 16 | t.is(task.cpuTime, undefined); 17 | t.is(task.windowTitle, undefined); 18 | }; 19 | 20 | const hasVerboseTaskProps = (t, task) => { 21 | t.is(typeof task.status, 'string'); 22 | t.is(typeof task.username, 'string'); 23 | t.is(typeof task.cpuTime, 'number'); 24 | t.is(typeof task.windowTitle, 'string'); 25 | }; 26 | 27 | const hasAppsProps = (t, task) => { 28 | t.is(typeof task.imageName, 'string'); 29 | t.is(typeof task.pid, 'number'); 30 | t.is(typeof task.memUsage, 'number'); 31 | t.is(typeof task.packageName, 'string'); 32 | }; 33 | 34 | const hasNonVerboseAppsProps = (t, task) => { 35 | t.is(task.status, undefined); 36 | t.is(task.username, undefined); 37 | t.is(task.cpuTime, undefined); 38 | t.is(task.windowTitle, undefined); 39 | t.is(task.sessionName, undefined); 40 | t.is(task.sessionNumber, undefined); 41 | }; 42 | 43 | const hasVerboseAppsProps = (t, task) => { 44 | t.is(typeof task.status, 'string'); 45 | t.is(typeof task.username, 'string'); 46 | t.is(typeof task.cpuTime, 'number'); 47 | t.is(typeof task.windowTitle, 'string'); 48 | t.is(typeof task.sessionName, 'string'); 49 | t.is(typeof task.sessionNumber, 'number'); 50 | }; 51 | 52 | const hasModulesProps = (t, task) => { 53 | t.is(typeof task.imageName, 'string'); 54 | t.is(typeof task.pid, 'number'); 55 | t.true(Array.isArray(task.modules)); 56 | }; 57 | 58 | const hasServicesProps = (t, task) => { 59 | t.is(typeof task.imageName, 'string'); 60 | t.is(typeof task.pid, 'number'); 61 | t.true(Array.isArray(task.services)); 62 | }; 63 | 64 | const _call = options => new Promise((resolve, reject) => { 65 | try { 66 | const apiStream = tasklistStream(options); 67 | resolve(getStream.array(apiStream)); 68 | } catch (error) { 69 | reject(error); 70 | } 71 | }); 72 | 73 | const _callAndClose = options => new Promise((resolve, reject) => { 74 | try { 75 | const apiStream = tasklistStream(options); 76 | apiStream.on('data', () => apiStream.end()); 77 | apiStream.on('end', () => resolve()); 78 | } catch (error) { 79 | reject(error); 80 | } 81 | }); 82 | 83 | const macro = async (t, options) => { 84 | const tasks = await _call(options); 85 | t.true(tasks.length > 0); 86 | 87 | for (const task of tasks) { 88 | hasDefaultTaskProps(t, task); 89 | 90 | if (options.verbose) { 91 | hasVerboseTaskProps(t, task); 92 | } else { 93 | hasNonVerboseTaskProps(t, task); 94 | } 95 | } 96 | }; 97 | 98 | const appsMacro = async (t, options) => { 99 | const tasks = await _call(options); 100 | if (tasks.length === 0) { 101 | // TravisCI doesn't seem to have any apps so this test fails 102 | t.pass('Test passing with empty result, probably running inside TravisCI'); 103 | } else { 104 | for (const task of tasks) { 105 | hasAppsProps(t, task); 106 | 107 | if (options.verbose) { 108 | hasVerboseAppsProps(t, task); 109 | } else { 110 | hasNonVerboseAppsProps(t, task); 111 | } 112 | } 113 | } 114 | }; 115 | 116 | test('default', macro, {}); 117 | test('verbose option', macro, {verbose: true}); 118 | test('filter option', macro, {filter: ['sessionname eq console', 'username ne F4k3U53RN4M3']}); 119 | 120 | test('apps', appsMacro, {apps: true}); 121 | test('apps with verbose', appsMacro, {apps: true, verbose: true}); 122 | 123 | test('modules', async t => { 124 | const tasks = await _call({modules: ''}); 125 | t.true(tasks.length > 0); 126 | 127 | for (const task of tasks) { 128 | hasModulesProps(t, task); 129 | } 130 | }); 131 | 132 | test('services', async t => { 133 | const tasks = await _call({services: true}); 134 | t.true(tasks.length > 0); 135 | 136 | for (const task of tasks) { 137 | hasServicesProps(t, task); 138 | } 139 | }); 140 | 141 | test('test handle no matching tasks gracefully', async t => { 142 | const tasks = await _call({ 143 | filter: ['imagename eq does-not-exist'], 144 | }); 145 | t.is(tasks.length, 0); 146 | }); 147 | 148 | test('test handle stream close gracefully', async t => { 149 | await t.notThrowsAsync(_callAndClose()); 150 | }); 151 | -------------------------------------------------------------------------------- /transform.js: -------------------------------------------------------------------------------- 1 | import {PassThrough as PassThroughStream, Transform as TransformStream} from 'node:stream'; 2 | import sec from 'sec'; 3 | 4 | const makeTransform = convert => new TransformStream({ 5 | objectMode: true, 6 | transform: (task, _, callback) => 7 | callback(null, convert(task)), 8 | }); 9 | 10 | const defaultTransform = task => { 11 | task.pid = Number(task.pid); 12 | task.sessionNumber = Number(task.sessionNumber); 13 | task.memUsage = Number(task.memUsage.replace(/\D/g, '')) * 1024; 14 | return task; 15 | }; 16 | 17 | const defaultVerboseTransform = task => { 18 | task.pid = Number(task.pid); 19 | task.sessionNumber = Number(task.sessionNumber); 20 | task.memUsage = Number(task.memUsage.replace(/\D/g, '')) * 1024; 21 | task.cpuTime = sec(task.cpuTime); 22 | return task; 23 | }; 24 | 25 | const appsTransform = task => { 26 | task.pid = Number(task.pid); 27 | task.memUsage = Number(task.memUsage.replace(/\D/g, '')) * 1024; 28 | return task; 29 | }; 30 | 31 | const modulesTransform = task => { 32 | task.pid = Number(task.pid); 33 | task.modules = task.modules.split(','); 34 | return task; 35 | }; 36 | 37 | const servicesTransform = task => { 38 | task.pid = Number(task.pid); 39 | if (task.services) { 40 | task.services = task.services.split(','); 41 | } 42 | 43 | return task; 44 | }; 45 | 46 | const passThrough = () => new PassThroughStream({objectMode: true}); 47 | 48 | class ReportEmpty { 49 | constructor() { 50 | this.checked = false; 51 | } 52 | 53 | getTransform() { 54 | return new TransformStream({ 55 | transform: (input, _, callback) => { 56 | const stringInput = input.toString(); 57 | if (!stringInput.startsWith('"') && !this.checked) { 58 | callback(null, null); 59 | } else { 60 | callback(null, input); 61 | this.checked = true; 62 | } 63 | }, 64 | }); 65 | } 66 | } 67 | 68 | const transform = { 69 | passThrough, 70 | ReportEmpty, 71 | makeTransform, 72 | transforms: { 73 | default: defaultTransform, 74 | defaultVerbose: defaultVerboseTransform, 75 | apps: appsTransform, 76 | appsVerbose: defaultVerboseTransform, 77 | modules: modulesTransform, 78 | services: servicesTransform, 79 | }, 80 | }; 81 | 82 | export default transform; 83 | --------------------------------------------------------------------------------