Skip to content

Commit 18a519c

Browse files
axiaoansoyuka
authored andcommitted
fix(wmic): add gwmi command to be compatible with deleted wmic
1 parent 61480f7 commit 18a519c

File tree

5 files changed

+885
-2373
lines changed

5 files changed

+885
-2373
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
### 4.0.0
2+
3+
- fix wmic removed on Windows 11 and add gwmi support
4+
15
### 3.0.1
26

37
- removed dynamic requires to allow for bundling #154

lib/gwmi.js

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
'use strict'
2+
3+
const os = require('os')
4+
const bin = require('./bin')
5+
const history = require('./history')
6+
7+
function parseDate (datestr) {
8+
const year = datestr.substring(0, 4)
9+
const month = datestr.substring(4, 6)
10+
const day = datestr.substring(6, 8)
11+
const hour = datestr.substring(8, 10)
12+
const minutes = datestr.substring(10, 12)
13+
const seconds = datestr.substring(12, 14)
14+
const useconds = datestr.substring(15, 21)
15+
const sign = datestr.substring(21, 22)
16+
const tmz = parseInt(datestr.substring(22, 25), 10)
17+
const tmzh = Math.floor(tmz / 60)
18+
const tmzm = tmz % 60
19+
20+
return new Date(
21+
year + '-' + month + '-' + day + 'T' + hour +
22+
':' + minutes + ':' + seconds +
23+
'.' + useconds +
24+
sign + (tmzh > 9 ? tmzh : '0' + tmzh) + '' + (tmzm > 9 ? tmzm : '0' + tmzm)
25+
)
26+
}
27+
28+
function gwmi (pids, options, done) {
29+
let whereClause = 'ProcessId=' + pids[0]
30+
for (let i = 1; i < pids.length; i++) {
31+
whereClause += ' or ' + 'ProcessId=' + pids[i]
32+
}
33+
34+
const property = 'CreationDate,KernelModeTime,ParentProcessId,ProcessId,UserModeTime,WorkingSetSize'
35+
const args = ['win32_process', '-Filter', '\'' + whereClause + '\'', '| select ' + property, '| format-table']
36+
37+
bin('gwmi', args, { windowsHide: true, windowsVerbatimArguments: true, shell: 'powershell.exe' }, function (err, stdout, code) {
38+
if (err) {
39+
if (err.message.indexOf('No Instance(s) Available.') !== -1) {
40+
const error = new Error('No matching pid found')
41+
error.code = 'ENOENT'
42+
return done(error)
43+
}
44+
return done(err)
45+
}
46+
if (code !== 0) {
47+
return done(new Error('pidusage gwmi command exited with code ' + code))
48+
}
49+
const date = Date.now()
50+
51+
// Note: On Windows the returned value includes fractions of a second.
52+
// Use Math.floor() to get whole seconds.
53+
// Fallback on current date when uptime is not allowed (see https://github.com/soyuka/pidusage/pull/130)
54+
const uptime = Math.floor(os.uptime() || (date / 1000))
55+
56+
// Example of stdout on Windows 10
57+
// CreationDate: is in the format yyyymmddHHMMSS.mmmmmmsUUU
58+
// KernelModeTime: is in units of 100 ns
59+
// UserModeTime: is in units of 100 ns
60+
// WorkingSetSize: is in bytes
61+
//
62+
// Refs: https://superuser.com/a/937401/470946
63+
// Refs: https://msdn.microsoft.com/en-us/library/aa394372(v=vs.85).aspx
64+
// NB: The columns are returned in lexicographical order
65+
//
66+
// Stdout Format
67+
//
68+
// Active code page: 936
69+
//
70+
// CreationDate KernelModeTime ParentProcessId ProcessId UserModeTime WorkingSetSize
71+
// ------------ -------------- --------------- --------- ------------ --------------
72+
// 20220220185531.619182+480 981406250 18940 2804 572656250 61841408
73+
74+
stdout = stdout.split(os.EOL).slice(1)
75+
const index = stdout.findIndex(v => !!v)
76+
stdout = stdout.slice(index + 2)
77+
78+
if (!stdout.length) {
79+
const error = new Error('No matching pid found')
80+
error.code = 'ENOENT'
81+
return done(error)
82+
}
83+
84+
let again = false
85+
const statistics = {}
86+
for (let i = 0; i < stdout.length; i++) {
87+
const line = stdout[i].trim().split(/\s+/)
88+
89+
if (!line || line.length === 1) {
90+
continue
91+
}
92+
93+
const creation = parseDate(line[0])
94+
const ppid = parseInt(line[2], 10)
95+
const pid = parseInt(line[3], 10)
96+
const kerneltime = Math.round(parseInt(line[1], 10) / 10000)
97+
const usertime = Math.round(parseInt(line[4], 10) / 10000)
98+
const memory = parseInt(line[5], 10)
99+
100+
let hst = history.get(pid, options.maxage)
101+
if (hst === undefined) {
102+
again = true
103+
hst = { ctime: kerneltime + usertime, uptime: uptime }
104+
}
105+
106+
// process usage since last call
107+
const total = (kerneltime + usertime - hst.ctime) / 1000
108+
// time elapsed between calls in seconds
109+
const seconds = uptime - hst.uptime
110+
const cpu = seconds > 0 ? (total / seconds) * 100 : 0
111+
112+
history.set(pid, { ctime: usertime + kerneltime, uptime: uptime }, options.maxage)
113+
114+
statistics[pid] = {
115+
cpu: cpu,
116+
memory: memory,
117+
ppid: ppid,
118+
pid: pid,
119+
ctime: usertime + kerneltime,
120+
elapsed: date - creation.getTime(),
121+
timestamp: date
122+
}
123+
}
124+
125+
if (again) {
126+
return gwmi(pids, options, function (err, stats) {
127+
if (err) return done(err)
128+
done(null, Object.assign(statistics, stats))
129+
})
130+
}
131+
done(null, statistics)
132+
})
133+
}
134+
135+
module.exports = gwmi

lib/stats.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
const fs = require('fs')
44
const os = require('os')
5+
const spawn = require('child_process').spawn
56

67
const requireMap = {
78
ps: () => require('./ps'),
89
procfile: () => require('./procfile'),
9-
wmic: () => require('./wmic')
10+
wmic: () => require('./wmic'),
11+
gwmi: () => require('./gwmi')
1012
}
1113

1214
const platformToMethod = {
@@ -57,6 +59,16 @@ function get (pids, options, callback) {
5759
if (platform !== 'win' && options.usePs === true) {
5860
fn = requireMap.ps()
5961
}
62+
if (platform === 'win') {
63+
// TODO: use https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/where to avoid try/catch
64+
try {
65+
spawn('wmic', function (err) {
66+
if (err) throw new Error(err)
67+
})
68+
} catch (err) {
69+
fn = requireMap.gwmi()
70+
}
71+
}
6072

6173
if (fn === undefined) {
6274
return callback(new Error(os.platform() + ' is not supported yet, please open an issue (https://github.com/soyuka/pidusage)'))

0 commit comments

Comments
 (0)