src/Get-PsCmdline.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#requires -version 6
using namespace System.ComponentModel
using namespace System.Runtime.InteropServices

Set-Alias -Name pscmd -Value Get-PsCmdline
function Get-PsCmdline {
  [CmdletBinding(DefaultParameterSetName='Name')]
  param(
    [Parameter(Mandatory, ParameterSetName='Name', Position=0)]
    [ValidateNotNullOrEmpty()]
    [ValidateScript({!!($script:ps = Get-Process $_ -ErrorAction 0)})]
    [String]$Name,

    [Parameter(Mandatory, ParameterSetName='Id', Position=0)]
    [ValidateScript({!!($script:ps = Get-Process -Id $_ -ErrorAction 0)})]
    [Int32]$Id
  )

  process {
    $ntdll = New-Delegate ntdll -Signature @{
      NtQueryInformationProcess = [Func[IntPtr, Int32, [Byte[]], Int32, [Byte[]], Int32]]
      RtlNtStatusToDosError = [Func[Int32, Int32]]
    }

    $ps.ForEach{
      if ($_.Handle) {
        $req, $to_i = [Byte[]]::new(4), "ToInt$((32, 64)[($sz = [IntPtr]::Size) / 4 - 1])"
        if ((
          $nts = $ntdll.NtQueryInformationProcess.Invoke($_.Handle, 60, $null, 0, $req)
        ) -ne 0xC0000004) {
          Write-Verbose "PID ($_.Id): $([Win32Exception]::new(
            $ntdll.RtlNtStatusToDosError.Invoke($nts)
          ).Message)"

          continue
        }

        $buf = [Byte[]]::new([BitConverter]::ToUInt32($req, 0))
        if ((
          $nts = $ntdll.NtQueryInformationProcess.Invoke($_.Handle, 60, $buf, $buf.Length, $null)
        ) -ne 0) {
          Write-Verbose "PID ($_.Id): $([Win32Exception]::new(
            $ntdll.RtlNtStatusToDosError.Invoke($nts)
          ).Message)"

          continue
        }

        $gch = [GCHandle]::Alloc($buf, [GCHandleType]::Pinned)
        $ptr = $gch.AddrOfPinnedObject()
        $cmd = [Marshal]::PtrToStringUni([Marshal]::ReadIntPtr([IntPtr]($ptr.$to_i() + $sz)))
        $gch.Free()

        [PSCustomObject]@{
          ProcessName = $_.ProcessName
          PID = $_.Id
          CommandLine = $cmd
        }
      }
      else { Write-Verbose "PID $($_.Id): could not retrieve required information." }

      $_.Dispose()
    } # foreach

    [GC]::Collect()
  }
}