Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CimAttachItemsProvider to replace WmicAttachItemsProvider #4848

Merged
merged 2 commits into from
Oct 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,24 @@ 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 names: string[] = ['pwsh.exe', 'powershell.exe'];
for (const name of names) {
const candidates: string[] = dirs.reduce<string[]>((paths, dir) => [
...paths, path.join(dir, name)
], []);
for (const candidate of candidates) {
try {
if (fs.statSync(candidate).isFile()) {
return name;
}
} catch (e) {
}
}
}
}
44 changes: 41 additions & 3 deletions src/features/processPicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand All @@ -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();
Expand Down Expand Up @@ -423,6 +424,43 @@ export class PsOutputParser {
}
}

export class CimAttachItemsProvider extends DotNetAttachItemsProvider {
constructor(private pwsh: string) { super(); }

protected async getInternalProcessEntries(): Promise<Process[]> {
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<Process[]> {
const wmicCommand = 'wmic process get Name,ProcessId,CommandLine /FORMAT:list';
Expand Down
54 changes: 53 additions & 1 deletion test/featureTests/processPicker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.", () => {
Expand Down Expand Up @@ -157,6 +157,58 @@ 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);

parsedOutput.length.should.equal(4);

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() {
Expand Down