Last active
January 8, 2026 22:39
-
-
Save joshooaj/291bc28362fbb2bca4fd1790e21e8a18 to your computer and use it in GitHub Desktop.
PowerShell script to schedule and create memory dumps using procdump
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
| #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