Choppy The Lumberjack
Choppy The Lumberjack

Reputation: 777

PowerShell get Command Line Args of Processes in Linux and OSX

In PowerShell, using Get-WmiObject win32_process it is possible to get the command-line arguments of any active process in windows (like this). How do you do the same in Linux and/or OSX?

The ps command can do this to be sure, but I am looking for a PowerShell-native solution in order to be able to maintain strong typing.

Upvotes: 2

Views: 1103

Answers (1)

mklement0
mklement0

Reputation: 438123

Note: As of PowerShell Core 7.1.0-rc.2, a new-for-7.1 .CommandLine ETS script property exists that determines the command line on Windows and on Linux. However:

  • No solution for macOS is provided as of this writing, which GitHub feature request #13943 aims to correct.
  • The behavior of the Linux solution is somewhat unexpected, as discussed in GitHub issue #13944 - the solution below works around that, albeit at the cost of loss of argument boundaries.

The solution detailed in the bottom section works on macOS (and at least on some Linux distros too), and can easily be integrated into the .CommandLine property as follows - you can do this even in earlier PowerShell [Core] versions, and even in Windows PowerShell:

# Run this once per session to add a / update the .CommandLine property
# on System.Diagnostics.Process to report the process' command line
# on Windows, Linux, and macOS
Update-TypeData -Force -Value {
  if ($env:OS -eq 'Windows_NT') {
    (Get-CimInstance Win32_Process -Filter "ProcessId = $($this.Id)").CommandLine
  } elseif ($IsLinux) {
    (Get-Content -LiteralPath "/proc/$($this.Id)/cmdline") -replace "`0", ' '
  } elseif ($IsMacOs) {
    ps -o command= $this.Id
  }
} -TypeName System.Diagnostics.Process -MemberName CommandLine -MemberType ScriptProperty 

Once enabled, you can use it as follows:

$pidOfInterest = $PID # use the PowerShell session's own as an example
(Get-Process -Id $pidOfInterest).CommandLine

Note:

  • Unlike on Windows, on Unix platforms the command line reported is not a faithful representation of the original command line; notably, argument boundaries originally enforced by single- or double-quoting are lost:

    • Note: On Unix-like platforms, processes receive no command line that they themselves must parse (as is unfortunately the case on Windows); instead, they receive an array of verbatim arguments. Thus, there is no original command line as such, only the shell-specific command line that was passed to the original shell, which then translated that into the array of verbatim arguments passed to the process on creation.

    • On macOS, the ps-based solution detailed below provides no information about the original argument boundaries and simply joins the verbatim argument with spaces, so the resulting command-line string cannot generally be expected to work like the original invocation.

    • On Linux, the special /proc/<pid>/cmdline files do preserve the original argument boundaries, simply by using NUL characters (0x0) rather than spaces to join the verbatim arguments; however, it would require a nontrivial effort to reconstruct a working command line from that (notably the need to recreate quoting and escaping as necessary), and even that presents a conceptual challenge: what shell do you reconstruct the command line for, given that PowerShell's syntax differs from Bash's, for instance? Therefore, the command above also simply joins the verbatim arguments with spaces on Linux, the same way that ps does on macOS.

  • Accessing the .CommandLine property is costly in terms of performance, because it invokes a CIM/WMI call on Windows, and running an external executable in a child process on Unix.


On macOS, you can use the standard ps utility[1] as follows, but note that the command line it returns lacks the original quoting:

# Print the command line used to invoke this PowerShell session, 
# represented by automatic variable $PID.
# Note that $PID is just an *example* PID (process identifier).
ps -o command= $PID

Note: command is a nonstandard variant of the args field name that isn't subject to the latter's limit of 64 chars., and it is supported on both Linux and macOS.


To illustrate the quoting issue:

The following command (executed from PowerShell):

pwsh -noprofile -noexit -c "'hi there'; ps -o command= `$PID"

outputs a string such as:

/usr/local/bin/pwsh -noprofile -noexit -c 'hi there'; ps -o command= $PID

Note how the "..." quoting enclosing the original -c argument was lost.


[1] The superior Linux-specific alternative is Get-Content -LiteralPath /proc/$PID/cmdline, which has the advantage of separating the arguments with NUL characters in order to preserve the original argument boundaries - but to see those boundaries extra work is needed. The ps solution works on at least some Linux distros too, but it is conceivable that there are ps implementation in use that do not support the command field. Either way, the ps solution lacks information about the original argument boundaries.

Upvotes: 5

Related Questions