Skip to content

Instantly share code, notes, and snippets.

@joshooaj
Last active January 8, 2026 22:39
Show Gist options
  • Select an option

  • Save joshooaj/291bc28362fbb2bca4fd1790e21e8a18 to your computer and use it in GitHub Desktop.

Select an option

Save joshooaj/291bc28362fbb2bca4fd1790e21e8a18 to your computer and use it in GitHub Desktop.
PowerShell script to schedule and create memory dumps using procdump
#Requires -RunAsAdministrator
<#
.SYNOPSIS
Creates memory dumps using procdump.exe with automatic compression and retention management.
.DESCRIPTION
This script captures memory dumps of a specified process using Sysinternals procdump.exe,
compresses them into ZIP archives, and manages retention by keeping only the most recent dumps.
The script can run once or continuously on a specified interval. It can also register and
unregister itself as a scheduled task that runs automatically at system startup.
Key features:
- Captures full memory dumps using procdump.exe -ma
- Automatically compresses dumps to ZIP format to save disk space
- Maintains a rolling window of dumps (oldest are deleted when MaxDumpFiles is exceeded)
- Supports continuous operation with configurable intervals
- Can be registered as a Windows scheduled task for automatic execution
- Logs all operations to transcript files in the destination directory
.PARAMETER Process
The name of the process to capture memory dumps from (e.g., 'notepad.exe' or 'w3wp').
Process name can include or exclude the .exe extension.
Default: 'VideoOS.Event.Server.exe'
.PARAMETER Destination
The directory path where memory dumps, ZIP files, and transcript logs will be saved.
The path will be expanded to support relative paths and tilde (~) notation.
Default: The directory containing the script ($PSScriptRoot)
.PARAMETER MaxDumpFiles
The maximum number of compressed dump files to retain. When this limit is exceeded,
the oldest ZIP files are automatically deleted to free up disk space.
Default: 12
.PARAMETER Interval
The time interval between memory dump captures when running continuously.
If not specified, the script will capture one dump and exit.
Accepts TimeSpan objects or strings like '01:00:00' (1 hour) or '00:30:00' (30 minutes).
When registering a scheduled task, defaults to 1 hour if not specified.
.PARAMETER ProcdumpPath
The full path to the procdump.exe executable. If not found at the specified path,
the script will search for procdump.exe in the system PATH.
Default: procdump.exe in the script directory ($PSScriptRoot\procdump.exe)
.PARAMETER Register
Registers a Windows scheduled task to run this script automatically at system startup.
The task runs as NT AUTHORITY\SYSTEM with highest privileges and will continue capturing
dumps on the specified interval indefinitely. If a task with the same name exists, it
will be removed and recreated.
.PARAMETER Unregister
Stops and unregisters the Windows scheduled task created by the -Register parameter.
The task name is based on the process name: "Scheduled procdump for <ProcessName>"
.EXAMPLE
.\procdump.ps1 -Register
Registers a scheduled task that captures one memory dump per hour for the process
VideoOS.Event.Server.exe, saves them to the same folder as this script, and retainings
a maximum of 12 files.
.EXAMPLE
.\procdump.ps1 -Process 'notepad.exe'
Captures a single memory dump of notepad.exe and saves it to the script directory.
.EXAMPLE
.\procdump.ps1 -Process 'w3wp' -Destination 'C:\Dumps' -Interval '00:15:00' -MaxDumpFiles 20
Captures memory dumps of w3wp.exe every 15 minutes, stores them in C:\Dumps, and keeps
only the 20 most recent dumps.
.EXAMPLE
.\procdump.ps1 -Process 'myapp.exe' -Interval '01:00:00' -Register
Registers a scheduled task that captures dumps of myapp.exe immediately, and every hour
afterward. The task will automatically start after a system restart and runs continuously
until unregistered.
.EXAMPLE
.\procdump.ps1 -Process 'myapp.exe' -Unregister
Stops and removes the scheduled task for myapp.exe memory dumps.
.OUTPUTS
The script outputs log messages to the console and creates transcript files in the
destination directory.
.NOTES
Requires Administrator privileges to run (#Requires -RunAsAdministrator).
Requires procdump.exe from Sysinternals Suite (https://docs.microsoft.com/sysinternals/).
The script automatically accepts the Sysinternals EULA on first run (-accepteula).
Transcript logs are created in the destination directory for troubleshooting.
ZIP file naming format: procdump_<ProcessName>_yyyy-MM-dd_HH-mm-ss.zip
If procdump.exe fails to capture a dump, the script waits 10 seconds before retrying.
#>
param(
[Parameter()]
[string]
$Process = 'VideoOS.Event.Server.exe',
[Parameter()]
[string]
$Destination = $PSScriptRoot,
[Parameter()]
[int]
$MaxDumpFiles = 12,
[Parameter()]
[timespan]
$Interval,
[Parameter()]
[string]
$ProcdumpPath = (Join-Path $PSScriptRoot 'procdump.exe'),
[Parameter()]
[switch]
$Register,
[Parameter()]
[switch]
$Unregister
)
function ConvertFrom-ProcdumpStdout {
param(
[Parameter(Mandatory)]
[AllowEmptyString()]
[string[]]
$StdOut
)
<# Sample Output
ProcDump v11.1 - Sysinternals process dump utility
Copyright (C) 2009-2025 Mark Russinovich and Andrew Richards
Sysinternals - www.sysinternals.com
[09:17:30]Dump 1 info: Available space: 29856894976
[09:17:30]Dump 1 initiated: C:\temp\notepad.exe_260108_091730.dmp
[09:17:31]Dump 1 writing: Estimated dump file size is 106 MB.
[09:17:31]Dump 1 complete: 106 MB written in 0.1 seconds
[09:17:31]Dump count reached.
#>
$obj = [pscustomobject]@{
AvailableGB = 0
DumpFile = ''
SizeMB = 0
Seconds = 0
}
$obj.AvailableGB = $StdOut | Where-Object {
$_ -match 'Available space: (?<space>\d+)'
} | Select-Object -First 1 | ForEach-Object {
($Matches['space'] -as [long]) / 1GB
}
$obj.DumpFile = $StdOut | Where-Object {
$_ -match 'initiated: (?<dumpfile>.+)'
} | Select-Object -First 1 | ForEach-Object {
$Matches['dumpfile']
}
$StdOut | Where-Object {
$_ -match 'complete: (?<size>\d+) [a-z]{2} written in (?<seconds>[0-9\.]+)'
} | Select-Object -First 1 | ForEach-Object {
$obj.SizeMB = $Matches['size'] -as [double]
$obj.Seconds = $Matches['seconds'] -as [double]
}
$obj
}
function Log {
param(
[Parameter(Position = 0)]
[object[]]
$Message
)
process {
foreach ($msg in $Message) {
Write-Host "$(Get-Date -Format o) - $msg"
}
}
}
try {
# Fall back to $PWD if script is run in a way that $PSScriptRoot is empty
if ([string]::IsNullOrWhiteSpace($Destination)) {
$Destination = $PWD
}
# Expand the full path for $Destination in case "~" was used for example
$Destination = (Resolve-Path -Path $Destination).Path
Log "Memory dumps and logs will be written to $Destination"
$logPath = Join-Path $Destination "procdump_transcript_$(Get-Date -Format yyyy-MM-dd_HH-mm-ss).txt"
$null = Start-Transcript -Path $logPath
# If procdump.exe is not found in current folder, check if the command is available
# in $env:Path and if not, throw an error
if (!(Test-Path $ProcdumpPath)) {
if ($location = (Get-Command procdump -ErrorAction SilentlyContinue).Source) {
Write-Host "Procdump executable found in `$env:Path"
$ProcdumpPath = $location
} else {
throw ([System.IO.FileNotFoundException]::new('procdump.exe not found', $ProcdumpPath))
}
}
Log "procdump path is $ProcdumpPath"
if ($Unregister) {
$taskName = "Scheduled procdump for $Process"
Log "Stopping and unregistering scheduled task '$taskName' if it exists"
Stop-ScheduledTask -TaskName $taskName -ErrorAction Stop
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -PassThru
exit
}
# Register a scheduled task
if ($Register) {
if (!$PSCmdlet.MyInvocation.BoundParameters.ContainsKey('Interval')) {
$Interval = New-TimeSpan -Hours 1
}
$settings = @{
AllowStartIfOnBatteries = $true
StartWhenAvailable = $true
MultipleInstances = 'IgnoreNew'
ExecutionTimeLimit = [timespan]::Zero
}
$action = @{
Execute = 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe'
Argument = @(
'-NoLogo'
'-NoProfile'
'-NonInteractive'
'-ExecutionPolicy Bypass'
'-Command'
$PSCmdlet.MyInvocation.MyCommand.Source
"-Process $Process"
"-Destination '$Destination'"
"-MaxDumpFiles $MaxDumpFiles"
"-Interval $Interval"
"-ProcDumpPath $ProcdumpPath"
) -join ' '
}
$splat = @{
TaskName = "Scheduled procdump for $Process"
Trigger = New-ScheduledTaskTrigger -AtStartup
Settings = New-ScheduledTaskSettingsSet @settings
Action = New-ScheduledTaskAction @action
Principal = New-ScheduledTaskPrincipal -UserId 'NT AUTHORITY\SYSTEM' -LogonType ServiceAccount -RunLevel Highest
}
if ($task = Get-ScheduledTask -TaskName $splat.TaskName -ErrorAction SilentlyContinue) {
# Remove existing scheduled task if it exists
Stop-ScheduledTask -TaskName $task.TaskName
Unregister-ScheduledTask -TaskName $task.TaskName -Confirm:$false
}
Log "Registering and starting scheduled task '$($splat.TaskName)'"
$task = Register-ScheduledTask @splat
Start-ScheduledTask -TaskName $task.TaskName
exit
}
do {
# Clean up oldest ZIP files
$dumpFiles = Get-ChildItem (Join-Path $Destination "procdump_$Process*.zip") | Sort-Object CreationTime -Descending
$oldDumpFiles = $dumpFiles | Select-Object -Skip ($MaxDumpFiles - 1)
Log "Memory dump count: $($dumpFiles.Count)"
Log "MaxDumpFiles: $MaxDumpFiles"
if ($oldDumpFiles) {
Log "Removing $($oldDumpFiles.Count) old memory dump(s)"
$oldDumpFiles | Remove-Item -Verbose
}
# Create ZIP file name using timestamp when procdump is called
$zipPath = Join-Path $Destination "procdump_${Process}_$(Get-Date -Format yyyy-MM-dd_HH-mm-ss).zip"
Log "Saving memory dump for $Process to $Destination"
$stdOut = & $ProcdumpPath -accepteula -ma $Process $Destination
# Parse output from procdump to retrieve *.dmp file path
$result = ConvertFrom-ProcdumpStdout -StdOut $stdOut
if ([string]::IsNullOrWhiteSpace($result.DumpFile)) {
Write-Error "Exit code $LASTEXITCODE`n$($stdOut -join "`n")"
$waitTime = New-TimeSpan -Seconds 10
} else {
Log $stdOut
# Create ZIP file
Log "Compressing memory dump to $zipPath"
Compress-Archive -Path $result.DumpFile -DestinationPath $zipPath -CompressionLevel Optimal
Log 'Compression finished'
# Delete the .DMP file
Log "Deleting uncompressed memory dump $($result.DumpFile)"
Remove-Item $result.DumpFile -Verbose
$waitTime = $Interval
}
# Wait for <Interval> time and run again, or quit if no interval specified
if ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey('Interval')) {
Log "Sleeping for $($waitTime.TotalMinutes) minutes"
Log "Next memory dump scheduled for $((Get-Date).Add($waitTime))"
Start-Sleep -Seconds $waitTime.TotalSeconds
} else {
break
}
} while ($true)
} finally {
Log 'DONE'
$null = Stop-Transcript
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment