@@ -229,6 +229,14 @@ export interface IProcessInfo {
229229 */
230230 processName : string ;
231231
232+ /**
233+ * The full command line of the process, when available.
234+ *
235+ * @remarks On some platforms this may be empty or truncated for kernel processes
236+ * or when the OS does not expose the command line.
237+ */
238+ commandLine ?: string ;
239+
232240 /**
233241 * The process ID.
234242 */
@@ -288,27 +296,62 @@ export function parseProcessListOutput(
288296// PPID PID COMMAND
289297// 51234 56784 process name
290298const NAME_GROUP : 'name' = 'name' ;
299+ const COMMAND_LINE_GROUP : 'command' = 'command' ;
291300const PROCESS_ID_GROUP : 'pid' = 'pid' ;
292301const PARENT_PROCESS_ID_GROUP : 'ppid' = 'ppid' ;
293302const PROCESS_LIST_ENTRY_REGEX : RegExp = new RegExp (
294- `^\\s*(?<${ PARENT_PROCESS_ID_GROUP } >\\d+)\\s+(?<${ PROCESS_ID_GROUP } >\\d+)\\s+(?<${ NAME_GROUP } >.+?) \\s*$`
303+ `^\\s*(?<${ PARENT_PROCESS_ID_GROUP } >\\d+)\\s+(?<${ PROCESS_ID_GROUP } >\\d+)\\s+(?<${ NAME_GROUP } >[^\\s]+)(?:\\s+(?< ${ COMMAND_LINE_GROUP } >.+))? \\s*$`
295304) ;
296305
297306function parseProcessInfoEntry (
298307 line : string ,
299308 existingProcessInfoById : Map < number , IProcessInfo > ,
300309 platform : NodeJS . Platform
301310) : void {
302- const processListEntryRegex : RegExp = PROCESS_LIST_ENTRY_REGEX ;
303- const match : RegExpMatchArray | null = line . match ( processListEntryRegex ) ;
304- if ( ! match ?. groups ) {
305- throw new InternalError ( `Invalid process list entry: ${ line } ` ) ;
311+ let processName : string ;
312+ let commandLine : string | undefined ;
313+ let processId : number ;
314+ let parentProcessId : number ;
315+
316+ if ( platform === 'win32' ) {
317+ if ( line . includes ( '\t' ) ) {
318+ // Tab-delimited output (PowerShell path with CommandLine)
319+ const win32Match : RegExpMatchArray | null = line . match (
320+ / ^ \s * (?< ppid > \d + ) \s + (?< pid > \d + ) \s + (?< name > [ ^ \s ] + ) (?: \s + (?< cmd > .+ ) ) ? \s * $ /
321+ ) ;
322+ if ( ! win32Match ?. groups ) {
323+ throw new InternalError ( `Invalid process list entry: ${ line } ` ) ;
324+ }
325+ processName = win32Match . groups . name ;
326+ const cmd : string | undefined = win32Match . groups . cmd ;
327+ commandLine = cmd && cmd . length > 0 ? cmd : undefined ;
328+ processId = parseInt ( win32Match . groups . pid , 10 ) ;
329+ parentProcessId = parseInt ( win32Match . groups . ppid , 10 ) ;
330+ } else {
331+ // Legacy space-delimited listing: treat everything after pid as name, no command line
332+ const tokens : string [ ] = line . trim ( ) . split ( / \s + / ) ;
333+ if ( tokens . length < 3 ) {
334+ throw new InternalError ( `Invalid process list entry: ${ line } ` ) ;
335+ }
336+ const [ ppidString , pidString , ...nameParts ] = tokens ;
337+ processName = nameParts . join ( ' ' ) ;
338+ commandLine = undefined ;
339+ processId = parseInt ( pidString , 10 ) ;
340+ parentProcessId = parseInt ( ppidString , 10 ) ;
341+ }
342+ } else {
343+ const processListEntryRegex : RegExp = PROCESS_LIST_ENTRY_REGEX ;
344+ const match : RegExpMatchArray | null = line . match ( processListEntryRegex ) ;
345+ if ( ! match ?. groups ) {
346+ throw new InternalError ( `Invalid process list entry: ${ line } ` ) ;
347+ }
348+ processName = match . groups [ NAME_GROUP ] ;
349+ const parsedCommandLine : string | undefined = match . groups [ COMMAND_LINE_GROUP ] ;
350+ commandLine = parsedCommandLine && parsedCommandLine . length > 0 ? parsedCommandLine : undefined ;
351+ processId = parseInt ( match . groups [ PROCESS_ID_GROUP ] , 10 ) ;
352+ parentProcessId = parseInt ( match . groups [ PARENT_PROCESS_ID_GROUP ] , 10 ) ;
306353 }
307354
308- const processName : string = match . groups [ NAME_GROUP ] ;
309- const processId : number = parseInt ( match . groups [ PROCESS_ID_GROUP ] , 10 ) ;
310- const parentProcessId : number = parseInt ( match . groups [ PARENT_PROCESS_ID_GROUP ] , 10 ) ;
311-
312355 // Only care about the parent process if it is not the same as the current process.
313356 let parentProcessInfo : IProcessInfo | undefined ;
314357 if ( parentProcessId !== processId ) {
@@ -334,10 +377,16 @@ function parseProcessInfoEntry(
334377 parentProcessInfo,
335378 childProcessInfos : [ ]
336379 } ;
380+ if ( commandLine !== undefined ) {
381+ processInfo . commandLine = commandLine ;
382+ }
337383 existingProcessInfoById . set ( processId , processInfo ) ;
338384 } else {
339385 // Update placeholder entry
340386 processInfo . processName = processName ;
387+ if ( commandLine !== undefined ) {
388+ processInfo . commandLine = commandLine ;
389+ }
341390 processInfo . parentProcessInfo = parentProcessInfo ;
342391 }
343392
@@ -368,11 +417,11 @@ function getProcessListProcessOptions(): ICommandLineOptions {
368417 if ( OS_PLATFORM === 'win32' ) {
369418 command = 'powershell.exe' ;
370419 // Order of declared properties sets the order of the output.
371- // Put name last to simplify parsing, since it can contain spaces.
420+ // Emit tab-delimited columns to allow the command line to contain spaces.
372421 args = [
373422 '-NoProfile' ,
374423 '-Command' ,
375- `'PPID PID Name '; Get-CimInstance Win32_Process | % { '{0} {1} {2}' -f $_.ParentProcessId, $_.ProcessId, $_.Name }`
424+ `'PPID\`tPID\`tName\`tCommandLine '; Get-CimInstance Win32_Process | % { '{0}\`t {1}\`t {2}\`t{3} ' -f $_.ParentProcessId, $_.ProcessId, $_.Name, ($_.CommandLine -replace "\`t", " ") }`
376425 ] ;
377426 } else {
378427 command = 'ps' ;
@@ -382,7 +431,8 @@ function getProcessListProcessOptions(): ICommandLineOptions {
382431 // Order of declared properties sets the order of the output. We will
383432 // need to request the "comm" property last in order to ensure that the
384433 // process names are not truncated on certain platforms
385- args = [ '-Awo' , 'ppid,pid,comm' ] ;
434+ // Include both the comm (thread name) and args (full command line) columns.
435+ args = [ '-Awwxo' , 'ppid,pid,comm,args' ] ;
386436 }
387437 return { path : command , args } ;
388438}
0 commit comments