From ff7c5390e34f01ac676ece4bc89d108939b9860d Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Wed, 27 Oct 2021 13:02:56 -0700 Subject: [PATCH 1/2] Add CimAttachItemsProvider Add CimAttachItemsProvider to retrieve processes with Get-CimInstance Win32_Process if powershell is found in PATH. From https://github.com/microsoft/vscode-cpptools/pull/8329 --- src/common.ts | 22 +++++++++++ src/features/processPicker.ts | 47 ++++++++++++++++++++-- test/featureTests/processPicker.test.ts | 52 ++++++++++++++++++++++++- 3 files changed, 117 insertions(+), 4 deletions(-) diff --git a/src/common.ts b/src/common.ts index f46eca015..f0dad1059 100644 --- a/src/common.ts +++ b/src/common.ts @@ -209,3 +209,25 @@ export function isSubfolderOf(subfolder: string, folder: string): boolean { // Check to see that every sub directory in subfolder exists in folder. return subfolderArray.length <= folderArray.length && subfolderArray.every((subpath, index) => folderArray[index] === subpath); } + +/** + * Find PowerShell executable from PATH (for Windows only). + */ + export function findPowerShell(): string | undefined { + const dirs: string[] = (process.env.PATH || '').replace(/"+/g, '').split(';').filter(x => x); + const exts: string[] = (process.env.PATHEXT || '').split(';'); + const names: string[] = ['pwsh', 'powershell']; + for (const name of names) { + const candidates: string[] = dirs.reduce((paths, dir) => [ + ...paths, ...exts.map(ext => path.join(dir, name + ext)) + ], []); + for (const candidate of candidates) { + try { + if (fs.statSync(candidate).isFile()) { + return name; + } + } catch (e) { + } + } + } +} \ No newline at end of file diff --git a/src/features/processPicker.ts b/src/features/processPicker.ts index 900efe37b..730f5ef31 100644 --- a/src/features/processPicker.ts +++ b/src/features/processPicker.ts @@ -10,7 +10,7 @@ import * as path from 'path'; import * as vscode from 'vscode'; import { PlatformInformation } from '../platform'; -import { getExtensionPath } from '../common'; +import { findPowerShell, getExtensionPath } from '../common'; export interface AttachItem extends vscode.QuickPickItem { id: string; @@ -265,7 +265,7 @@ export class RemoteAttachPicker { } } -class Process { +export class Process { constructor(public name: string, public pid: string, public commandLine: string, public flags: number) { } public toAttachItem(): AttachItem { @@ -282,7 +282,8 @@ class Process { export class DotNetAttachItemsProviderFactory { static Get(): AttachItemsProvider { if (os.platform() === 'win32') { - return new WmicAttachItemsProvider(); + const pwsh: string | undefined = findPowerShell(); + return pwsh ? new CimAttachItemsProvider(pwsh) : new WmicAttachItemsProvider(); } else { return new PsAttachItemsProvider(); @@ -423,6 +424,46 @@ export class PsOutputParser { } } +export class CimAttachItemsProvider extends DotNetAttachItemsProvider { + constructor(private pwsh: string) { super(); } + + // Perf numbers on Win10: + // TODO + + protected async getInternalProcessEntries(): Promise { + const pwshCommand: string = `${this.pwsh} -NoProfile -Command`; + const cimCommand: string = 'Get-CimInstance Win32_Process | Select-Object Name,ProcessId,CommandLine | ConvertTo-JSON'; + const processes: string = await execChildProcess(`${pwshCommand} "${cimCommand}"`, undefined); + return CimProcessParser.ParseProcessFromCim(processes); + } +} + +type CimProcessInfo = { + Name: string; + ProcessId: number; + CommandLine: string | null; +}; + +export class CimProcessParser { + private static get extendedLengthPathPrefix(): string { return '\\\\?\\'; } + private static get ntObjectManagerPathPrefix(): string { return '\\??\\'; } + + // Only public for tests. + public static ParseProcessFromCim(processes: string): Process[] { + const processInfos: CimProcessInfo[] = JSON.parse(processes); + return processInfos.map(info => { + let cmdline: string | undefined = info.CommandLine || undefined; + if (cmdline?.startsWith(this.extendedLengthPathPrefix)) { + cmdline = cmdline.slice(this.extendedLengthPathPrefix.length); + } + if (cmdline?.startsWith(this.ntObjectManagerPathPrefix)) { + cmdline = cmdline.slice(this.ntObjectManagerPathPrefix.length); + } + return new Process(info.Name, `${info.ProcessId}`, cmdline, null); + }); + } +} + export class WmicAttachItemsProvider extends DotNetAttachItemsProvider { protected async getInternalProcessEntries(): Promise { const wmicCommand = 'wmic process get Name,ProcessId,CommandLine /FORMAT:list'; diff --git a/test/featureTests/processPicker.test.ts b/test/featureTests/processPicker.test.ts index c5ab57d43..a4f358e3b 100644 --- a/test/featureTests/processPicker.test.ts +++ b/test/featureTests/processPicker.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { RemoteAttachPicker } from '../../src/features/processPicker'; +import { RemoteAttachPicker, Process, CimProcessParser } from '../../src/features/processPicker'; import { should } from 'chai'; suite("Remote Process Picker: Validate quoting arguments.", () => { @@ -157,6 +157,56 @@ suite("Remote Process Picker: Validate quoting arguments.", () => { pipeTransport.pipeArgs.should.deep.equal([]); }); + + test('Parse valid CIM output', () => { + // output from the command used in CimAttachItemsProvider + const cimOutput: string = String.raw`[ + { + "Name": "System Idle Process", + "ProcessId": 0, + "CommandLine": null + }, + { + "Name": "WindowsTerminal.exe", + "ProcessId": 5112, + "CommandLine": "\"C:\\Program Files\\WindowsApps\\Microsoft.WindowsTerminalPreview_1.12.2931.0_x64__8wekyb3d8bbwe\\WindowsTerminal.exe\"" + }, + { + "Name": "conhost.exe", + "ProcessId": 34560, + "CommandLine": "\\\\?\\C:\\WINDOWS\\system32\\conhost.exe --headless --width 80 --height 30 --signal 0x8e0 --server 0x824" + }, + { + "Name": "conhost.exe", + "ProcessId": 33732, + "CommandLine": "\\??\\C:\\WINDOWS\\system32\\conhost.exe 0x4" + } +]`; + + const parsedOutput: Process[] = CimProcessParser.ParseProcessFromCim(cimOutput); + + const process1: Process = parsedOutput[0]; + const process2: Process = parsedOutput[1]; + const process3: Process = parsedOutput[2]; + const process4: Process = parsedOutput[3]; + + var should = require('chai').should(); + should.not.exist(process1.commandLine); + process1.name.should.equal('System Idle Process'); + process1.pid.should.equal('0'); + + process2.commandLine.should.equal('"C:\\Program Files\\WindowsApps\\Microsoft.WindowsTerminalPreview_1.12.2931.0_x64__8wekyb3d8bbwe\\WindowsTerminal.exe"'); + process2.name.should.equal('WindowsTerminal.exe'); + process2.pid.should.equal('5112'); + + process3.commandLine.should.equal('C:\\WINDOWS\\system32\\conhost.exe --headless --width 80 --height 30 --signal 0x8e0 --server 0x824'); + process3.name.should.equal('conhost.exe'); + process3.pid.should.equal('34560'); + + process4.commandLine.should.equal('C:\\WINDOWS\\system32\\conhost.exe 0x4'); + process4.name.should.equal('conhost.exe'); + process4.pid.should.equal('33732'); + }); }); function GetWindowsWSLLaunchJSONWithArrayArgs() { From 48650b73ca402c37510d113bc0e60cd6abe6391d Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Wed, 27 Oct 2021 14:44:03 -0700 Subject: [PATCH 2/2] Address PR issues --- src/common.ts | 5 ++--- src/features/processPicker.ts | 3 --- test/featureTests/processPicker.test.ts | 2 ++ 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/common.ts b/src/common.ts index f0dad1059..b3fd90247 100644 --- a/src/common.ts +++ b/src/common.ts @@ -215,11 +215,10 @@ export function isSubfolderOf(subfolder: string, folder: string): boolean { */ export function findPowerShell(): string | undefined { const dirs: string[] = (process.env.PATH || '').replace(/"+/g, '').split(';').filter(x => x); - const exts: string[] = (process.env.PATHEXT || '').split(';'); - const names: string[] = ['pwsh', 'powershell']; + const names: string[] = ['pwsh.exe', 'powershell.exe']; for (const name of names) { const candidates: string[] = dirs.reduce((paths, dir) => [ - ...paths, ...exts.map(ext => path.join(dir, name + ext)) + ...paths, path.join(dir, name) ], []); for (const candidate of candidates) { try { diff --git a/src/features/processPicker.ts b/src/features/processPicker.ts index 730f5ef31..e7e77b738 100644 --- a/src/features/processPicker.ts +++ b/src/features/processPicker.ts @@ -427,9 +427,6 @@ export class PsOutputParser { export class CimAttachItemsProvider extends DotNetAttachItemsProvider { constructor(private pwsh: string) { super(); } - // Perf numbers on Win10: - // TODO - protected async getInternalProcessEntries(): Promise { const pwshCommand: string = `${this.pwsh} -NoProfile -Command`; const cimCommand: string = 'Get-CimInstance Win32_Process | Select-Object Name,ProcessId,CommandLine | ConvertTo-JSON'; diff --git a/test/featureTests/processPicker.test.ts b/test/featureTests/processPicker.test.ts index a4f358e3b..24cb59f8f 100644 --- a/test/featureTests/processPicker.test.ts +++ b/test/featureTests/processPicker.test.ts @@ -185,6 +185,8 @@ suite("Remote Process Picker: Validate quoting arguments.", () => { const parsedOutput: Process[] = CimProcessParser.ParseProcessFromCim(cimOutput); + parsedOutput.length.should.equal(4); + const process1: Process = parsedOutput[0]; const process2: Process = parsedOutput[1]; const process3: Process = parsedOutput[2];