# encoding: ascii
# api: powershell
# title: Get-ProcessTree
# description: Original post of gregzakh can be found at https://github.com/gregzakh/alt-ps/blob/master/Get-ProcessTree.ps1
# version: 0.1
# type: function
# author: Dan Jones
# license: CC0
# function: Get-ProcessTree
# x-poshcode-id: 6362
# x-archived: 2016-07-08T10:28:02
# x-published: 2016-05-29T09:12:00
#
#
function Get-ProcessTree {
  begin {
    Set-Variable ($$ = [Regex].Assembly.GetType(
      'Microsoft.Win32.NativeMethods'
    ).GetMethod('NtQuerySystemInformation')).Name $$
    
    function Get-ProcessChild {
      param(
        [Parameter(Mandatory=$true, Position=0)]
        [PSObject]$Process,
        
        [Parameter(Position=1)]
        [Int32]$Depth = 1
      )
      
      $Processes | Where-Object {
        $_.PPID -eq $Process.PID -and $_.PPID -ne 0
      } | ForEach-Object {
        '{0}{1} ({2})' -f (
          "$([Char]32)" * 2 * $depth
        ), $_.ProcessName, $_.PID
        Get-ProcessChild $_ (++$Depth)
        $Depth--
      }
    }
    
    if (($ta = [PSObject].Assembly.GetType(
      'System.Management.Automation.TypeAccelerators'
    ))::Get.Keys -notcontains 'Marshal') {
      $ta::Add('Marshal', [Runtime.InteropServices.Marshal])
    }
  }
  process {
    try {
      $ret = 0
      $ptr = [Marshal]::AllocHGlobal(1024)
      
      if ($NtQuerySystemInformation.Invoke($null, (
        $par = [Object[]]@(5, $ptr, 1024, $ret)
      )) -eq 0xC0000004) { #STATUS_INFO_LENGTH_MISMATCH
        $ptr = [Marshal]::ReAllocHGlobal($ptr, [IntPtr]$par[3])
        if (($nts = $NtQuerySystemInformation.Invoke($null, (
          $par = [Object[]]@(5, $ptr, $par[3], 0)
        ))) -ne 0) {
          throw New-Object InvalidOperationException(
            'NTSTATUS: 0x{0:X}' -f $nts
          )
        }
      }
      
      $tmp = $ptr
      $Processes = while (($$ = [Marshal]::ReadInt32($tmp))) {
        New-Object PSObject -Property @{
          ProcessName = [Diagnostics.Process]::GetProcessById((
            $id = [Marshal]::ReadInt32($tmp, 0x44)
          )).Name
          PID = $id
          PPID = [Marshal]::ReadInt32($tmp, 0x48)
        }
        $tmp = [IntPtr]($tmp.ToInt32() + $$)
      }
    }
    catch { $_.Exception }
    finally {
      if ($ptr -ne $null) {
        [Marshal]::FreeHGlobal($ptr)
      }
    }
  }
  end {
    if ($Processes -eq $null) {
      break
    }
    
    $Processes | Where-Object {
      -not (Get-Process -Id $_.PPID -ea 0) -or $_.PPID -eq 0
    } | ForEach-Object {
      '{0} ({1})' -f $_.ProcessName, $_.PID
      Get-ProcessChild $_
    }
    
    [void]$ta::Remove('Marshal')
  }
}
