From 9770c623f92f0defd9714e51b69050514bb31e4f Mon Sep 17 00:00:00 2001 From: ZA139 Date: Mon, 13 Oct 2025 15:59:22 +0800 Subject: [PATCH 01/10] Add powerShell process parser due to wmic has been deprecated create this file to replace wmicProcessParser.ts Successfully attach and debug python debugger todo: use pipe to instead powershell file perf data test cases --- .../scripts/noConfigScripts/processSelect.ps1 | 4 + .../powerShellProcessParser.ts | 86 +++++++++++++++++++ .../debugger/attachQuickPick/provider.ts | 7 +- 3 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 bundled/scripts/noConfigScripts/processSelect.ps1 create mode 100644 src/extension/debugger/attachQuickPick/powerShellProcessParser.ts diff --git a/bundled/scripts/noConfigScripts/processSelect.ps1 b/bundled/scripts/noConfigScripts/processSelect.ps1 new file mode 100644 index 00000000..dc31f354 --- /dev/null +++ b/bundled/scripts/noConfigScripts/processSelect.ps1 @@ -0,0 +1,4 @@ +# PowerShell script +$env:DEBUGPY_ADAPTER_ENDPOINTS = $env:VSCODE_DEBUGPY_ADAPTER_ENDPOINTS +$os = [System.Environment]::OSVersion.Platform +Get-WmiObject Win32_Process | Select-Object Name,CommandLine,ProcessId | Format-List \ No newline at end of file diff --git a/src/extension/debugger/attachQuickPick/powerShellProcessParser.ts b/src/extension/debugger/attachQuickPick/powerShellProcessParser.ts new file mode 100644 index 00000000..d2bb5d52 --- /dev/null +++ b/src/extension/debugger/attachQuickPick/powerShellProcessParser.ts @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// due to wmic has been deprecated create this file to replace wmicProcessParser.ts + +'use strict'; + +import { IAttachItem, ProcessListCommand } from './types'; + +export namespace PowerShellProcessParser { + const powerShellNameTitle = 'Name'; + const powerShellCommandLineTitle = 'CommandLine'; + const powerShellPidTitle = 'ProcessId'; + const defaultEmptyEntry: IAttachItem = { + label: '', + description: '', + detail: '', + id: '', + processName: '', + commandLine: '', + }; + + // Perf numbers on Win10: + // | # of processes | Time (ms) | + // |----------------+-----------| + // | 309 | 413 | + // | 407 | 463 | + // | 887 | 746 | + // | 1308 | 1132 | + export const powerShellCommand: ProcessListCommand = { + command: 'powershell', + args: ['-ExecutionPolicy','ByPass','-File','D:\\vscode-python-debugger\\bundled\\scripts\\noConfigScripts\\processSelect.ps1'], + }; + + export function parseProcesses(processes: string): IAttachItem[] { + const lines: string[] = processes.split('\r\n'); + const processEntries: IAttachItem[] = []; + let entry = { ...defaultEmptyEntry }; + for (const line of lines) { + if (!line.length) { + continue; + } + + parseLineFromPowerShell(line, entry); + + // Each entry of processes has ProcessId as the last line + if (line.lastIndexOf(powerShellPidTitle, 0) === 0) { + processEntries.push(entry); + entry = { ...defaultEmptyEntry }; + console.log(line); + console.log('pushed' + JSON.stringify(entry)); + } + } + + return processEntries; + } + + function parseLineFromPowerShell(line: string, item: IAttachItem): IAttachItem { + const splitter = line.indexOf(':'); + const currentItem = item; + + if (splitter > 0) { + const key = line.slice(0, splitter).trim().replace(/\s+/g, ''); + let value = line.slice(splitter + 1).trim(); + if (key === powerShellNameTitle) { + currentItem.label = value; + currentItem.processName = value; + } else if (key === powerShellPidTitle) { + currentItem.description = value; + currentItem.id = value; + } else if (key === powerShellCommandLineTitle) { + const dosDevicePrefix = '\\??\\'; // DOS device prefix, see https://reverseengineering.stackexchange.com/a/15178 + if (value.lastIndexOf(dosDevicePrefix, 0) === 0) { + value = value.slice(dosDevicePrefix.length); + } + + currentItem.detail = value; + currentItem.commandLine = value; + } + + console.log(`key='${key}', value='${value}' item=${JSON.stringify(currentItem)}`); + } + + return currentItem; + } +} diff --git a/src/extension/debugger/attachQuickPick/provider.ts b/src/extension/debugger/attachQuickPick/provider.ts index 8699f479..d4daab65 100644 --- a/src/extension/debugger/attachQuickPick/provider.ts +++ b/src/extension/debugger/attachQuickPick/provider.ts @@ -7,7 +7,7 @@ import { l10n } from 'vscode'; import { getOSType, OSType } from '../../common/platform'; import { PsProcessParser } from './psProcessParser'; import { IAttachItem, IAttachProcessProvider, ProcessListCommand } from './types'; -import { WmicProcessParser } from './wmicProcessParser'; +import { PowerShellProcessParser } from './powerShellProcessParser'; import { getEnvironmentVariables } from '../../common/python'; import { plainExec } from '../../common/process/rawProcessApis'; import { logProcess } from '../../common/process/logger'; @@ -65,17 +65,18 @@ export class AttachProcessProvider implements IAttachProcessProvider { } else if (osType === OSType.Linux) { processCmd = PsProcessParser.psLinuxCommand; } else if (osType === OSType.Windows) { - processCmd = WmicProcessParser.wmicCommand; + processCmd = PowerShellProcessParser.powerShellCommand; } else { throw new Error(l10n.t("Operating system '{0}' not supported.", osType)); } const customEnvVars = await getEnvironmentVariables(); const output = await plainExec(processCmd.command, processCmd.args, { throwOnStdErr: true }, customEnvVars); + console.log(output); logProcess(processCmd.command, processCmd.args, { throwOnStdErr: true }); return osType === OSType.Windows - ? WmicProcessParser.parseProcesses(output.stdout) + ? PowerShellProcessParser.parseProcesses(output.stdout) : PsProcessParser.parseProcesses(output.stdout); } } From b35f0c782c6e86e02f861b77d350a0bfcb30b600 Mon Sep 17 00:00:00 2001 From: ZA139 Date: Mon, 13 Oct 2025 17:36:25 +0800 Subject: [PATCH 02/10] Instead Json Parser of string parser with powershell --- .../powerShellProcessParser.ts | 76 +++++-------------- 1 file changed, 21 insertions(+), 55 deletions(-) diff --git a/src/extension/debugger/attachQuickPick/powerShellProcessParser.ts b/src/extension/debugger/attachQuickPick/powerShellProcessParser.ts index d2bb5d52..e5ce781f 100644 --- a/src/extension/debugger/attachQuickPick/powerShellProcessParser.ts +++ b/src/extension/debugger/attachQuickPick/powerShellProcessParser.ts @@ -8,18 +8,7 @@ import { IAttachItem, ProcessListCommand } from './types'; export namespace PowerShellProcessParser { - const powerShellNameTitle = 'Name'; - const powerShellCommandLineTitle = 'CommandLine'; - const powerShellPidTitle = 'ProcessId'; - const defaultEmptyEntry: IAttachItem = { - label: '', - description: '', - detail: '', - id: '', - processName: '', - commandLine: '', - }; - + // Perf numbers on Win10: // | # of processes | Time (ms) | // |----------------+-----------| @@ -33,54 +22,31 @@ export namespace PowerShellProcessParser { }; export function parseProcesses(processes: string): IAttachItem[] { - const lines: string[] = processes.split('\r\n'); + const processesArray = JSON.parse(processes); const processEntries: IAttachItem[] = []; - let entry = { ...defaultEmptyEntry }; - for (const line of lines) { - if (!line.length) { + for (const process of processesArray) { + if (!process.ProcessId) { continue; } - - parseLineFromPowerShell(line, entry); - - // Each entry of processes has ProcessId as the last line - if (line.lastIndexOf(powerShellPidTitle, 0) === 0) { - processEntries.push(entry); - entry = { ...defaultEmptyEntry }; - console.log(line); - console.log('pushed' + JSON.stringify(entry)); - } - } - - return processEntries; - } - - function parseLineFromPowerShell(line: string, item: IAttachItem): IAttachItem { - const splitter = line.indexOf(':'); - const currentItem = item; - - if (splitter > 0) { - const key = line.slice(0, splitter).trim().replace(/\s+/g, ''); - let value = line.slice(splitter + 1).trim(); - if (key === powerShellNameTitle) { - currentItem.label = value; - currentItem.processName = value; - } else if (key === powerShellPidTitle) { - currentItem.description = value; - currentItem.id = value; - } else if (key === powerShellCommandLineTitle) { - const dosDevicePrefix = '\\??\\'; // DOS device prefix, see https://reverseengineering.stackexchange.com/a/15178 - if (value.lastIndexOf(dosDevicePrefix, 0) === 0) { - value = value.slice(dosDevicePrefix.length); + const entry: IAttachItem = { + label: process.Name || '', + processName: process.Name || '', + description: String(process.ProcessId), + id: String(process.ProcessId), + detail: '', + commandLine: '', + }; + if (process.CommandLine) { + const dosDevicePrefix = '\\??\\';// DOS device prefix, see https://reverseengineering.stackexchange.com/a/15178 + let commandLine = process.CommandLine; + if (commandLine.startsWith(dosDevicePrefix)) { + commandLine = commandLine.slice(dosDevicePrefix.length); } - - currentItem.detail = value; - currentItem.commandLine = value; + entry.detail = commandLine; + entry.commandLine = commandLine; } - - console.log(`key='${key}', value='${value}' item=${JSON.stringify(currentItem)}`); + processEntries.push(entry); } - - return currentItem; + return processEntries; } } From 246b510347c21e80d5c504e7724520417a087c5f Mon Sep 17 00:00:00 2001 From: ZA139 Date: Wed, 15 Oct 2025 21:11:52 +0800 Subject: [PATCH 03/10] update json format & Add Get-WmiObject For the legacy compatibility json format with camelCase Add Get-WmiObject For the legacy compatibility --- .../powerShellProcessParser.ts | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/extension/debugger/attachQuickPick/powerShellProcessParser.ts b/src/extension/debugger/attachQuickPick/powerShellProcessParser.ts index e5ce781f..74d42b5d 100644 --- a/src/extension/debugger/attachQuickPick/powerShellProcessParser.ts +++ b/src/extension/debugger/attachQuickPick/powerShellProcessParser.ts @@ -8,7 +8,6 @@ import { IAttachItem, ProcessListCommand } from './types'; export namespace PowerShellProcessParser { - // Perf numbers on Win10: // | # of processes | Time (ms) | // |----------------+-----------| @@ -18,27 +17,31 @@ export namespace PowerShellProcessParser { // | 1308 | 1132 | export const powerShellCommand: ProcessListCommand = { command: 'powershell', - args: ['-ExecutionPolicy','ByPass','-File','D:\\vscode-python-debugger\\bundled\\scripts\\noConfigScripts\\processSelect.ps1'], + args: [ + '-Command', + '$processes = if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) { Get-CimInstance Win32_Process } else { Get-WmiObject Win32_Process }; \ + $processes | % { @{ name = $_.Name; commandLine = $_.CommandLine; processId = $_.ProcessId } } | ConvertTo-Json', + ], // Get-WmiObject For the legacy compatibility }; export function parseProcesses(processes: string): IAttachItem[] { const processesArray = JSON.parse(processes); const processEntries: IAttachItem[] = []; for (const process of processesArray) { - if (!process.ProcessId) { + if (!process.processId) { continue; } const entry: IAttachItem = { - label: process.Name || '', - processName: process.Name || '', - description: String(process.ProcessId), - id: String(process.ProcessId), + label: process.name || '', + processName: process.name || '', + description: String(process.processId), + id: String(process.processId), detail: '', commandLine: '', }; - if (process.CommandLine) { - const dosDevicePrefix = '\\??\\';// DOS device prefix, see https://reverseengineering.stackexchange.com/a/15178 - let commandLine = process.CommandLine; + if (process.commandLine) { + const dosDevicePrefix = '\\??\\'; // DOS device prefix, see https://reverseengineering.stackexchange.com/a/15178 + let commandLine = process.commandLine; if (commandLine.startsWith(dosDevicePrefix)) { commandLine = commandLine.slice(dosDevicePrefix.length); } From 9bf13911b293261f992d36942e724d4019aba151 Mon Sep 17 00:00:00 2001 From: ZA139 Date: Tue, 28 Oct 2025 15:41:05 +0800 Subject: [PATCH 04/10] Add WMIC fallback for Windows process listing If PowerShell is unavailable on Windows, the process provider now falls back to using WMIC for process listing. This improves compatibility with older Windows systems where PowerShell may not be present. --- .../debugger/attachQuickPick/provider.ts | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/extension/debugger/attachQuickPick/provider.ts b/src/extension/debugger/attachQuickPick/provider.ts index d4daab65..56202e98 100644 --- a/src/extension/debugger/attachQuickPick/provider.ts +++ b/src/extension/debugger/attachQuickPick/provider.ts @@ -11,6 +11,7 @@ import { PowerShellProcessParser } from './powerShellProcessParser'; import { getEnvironmentVariables } from '../../common/python'; import { plainExec } from '../../common/process/rawProcessApis'; import { logProcess } from '../../common/process/logger'; +import { WmicProcessParser } from './wmicProcessParser'; export class AttachProcessProvider implements IAttachProcessProvider { constructor() {} @@ -71,12 +72,33 @@ export class AttachProcessProvider implements IAttachProcessProvider { } const customEnvVars = await getEnvironmentVariables(); + if (processCmd === PowerShellProcessParser.powerShellCommand) { + try { + const checkPowerShell = await plainExec( + 'where', + ['powershell'], + { throwOnStdErr: false }, + customEnvVars, + ); + if (checkPowerShell.stdout.length === 0) { + processCmd = WmicProcessParser.wmicCommand; + } + } catch { + // If 'where' fails, fall back to wmic (most likely powershell is not available).(Windows Xp or below? + console.log('PowerShell check failed, using WMIC fallback'); + processCmd = WmicProcessParser.wmicCommand; + } + } const output = await plainExec(processCmd.command, processCmd.args, { throwOnStdErr: true }, customEnvVars); - console.log(output); logProcess(processCmd.command, processCmd.args, { throwOnStdErr: true }); - return osType === OSType.Windows - ? PowerShellProcessParser.parseProcesses(output.stdout) - : PsProcessParser.parseProcesses(output.stdout); + if (osType === OSType.Windows) { + if (processCmd === WmicProcessParser.wmicCommand) { + return WmicProcessParser.parseProcesses(output.stdout); + } else if (processCmd === PowerShellProcessParser.powerShellCommand) { + return PowerShellProcessParser.parseProcesses(output.stdout); + } + } + return PsProcessParser.parseProcesses(output.stdout); } } From febb9d3f0764bc2c185d758c8bd093ce68ec2cb7 Mon Sep 17 00:00:00 2001 From: ZA139 Date: Tue, 28 Oct 2025 16:36:24 +0800 Subject: [PATCH 05/10] Delete processSelect.ps1 --- bundled/scripts/noConfigScripts/processSelect.ps1 | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 bundled/scripts/noConfigScripts/processSelect.ps1 diff --git a/bundled/scripts/noConfigScripts/processSelect.ps1 b/bundled/scripts/noConfigScripts/processSelect.ps1 deleted file mode 100644 index dc31f354..00000000 --- a/bundled/scripts/noConfigScripts/processSelect.ps1 +++ /dev/null @@ -1,4 +0,0 @@ -# PowerShell script -$env:DEBUGPY_ADAPTER_ENDPOINTS = $env:VSCODE_DEBUGPY_ADAPTER_ENDPOINTS -$os = [System.Environment]::OSVersion.Platform -Get-WmiObject Win32_Process | Select-Object Name,CommandLine,ProcessId | Format-List \ No newline at end of file From 8986c51197d5f7a81aa32840d7060d0771fc4313 Mon Sep 17 00:00:00 2001 From: ZA139 Date: Wed, 29 Oct 2025 17:17:04 +0800 Subject: [PATCH 06/10] Add optional process command to attach provider The unit tests need to specify list command because we have two commands on Windows now. The AttachProcessProvider now accepts an optional ProcessListCommand parameter in getAttachItems and _getInternalProcessEntries, allowing callers to specify the process listing command. Updated unit tests to use the new parameter for more precise control over process command selection. --- .../debugger/attachQuickPick/provider.ts | 39 ++++++++++--------- .../attachQuickPick/provider.unit.test.ts | 6 +-- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/extension/debugger/attachQuickPick/provider.ts b/src/extension/debugger/attachQuickPick/provider.ts index 56202e98..1a087cae 100644 --- a/src/extension/debugger/attachQuickPick/provider.ts +++ b/src/extension/debugger/attachQuickPick/provider.ts @@ -16,8 +16,8 @@ import { WmicProcessParser } from './wmicProcessParser'; export class AttachProcessProvider implements IAttachProcessProvider { constructor() {} - public getAttachItems(): Promise { - return this._getInternalProcessEntries().then((processEntries) => { + public getAttachItems(specCommand?: ProcessListCommand): Promise { + return this._getInternalProcessEntries(specCommand).then((processEntries) => { processEntries.sort( ( { processName: aprocessName, commandLine: aCommandLine }, @@ -58,19 +58,22 @@ export class AttachProcessProvider implements IAttachProcessProvider { }); } - public async _getInternalProcessEntries(): Promise { + public async _getInternalProcessEntries(specCommand?: ProcessListCommand): Promise { let processCmd: ProcessListCommand; - const osType = getOSType(); - if (osType === OSType.OSX) { - processCmd = PsProcessParser.psDarwinCommand; - } else if (osType === OSType.Linux) { - processCmd = PsProcessParser.psLinuxCommand; - } else if (osType === OSType.Windows) { - processCmd = PowerShellProcessParser.powerShellCommand; + if (specCommand === undefined) { + const osType = getOSType(); + if (osType === OSType.OSX) { + processCmd = PsProcessParser.psDarwinCommand; + } else if (osType === OSType.Linux) { + processCmd = PsProcessParser.psLinuxCommand; + } else if (osType === OSType.Windows) { + processCmd = PowerShellProcessParser.powerShellCommand; + } else { + throw new Error(l10n.t("Operating system '{0}' not supported.", osType)); + } } else { - throw new Error(l10n.t("Operating system '{0}' not supported.", osType)); + processCmd = specCommand; } - const customEnvVars = await getEnvironmentVariables(); if (processCmd === PowerShellProcessParser.powerShellCommand) { try { @@ -83,7 +86,7 @@ export class AttachProcessProvider implements IAttachProcessProvider { if (checkPowerShell.stdout.length === 0) { processCmd = WmicProcessParser.wmicCommand; } - } catch { + } catch (error) { // If 'where' fails, fall back to wmic (most likely powershell is not available).(Windows Xp or below? console.log('PowerShell check failed, using WMIC fallback'); processCmd = WmicProcessParser.wmicCommand; @@ -92,12 +95,10 @@ export class AttachProcessProvider implements IAttachProcessProvider { const output = await plainExec(processCmd.command, processCmd.args, { throwOnStdErr: true }, customEnvVars); logProcess(processCmd.command, processCmd.args, { throwOnStdErr: true }); - if (osType === OSType.Windows) { - if (processCmd === WmicProcessParser.wmicCommand) { - return WmicProcessParser.parseProcesses(output.stdout); - } else if (processCmd === PowerShellProcessParser.powerShellCommand) { - return PowerShellProcessParser.parseProcesses(output.stdout); - } + if (processCmd === WmicProcessParser.wmicCommand) { + return WmicProcessParser.parseProcesses(output.stdout); + } else if (processCmd === PowerShellProcessParser.powerShellCommand) { + return PowerShellProcessParser.parseProcesses(output.stdout); } return PsProcessParser.parseProcesses(output.stdout); } diff --git a/src/test/unittest/attachQuickPick/provider.unit.test.ts b/src/test/unittest/attachQuickPick/provider.unit.test.ts index 259eaa07..007b425f 100644 --- a/src/test/unittest/attachQuickPick/provider.unit.test.ts +++ b/src/test/unittest/attachQuickPick/provider.unit.test.ts @@ -174,7 +174,7 @@ ProcessId=5912\r .withArgs(WmicProcessParser.wmicCommand.command, sinon.match.any, sinon.match.any, sinon.match.any) .resolves({ stdout: windowsOutput }); - const attachItems = await provider._getInternalProcessEntries(); + const attachItems = await provider._getInternalProcessEntries(WmicProcessParser.wmicCommand); sinon.assert.calledOnceWithExactly( plainExecStub, WmicProcessParser.wmicCommand.command, @@ -353,7 +353,7 @@ ProcessId=5728\r .withArgs(WmicProcessParser.wmicCommand.command, sinon.match.any, sinon.match.any, sinon.match.any) .resolves({ stdout: windowsOutput }); - const output = await provider.getAttachItems(); + const output = await provider.getAttachItems(WmicProcessParser.wmicCommand); assert.deepEqual(output, expectedOutput); }); @@ -445,7 +445,7 @@ ProcessId=8026\r .withArgs(WmicProcessParser.wmicCommand.command, sinon.match.any, sinon.match.any, sinon.match.any) .resolves({ stdout: windowsOutput }); - const output = await provider.getAttachItems(); + const output = await provider.getAttachItems(WmicProcessParser.wmicCommand); assert.deepEqual(output, expectedOutput); }); From 337dfad9a2efbe9665d225739422b1cb500e05ff Mon Sep 17 00:00:00 2001 From: ZA139 Date: Mon, 3 Nov 2025 16:31:25 +0800 Subject: [PATCH 07/10] Add test for PowerShell process parser ordering Added a unit test to verify that Python processes are listed at the top when using the PowerShell process parser in getAttachItems. This ensures consistent process ordering across both WMIC and PowerShell implementations. --- .../attachQuickPick/provider.unit.test.ts | 109 +++++++++++++++++- 1 file changed, 108 insertions(+), 1 deletion(-) diff --git a/src/test/unittest/attachQuickPick/provider.unit.test.ts b/src/test/unittest/attachQuickPick/provider.unit.test.ts index 007b425f..4340147a 100644 --- a/src/test/unittest/attachQuickPick/provider.unit.test.ts +++ b/src/test/unittest/attachQuickPick/provider.unit.test.ts @@ -13,6 +13,7 @@ import { IAttachItem } from '../../../extension/debugger/attachQuickPick/types'; import { WmicProcessParser } from '../../../extension/debugger/attachQuickPick/wmicProcessParser'; import * as platform from '../../../extension/common/platform'; import * as rawProcessApis from '../../../extension/common/process/rawProcessApis'; +import { PowerShellProcessParser } from '../../../extension/debugger/attachQuickPick/powerShellProcessParser'; use(chaiAsPromised); @@ -358,7 +359,7 @@ ProcessId=5728\r assert.deepEqual(output, expectedOutput); }); - test('Python processes should be at the top of the list returned by getAttachItems', async () => { + test('Python processes should be at the top of the list returned by getAttachItems with wmic', async () => { const windowsOutput = `CommandLine=\r Name=System\r ProcessId=4\r @@ -447,6 +448,112 @@ ProcessId=8026\r const output = await provider.getAttachItems(WmicProcessParser.wmicCommand); + assert.deepEqual(output, expectedOutput); + }); + test('Python processes should be at the top of the list returned by getAttachItems with powershell', async () => { + const windowsProcesses = [ + { + processId: 4, + commandLine: null, + name: 'System', + }, + { + processId: 5372, + commandLine: null, + name: 'svchost.exe', + }, + { + processId: 5728, + commandLine: 'sihost.exe', + name: 'sihost.exe', + }, + { + processId: 5912, + commandLine: 'C:\\WINDOWS\\system32\\svchost.exe -k UnistackSvcGroup -s CDPUserSvc', + name: 'svchost.exe', + }, + { + processId: 6028, + commandLine: + 'C:\\Users\\Contoso\\AppData\\Local\\Programs\\Python\\Python37\\python.exe c:/Users/Contoso/Documents/hello_world.py', + name: 'python.exe', + }, + { + processId: 8026, + commandLine: + 'C:\\Users\\Contoso\\AppData\\Local\\Programs\\Python\\Python37\\python.exe c:/Users/Contoso/Documents/foo_bar.py', + name: 'python.exe', + }, + ]; + const windowsOutput = JSON.stringify(windowsProcesses, null, 4); + const expectedOutput: IAttachItem[] = [ + { + label: 'python.exe', + description: '8026', + detail: 'C:\\Users\\Contoso\\AppData\\Local\\Programs\\Python\\Python37\\python.exe c:/Users/Contoso/Documents/foo_bar.py', + id: '8026', + processName: 'python.exe', + commandLine: + 'C:\\Users\\Contoso\\AppData\\Local\\Programs\\Python\\Python37\\python.exe c:/Users/Contoso/Documents/foo_bar.py', + }, + { + label: 'python.exe', + description: '6028', + detail: 'C:\\Users\\Contoso\\AppData\\Local\\Programs\\Python\\Python37\\python.exe c:/Users/Contoso/Documents/hello_world.py', + id: '6028', + processName: 'python.exe', + commandLine: + 'C:\\Users\\Contoso\\AppData\\Local\\Programs\\Python\\Python37\\python.exe c:/Users/Contoso/Documents/hello_world.py', + }, + { + label: 'sihost.exe', + description: '5728', + detail: 'sihost.exe', + id: '5728', + processName: 'sihost.exe', + commandLine: 'sihost.exe', + }, + { + label: 'svchost.exe', + description: '5372', + detail: '', + id: '5372', + processName: 'svchost.exe', + commandLine: '', + }, + { + label: 'svchost.exe', + description: '5912', + detail: 'C:\\WINDOWS\\system32\\svchost.exe -k UnistackSvcGroup -s CDPUserSvc', + id: '5912', + processName: 'svchost.exe', + commandLine: 'C:\\WINDOWS\\system32\\svchost.exe -k UnistackSvcGroup -s CDPUserSvc', + }, + { + label: 'System', + description: '4', + detail: '', + id: '4', + processName: 'System', + commandLine: '', + }, + ]; + const foundPowerShellOutput = 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\r\n'; + //const notFoundPowerShellOutput = 'INFO: Could not find files for the given pattern(s).\r\n'; + plainExecStub + .withArgs('where', ['powershell'], sinon.match.any, sinon.match.any) + .resolves({ stderr: '', stdout: foundPowerShellOutput }); + plainExecStub + .withArgs( + PowerShellProcessParser.powerShellCommand.command, + sinon.match.any, + sinon.match.any, + sinon.match.any, + ) + .resolves({ stdout: windowsOutput }); + + const output = await provider.getAttachItems(PowerShellProcessParser.powerShellCommand); + assert.deepEqual(output, expectedOutput); }); }); From bd0bc2d1ae64e82710e6cf21c4c45a4ded50b116 Mon Sep 17 00:00:00 2001 From: ZA139 <40553487+ZA139@users.noreply.github.com> Date: Tue, 4 Nov 2025 00:48:30 +0800 Subject: [PATCH 08/10] Update provider powershell test cases add single powershell unit test add windows powershell call when powershell has been installed add windows wmic call when powershell hasn't been installed --- .../attachQuickPick/provider.unit.test.ts | 206 +++++++++++++++++- 1 file changed, 201 insertions(+), 5 deletions(-) diff --git a/src/test/unittest/attachQuickPick/provider.unit.test.ts b/src/test/unittest/attachQuickPick/provider.unit.test.ts index 4340147a..b67f06bd 100644 --- a/src/test/unittest/attachQuickPick/provider.unit.test.ts +++ b/src/test/unittest/attachQuickPick/provider.unit.test.ts @@ -129,7 +129,7 @@ suite('Attach to process - process provider', () => { assert.deepEqual(attachItems, expectedOutput); }); - test('The Windows process list command should be called if the platform is Windows', async () => { + test('The Windows wmic process list command should be called if the platform is Windows when powershell has not been installed', async () => { const windowsOutput = `CommandLine=\r Name=System\r ProcessId=4\r @@ -171,13 +171,25 @@ ProcessId=5912\r }, ]; getOSTypeStub.returns(platform.OSType.Windows); + const notFoundPowerShellOutput = 'INFO: Could not find files for the given pattern(s).\r\n'; + plainExecStub + .withArgs('where', ['powershell'], sinon.match.any, sinon.match.any) + .resolves({ stderr: notFoundPowerShellOutput, stdout: '' }); plainExecStub .withArgs(WmicProcessParser.wmicCommand.command, sinon.match.any, sinon.match.any, sinon.match.any) .resolves({ stdout: windowsOutput }); - const attachItems = await provider._getInternalProcessEntries(WmicProcessParser.wmicCommand); - sinon.assert.calledOnceWithExactly( - plainExecStub, + const attachItems = await provider._getInternalProcessEntries(); + sinon.assert.calledTwice(plainExecStub); + sinon.assert.calledWithExactly( + plainExecStub.firstCall, + 'where', + ['powershell'], + sinon.match.any, + sinon.match.any, + ); + sinon.assert.calledWithExactly( + plainExecStub.secondCall, WmicProcessParser.wmicCommand.command, WmicProcessParser.wmicCommand.args, sinon.match.any, @@ -308,7 +320,7 @@ ProcessId=5912\r getOSTypeStub.returns(platform.OSType.Windows); }); - test('Items returned by getAttachItems should be sorted alphabetically', async () => { + test('Items returned by getAttachItems should be sorted alphabetically with wmic', async () => { const windowsOutput = `CommandLine=\r Name=System\r ProcessId=4\r @@ -359,6 +371,69 @@ ProcessId=5728\r assert.deepEqual(output, expectedOutput); }); + test('Items returned by getAttachItems should be sorted alphabetically with powershell', async () => { + const windowsProcesses = [ + { + processId: 4, + commandLine: null, + name: 'System', + }, + { + processId: 5372, + commandLine: null, + name: 'svchost.exe', + }, + { + processId: 5728, + commandLine: 'sihost.exe', + name: 'sihost.exe', + }, + ]; + const expectedOutput: IAttachItem[] = [ + { + label: 'sihost.exe', + description: '5728', + detail: 'sihost.exe', + id: '5728', + processName: 'sihost.exe', + commandLine: 'sihost.exe', + }, + { + label: 'svchost.exe', + description: '5372', + detail: '', + id: '5372', + processName: 'svchost.exe', + commandLine: '', + }, + { + label: 'System', + description: '4', + detail: '', + id: '4', + processName: 'System', + commandLine: '', + }, + ]; + const foundPowerShellOutput = 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\r\n'; + plainExecStub + .withArgs('where', ['powershell'], sinon.match.any, sinon.match.any) + .resolves({ stderr: '', stdout: foundPowerShellOutput }); + const windowsOutput = JSON.stringify(windowsProcesses, null, 4); + plainExecStub + .withArgs( + PowerShellProcessParser.powerShellCommand.command, + sinon.match.any, + sinon.match.any, + sinon.match.any, + ) + .resolves({ stdout: windowsOutput }); + + const output = await provider.getAttachItems(PowerShellProcessParser.powerShellCommand); + + assert.deepEqual(output, expectedOutput); + }); + test('Python processes should be at the top of the list returned by getAttachItems with wmic', async () => { const windowsOutput = `CommandLine=\r Name=System\r @@ -556,5 +631,126 @@ ProcessId=8026\r assert.deepEqual(output, expectedOutput); }); + + test('The Windows powershell process list command should be called if the platform is Windows when powershell has been installed', async () => { + const windowsProcesses = [ + { + processId: 4, + commandLine: null, + name: 'System', + }, + { + processId: 5372, + commandLine: null, + name: 'svchost.exe', + }, + { + processId: 5728, + commandLine: 'sihost.exe', + name: 'sihost.exe', + }, + { + processId: 5912, + commandLine: 'C:\\WINDOWS\\system32\\svchost.exe -k UnistackSvcGroup -s CDPUserSvc', + name: 'svchost.exe', + }, + { + processId: 6028, + commandLine: + 'C:\\Users\\ZA139\\AppData\\Local\\Programs\\Python\\Python37\\python.exe c:/Users/Contoso/Documents/hello_world.py', + name: 'python.exe', + }, + { + processId: 8026, + commandLine: + 'C:\\Users\\ZA139\\AppData\\Local\\Programs\\Python\\Python37\\python.exe c:/Users/Contoso/Documents/foo_bar.py', + name: 'python.exe', + }, + ]; + const windowsOutput = JSON.stringify(windowsProcesses, null, 4); + const expectedOutput: IAttachItem[] = [ + { + label: 'python.exe', + description: '8026', + detail: 'C:\\Users\\ZA139\\AppData\\Local\\Programs\\Python\\Python37\\python.exe c:/Users/Contoso/Documents/foo_bar.py', + id: '8026', + processName: 'python.exe', + commandLine: + 'C:\\Users\\ZA139\\AppData\\Local\\Programs\\Python\\Python37\\python.exe c:/Users/Contoso/Documents/foo_bar.py', + }, + { + label: 'python.exe', + description: '6028', + detail: 'C:\\Users\\ZA139\\AppData\\Local\\Programs\\Python\\Python37\\python.exe c:/Users/Contoso/Documents/hello_world.py', + id: '6028', + processName: 'python.exe', + commandLine: + 'C:\\Users\\ZA139\\AppData\\Local\\Programs\\Python\\Python37\\python.exe c:/Users/Contoso/Documents/hello_world.py', + }, + { + label: 'sihost.exe', + description: '5728', + detail: 'sihost.exe', + id: '5728', + processName: 'sihost.exe', + commandLine: 'sihost.exe', + }, + { + label: 'svchost.exe', + description: '5372', + detail: '', + id: '5372', + processName: 'svchost.exe', + commandLine: '', + }, + { + label: 'svchost.exe', + description: '5912', + detail: 'C:\\WINDOWS\\system32\\svchost.exe -k UnistackSvcGroup -s CDPUserSvc', + id: '5912', + processName: 'svchost.exe', + commandLine: 'C:\\WINDOWS\\system32\\svchost.exe -k UnistackSvcGroup -s CDPUserSvc', + }, + { + label: 'System', + description: '4', + detail: '', + id: '4', + processName: 'System', + commandLine: '', + }, + ]; + getOSTypeStub.returns(platform.OSType.Windows); + const foundPowerShellOutput = 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\r\n'; + plainExecStub + .withArgs('where', ['powershell'], sinon.match.any, sinon.match.any) + .resolves({ stderr: '', stdout: foundPowerShellOutput }); + plainExecStub + .withArgs( + PowerShellProcessParser.powerShellCommand.command, + sinon.match.any, + sinon.match.any, + sinon.match.any, + ) + .resolves({ stdout: windowsOutput }); + + const output = await provider.getAttachItems(); + sinon.assert.calledTwice(plainExecStub); + sinon.assert.calledWithExactly( + plainExecStub.firstCall, + 'where', + ['powershell'], + sinon.match.any, + sinon.match.any, + ); + sinon.assert.calledWithExactly( + plainExecStub.secondCall, + PowerShellProcessParser.powerShellCommand.command, + PowerShellProcessParser.powerShellCommand.args, + sinon.match.any, + sinon.match.any, + ); + assert.deepEqual(output, expectedOutput); + }); }); }); From 332c2dfd30202f0b5385b70186efed7557da5f6c Mon Sep 17 00:00:00 2001 From: ZA139 <40553487+ZA139@users.noreply.github.com> Date: Tue, 4 Nov 2025 01:36:53 +0800 Subject: [PATCH 09/10] Add unit tests when Get-CimInstance fails --- .../powerShellProcessParser.ts | 17 +++--- .../debugger/attachQuickPick/provider.ts | 5 +- .../attachQuickPick/provider.unit.test.ts | 58 +++++++++++++++++++ 3 files changed, 71 insertions(+), 9 deletions(-) diff --git a/src/extension/debugger/attachQuickPick/powerShellProcessParser.ts b/src/extension/debugger/attachQuickPick/powerShellProcessParser.ts index 74d42b5d..49dea96a 100644 --- a/src/extension/debugger/attachQuickPick/powerShellProcessParser.ts +++ b/src/extension/debugger/attachQuickPick/powerShellProcessParser.ts @@ -8,13 +8,6 @@ import { IAttachItem, ProcessListCommand } from './types'; export namespace PowerShellProcessParser { - // Perf numbers on Win10: - // | # of processes | Time (ms) | - // |----------------+-----------| - // | 309 | 413 | - // | 407 | 463 | - // | 887 | 746 | - // | 1308 | 1132 | export const powerShellCommand: ProcessListCommand = { command: 'powershell', args: [ @@ -24,6 +17,16 @@ export namespace PowerShellProcessParser { ], // Get-WmiObject For the legacy compatibility }; + //for unit test with Get-WmiObject + export const powerShellWithoutCimCommand: ProcessListCommand = { + command: 'powershell', + args: [ + '-Command', + '$processes = if (Get-Command NotExistCommand-That-Will-Never-Exist -ErrorAction SilentlyContinue) { Get-CimInstance Win32_Process } else { Get-WmiObject Win32_Process }; \ + $processes | % { @{ name = $_.Name; commandLine = $_.CommandLine; processId = $_.ProcessId } } | ConvertTo-Json', + ], + }; + export function parseProcesses(processes: string): IAttachItem[] { const processesArray = JSON.parse(processes); const processEntries: IAttachItem[] = []; diff --git a/src/extension/debugger/attachQuickPick/provider.ts b/src/extension/debugger/attachQuickPick/provider.ts index 1a087cae..39f665e4 100644 --- a/src/extension/debugger/attachQuickPick/provider.ts +++ b/src/extension/debugger/attachQuickPick/provider.ts @@ -94,10 +94,11 @@ export class AttachProcessProvider implements IAttachProcessProvider { } const output = await plainExec(processCmd.command, processCmd.args, { throwOnStdErr: true }, customEnvVars); logProcess(processCmd.command, processCmd.args, { throwOnStdErr: true }); - + console.log(processCmd); if (processCmd === WmicProcessParser.wmicCommand) { return WmicProcessParser.parseProcesses(output.stdout); - } else if (processCmd === PowerShellProcessParser.powerShellCommand) { + } else if (processCmd === PowerShellProcessParser.powerShellCommand || + processCmd === PowerShellProcessParser.powerShellWithoutCimCommand) { return PowerShellProcessParser.parseProcesses(output.stdout); } return PsProcessParser.parseProcesses(output.stdout); diff --git a/src/test/unittest/attachQuickPick/provider.unit.test.ts b/src/test/unittest/attachQuickPick/provider.unit.test.ts index b67f06bd..3bd8fd65 100644 --- a/src/test/unittest/attachQuickPick/provider.unit.test.ts +++ b/src/test/unittest/attachQuickPick/provider.unit.test.ts @@ -752,5 +752,63 @@ ProcessId=8026\r ); assert.deepEqual(output, expectedOutput); }); + + test('The Windows powershell Get-WmiObject process list command should be called when Get-CimInstance fails', async () => { + const windowsProcesses = [ + { + processId: 4, + commandLine: null, + name: 'System', + }, + { + processId: 5372, + commandLine: null, + name: 'svchost.exe', + }, + ]; + const expectedOutput: IAttachItem[] = [ + { + label: 'svchost.exe', + description: '5372', + detail: '', + id: '5372', + processName: 'svchost.exe', + commandLine: '', + }, + { + label: 'System', + description: '4', + detail: '', + id: '4', + processName: 'System', + commandLine: '', + }, + ]; + const foundPowerShellOutput = 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\r\n'; + + plainExecStub + .withArgs('where', ['powershell'], sinon.match.any, sinon.match.any) + .resolves({ stderr: '', stdout: foundPowerShellOutput }); + + const windowsOutput = JSON.stringify(windowsProcesses, null, 4); + plainExecStub + .withArgs( + PowerShellProcessParser.powerShellWithoutCimCommand.command, + sinon.match.any, + sinon.match.any, + sinon.match.any, + ) + .resolves({ stdout: windowsOutput }); + + const output = await provider.getAttachItems(PowerShellProcessParser.powerShellWithoutCimCommand); + sinon.assert.calledWithExactly( + plainExecStub.firstCall, + PowerShellProcessParser.powerShellWithoutCimCommand.command, + PowerShellProcessParser.powerShellWithoutCimCommand.args, + sinon.match.any, + sinon.match.any, + ); + assert.deepEqual(output, expectedOutput); + }); }); }); From 239aee20d37e7972c33afbee08d12edbea9d9530 Mon Sep 17 00:00:00 2001 From: ZA139 <40553487+ZA139@users.noreply.github.com> Date: Tue, 4 Nov 2025 10:17:38 +0800 Subject: [PATCH 10/10] Remove debug console.log for processCmd Remove console.log for processCmd to clean up output. --- src/extension/debugger/attachQuickPick/provider.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/extension/debugger/attachQuickPick/provider.ts b/src/extension/debugger/attachQuickPick/provider.ts index 39f665e4..3a9fee2c 100644 --- a/src/extension/debugger/attachQuickPick/provider.ts +++ b/src/extension/debugger/attachQuickPick/provider.ts @@ -94,7 +94,6 @@ export class AttachProcessProvider implements IAttachProcessProvider { } const output = await plainExec(processCmd.command, processCmd.args, { throwOnStdErr: true }, customEnvVars); logProcess(processCmd.command, processCmd.args, { throwOnStdErr: true }); - console.log(processCmd); if (processCmd === WmicProcessParser.wmicCommand) { return WmicProcessParser.parseProcesses(output.stdout); } else if (processCmd === PowerShellProcessParser.powerShellCommand ||