src/Get-PsHandles.ps1

#requires -version 6
using namespace System.Runtime.InteropServices

Set-Alias -Name pshndl -Value Get-PsHandles
function Get-PsHandles {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory, Position=0)]
    [ValidateScript({!!($script:ps = Get-Process -Id $_ -ErrorAction 0)})]
    [Int32]$Id,

    [Parameter()][Switch]$ShowEmpty
  )

  begin {
    $kernel32, $ntdll, $page, $to_i = (New-Delegate kernel32 -Signature @{
      CloseHandle = [Func[IntPtr, Boolean]]
      OpenProcess = [Func[UInt32, Boolean, Int32, IntPtr]]
    }), (New-Delegate ntdll -Signature @{
      NtDuplicateObject = (
        [IntPtr], [IntPtr], [IntPtr], [IntPtr].MakeByRefType(),
        [UInt32], [UInt32], [UInt32], [Int32]
      )
      NtQueryInformationProcess = [Func[IntPtr, Int32, IntPtr, Int32, [Byte[]], Int32]]
      NtQueryObject = [Func[IntPtr, UInt32, [Byte[]], UInt32, [Byte[]], Int32]]
    }), [Byte[]]::new(0x1000), "ToInt$((32, 64)[($x = ($sz = [IntPtr]::Size) / 4 - 1)])"

    function Expand-UnicodeString([IntPtr]$h, [UInt32]$o) {
      if (!$ntdll.NtQueryObject.Invoke($h, $o, $page, $page.Length, $null)) {
        try {
          $uni = [GCHandle]::Alloc($page, [GCHandleType]::Pinned)

          $str = $uni.AddrOfPinnedObject()
          [Marshal]::PtrToStringUni([Marshal]::ReadIntPtr([IntPtr]($str.$to_i() + $sz)))
        }
        catch { Write-Verbose $_ }
        finally {
          if ($gch) { $gch.Free() }
        }
      }
    }
  }
  process {
    try {
      if (($hndl = $kernel32.OpenProcess.Invoke(0x440, $false, $Id)) -eq [IntPtr]::Zero) {
        throw [InvalidOperationException]::new()
      }

      $ptr = [Marshal]::AllocHGlobal(($bufsz = 0x1000))
      while ($ntdll.NtQueryInformationProcess.Invoke($hndl, 51, $ptr, $bufsz, $null)) {
        $ptr = [Marshal]::ReAllocHGlobal($ptr, [IntPtr]($bufsz *= 2))
      }

      $tmp = $ptr
      $NumberOfHandles = [Marshal]::ReadIntPtr($tmp).$to_i()
      $handles = (0..($NumberOfHandles - 1)).ForEach{
        $HandleValue = [Marshal]::ReadIntPtr([IntPtr]($tmp.$to_i() + (0x08, 0x10)[$x]))
        if ($Id -ne $PID) {
          [IntPtr]$duple = [IntPtr]::Zero
          if (!$ntdll.NtDuplicateObject.Invoke(
            $hndl, $HandleValue, [IntPtr]-1, [ref]$duple, 0, 0, 0x02
          )) {
            $tmp = [IntPtr]($tmp.$to_i() + (28, 40)[$x])
            continue
          }
          $page.Clear()
          $type = Expand-UnicodeString $duple 2
          $page.Clear()
          $name = Expand-UnicodeString $duple 1

          if ($duple -ne [IntPtr]::Zero) {
            if (!$kernel32.CloseHandle.Invoke($duple)) {
              Write-Verbose "Could not close duple of $($HandleValue.$to_i()) handle."
            }
          }
        }
        else {
          $page.Clear()
          $type = Expand-UnicodeString $HandleValue 2
          $page.Clear()
          $name = Expand-UnicodeString $HandleValue 1
        }

        [PScustomObject]@{
          Value = '0x{0:X}' -f $HandleValue
          Type  = $type
          Name  = $name
        }

        $tmp = [IntPtr]($tmp.$to_i() + (28, 40)[$x])
      }
    }
    catch { Write-Verbose $_ }
    finally {
      if ($ptr)  { [Marshal]::FreeHGlobal($ptr) }
      if ($hndl -and $hndl -ne [IntPtr]::Zero) {
        if (!$kernel32.CloseHandle.Invoke($hndl)) {
          Write-Verbose "Could not close process handle."
        }
      }
    }
  }
  end {
    $ps.Dispose()
    if ($handles) {
      if (!$ShowEmpty) { $handles.Where{$_.Name} } else { $handles }
    }
    [GC]::Collect()
  }
}