import * as child_process from 'child_process' ; import * as os from 'os' ; import { AttachItemsProvider } from './attachToProcess' ; import { AttachItem } from './attachQuickPick' ; import * as nls from 'vscode-nls' ; import { findPowerShell } from '../common' ; nls . config ( { messageFormat : nls . MessageFormat . bundle , bundleFormat : nls . BundleFormat . standalone } ) ( ) ; const localize = nls . loadMessageBundle ( ) ; export class Process { constructor ( public name , public pid ? , public commandLine ? ) { } public toAttachItem ( ) { return { label : this . name , description : this . pid , detail : this . commandLine , id : this . pid } ; } } export class NativeAttachItemsProviderFactory { static Get ( ) { if ( os . platform ( ) === 'win32' ) { const pwsh = findPowerShell ( ) ; return pwsh ? new CimAttachItemsProvider ( pwsh ) : new WmicAttachItemsProvider ( ) ; } else { return new PsAttachItemsProvider ( ) ; } } } abstract class NativeAttachItemsProvider implements AttachItemsProvider { protected abstract getInternalProcessEntries ( ) ; async getAttachItems ( ) { const processEntries = await this . getInternalProcessEntries ( ) ; processEntries . sort ( ( a , b ) => { if ( a . name === undefined ) { if ( b . name === undefined ) { return 0 ; } return 1 ; } if ( b . name === undefined ) { return - 1 ; } const aLower = a . name . toLowerCase ( ) ; const bLower = b . name . toLowerCase ( ) ; if ( aLower === bLower ) { return 0 ; } return aLower < bLower ? - 1 : 1 ; } ) ; const attachItems = processEntries . map ( p => p . toAttachItem ( ) ) ; return attachItems ; } } export class PsAttachItemsProvider extends NativeAttachItemsProvider { protected async getInternalProcessEntries ( ) { let processCmd = '' ; switch ( os . platform ( ) ) { case 'darwin' : processCmd = PsProcessParser . psDarwinCommand ; break ; case 'linux' : processCmd = PsProcessParser . psLinuxCommand ; break ; default : throw new Error ( localize ( "os.not.supported" , 'Operating system "{0}" not supported.' , os . platform ( ) ) ) ; } const processes = await execChildProcess ( processCmd , undefined ) ; return PsProcessParser . ParseProcessFromPs ( processes ) ; } } export class PsProcessParser { private static get secondColumnCharacters ( ) : number { return 50 ; } private static get commColumnTitle ( ) : string { return Array ( PsProcessParser . secondColumnCharacters ) . join ( "a" ) ; } public static get psLinuxCommand ( ) : string { return `ps axww -o pid=,comm=${ PsProcessParser . commColumnTitle },args=` ; } public static get psDarwinCommand ( ) : string { return `ps axww -o pid=,comm=${ PsProcessParser . commColumnTitle },args= -c` ; } public static ParseProcessFromPs ( processes ) { const lines = processes . split ( os . EOL ) ; return PsProcessParser . ParseProcessFromPsArray ( lines ) ; } public static ParseProcessFromPsArray ( processArray ) { const processEntries = [ ] ; for ( let i = 1 ; i < processArray . length ; i ++ ) { const line = processArray [ i ] ; if ( ! line ) { continue ; } const processEntry = PsProcessParser . parseLineFromPs ( line ) ; if ( processEntry ) { processEntries . push ( processEntry ) ; } } return processEntries ; } private static parseLineFromPs ( line ) { const psEntry = new RegExp ( `^\\s*([0-9]+)\\s+(.{${ PsProcessParser . secondColumnCharacters - 1 }})\\s+(.*)$` ) ; const matches = psEntry . exec ( line ) ; if ( matches && matches . length === 4 ) { const pid = matches [ 1 ] . trim ( ) ; const executable = matches [ 2 ] . trim ( ) ; const cmdline = matches [ 3 ] . trim ( ) ; return new Process ( executable , pid , cmdline ) ; } } } /**
 * Originally from common.ts. Due to test code not having vscode, it was refactored to not have vscode.OutputChannel.
 */ function execChildProcess ( process , workingDirectory ? ) { return new Promise < string > ( ( resolve , reject ) => { child_process . exec ( process , { cwd : workingDirectory , maxBuffer : 500 * 1024 } , ( error , stdout , stderr ) => { if ( error ) { reject ( error ) ; return ; } if ( stderr && stderr . length > 0 ) { if ( stderr . indexOf ( 'screen size is bogus' ) >= 0 ) { } else { reject ( new Error ( stderr ) ) ; return ; } } resolve ( stdout ) ; } ) ; } ) ; } export class WmicAttachItemsProvider extends NativeAttachItemsProvider { protected async getInternalProcessEntries ( ) { const wmicCommand = 'wmic process get Name,ProcessId,CommandLine /FORMAT:list' ; const processes = await execChildProcess ( wmicCommand , undefined ) ; return WmicProcessParser . ParseProcessFromWmic ( processes ) ; } } export class WmicProcessParser { private static get wmicNameTitle ( ) : string { return 'Name' ; } private static get wmicCommandLineTitle ( ) : string { return 'CommandLine' ; } private static get wmicPidTitle ( ) : string { return 'ProcessId' ; } public static ParseProcessFromWmic ( processes ) { const lines = processes . split ( os . EOL ) ; let currentProcess = new Process ( "current process" , undefined , undefined ) ; const processEntries = [ ] ; for ( let i = 0 ; i < lines . length ; i ++ ) { const line = lines [ i ] ; if ( ! line ) { continue ; } WmicProcessParser . parseLineFromWmic ( line , currentProcess ) ; if ( line . lastIndexOf ( WmicProcessParser . wmicPidTitle , 0 ) === 0 ) { processEntries . push ( currentProcess ) ; currentProcess = new Process ( "current process" , undefined , undefined ) ; } } return processEntries ; } private static parseLineFromWmic ( line , process ) { const splitter = line . indexOf ( '=' ) ; if ( splitter >= 0 ) { const key = line . slice ( 0 , line . indexOf ( '=' ) ) . trim ( ) ; let value = line . slice ( line . indexOf ( '=' ) + 1 ) . trim ( ) ; if ( key === WmicProcessParser . wmicNameTitle ) { process . name = value ; } else if ( key === WmicProcessParser . wmicPidTitle ) { process . pid = value ; } else if ( key === WmicProcessParser . wmicCommandLineTitle ) { const extendedLengthPath = '\\??\\' ; if ( value . lastIndexOf ( extendedLengthPath , 0 ) === 0 ) { value = value . slice ( extendedLengthPath . length ) ; } process . commandLine = value ; } } } } export class CimAttachItemsProvider extends NativeAttachItemsProvider { constructor ( private pwsh ) { super ( ) ; } protected async getInternalProcessEntries ( ) { const pwshCommand = `${ this . pwsh } -NoProfile -Command` ; const cimCommand = 'Get-CimInstance Win32_Process | Select-Object Name,ProcessId,CommandLine | ConvertTo-JSON' ; const processes = 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 '\\??\\' ; } public static ParseProcessFromCim ( processes ) { const processInfos = JSON . parse ( processes ) ; return processInfos . map ( info => { let cmdline = 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 ) ; } ) ; } }