Created
November 3, 2018 05:47
-
-
Save russcam/3a44c8dad43cf5ccbda0b4dd0832e0b5 to your computer and use it in GitHub Desktop.
PowerShell Script module for interacting with Process Monitor (Procmon)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.Synopsis | |
Functions for working with Process monitor | |
.Link | |
https://docs.microsoft.com/en-us/sysinternals/downloads/procmon | |
#> | |
#Requires -Version 3.0 | |
New-Module -Name Procmon -Scriptblock { | |
Add-Type -Assembly System.IO.Compression.Filesystem | |
$columns = @{ | |
"Date & Time" = 116,156 | |
"Process Name" = 117,156 | |
"PID" = 118,156 | |
"Operation" = 119,156 | |
"Result" = 120,156 | |
"Detail" = 121,156 | |
"Sequence" = 122,156 | |
"Company" = 128,156 | |
"Description" = 129,156 | |
"Command Line" = 130,156 | |
"User" = 131,156 | |
"Image Path" = 132,156 | |
"Session" = 133,156 | |
"Path" = 135,156 | |
"TID" = 136,156 | |
"Relative Time" = 140,156 | |
"Duration" = 141,156 | |
"Time Of Day" = 142,156 | |
"Version" = 145,156 | |
"Event Class" = 146,156 | |
"Authentication ID" = 147,156 | |
"Virtualized" = 148,156 | |
"Integrity" = 149,156 | |
"Category" = 150,156 | |
"Parent PID" = 151,156 | |
"Architecture" = 152,156 | |
"Completion Time" = 228,156 | |
} | |
$relations = @{ | |
"is" = 0 | |
"is not" = 1 | |
"less than" = 2 | |
"more than" = 3 | |
"begins with" = 4 | |
"ends with" = 5 | |
"contains" = 6 | |
"excludes" = 7 | |
} | |
$actions = @{ | |
"Include" = 1 | |
"Exclude" = 0 | |
} | |
function ConvertTo-Bytes([int]$Int) { | |
# 16 bit only | |
([BitConverter]::GetBytes($Int))[0..1] | |
} | |
function Is-Admin { | |
$principal = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent() | |
if( -not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { | |
throw "You must be an Administrator to run this function" | |
} | |
} | |
function New-RuntimeDefinedParameter { | |
Param( | |
[string]$Name, | |
[int]$Position, | |
[string[]]$ValidateSet | |
) | |
$ParameterName = $Name | |
$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute | |
$ParameterAttribute.Mandatory = $true | |
$ParameterAttribute.Position = $Position | |
$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] | |
$AttributeCollection.Add($ParameterAttribute) | |
$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($ValidateSet) | |
$AttributeCollection.Add($ValidateSetAttribute) | |
$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection) | |
return $RuntimeParameter | |
} | |
function Get-ColumnBinaryValue { | |
[CmdletBinding()] | |
Param() | |
DynamicParam { | |
$RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary | |
$Column = New-RuntimeDefinedParameter -Name 'Column' -Position 0 -ValidateSet $columns.Keys | |
$RuntimeParameterDictionary.Add('Column', $Column) | |
return $RuntimeParameterDictionary | |
} | |
Process { | |
return $columns[$PSBoundParameters['Column']] | |
} | |
} | |
function Get-RelationBinaryValue { | |
[CmdletBinding()] | |
Param() | |
DynamicParam { | |
$RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary | |
$Relation = New-RuntimeDefinedParameter -Name 'Relation' -Position 0 -ValidateSet $relations.Keys | |
$RuntimeParameterDictionary.Add('Relation', $Relation) | |
return $RuntimeParameterDictionary | |
} | |
Process { | |
return $relations[$PSBoundParameters['Relation']] | |
} | |
} | |
function Get-ActionBinaryValue { | |
[CmdletBinding()] | |
Param() | |
DynamicParam { | |
$RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary | |
$Action = New-RuntimeDefinedParameter -Name 'Action' -Position 0 -ValidateSet $actions.Keys | |
$RuntimeParameterDictionary.Add('Action', $Action) | |
return $RuntimeParameterDictionary | |
} | |
Process { | |
return $actions[$PSBoundParameters['Action']] | |
} | |
} | |
<# | |
.Synopsis | |
Creates a new Process monitor filter | |
.Description | |
Creates a new Process monitor filter that can be applied to the | |
registry to filter events in Process monitor | |
DYNAMIC PARAMETERS | |
-Column <String> | |
the Column name to apply the filter to. | |
Required? true | |
Position? 1 | |
Default value | |
Accept pipeline input? false | |
Accept wildcard characters? false | |
-Relation <String> | |
the Relation condition to apply | |
Required? true | |
Position? 2 | |
Default value | |
Accept pipeline input? false | |
Accept wildcard characters? false | |
-Action <String> | |
whether to include or exclude events that match | |
Required? true | |
Position? 4 | |
Default value | |
Accept pipeline input? false | |
Accept wildcard characters? false | |
.Example | |
New-ProcmonFilter -Column 'Process Name' -Relation is -Value Procmon.exe -Action Exclude; | |
.Parameter Value | |
the Value of the condition | |
#> | |
function New-ProcmonFilter { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Position=2, Mandatory=$true, ValueFromPipeline = $true)] | |
[string] | |
[ValidateNotNullOrEmpty()] | |
[ValidateLength(1,65535)] | |
$Value | |
) | |
DynamicParam { | |
$RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary | |
$Column = New-RuntimeDefinedParameter -Name 'Column' -Position 0 -ValidateSet $columns.Keys | |
$Relation = New-RuntimeDefinedParameter -Name 'Relation' -Position 1 -ValidateSet $relations.Keys | |
$Action = New-RuntimeDefinedParameter -Name 'Action' -Position 3 -ValidateSet $actions.Keys | |
$RuntimeParameterDictionary.Add('Column', $Column) | |
$RuntimeParameterDictionary.Add('Relation', $Relation) | |
$RuntimeParameterDictionary.Add('Action', $Action) | |
return $RuntimeParameterDictionary | |
} | |
Process { | |
return [PSCustomObject]@{ | |
PSTypeName = "ProcmonFilter" | |
Column = $PSBoundParameters['Column'] | |
Relation = $PSBoundParameters['Relation'] | |
Value = $Value | |
Action = $PSBoundParameters['Action'] | |
} | |
} | |
} | |
<# | |
.Synopsis | |
Gets the default Process monitor filters | |
.Description | |
Gets the default Process monitor filters. Useful to add to any other specific | |
filters that may be defined | |
.Example | |
Get-DefaultProcmonFilters | |
#> | |
function Get-DefaultProcmonFilters { | |
$filters = @() | |
$filters += @('Procmon.exe', 'Procexp.exe', 'Autoruns.exe', | |
'Procmon64.exe', 'Procexp64.exe', 'System') | | |
New-ProcmonFilter -Column 'Process Name' -Relation is -Action Exclude | |
$filters += New-ProcmonFilter -Column Operation -Relation 'begins with' -Value IRP_MJ_ -Action Exclude | |
$filters += New-ProcmonFilter -Column Operation -Relation 'begins with' -Value FASTIO_ -Action Exclude | |
$filters += New-ProcmonFilter -Column Result -Relation 'begins with' -Value 'FAST IO' -Action Exclude | |
$filters += @('pagefile.sys', '$Mft', '$MftMirr', '$LogFile', '$Volume', '$AttrDef', | |
'$Root', '$Bitmap', '$Boot', '$BadClus', '$Secure', '$UpCase') | | |
New-ProcmonFilter -Column Path -Relation 'ends with' -Action Exclude | |
$filters += New-ProcmonFilter -Column Path -Relation contains -Value '$Extend' -Action Exclude | |
$filters += New-ProcmonFilter -Column 'Event Class' -Relation is -Value Profiling -Action Exclude | |
return $filters | |
} | |
<# | |
.Synopsis | |
Gets the bytes for a collection of Process monitor filters | |
.Description | |
Gets the bytes for a collection of Process monitor filters that | |
can be persisted in the registry, to apply the filters to | |
Process monitor on start up | |
.Example | |
Get-ProcmonFiltersBytes -Filters @(New-ProcmonFilter -Column 'Process Name' -Relation is -Value Procmon.exe -Action Include) | |
.Parameter Filters | |
The Process monitor filters to get the bytes for. Each filter | |
must be a PSCustomObject with PsTypeName of 'ProcmonFilter' and | |
have the following valid properties: Column, Relation, Value, Action. | |
The best way to create a new filter is with New-ProcmonFilter | |
#> | |
function Get-ProcmonFiltersBytes { | |
[CmdletBinding()] | |
Param( | |
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] | |
[ValidateCount(0, 65535)] | |
[PSTypeName("ProcmonFilter")] | |
[PSCustomObject[]] | |
$Filters | |
) | |
Begin { | |
$collectedFilters = @() | |
} | |
Process { | |
$collectedFilters += $Filters | |
} | |
End { | |
[Byte[]]$bytes = 1 | |
$bytes += ConvertTo-Bytes $collectedFilters.Length | |
$bytes += 0,0 | |
$index = 0 | |
$collectedFilters | ForEach-Object { | |
$bytes += Get-ColumnBinaryValue $_.Column | |
$bytes += 0,0 | |
$bytes += Get-RelationBinaryValue $_.Relation | |
$bytes += 0,0,0 | |
$bytes += Get-ActionBinaryValue $_.Action | |
$value = $_.Value | |
if ($value.Length -gt 65535) { | |
Write-Warning "Procmon filter Value at index $index is larger than 65535. Value will be truncated" | |
$value = $value.Substring(0, 65535) | |
} | |
$bytes += ConvertTo-Bytes (($value.Length + 1) * 2) | |
$bytes += 0,0 | |
$bytes += [System.Text.Encoding]::Unicode.GetBytes($value) | |
$bytes += 0,0,0,0,0,0,0,0,0,0 | |
$index += 1 | |
} | |
return $bytes | |
} | |
} | |
<# | |
.Synopsis | |
Writes the Process monitor filters to the registry | |
.Description | |
Writes the Process monitor filters to the registry, | |
to apply the filters to Process monitor on start up | |
.Example | |
@(New-ProcmonFilter -Column 'Process Name' -Relation is -Value Procmon.exe -Action Include) ` | |
| Write-ProcmonFiltersToRegistry | |
.Parameter Filters | |
The Process monitor filters. Use New-ProcmonFilter to create new filters, | |
and Get-DefaultProcmonFilters to get the default filters | |
#> | |
function Write-ProcmonFiltersToRegistry { | |
[CmdletBinding()] | |
Param ( | |
[PSTypeName("ProcmonFilter")] | |
[PSCustomObject[]] | |
[Parameter(Mandatory = $true, ValueFromPipeline = $true)] | |
$Filters | |
) | |
Begin { | |
Is-Admin | |
$collectedFilters = @() | |
} | |
Process { | |
$collectedFilters += $Filters | |
} | |
End { | |
$collectedFilters | Get-ProcmonFiltersBytes | Write-ProcmonFiltersBytesToRegistry | |
} | |
} | |
<# | |
.Synopsis | |
Writes the Process monitor filter bytes to the registry | |
.Description | |
Writes the Process monitor filter bytes to the registry, | |
to apply the filters to Process monitor on start up | |
.Example | |
@(New-ProcmonFilter -Column 'Process Name' -Relation is -Value Procmon.exe -Action Include) ` | |
| Get-ProcmonFiltersBytes ` | |
| Write-ProcmonFiltersBytesToRegistry | |
.Parameter FilterBytes | |
The bytes of the Process monitor filters. Use Get-ProcmonFiltersBytes | |
to get the bytes for a collection of Process monitor filters | |
#> | |
function Write-ProcmonFiltersBytesToRegistry { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] | |
[Byte[]]$FilterBytes | |
) | |
Begin { | |
Is-Admin | |
$bytes = @() | |
} | |
Process { | |
$bytes += $FilterBytes | |
} | |
End { | |
New-ItemProperty "HKCU:\Software\Sysinternals\Process Monitor" "FilterRules" ` | |
-Value $bytes -PropertyType Binary -Force -ErrorVariable regError | Out-Null | |
if ($regError) { | |
throw "Writing Procmon filter rules to registry failed. $regError" | |
} | |
} | |
} | |
<# | |
.Synopsis | |
Clears the Process monitor filter bytes in the registry | |
.Description | |
Clears the Process monitor filter bytes in the registry | |
.Example | |
Clear-ProcmonFiltersRegistry | |
#> | |
function Clear-ProcmonFiltersRegistry { | |
Is-Admin | |
1,0,0,0,0 | Write-ProcmonFiltersBytesToRegistry | |
} | |
<# | |
.Synopsis | |
Downloads Process monitor zip to the destination file | |
.Description | |
Downloads Process monitor zip to the destination file | |
.Example | |
Download-Procmon | |
#> | |
function Download-Procmon { | |
Param( | |
[string]$Destination = "$env:TEMP\ProcessMonitor.zip" | |
) | |
if (-not (Test-Path $Destination)) { | |
(New-Object System.Net.WebClient).DownloadFile("https://download.sysinternals.com/files/ProcessMonitor.zip", $Destination) | |
} | |
} | |
<# | |
.Synopsis | |
Unzips Process monitor zip file to the destination directory | |
.Description | |
Unzips Process monitor zip file to the destination directory | |
.Example | |
Unzip-Procmon -ZipSource "C:\ProcessMonitor.zip -Destination C:\ProcessMonitor | |
.Parameter ZipSource | |
The Process monitor zip file | |
.Parameter Destination | |
The destination directory | |
#> | |
function Unzip-Procmon { | |
Param( | |
[string]$ZipSource = "$env:TEMP\ProcessMonitor.zip", | |
[string]$Destination = "$env:TEMP\ProcessMonitor" | |
) | |
if (-not (Test-Path $Destination) -and -not (Test-Path "$Destination\Procmon.exe")) { | |
New-Item $Destination -Type Directory -ErrorAction Ignore | Out-Null | |
[IO.Compression.ZipFile]::ExtractToDirectory($ZipSource, $Destination) | |
} | |
} | |
<# | |
.Synopsis | |
Waits for Process monitor processes to finish running | |
.Description | |
Waits for Process monitor processes to finish running | |
.Example | |
Wait-Procmon | |
.Example | |
Wait-Procmon -Wait 10 | |
.Parameter Wait | |
The maximum amount of seconds to wait. If the processes | |
are still running after this time, an error will be written | |
to Standard error | |
#> | |
function Wait-Procmon { | |
Param( | |
[Parameter(Mandatory = $false)] | |
[ValidateRange(0, 3600)] | |
[int] | |
$Wait | |
) | |
$stopWatch = [System.Diagnostics.Stopwatch]::StartNew() | |
while ($true) { | |
$processes = Get-Process Procmon,Procmon64 -ErrorAction Ignore | |
if ($processes -eq $null) { | |
break | |
} | |
if ($Wait -and $Wait -gt 0 -and $stopWatch.Elapsed.TotalSeconds -gt $Wait) { | |
Write-Error "Procmon processes still running after waiting $Wait" | |
break | |
} | |
} | |
} | |
<# | |
.Synopsis | |
Invokes Process monitor with given arguments | |
.Description | |
Invokes Process monitor with given arguments. | |
Looks for Procmon.exe in the passed directory. If the | |
directory does not exist, Process monitor is downloaded | |
to the given directory. | |
.Example | |
Invoke-Procmon -ProcmonDir C:\ -ExeArgs "/BackingFile C:\events.pml /Quiet /AcceptEula /Minimized" | |
.Example | |
Invoke-Procmon -ExeArgs "/Quiet /AcceptEula /Minimized" | |
.Example | |
Invoke-Procmon | |
.Parameter ProcmonDir | |
The directory in which to locate Procmon.exe. If the directory | |
does not exist or does not contain Procmon.exe, will be downloaded | |
.Parameter ExeArgs | |
The arguments for the Procmon.exe executable | |
#> | |
function Invoke-Procmon { | |
[CmdletBinding()] | |
Param( | |
[Parameter()] | |
[string] | |
$ProcmonDir = "$env:TEMP\ProcessMonitor", | |
[Parameter()] | |
[string[]] | |
$ExeArgs = @() | |
) | |
Is-Admin | |
Download-Procmon -Destination "$($ProcmonDir).zip" | |
Unzip-Procmon -Destination $ProcmonDir | |
if ($ExeArgs) { | |
Write-Verbose "Executing '$ProcmonDir\Procmon.exe $ExeArgs'" | |
Start-Process -FilePath "$ProcmonDir\Procmon.exe" -ArgumentList $ExeArgs -PassThru | |
} | |
else { | |
Write-Verbose "Executing '$ProcmonDir\Procmon.exe'" | |
Start-Process -FilePath "$ProcmonDir\Procmon.exe" -PassThru | |
} | |
} | |
<# | |
.Synopsis | |
Starts Process monitor with given filters applied, | |
writing events to backing file | |
.Description | |
Starts Process monitor with given filters applied, | |
writing events to backing file. | |
If filters are supplied, these are written to the registry, | |
to be applied to Process monitor on startup. | |
.Example | |
Start-Procmon | |
.Example | |
$(@(New-ProcmonFilter -Column 'Process Name' -Relation is -Value chrome.exe -Action Include) + | |
(Get-DefaultProcmonFilters)) | Start-Procmon | |
.Parameter ProcmonDir | |
The directory in which to locate Procmon.exe. If the directory | |
does not exist or does not contain Procmon.exe, it will be downloaded | |
to the directory | |
.Parameter Filters | |
The collection of filters to apply to Process Monitor. Use | |
New-ProcmonFilter to construct new filters, and Get-DefaultProcmonFilters | |
to get the default filters | |
.Parameter EventFile | |
The file in which to persist Process monitor events | |
.Parameter Runtime | |
Specify a number of seconds that Process monitor should run for | |
before terminating | |
.Parameter DefaultFilters | |
Whether to apply the default Process monitor filters to the | |
collection of filters | |
#> | |
function Start-Procmon { | |
[CmdletBinding()] | |
Param ( | |
[Parameter()] | |
[string] | |
$ProcmonDir = "$env:TEMP\ProcessMonitor", | |
[PSTypeName("ProcmonFilter")] | |
[PSCustomObject[]] | |
[Parameter(Mandatory = $false, ValueFromPipeline = $true)] | |
$Filters = @(), | |
[Parameter()] | |
[string] | |
$EventFile = "$env:TEMP\ProcessMonitor\events.pml", | |
[Parameter()] | |
[int] | |
$Runtime, | |
[Parameter()] | |
[switch] | |
$DefaultFilters | |
) | |
Begin { | |
$collectedFilters = @() | |
} | |
Process { | |
$collectedFilters += $Filters | |
} | |
End { | |
if ($DefaultFilters) { | |
$collectedFilters += Get-DefaultProcmonFilters | |
} | |
if ($collectedFilters) { | |
$collectedFilters | Sort-Object -Property Column,Relation,Value,Action -Unique | Write-ProcmonFiltersToRegistry | |
} | |
$runtimeSeconds = "" | |
if ($Runtime -and $Runtime -gt 0) { | |
$runtimeSeconds = "/Runtime $Runtime" | |
} | |
Invoke-Procmon -ProcmonDir "$ProcmonDir" -ExeArgs "/BackingFile `"$EventFile`" $runtimeSeconds /Quiet /AcceptEula /Minimized" | Out-Null | |
} | |
} | |
<# | |
.Synopsis | |
Stops Process monitor | |
.Description | |
Stops Process monitor, optionally waiting a timeout | |
.Example | |
Stop-Procmon | |
.Example | |
Stop-Procmon -Timeout (New-TimeSpan -Seconds 10) | |
.Parameter ProcmonDir | |
The directory in which to locate Procmon.exe. If the directory | |
does not exist or does not contain Procmon.exe, will be downloaded | |
.Parameter Wait | |
The maximum amount of time in seconds to wait for the Process monitor processes | |
to finish. Will wait indefinitely for processes to finish | |
#> | |
function Stop-Procmon { | |
[CmdletBinding()] | |
Param( | |
[Parameter()] | |
$ProcmonDir = "$env:TEMP\ProcessMonitor", | |
[Parameter(Mandatory = $false)] | |
[int] | |
$Wait | |
) | |
Invoke-Procmon -ProcmonDir "$ProcmonDir" -ExeArgs "/Terminate" | Out-Null | |
Wait-Procmon $Wait | |
} | |
<# | |
.Synopsis | |
Converts a Process monitor event file to CSV file | |
.Description | |
Converts a Process monitor event file to CSV file | |
.Example | |
Stop-Procmon | |
.Example | |
Stop-Procmon -Timeout (New-TimeSpan -Seconds 10) | |
.Parameter ProcmonDir | |
The directory in which to locate Procmon.exe. If the directory | |
does not exist or does not contain Procmon.exe, will be downloaded | |
.Parameter EventFile | |
The source file in which Process monitor events are persisted | |
.Parameter CsvFile | |
The desintation file to write Process monitor events in CSV format | |
.Parameter Wait | |
The maximum amount of time in seconds to wait for the Process monitor processes | |
to finish. Will wait indefinitely for processes to finish | |
.Parameter ApplySavedFilter | |
Whether to apply the saved event filter to the conversion | |
#> | |
function ConvertTo-ProcmonCsv { | |
[CmdletBinding()] | |
Param( | |
[Parameter()] | |
[string] | |
$ProcmonDir = "$env:TEMP\ProcessMonitor", | |
[Parameter()] | |
[string] | |
$EventFile = "$env:TEMP\ProcessMonitor\events.pml", | |
[Parameter()] | |
[string] | |
$CsvFile = "$env:TEMP\ProcessMonitor\events.csv", | |
[Parameter(Mandatory = $false)] | |
[int] | |
$Wait, | |
[switch] | |
$ApplySavedFilter = $true | |
) | |
$saveApplyFilter = "" | |
if ($ApplySavedFilter) { | |
$saveApplyFilter = "/SaveApplyFilter" | |
} | |
Invoke-Procmon -ProcmonDir "$ProcmonDir" -ExeArgs "/Openlog `"$EventFile`" /SaveAs `"$CsvFile`" $saveApplyFilter /AcceptEula" | Out-Null | |
Wait-Procmon $Wait | |
} | |
Export-ModuleMember -Function Download-Procmon,Unzip-Procmon,Invoke-Procmon, ` | |
Start-Procmon,Stop-Procmon,New-ProcmonFilter,Get-DefaultProcmonFilters, ` | |
Get-ProcmonFiltersBytes,Write-ProcmonFiltersToRegistry, ` | |
Write-ProcmonFiltersBytesToRegistry,Clear-ProcmonFiltersRegistry,ConvertTo-ProcmonCsv | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment