Show-ProcessTree

I wondered for fun if it’s possible for powershell to show the process tree like procexp.exe or pslist.exe -t do.
I also wanted to have the same display with indentation as the Get-WindowsFeature cmdlet.
I’ve found the ParentProcessID property in the Win32_Process WMI class that would help me build the tree.
Then I figured out using procexp.exe that top level processes don’t have their parent process existing anymore and that PID 0 is very special.
I knew also that I’ll have to write a sub function to recurse. Except the built-in -Recurse parameter on some cmdlets, I’ve seen only 2 advanced functions so far on the following pages that implement a recursion:

I borrowed the Indent function from the above page and after a few tests abusing some of the new powershell V3 features like implicit foreach or the nice new -in comparison operator, I came up with a working code.

I’ve also seen that a Microsft PFE from Germany, Michael Frommhold, proposed a vbscript with the tree view in BEG8 of the 2010 scripting games:

# Requires -Version 3.0            
            
Function Show-ProcessTree  {            
[CmdletBinding()]            
Param()            
    Begin {            
        # Identify top level processes            
        # They have either an identified processID that doesn't exist anymore            
        # Or they don't have a Parentprocess ID at all            
        $allprocess  = Get-WmiObject -Class Win32_process            
        $uniquetop  = ($allprocess).ParentProcessID | Sort-Object -Unique            
        $existingtop =  ($uniquetop | ForEach-Object -Process {$allprocess | Where ProcessId -EQ $_}).ProcessID            
        $nonexistent = (Compare-Object -ReferenceObject $uniquetop -DifferenceObject $existingtop).InPutObject            
        $topprocess = ($allprocess | ForEach-Object -Process {            
            if ($_.ProcessID -eq $_.ParentProcessID){            
                $_.ProcessID            
            }            
            if ($_.ParentProcessID -in $nonexistent) {            
                $_.ProcessID            
            }            
        })            
        # Sub functions            
        # Function that indents to a level i            
        function Indent {            
            Param([Int]$i)            
            $Global:Indent = $null            
            For ($x=1; $x -le $i; $x++)            
            {            
                $Global:Indent += [char]9            
            }            
        }            
        Function Get-ChildProcessesById {            
        Param($ID)            
            # use $allprocess variable instead of Get-WmiObject -Class Win32_process to speed up            
            $allprocess | Where { $_.ParentProcessID -eq $ID} | ForEach-Object {            
                Indent $i            
                '{0}{1} {2}' -f $Indent,$_.ProcessID,($_.Name -split "\.")[0]            
                $i++            
                # Recurse            
                Get-ChildProcessesById -ID $_.ProcessID            
                $i--            
            }            
        } # end of function            
    }            
    Process {            
        $topprocess | ForEach-Object {            
            '{0} {1}' -f $_,(Get-Process -Id $_).ProcessName            
            # Avoid processID 0 because parentProcessId = processID            
            if ($_ -ne 0 )            
            {            
                $i = 1            
                Get-ChildProcessesById -ID $_            
            }            
        }            
    }             
    End {}            
}

Now to simulate a pslist -t output with the above function, I just did:

do            
    {            
        Clear-Host             
        Show-ProcessTree            
        Start-Sleep -Seconds 1            
    }            
    while ($true)            

Note that I’ve produced this code for fun as a PoC. I know there are better ways to achieve this.
I should also probably start digging in format output views and see how the Get-WindowsFeature implements a tree view.

Advertisements

4 thoughts on “Show-ProcessTree

  1. Small improvement to your code:

    This part of the code runs about 2.5 times faster on my PC (100ms compared to 250ms) and I feel it also makes the code easier to read:

    ################################################

    $allprocess = Get-WmiObject -Class Win32_process
    $uniquetop = ($allprocess).ProcessID
    $topprocess = $allprocess | ForEach-Object -Process `
    {
    if ($_.ProcessID -eq $_.ParentProcessID)
    {
    $_.ProcessID
    }
    elseif ($_.ParentProcessID -notin $uniquetop)
    {
    $_.ProcessID
    }
    }

    ################################################

  2. Actually to make it even faster (total script execution down from 1.7 secs per call to 500ms), change the first line to:

    $allprocess = Get-WmiObject -Class Win32_process | Select-Object -Property ProcessID, ParentProcessID, Name

  3. A few more improvements:

    This runs in about 450ms on my PC:

    ################################################

    Function Show-ProcessTree {
    [CmdletBinding()]
    Param()
    Begin {
    # Sub functions
    # Function that indents to a level i
    function Indent {
    Param([Int]$i)
    ‘ ‘ * $i
    }
    Function Get-ChildProcessesById {
    Param($ID)
    # use $allprocess variable instead of Get-WmiObject -Class Win32_process to speed up
    $allprocess | Where { $_.ParentProcessID -eq $ID} | ForEach-Object {
    ‘{0}{1} {2}’ -f (Indent $i),$_.ProcessID,($_.Name -split “\.”)[0]
    $i++
    # Recurse
    Get-ChildProcessesById -ID $_.ProcessID
    $i–
    }
    } # end of function
    }
    Process {
    $allprocess = Get-WmiObject -Class Win32_process | Select-Object -Property ProcessID, ParentProcessID, Name
    $allprocess | ForEach-Object -Process `
    {
    if ($_.ProcessID -eq $_.ParentProcessID -or $_.ParentProcessID -notin $allprocess.ProcessID)
    {
    ‘{0} {1}’ -f $_.ProcessID,$_.Name
    # Avoid processID 0 because parentProcessId = processID
    if ($_.ProcessID -ne 0 )
    {
    $i = 1
    Get-ChildProcessesById -ID $_.ProcessID
    }
    }
    }
    }
    End {}
    }

    ################################################

    Since the code to determine top processes was simplified by a lot, I didn’t see the need to keep the function to determine top processes on the Begin part and use another pipeline to cycle through all elements of the $topprocess object in the Process part.

  4. Pingback: Mittels Powershell geladene DLL-Dateien aus einem Prozess auslesen | Das nie endende Chaos!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s