Skip to content

Commit dac8e5a

Browse files
authored
[node-core-library] Support latest Win11 in getProcessBy* (#5461)
* [node-core-library] Support latest Win11 in getProcessBy* * Update doc comments --------- Co-authored-by: David Michon <[email protected]>
1 parent f886f6f commit dac8e5a

File tree

3 files changed

+50
-24
lines changed

3 files changed

+50
-24
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@rushstack/node-core-library",
5+
"comment": "Update `Executable.getProcessInfoBy*` APIs to use PowerShell on Windows to support latest Windows 11 versions.",
6+
"type": "minor"
7+
}
8+
],
9+
"packageName": "@rushstack/node-core-library"
10+
}

libraries/node-core-library/src/Executable.ts

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,8 @@ interface ICommandLineOptions {
214214
/**
215215
* Process information sourced from the system. This process info is sourced differently depending
216216
* on the operating system:
217-
* - On Windows, this uses the `wmic.exe` utility.
217+
* - On Windows, this uses `powershell.exe` and a scriptlet to retrieve process information.
218+
* The wmic utility that was previously used is no longer present on the latest Windows versions.
218219
* - On Unix, this uses the `ps` utility.
219220
*
220221
* @public
@@ -281,18 +282,15 @@ export function parseProcessListOutput(
281282
}
282283

283284
// win32 format:
284-
// Name ParentProcessId ProcessId
285-
// process name 1234 5678
285+
// PPID PID NAME
286+
// 51234 56784 process name
286287
// unix format:
287288
// PPID PID COMMAND
288289
// 51234 56784 process name
289290
const NAME_GROUP: 'name' = 'name';
290291
const PROCESS_ID_GROUP: 'pid' = 'pid';
291292
const PARENT_PROCESS_ID_GROUP: 'ppid' = 'ppid';
292-
const PROCESS_LIST_ENTRY_REGEX_WIN32: RegExp = new RegExp(
293-
`^(?<${NAME_GROUP}>.+?)\\s+(?<${PARENT_PROCESS_ID_GROUP}>\\d+)\\s+(?<${PROCESS_ID_GROUP}>\\d+)\\s*$`
294-
);
295-
const PROCESS_LIST_ENTRY_REGEX_UNIX: RegExp = new RegExp(
293+
const PROCESS_LIST_ENTRY_REGEX: RegExp = new RegExp(
296294
`^\\s*(?<${PARENT_PROCESS_ID_GROUP}>\\d+)\\s+(?<${PROCESS_ID_GROUP}>\\d+)\\s+(?<${NAME_GROUP}>.+?)\\s*$`
297295
);
298296

@@ -301,8 +299,7 @@ function parseProcessInfoEntry(
301299
existingProcessInfoById: Map<number, IProcessInfo>,
302300
platform: NodeJS.Platform
303301
): void {
304-
const processListEntryRegex: RegExp =
305-
platform === 'win32' ? PROCESS_LIST_ENTRY_REGEX_WIN32 : PROCESS_LIST_ENTRY_REGEX_UNIX;
302+
const processListEntryRegex: RegExp = PROCESS_LIST_ENTRY_REGEX;
306303
const match: RegExpMatchArray | null = line.match(processListEntryRegex);
307304
if (!match?.groups) {
308305
throw new InternalError(`Invalid process list entry: ${line}`);
@@ -369,15 +366,20 @@ function getProcessListProcessOptions(): ICommandLineOptions {
369366
let command: string;
370367
let args: string[];
371368
if (OS_PLATFORM === 'win32') {
372-
command = 'wmic.exe';
373-
// Order of declared properties does not impact the order of the output
374-
args = ['process', 'get', 'Name,ParentProcessId,ProcessId'];
369+
command = 'powershell.exe';
370+
// Order of declared properties sets the order of the output.
371+
// Put name last to simplify parsing, since it can contain spaces.
372+
args = [
373+
'-NoProfile',
374+
'-Command',
375+
`'PPID PID Name'; Get-CimInstance Win32_Process | % { '{0} {1} {2}' -f $_.ParentProcessId, $_.ProcessId, $_.Name }`
376+
];
375377
} else {
376378
command = 'ps';
377379
// -A: Select all processes
378380
// -w: Wide format
379381
// -o: User-defined format
380-
// Order of declared properties impacts the order of the output. We will
382+
// Order of declared properties sets the order of the output. We will
381383
// need to request the "comm" property last in order to ensure that the
382384
// process names are not truncated on certain platforms
383385
args = ['-Awo', 'ppid,pid,comm'];
@@ -654,7 +656,7 @@ export class Executable {
654656
* Get the list of processes currently running on the system, keyed by the process ID.
655657
*
656658
* @remarks The underlying implementation depends on the operating system:
657-
* - On Windows, this uses the `wmic.exe` utility.
659+
* - On Windows, this uses `powershell.exe` and the `Get-CimInstance` cmdlet.
658660
* - On Unix, this uses the `ps` utility.
659661
*/
660662
public static async getProcessInfoByIdAsync(): Promise<Map<number, IProcessInfo>> {
@@ -693,7 +695,7 @@ export class Executable {
693695
* with the same name will be grouped.
694696
*
695697
* @remarks The underlying implementation depends on the operating system:
696-
* - On Windows, this uses the `wmic.exe` utility.
698+
* - On Windows, this uses `powershell.exe` and the `Get-CimInstance` cmdlet.
697699
* - On Unix, this uses the `ps` utility.
698700
*/
699701
public static async getProcessInfoByNameAsync(): Promise<Map<string, IProcessInfo[]>> {

libraries/node-core-library/src/test/Executable.test.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -354,22 +354,20 @@ describe('Executable process tests', () => {
354354

355355
describe('Executable process list', () => {
356356
const WIN32_PROCESS_LIST_OUTPUT: (string | null)[] = [
357-
'Name ParentProcessId ProcessId\r\r\n',
357+
'PPID PID NAME\r\n',
358358
// Test that the parser can handle referencing a parent that is the same as the current process
359359
// Test that the parser can handle multiple return characters
360-
'System Idle Process 0 0\r\r\n',
361-
'System 0 1\r\r\n',
362-
'executable2.exe ',
363-
// Test that the parser can handle a line that is truncated in the middle of a field
360+
'0 0 System Idle Process\r\n',
361+
'0 1 System\r\n',
364362
// Test that the parser can handle an entry referencing a parent that hasn't been seen yet
365-
' 2 4\r\r\n',
366-
'executable0.exe 1 2\r\r\n',
363+
'2 4 executable2.exe\r\n',
364+
'1 2 executable0.exe\r\n',
367365
// Test children handling when multiple entries reference the same parent
368-
'executable1.exe 1 3\r\r\n',
366+
'1 3 executable1.exe\r\n',
369367
// Test that the parser can handle empty strings
370368
'',
371369
// Test that the parser can handle referencing a parent that doesn't exist
372-
'executable3.exe 6 5\r\r\n'
370+
'6 5 executable3.exe\r\n'
373371
];
374372

375373
const UNIX_PROCESS_LIST_OUTPUT: (string | null)[] = [
@@ -388,6 +386,22 @@ describe('Executable process list', () => {
388386
' 1 3 process1\n'
389387
];
390388

389+
test('contains the current pid (sync)', () => {
390+
const results: ReadonlyMap<number, IProcessInfo> = Executable.getProcessInfoById();
391+
const currentProcessInfo: IProcessInfo | undefined = results.get(process.pid);
392+
expect(currentProcessInfo).toBeDefined();
393+
expect(currentProcessInfo?.parentProcessInfo?.processId).toEqual(process.ppid);
394+
expect(currentProcessInfo?.processName.startsWith('node')).toBe(true);
395+
});
396+
397+
test('contains the current pid (async)', async () => {
398+
const results: ReadonlyMap<number, IProcessInfo> = await Executable.getProcessInfoByIdAsync();
399+
const currentProcessInfo: IProcessInfo | undefined = results.get(process.pid);
400+
expect(currentProcessInfo).toBeDefined();
401+
expect(currentProcessInfo?.parentProcessInfo?.processId).toEqual(process.ppid);
402+
expect(currentProcessInfo?.processName.startsWith('node')).toBe(true);
403+
});
404+
391405
test('parses win32 output', () => {
392406
const processListMap: Map<number, IProcessInfo> = parseProcessListOutput(
393407
WIN32_PROCESS_LIST_OUTPUT,

0 commit comments

Comments
 (0)