pstools.psm1
using namespace System.IO using namespace System.Linq 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 $keys, $types = ($x = [PSObject].Assembly.GetType( 'System.Management.Automation.TypeAccelerators' ))::Get.Keys, @{ buf = [Byte[]] ptr = [IntPtr] 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 ([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("Can not 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("Can not 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 Read-SourceData { [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.') } switch ($matches.Count) { 2 {$matches[0]} 3 {[Char[]]($matches[1] * $matches[2])} } # switch }.ForEach{ $tmp = [Marshal]::"Read$($set["$_"])"($ptr, $pos) if ([Char]::IsUpper([Char]$_) -and $_ -ne 'p') { [BitConverter]::"To$('SU'[$_ -ne 'B'])$($set["$_"])"( [BitConverter]::GetBytes($tmp), 0 ) } else {$tmp} Write-Verbose "+0x$($pos.ToString('X3')) $($set["$_"])" $pos += [Marshal]::SizeOf(0 -as ($set["$_"] -as [Type])) } } catch { Write-Verbose $_ } finally { if ($gch) { $gch.Free() } } } } function New-PsProxy { [CmdletBinding(DefaultParameterSetName='Name')] param( [Parameter(Mandatory, ParameterSetName='Name', Position=0)] [ValidateScript({!!($script:ps = Get-Process $_ -ErrorAction 0)})] [ValidateNotNullOrEmpty()] [String]$Name, [Parameter(Mandatory, ParameterSetName='Id', Position=0)] [ValidateScript({!!($script:ps = Get-Process -Id $_ -ErrorAction 0)})] [Int32]$Id, [Parameter(Mandatory, Position=1)] [ValidateScript({![String]::IsNullOrEmpty($_)})] [ScriptBlock]$Callback ) process { $ps.ForEach{ .$Callback $_ $_.Dispose() } } } Set-Alias -Name pswsc -Value Clear-PsWorkingSet function Clear-PsWorkingSet { [CmdletBinding()]param($PSBoundParameters) process { New-Delegate kernel32 { bool SetProcessWorkingSetSize([ptr, int, int]) } New-PsProxy $PSBoundParameters -Callback { .({'PID {0}: {1}' -f $_.Id, $kernel32.SetProcessWorkingSetSize.Invoke( $_.Handle, -1, -1 )},{ Write-Verbose "PID $($_.Id): could not clear working set." })[!$_.Handle] } } } Set-Alias -Name psdump -Value Get-PsDump function Get-PsDump { [CmdletBinding()]param($PSBoundParameters) DynamicParam { $dict = [RuntimeDefinedParameterDictionary]::new() $attr = [Collection[Attribute]]::new() $attr.Add((New-Object ParameterAttribute -Property @{ ParameterSetName = '__AllParameterSets' })) $attr.Add([ValidateNotNullOrEmptyAttribute]::new()) $attr.Add((New-Object ValidateSetAttribute(@('MiniDump', 'FullDump')))) $paramDumpType = [RuntimeDefinedParameter]::new('DumpType', [String], $attr) $paramDumpType.Value = 'MiniDump' $dict.Add('DumpType', $paramDumpType) $attr = [Collection[Attribute]]::new() $attr.Add((New-Object ParameterAttribute -Property @{ ParameterSetName = '__AllParameterSets' })) $attr.Add([ValidateNotNullOrEmptyAttribute]::new()) $attr.Add((New-Object ValidateScriptAttribute({Test-Path $_}))) $paramSavePath = [RuntimeDefinedParameter]::new('SavePath', [String], $attr) $paramSavePath.Value = $pwd.Path $dict.Add('SavePath', $paramSavePath) return $dict } process { New-Delegate kernel32 { ptr LoadLibraryW([buf]) bool FreeLibrary([ptr]) } if (($dll = $kernel32.LoadLibraryW.Invoke( [buf].Uni('dbghelp.dll') )) -eq [IntPtr]::Zero) { Write-Verbose 'can not load dbghelp.dll library.' return } New-Delegate dbghelp { bool MiniDumpWriteDump([ptr, uint, sfh, uint, ptr, ptr, ptr]) } $numeric = (6, 261)[$paramDumpType.Value -eq 'MiniDump'] New-PsProxy $PSBoundParameters -Callback { $dmp = "$(Resolve-Path $paramSavePath.Value)\$($_.Name)_$($_.Id).dmp" try { $fs = [File]::Create($dmp) if (!$dbghelp.MiniDumpWriteDump.Invoke( $_.Handle, $_.Id, $fs.SafeFileHandle, $numeric, [IntPtr]::Zero, [IntPtr]::Zero, [IntPtr]::Zero )) { $err = $true throw [InvalidOperationException]::new("Dumping failure PID: $($_.Id)") } } catch { Write-Verbose $_ } finally { if ($fs) { $fs.Dispose() } if ($err) { Remove-Item $dmp -Force } } } if (!$kernel32.FreeLibrary.Invoke($dll)) { Write-Verbose 'can not release dbghelp.dll library.' } } } Set-Alias -Name psvprot -Value Get-PsVMInfo function Get-PsVMInfo { [CmdletBinding()]param($PSBoundParameters) DynamicParam { $dict = [RuntimeDefinedParameterDictionary]::new() $attr = [Collection[Attribute]]::new() $attr.Add((New-Object ParameterAttribute -Property @{ ParameterSetName = '__AllParameterSets' })) $paramAddress = [RuntimeDefinedParameter]::new('Address', [IntPtr], $attr) $paramAddress.Value = [IntPtr]::Zero $dict.Add('Address', $paramAddress) return $dict } process { New-Delegate kernel32 { int VirtualQueryEx([ptr, ptr, ptr, uint]) } $MEM_PROTECT, $MEM_STATE, $MEM_TYPE, $sz = @{ PAGE_NOACCESS = 0x00000001 PAGE_READONLY = 0x00000002 PAGE_READWRITE = 0x00000004 PAGE_WRITECOPY = 0x00000008 PAGE_EXECUTE = 0x00000010 PAGE_EXECUTE_READ = 0x00000020 PAGE_EXECUTE_READWRITE = 0x00000040 PAGE_EXECUTE_WRITECOPY = 0x00000080 PAGE_GUARD = 0x00000100 PAGE_NOCACHE = 0x00000200 PAGE_WRITECOMBINE = 0x00000400 }, @{ MEM_COMMIT = 0x00001000 MEM_RESERVE = 0x00002000 MEM_FREE = 0x00010000 }, @{ MEM_PRIVATE = 0x00020000 MEM_MAPPED = 0x00040000 MEM_IMAGE = 0x00100000 }, [IntPtr]::Size $fmt, $to_i = "{0:x$($sz * 2)}", "ToInt$($sz * 8)" New-PsProxy $PSBoundParameters -Callback { try { $ptr = [Marshal]::AllocHGlobal((0x1C, 0x30)[$sz / 4 - 1]) # sizeof(MEMORY_BASIC_INFORMATION) if ($kernel32.VirtualQueryEx.Invoke($_.Handle, $paramAddress.Value, $ptr, 0x1000) -ne 0) { $mbi = Read-SourceData -Handle $ptr -Map p4I3 [PSCustomObject]@{ BaseAddress = $fmt -f $mbi[0].$to_i() AllocationBase = $fmt -f $mbi[1].$to_i() AllocationProtect = ($x = [BitConverter]::ToUInt32( [BitConverter]::GetBytes($mbi[2].$to_i()), 0 )).ToString('x8'), [Enum].All($MEM_PROTECT, $x) RegionSize = $fmt -f $mbi[3].$to_i() State = $mbi[4].ToString('x8'), [Enum].All($MEM_STATE, $mbi[4]) Protect = $mbi[5].ToString('x8'), [Enum].All($MEM_PROTECT, $mbi[5]) Type = $mbi[6].ToString('x8'), [Enum].All($MEM_TYPE, $mbi[6]) } } else { Write-Verbose 'Invalid address has been specified.' } } catch { Write-Verbose $_ } finally { if ($ptr) { [Marshal]::FreeHGlobal($ptr) } } } } } Set-Alias -Name psresume -Value Resume-PsProcess function Resume-PsProcess { [CmdletBinding()]param($PSBoundParameters) process { New-Delegate ntdll { int NtResumeProcess([ptr]) int RtlNtStatusToDosError([int]) } New-PsProxy $PSBoundParameters -Callback { if ([Enumerable]::Sum([Int32[]]( Select-Object -InputObject $_.Threads[0] -Property ThreadState, WaitReason ).PSObject.Properties.Value.ForEach{$_ -eq 5}) -eq 2) { if (($nts = $ntdll.NtResumeProcess.Invoke($_.Handle)) -ne 0) { Write-Verbose "$([Win32Exception]::new( $ntdll.RtlNtStatusToDosError.Invoke($nts) ).Message)" } else { Write-Verbose "Process $($_.Id) is resumed." } } else { Write-Verbose "Process $($_.Id) is already active." } } } } Set-Alias -Name psuspend -Value Suspend-PsProcess function Suspend-PsProcess { [CmdletBinding()]param($PSBoundParameters) process { New-Delegate ntdll { int NtSuspendProcess([ptr]) int RtlNtStatusToDosError([int]) } New-PsProxy $PSBoundParameters -Callback { if ([Enumerable]::Sum([Int32[]]( Select-Object -InputObject $_.Threads[0] -Property ThreadState, WaitReason ).PSObject.Properties.Value.ForEach{$_ -eq 5}) -ne 2) { if (($nts = $ntdll.NtSuspendProcess.Invoke($_.Handle)) -ne 0) { Write-Verbose "$([Win32Exception]::new( $ntdll.RtlNtStatusToDosError.Invoke($nts) ).Message)" } else { Write-Verbose "Process $($_.Id) is suspended." } } else { Write-Verbose "Process $($_.Id) is already suspended." } } } } Export-ModuleMember -Alias * -Function ( 'Clear-PsWorkingSet', 'Get-PsDump', 'Get-PsVMInfo', 'Resume-PsProcess', 'Suspend-PsProcess' ) |