Powershell: Get an indented process tree

      Comments Off on Powershell: Get an indented process tree

In PowerShell, there is the Get-Process cmdlet.
However, this does not return the ParentProcessId property, so it is unsuitable for displaying an indented process tree, which you, for instance, could send by email.
Luckily, there is the Get-WMIObject cmdlet that can return the ParentProcessId.

How to get a Process Tree in PowerShell.

The first step is to get a list of all processes on the system in the $Processes array. Since I need to access the $Processes array from the recursive function, I chose to store it in the Global scope.
Once I have the list of processes, I have to check which processes do not have an existing parent. These are then stored in a $RootProcesses array, so I can recursively process these.
Since the first element of the $Processes array is the System Idle process (which has ProcessId 0 and ParentProcessId 0), I skip it and display it separately. This prevents an endless loop or a check in the recursive function (for efficiency).

Note that I do not only check for the ProcessId-ParentProcessId relationship, but also that the CreationDate of the child is greater than that of the parent. When testing the script on live servers, I found that you could get processes created earlier than what seemed to be its parent, but which of course could not be related (so the supposedly parent’s ProcessId had been re-assigned to a process that started later).

Here is the full source of the script.

Get-ProcessTree.ps1:
function Get-ProcessAndChildProcesses($Level, $Process) {
  "{0}[{1,-5}] [{2}]" -f ("  " * $Level), $Process.ProcessId, $Process.Name
  $Children = $Global:Processes | where-object {$_.ParentProcessId -eq $Process.ProcessId -and $_.CreationDate -ge $Process.CreationDate}
  if ($Children -ne $null) {
    foreach ($Child in $Children) {
      Get-ProcessAndChildProcesses ($Level + 1) $Child
    }
  }
}

$Global:Processes = Get-WMIObject -Class Win32_Process
$RootProcesses = @()
# Process "System Idle Process" is processed differently, as ProcessId and ParentProcessId are 0
# $Global:Processes is sliced from index 1 to the end of the array
foreach ($Process in $Global:Processes[1..($Global:Processes.length-1)]) {
  $Parent = $global:Processes | where-object {$_.ProcessId -eq $Process.ParentProcessId -and $_.CreationDate -lt $Process.CreationDate}
  if ($Parent -eq $null) {
    $RootProcesses += $Process
  }
}
#Process the "System Idle process" separately
"[{0,-5}] [{1}]" -f $Global:Processes[0].ProcessId, $Global:Processes[0].Name
foreach ($Process in $RootProcesses) {
  Get-ProcessAndChildProcesses 0 $Process
}