pstools.psm1

using namespace System.Reflection
using namespace System.ComponentModel
using namespace System.Linq.Expressions
using namespace System.Management.Automation
using namespace System.Runtime.InteropServices
using namespace System.Collections.ObjectModel

Set-Content -Path env:__COMPAT_LAYER -Value RunAsInvoker -Force -ErrorAction 0

$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = {
  Remove-Item -Path env:__COMPAT_LAYER -Force -ErrorAction 0
}

$keys, $types = ($x = [PSObject].Assembly.GetType(
  'System.Management.Automation.TypeAccelerators'
))::Get.Keys, @{
  buf   = [Byte[]]
  dptr  = [UIntPtr]
  ptr   = [IntPtr]
  ptr_  = [IntPtr].MakeByRefType()
  uint_ = [UInt32].MakeByRefType()
  sfh   = [Microsoft.Win32.SafeHandles.SafeFileHandle]
}
$types.Keys.ForEach{ if ($_ -notin $keys) { $x::Add($_, $types.$_) } }

Add-Member -InputObject ([buf]) -Name Uni -MemberType ScriptMethod -Value {
  param([String]$str) [Text.Encoding]::Unicode.GetBytes($str)
} -Force

Add-Member -InputObject ([ptr]) -Name Mov -MemberType ScriptMethod -Value {
  param([IntPtr]$ptr, [Int32]$offset)
  [IntPtr]($ptr."ToInt$([IntPtr]::Size * 8)"() + $offset)
} -Force

Add-Member -InputObject ([enum]) -Name All -MemberType ScriptMethod -Value {
  param([Hashtable]$flags, [Int32]$val) ($flags.Keys.Where{
    ($flags[$_] -band $val) -eq $flags[$_]
  }, 'n/a')[!$val] -join ' + '
} -Force

function New-Delegate {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory, Position=0)]
    [ValidateNotNullOrEmpty()]
    [String]$Module,

    [Parameter(Mandatory, Position=1)]
    [ValidateScript({![String]::IsNullOrEmpty($_)})]
    [ScriptBlock]$Signature
  )

  begin {
    $kernel32 = @{}
    [Array]::Find((
      Add-Type -AssemblyName Microsoft.Win32.SystemEvents -PassThru
    ), [Predicate[Type]]{$args[0].Name -eq 'kernel32'}
    ).GetMethods([BindingFlags]'NonPublic, Static, Public').Where{
      $_.Name -cmatch '\AGet(P|M)'
    }.ForEach{ $kernel32[$_.Name] = $_ }

    if (($mod = $kernel32.GetModuleHandle.Invoke($null, @($Module))) -eq [IntPtr]::Zero) {
      throw [DllNotFoundException]::new("Cannot find $Module library.")
    }
  }
  process {}
  end {
    $funcs = @{}
    for ($i, $m, $fn, $p = 0, ([Expression].Assembly.GetType(
        'System.Linq.Expressions.Compiler.DelegateHelpers'
      ).GetMethod('MakeNewCustomDelegate', [BindingFlags]'NonPublic, Static')
      ), [Marshal].GetMethod('GetDelegateForFunctionPointer', ([IntPtr])),
      $Signature.Ast.FindAll({$args[0].CommandElements}, $true).ToArray();
      $i -lt $p.Length; $i++
    ) {
      $fnret, $fname = ($def = $p[$i].CommandElements).Value

      if ((
        $fnsig = $kernel32.GetProcAddress.Invoke($null, @($mod, $fname))
      ) -eq [IntPtr]::Zero) {
        throw [InvalidOperationException]::new("Cannot find $fname signature.")
      }

      $fnargs = $def.Pipeline.Extent.Text
      [Object[]]$fnargs = ((
        ($fnargs -replace '\[|\]' -split ',\s+?') + $fnret
      ), $fnret)[[String]::IsNullOrEmpty($fnargs)]

      $funcs[$fname] = $fn.MakeGenericMethod(
        [Delegate]::CreateDelegate([Func[[Type[]], Type]], $m).Invoke($fnargs)
      ).Invoke([Marshal], $fnsig)
    }
    Set-Variable $Module -Value $funcs -Scope Script -Force
  }
}

function New-DynParameter {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory)]
    [Hashtable[]]$Parameter
  )

  process {
    $dict = [RuntimeDefinedParameterDictionary]::new()

    $Parameter.ForEach{
      if ($_.GetType().Name -ne 'Hashtable' -or !$_.Count) {
        throw [ArgumentException]::new('Invalid argument.')
      }

      $attr = [Collection[Attribute]]::new()
      $attr.Add((New-Object Management.Automation.ParameterAttribute -Property @{
        ParameterSetName = $_.ParameterSetName # __AllParameterSets by default
        Mandatory = $_.Mandatory
        Position = (0x80000000, $_.Position)[$_.Position -ge 0]
        ValueFromPipeline = $_.ValueFromPipeline
        ValueFromPipelineByPropertyName = $_.ValueFromPipelineByPropertyName
      }))

      if ($_.ValidateNotNullOrEmpty) {
        $attr.Add([ValidateNotNullOrEmptyAttribute]::new())
      }

      if ($_.ValidateScript) {
        $attr.Add((New-Object Management.Automation.ValidateScriptAttribute($_.ValidateScript)))
      }

      if ($_.ValidateSet) {
        $attr.Add((New-Object Management.Automation.ValidateSetAttribute($_.ValidateSet)))
      }

      $dict.Add($_.Name, (New-Object Management.Automation.RuntimeDefinedParameter(
        $_.Name, $_.Type, $attr) -Property @{
          Value = $_.Value # this makes it easy to call dynamic parameters
        } -OutVariable "script:param$($_.Name)" # example: $param(Name)
      ))
    }

    $dict
  }
}

function Read-DataValues {
  [CmdletBinding(DefaultParameterSetName='Buffer')]
  param(
    [Parameter(Mandatory, ParameterSetName='Buffer', Position=0)]
    [ValidateNotNull()]
    [Byte[]]$Buffer,

    [Parameter(Mandatory, ParameterSetName='Handle', Position=0)]
    [ValidateScript({$_ -ne [IntPtr]::Zero})]
    [IntPtr]$Handle,

    [Parameter(Mandatory, Position=1)]
    [ValidateNotNullOrEmpty()]
    [String]$Map
  )

  process {
    $pos, $set = 0, @{b='Byte';s='Int16';i='Int32';l='Int64';p='IntPtr'}

    try {
      $ptr = ($Handle, (
        $gch = [GCHandle]::Alloc($Buffer, [GCHandleType]::Pinned)
      ).AddrOfPinnedObject())[$PSCmdlet.ParameterSetName -eq 'Buffer']

      ($Map -split '(\S(?:\d+)?)').Where{$_}.ForEach{
        if ($_ -notmatch '([bsilp])(\d+)?') {
          throw [InvalidOperationException]::new('Invalid map value.')
        }

        $block = ((,$null * 2) + ( # char and pair "char-number"
          $matches[0], [Char[]]($matches[1] * $matches[2])
        ))[$matches.Count]

        .({ # Int16, Int32, Int64 and IntPtr
          foreach ($char in $block) {
            $tmp = [Marshal]::"Read$($set["$char"])"($ptr, $pos)
            if ([Char]::IsUpper($char) -and $char -ne 'p') {
              [BitConverter]::"ToU$($set["$char"])"(
                [BitConverter]::GetBytes($tmp), 0
              )
            }
            else { $tmp }
            Write-Verbose "+0x$($pos.ToString('X3'))"
            $pos += [Marshal]::SizeOf(0 -as ([Type]$set["$char"]))
          }
        },{ # Byte
          if ([Char]::IsUpper($block[0])) {
            [Marshal]::PtrToStringAuto([IntPtr](
              $ptr."ToInt$([IntPtr]::Size * 8)"() + $pos
            ), $block.Length).Split("`0")[0]
            Write-Verbose "+0x$($pos.ToString('X3'))"
            $pos += $block.Length
          }
          else {
            -join$(foreach ($$ in $block) {
              "\x$([Marshal]::ReadByte($ptr, $pos).ToString('X2'))"
              Write-Verbose "+0x$($pos.ToString('X3'))"
              $pos += [Marshal]::SizeOf([Byte]0)
            })
          }
        })[$block -contains 'b']
      }
    }
    catch { Write-Verbose $_ }
    finally { if ($gch) { $gch.Free() } }
  }
}

function New-PsProxy {
  [CmdletBinding(DefaultParameterSetName='Name')]
  param(
    [Parameter(Mandatory, ParameterSetName='Name', Position=0)]
    [ValidateNotNullOrEmpty()]
    [String[]]$Name,

    [Parameter(Mandatory, ParameterSetName='Id', Position=0)]
    [Alias('PID')]
    [Int32[]]$Id,

    [Parameter(Mandatory, Position=1)]
    [ValidateScript({![String]::IsNullOrEmpty($_)})]
    [ScriptBlock]$Callback
  )

  begin {
    if ($PSBoundParameters.Callback) {
      [void]$PSBoundParameters.Remove('Callback')
    }
    $PSBoundParameters.Add('OutVariable', 'ps')

    $cmd = {Out-Null -InputObject (& (
      Get-Command -CommandType Cmdlet -Name Get-Process
    ) @PSBoundParameters)}.GetSteppablePipeline($MyInvocation.CommandOrigin)
    $cmd.Begin($PSCmdlet)
  }
  process { $cmd.Process($_) }
  end {
    $cmd.End()
    $ps.ForEach{
      .$Callback $_
      $_.Dispose()
    }
  }
}

function ConvertTo-ErrMessage {
  [CmdletBinding(DefaultParameterSetName='Win32')]
  param(
    [Parameter(Mandatory, ParameterSetName='Win32', Position=0)]
    [Int32]$Win32,

    [Parameter(Mandatory, ParameterSetName='NtStatus', Position=0)]
    [Int32]$NtStatus
  )

  process {
    [Win32Exception]::new((.({$Win32},{
      New-Delegate ntdll {
        int RtlNtStatusToDosError([int])
      }
      $ntdll.RtlNtStatusToDosError.Invoke($NtStatus)
    })[$PSCmdlet.ParameterSetName -eq 'NtStatus'])).Message.ToLower()
  }
}

(Get-ChildItem "$PSScriptRoot\pwsh" -Filter *.ps1).ForEach{.$_.FullName}