Skip to content

Instantly share code, notes, and snippets.

@russcam
Created November 3, 2018 05:47
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save russcam/3a44c8dad43cf5ccbda0b4dd0832e0b5 to your computer and use it in GitHub Desktop.
Save russcam/3a44c8dad43cf5ccbda0b4dd0832e0b5 to your computer and use it in GitHub Desktop.
PowerShell Script module for interacting with Process Monitor (Procmon)
<#
.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