diff --git a/lib/get.js b/lib/get.js index 7f94efd..5b63988 100644 --- a/lib/get.js +++ b/lib/get.js @@ -7,14 +7,14 @@ var platformToMethod = { sunos: 'ps', freebsd: 'ps', netbsd: 'ps', - win: 'wmic', + win: 'powershell', linux: 'ps', aix: 'ps', }; var methodToRequireFn = { ps: () => require('./ps'), - wmic: () => require('./wmic'), + powershell: () => require('./powershell'), }; var platform = os.platform(); diff --git a/lib/powershell.js b/lib/powershell.js new file mode 100644 index 0000000..dd5a0b7 --- /dev/null +++ b/lib/powershell.js @@ -0,0 +1,87 @@ +'use strict'; +var os = require('os'); +var bin = require('./bin'); + +/** + * Gets the list of all the pids of the system through PowerShell. + * Works with both older and newer Windows versions. + * @param {Function} callback(err, list) + */ +function getProcesses(callback) { + // Check PowerShell version first to determine what command to use + var checkVersionArgs = [ + '-NoProfile', + '-NonInteractive', + '-Command', + '$PSVersionTable.PSVersion.Major' + ]; + + bin('powershell', checkVersionArgs, {windowsHide: true}, function(err, stdout, code) { + if (err) { + callback(err); + return; + } + + var psVersion = parseInt(stdout.trim(), 10); + var args; + + if (psVersion >= 3) { + // For PowerShell 3.0 and above, use Get-CimInstance + args = [ + '-NoProfile', + '-NonInteractive', + '-Command', + 'Get-CimInstance -ClassName Win32_Process | Select-Object ParentProcessId, ProcessId | Format-Table -HideTableHeaders' + ]; + } else { + // For PowerShell 2.0 and below, use Get-WmiObject + args = [ + '-NoProfile', + '-NonInteractive', + '-Command', + 'Get-WmiObject -Class Win32_Process | Select-Object ParentProcessId, ProcessId | Format-Table -HideTableHeaders' + ]; + } + + var options = {windowsHide: true, windowsVerbatimArguments: true}; + + bin('powershell', args, options, function(err, stdout, code) { + if (err) { + callback(err); + return; + } + if (code !== 0) { + callback(new Error('pidtree PowerShell command exited with code ' + code)); + return; + } + + try { + stdout = stdout.split(os.EOL); + var list = []; + for (var i = 0; i < stdout.length; i++) { + var line = stdout[i].trim(); + if (!line) continue; // Skip empty lines + + // Split by multiple spaces + var parts = line.split(/\s+/); + + // Skip lines that don't have at least two parts + if (parts.length < 2) continue; + + var ppid = parseInt(parts[0], 10); + var pid = parseInt(parts[1], 10); + + // Skip if either parse resulted in NaN + if (isNaN(ppid) || isNaN(pid)) continue; + + list.push([ppid, pid]); + } + callback(null, list); + } catch (error) { + callback(error); + } + }); + }); +} + +module.exports = getProcesses; diff --git a/lib/wmic.js b/lib/wmic.js deleted file mode 100644 index 0361728..0000000 --- a/lib/wmic.js +++ /dev/null @@ -1,49 +0,0 @@ -'use strict'; - -var os = require('os'); -var bin = require('./bin'); - -/** - * Gets the list of all the pids of the system through the wmic command. - * @param {Function} callback(err, list) - */ -function wmic(callback) { - var args = ['PROCESS', 'get', 'ParentProcessId,ProcessId']; - var options = {windowsHide: true, windowsVerbatimArguments: true}; - bin('wmic', args, options, function(err, stdout, code) { - if (err) { - callback(err); - return; - } - - if (code !== 0) { - callback(new Error('pidtree wmic command exited with code ' + code)); - return; - } - - // Example of stdout - // - // ParentProcessId ProcessId - // 0 777 - - try { - stdout = stdout.split(os.EOL); - - var list = []; - for (var i = 1; i < stdout.length; i++) { - stdout[i] = stdout[i].trim(); - if (!stdout[i]) continue; - stdout[i] = stdout[i].split(/\s+/); - stdout[i][0] = parseInt(stdout[i][0], 10); // PPID - stdout[i][1] = parseInt(stdout[i][1], 10); // PID - list.push(stdout[i]); - } - - callback(null, list); - } catch (error) { - callback(error); - } - }); -} - -module.exports = wmic; diff --git a/package.json b/package.json index 6e1e672..f23f4a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pidtree", - "version": "0.6.0", + "version": "0.6.1", "description": "Cross platform children list of a PID", "license": "MIT", "homepage": "http://github.com/simonepri/pidtree#readme", diff --git a/test/powershell.js b/test/powershell.js new file mode 100644 index 0000000..535015f --- /dev/null +++ b/test/powershell.js @@ -0,0 +1,112 @@ +import test from 'ava'; +import mockery from 'mockery'; + +import pify from 'pify'; + +import mocks from './helpers/mocks'; + +test.before(() => { + mockery.enable({ + warnOnReplace: false, + warnOnUnregistered: false, + useCleanCache: true, + }); +}); + +test.beforeEach(() => { + mockery.resetCache(); +}); + +test.after(() => { + mockery.disable(); +}); + +test('should parse PowerShell output on Windows', async t => { + // Format mimics PowerShell output from Get-CimInstance with Format-Table -HideTableHeaders + const stdout = + ` 0 777\r\r\n` + + ` 777 778\r\r\n` + + ` 0 779\r\r\n\r\r\n`; + + mockery.registerMock('child_process', { + spawn: () => mocks.spawn(stdout, '', null, 0, null), + }); + mockery.registerMock('os', { + EOL: '\r\n', + platform: () => 'linux', + type: () => 'type', + release: () => 'release', + }); + + const getProcesses = require('../lib/powershell'); + + const result = await pify(getProcesses)(); + t.deepEqual(result, [[0, 777], [777, 778], [0, 779]]); + + mockery.deregisterMock('child_process'); + mockery.deregisterMock('os'); +}); + +test('should handle empty or invalid lines', async t => { + // Test with some empty lines and invalid format lines + const stdout = + ` 0 777\r\r\n` + + `\r\r\n` + + ` invalid line\r\r\n` + + ` 777 778\r\r\n` + + ` 0 779\r\r\n\r\r\n`; + + mockery.registerMock('child_process', { + spawn: () => mocks.spawn(stdout, '', null, 0, null), + }); + mockery.registerMock('os', { + EOL: '\r\n', + platform: () => 'linux', + type: () => 'type', + release: () => 'release', + }); + + const getProcesses = require('../lib/powershell'); + + const result = await pify(getProcesses)(); + + // Let's log the actual result for debugging + console.log('Actual result:', JSON.stringify(result)); + + // Check that we have the expected number of valid entries + t.is(result.length, 3); + + // Check individual entries match expected values + // Using JSON.stringify for comparison to handle array nesting + t.is(JSON.stringify(result[0]), JSON.stringify([0, 777])); + t.is(JSON.stringify(result[1]), JSON.stringify([777, 778])); + t.is(JSON.stringify(result[2]), JSON.stringify([0, 779])); + + mockery.deregisterMock('child_process'); + mockery.deregisterMock('os'); +}); + +test('should handle error code from PowerShell', async t => { + mockery.registerMock('child_process', { + spawn: () => mocks.spawn('', '', null, 1, null), + }); + mockery.registerMock('os', { + EOL: '\r\n', + platform: () => 'linux', + type: () => 'type', + release: () => 'release', + }); + + const getProcesses = require('../lib/powershell'); + + // Use older AVA throws method instead of throwsAsync + try { + await pify(getProcesses)(); + t.fail('Expected promise to reject'); + } catch (error) { + t.is(error.message, 'pidtree PowerShell command exited with code 1'); + } + + mockery.deregisterMock('child_process'); + mockery.deregisterMock('os'); +}); diff --git a/test/wmic.js b/test/wmic.js deleted file mode 100644 index 506cedb..0000000 --- a/test/wmic.js +++ /dev/null @@ -1,48 +0,0 @@ -import test from 'ava'; -import mockery from 'mockery'; - -import pify from 'pify'; - -import mocks from './helpers/mocks'; - -test.before(() => { - mockery.enable({ - warnOnReplace: false, - warnOnUnregistered: false, - useCleanCache: true, - }); -}); - -test.beforeEach(() => { - mockery.resetCache(); -}); - -test.after(() => { - mockery.disable(); -}); - -test('should parse wmic output on Windows', async t => { - const stdout = - `ParentProcessId ProcessId\r\r\n` + - `0 777 \r\r\n` + - `777 778 \r\r\n` + - `0 779 \r\r\n\r\r\n`; - - mockery.registerMock('child_process', { - spawn: () => mocks.spawn(stdout, '', null, 0, null), - }); - mockery.registerMock('os', { - EOL: '\r\n', - platform: () => 'linux', - type: () => 'type', - release: () => 'release', - }); - - const wmic = require('../lib/wmic'); - - const result = await pify(wmic)(); - t.deepEqual(result, [[0, 777], [777, 778], [0, 779]]); - - mockery.deregisterMock('child_process'); - mockery.deregisterMock('os'); -});