Created
May 9, 2026 17:29
-
-
Save aaaddress1/739a087ba3bf5cfb4aab78ab0d7c19fc to your computer and use it in GitHub Desktop.
This script automatically cleans browser history/cache, Windows Explorer Recent/JumpList/MRU traces, PowerShell command history, IDA and Visual Studio recent project/file history, temporary files, Recycle Bin contents, Windows Event Logs, and can optionally zero-fill free disk space to reduce residual data artifacts within the VM.
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
| # Cleanup-TrainingVM-History.ps1 | |
| # powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\Cleanup-TrainingVM-History.ps1 | |
| #Requires -Version 5.1 | |
| <# | |
| Purpose: | |
| Clean common personal/history traces before exporting a Windows training VM as OVA. | |
| Default cleanup: | |
| - Browser local history/cache/session data: | |
| Chrome, Edge, Chromium, Brave, Vivaldi, Opera, Firefox | |
| - Windows Explorer: | |
| Recent files, Jump Lists, TypedPaths, OpenSave MRU, folder view bags | |
| - PowerShell: | |
| PSReadLine command history | |
| - IDA / Hex-Rays: | |
| Quick Start -> Previous list | |
| Recently opened binary/database history | |
| Registry MRU/history entries | |
| License files and license-like registry values are protected | |
| - Visual Studio Community / Visual Studio: | |
| Recent projects/files | |
| ApplicationPrivateSettings.xml | |
| privateregistry.bin | |
| ComponentModelCache | |
| Roslyn/cache/browser cache | |
| .vs solution metadata | |
| VS registry MRU values | |
| - Temp folders | |
| - Recycle Bin | |
| - Windows Event Logs | |
| - Zero free space with cipher /w | |
| Options: | |
| -CurrentUserOnly: | |
| Clean only the current user profile. | |
| -DisableFutureRecent: | |
| Disable future recent-document tracking for Explorer UI. | |
| -NoKillApps: | |
| Do not close browsers, IDA, VS, or Explorer before cleanup. | |
| -SkipBrowser: | |
| Skip browser cleanup. | |
| -SkipIda: | |
| Skip IDA cleanup. | |
| -SkipVisualStudio: | |
| Skip Visual Studio cleanup. | |
| -SkipEventLogs: | |
| Skip Windows Event Log cleanup. | |
| -SkipZeroFreeSpace: | |
| Skip cipher /w free-space zeroing. | |
| -NoSelfElevate: | |
| Do not auto-relaunch as Administrator. | |
| Notes: | |
| - This is for VM export hygiene, not forensic stealth. | |
| - Event logs may immediately contain new events after being cleared. | |
| - Browser sync/cloud history is not removed by local cleanup. | |
| - IDA license-like files and registry values are protected. | |
| #> | |
| [CmdletBinding()] | |
| param( | |
| [switch]$CurrentUserOnly, | |
| [switch]$DisableFutureRecent, | |
| [switch]$NoKillApps, | |
| [switch]$SkipBrowser, | |
| [switch]$SkipIda, | |
| [switch]$SkipVisualStudio, | |
| [switch]$SkipEventLogs, | |
| [switch]$SkipZeroFreeSpace, | |
| [switch]$NoSelfElevate | |
| ) | |
| $ErrorActionPreference = "Continue" | |
| function Write-Info { | |
| param([string]$Message) | |
| Write-Host "[*] $Message" | |
| } | |
| function Write-Ok { | |
| param([string]$Message) | |
| Write-Host "[+] $Message" | |
| } | |
| function Write-Warn { | |
| param([string]$Message) | |
| Write-Host "[!] $Message" | |
| } | |
| function Test-IsAdministrator { | |
| $principal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) | |
| return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) | |
| } | |
| function Invoke-SelfElevationIfNeeded { | |
| if ($NoSelfElevate) { | |
| return | |
| } | |
| if (Test-IsAdministrator) { | |
| return | |
| } | |
| Write-Warn "Administrator privilege is required. Relaunching with UAC prompt." | |
| $argList = @( | |
| "-NoProfile", | |
| "-ExecutionPolicy", "Bypass", | |
| "-File", "`"$PSCommandPath`"" | |
| ) | |
| foreach ($key in $PSBoundParameters.Keys) { | |
| $value = $PSBoundParameters[$key] | |
| if ($value -is [switch]) { | |
| if ($value.IsPresent) { | |
| $argList += "-$key" | |
| } | |
| } | |
| else { | |
| $argList += "-$key" | |
| $argList += "`"$value`"" | |
| } | |
| } | |
| Start-Process powershell.exe -ArgumentList $argList -Verb RunAs | |
| exit | |
| } | |
| function Test-HasWildcard { | |
| param([string]$Path) | |
| if ([string]::IsNullOrWhiteSpace($Path)) { | |
| return $false | |
| } | |
| return ($Path.IndexOfAny([char[]]"*?[]") -ge 0) | |
| } | |
| function Remove-PathSafe { | |
| param( | |
| [Parameter(ValueFromPipeline = $true)] | |
| [string[]]$Path | |
| ) | |
| process { | |
| foreach ($p in $Path) { | |
| if ([string]::IsNullOrWhiteSpace($p)) { | |
| continue | |
| } | |
| try { | |
| if (Test-HasWildcard -Path $p) { | |
| Get-ChildItem -Path $p -Force -ErrorAction SilentlyContinue | | |
| ForEach-Object { | |
| Remove-Item -LiteralPath $_.FullName -Recurse -Force -ErrorAction SilentlyContinue | |
| } | |
| } | |
| else { | |
| if (Test-Path -LiteralPath $p) { | |
| Remove-Item -LiteralPath $p -Recurse -Force -ErrorAction SilentlyContinue | |
| } | |
| } | |
| } | |
| catch { | |
| } | |
| } | |
| } | |
| } | |
| function Clear-FolderContents { | |
| param([string]$Path) | |
| if (-not (Test-Path -LiteralPath $Path)) { | |
| return | |
| } | |
| try { | |
| Get-ChildItem -LiteralPath $Path -Force -ErrorAction SilentlyContinue | | |
| ForEach-Object { | |
| Remove-Item -LiteralPath $_.FullName -Recurse -Force -ErrorAction SilentlyContinue | |
| } | |
| } | |
| catch { | |
| } | |
| } | |
| function Stop-TargetApps { | |
| if ($NoKillApps) { | |
| Write-Warn "Skipping process termination because -NoKillApps was specified." | |
| return | |
| } | |
| $processNames = @( | |
| "chrome", | |
| "msedge", | |
| "firefox", | |
| "brave", | |
| "opera", | |
| "vivaldi", | |
| "iexplore", | |
| "ida", | |
| "ida64", | |
| "idat", | |
| "idat64", | |
| "idaq", | |
| "idaq64", | |
| "devenv", | |
| "VSLauncher", | |
| "VSWebHandler", | |
| "VSIXInstaller", | |
| "ServiceHub.Host.netfx", | |
| "ServiceHub.Host.dotnet.x64", | |
| "ServiceHub.IdentityHost", | |
| "ServiceHub.SettingsHost", | |
| "ServiceHub.ThreadedWaitDialog", | |
| "ServiceHub.VSDetouredHost", | |
| "ServiceHub.RoslynCodeAnalysisService32", | |
| "ServiceHub.RoslynCodeAnalysisService", | |
| "VBCSCompiler", | |
| "MSBuild", | |
| "PerfWatson2", | |
| "vsjitdebugger" | |
| ) | |
| foreach ($name in $processNames) { | |
| Get-Process -Name $name -ErrorAction SilentlyContinue | | |
| Stop-Process -Force -ErrorAction SilentlyContinue | |
| } | |
| Get-Process -Name "explorer" -ErrorAction SilentlyContinue | | |
| Stop-Process -Force -ErrorAction SilentlyContinue | |
| Write-Ok "Closed browser, IDA, Visual Studio, and Explorer processes when possible." | |
| } | |
| function Clear-CurrentPowerShellSessionHistory { | |
| try { | |
| Clear-History -ErrorAction SilentlyContinue | |
| } | |
| catch { | |
| } | |
| try { | |
| [Microsoft.PowerShell.PSConsoleReadLine]::ClearHistory() | |
| } | |
| catch { | |
| } | |
| } | |
| function Get-TargetProfiles { | |
| if ($CurrentUserOnly) { | |
| $sid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value | |
| return @( | |
| [pscustomobject]@{ | |
| SID = $sid | |
| LocalPath = $env:USERPROFILE | |
| Special = $false | |
| } | |
| ) | |
| } | |
| $profiles = Get-CimInstance Win32_UserProfile -ErrorAction SilentlyContinue | | |
| Where-Object { | |
| $_.LocalPath -and | |
| $_.SID -and | |
| ($_.Special -eq $false) -and | |
| (Test-Path -LiteralPath $_.LocalPath) -and | |
| ($_.LocalPath -match "\\Users\\") | |
| } | |
| return @($profiles) | |
| } | |
| function Invoke-WithUserHive { | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [object]$Profile, | |
| [Parameter(Mandatory = $true)] | |
| [scriptblock]$Action | |
| ) | |
| $sid = $Profile.SID | |
| $profilePath = $Profile.LocalPath | |
| if ([string]::IsNullOrWhiteSpace($sid) -or [string]::IsNullOrWhiteSpace($profilePath)) { | |
| return | |
| } | |
| $hiveRoot = "Registry::HKEY_USERS\$sid" | |
| $loadedTempHive = $false | |
| $tempHiveName = $null | |
| if (-not (Test-Path -LiteralPath $hiveRoot)) { | |
| $ntUserDat = Join-Path $profilePath "NTUSER.DAT" | |
| if (-not (Test-Path -LiteralPath $ntUserDat)) { | |
| Write-Warn "NTUSER.DAT not found for profile: $profilePath" | |
| return | |
| } | |
| $tempHiveName = "VMExportClean_$($sid -replace '[^A-Za-z0-9]', '_')" | |
| $regHivePath = "HKU\$tempHiveName" | |
| & reg.exe load $regHivePath $ntUserDat | Out-Null | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Warn "Failed to load registry hive for: $profilePath" | |
| return | |
| } | |
| $hiveRoot = "Registry::HKEY_USERS\$tempHiveName" | |
| $loadedTempHive = $true | |
| } | |
| try { | |
| & $Action $hiveRoot | |
| } | |
| finally { | |
| if ($loadedTempHive -and $tempHiveName) { | |
| [GC]::Collect() | |
| Start-Sleep -Milliseconds 700 | |
| & reg.exe unload "HKU\$tempHiveName" | Out-Null | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Warn "Failed to unload temporary hive: HKU\$tempHiveName" | |
| } | |
| } | |
| } | |
| } | |
| function Clear-UserRegistryTraces { | |
| param([string]$HiveRoot) | |
| $keysToRemove = @( | |
| "Software\Microsoft\Windows\CurrentVersion\Explorer\RecentDocs", | |
| "Software\Microsoft\Windows\CurrentVersion\Explorer\RunMRU", | |
| "Software\Microsoft\Windows\CurrentVersion\Explorer\TypedPaths", | |
| "Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\OpenSavePidlMRU", | |
| "Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\LastVisitedPidlMRU", | |
| "Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\CIDSizeMRU", | |
| "Software\Microsoft\Windows\CurrentVersion\Explorer\WordWheelQuery", | |
| "Software\Microsoft\Windows\CurrentVersion\Explorer\UserAssist", | |
| "Software\Microsoft\Windows\CurrentVersion\Explorer\StreamMRU", | |
| "Software\Microsoft\Windows\CurrentVersion\Explorer\Map Network Drive MRU", | |
| "Software\Microsoft\Windows\CurrentVersion\Search\RecentApps", | |
| "Software\Microsoft\Windows\CurrentVersion\Search\JumplistData", | |
| "Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\BagMRU", | |
| "Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\Bags", | |
| "Software\Microsoft\Windows\Shell\BagMRU", | |
| "Software\Microsoft\Windows\Shell\Bags", | |
| "Software\Microsoft\Windows\ShellNoRoam\BagMRU", | |
| "Software\Microsoft\Windows\ShellNoRoam\Bags" | |
| ) | |
| foreach ($relativeKey in $keysToRemove) { | |
| $fullKey = Join-Path $HiveRoot $relativeKey | |
| Remove-Item -LiteralPath $fullKey -Recurse -Force -ErrorAction SilentlyContinue | |
| } | |
| if ($DisableFutureRecent) { | |
| $explorerKey = Join-Path $HiveRoot "Software\Microsoft\Windows\CurrentVersion\Explorer" | |
| $advancedKey = Join-Path $HiveRoot "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" | |
| New-Item -Path $explorerKey -Force -ErrorAction SilentlyContinue | Out-Null | |
| New-Item -Path $advancedKey -Force -ErrorAction SilentlyContinue | Out-Null | |
| New-ItemProperty -Path $explorerKey -Name "ShowRecent" -PropertyType DWord -Value 0 -Force -ErrorAction SilentlyContinue | Out-Null | |
| New-ItemProperty -Path $advancedKey -Name "Start_TrackDocs" -PropertyType DWord -Value 0 -Force -ErrorAction SilentlyContinue | Out-Null | |
| } | |
| } | |
| function Clear-IdaRegistryHistory { | |
| param([string]$HiveRoot) | |
| if ($SkipIda) { | |
| return | |
| } | |
| Write-Info "Cleaning IDA / Hex-Rays registry recent-file history." | |
| $candidateRoots = @( | |
| (Join-Path $HiveRoot "Software\Hex-Rays"), | |
| (Join-Path $HiveRoot "Software\IDA"), | |
| (Join-Path $HiveRoot "Software\IDA Pro") | |
| ) | |
| $historyKeyNameRegex = '(?i)(^|\\)(History|Recent|RecentFiles|MRU|FileMRU|DatabaseMRU|Previous|LastFiles|InputFiles)$' | |
| $historyValueNameRegex = '(?i)(history|recent|mru|filemru|databasemru|previous|lastfile|last_file|inputfile|input_file|database_history|file_history|openedfile|opened_file|idb|i64)' | |
| $pathValueRegex = '(?i)([A-Z]:\\|\\\\).*\.(exe|dll|sys|ocx|cpl|scr|bin|dat|idb|i64|til|dmp|elf|so|o|obj|lib|a|ko|apk|dex|jar|class|wasm|efi|rom|img|hex|com|msi|drv|vxd|pdb|dbg)$' | |
| $protectedNameRegex = '(?i)(license|licence|hexlic|\.key$|keyfile|activation)' | |
| foreach ($root in $candidateRoots) { | |
| if (-not (Test-Path -LiteralPath $root)) { | |
| continue | |
| } | |
| Write-Info "Scanning IDA registry root: $root" | |
| try { | |
| Get-ChildItem -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue | | |
| Where-Object { | |
| $_.Name -match $historyKeyNameRegex -and | |
| $_.Name -notmatch $protectedNameRegex | |
| } | | |
| Sort-Object { $_.Name.Length } -Descending | | |
| ForEach-Object { | |
| Write-Info "Removing IDA registry history key: $($_.Name)" | |
| Remove-Item -LiteralPath $_.PSPath -Recurse -Force -ErrorAction SilentlyContinue | |
| } | |
| } | |
| catch { | |
| } | |
| try { | |
| Get-ChildItem -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue | | |
| ForEach-Object { | |
| $key = Get-Item -LiteralPath $_.PSPath -ErrorAction SilentlyContinue | |
| if ($null -eq $key) { | |
| return | |
| } | |
| foreach ($valueName in $key.GetValueNames()) { | |
| if ($valueName -match $protectedNameRegex) { | |
| continue | |
| } | |
| $value = $key.GetValue($valueName, $null) | |
| $removeValue = $false | |
| if ($valueName -match $historyValueNameRegex) { | |
| $removeValue = $true | |
| } | |
| if ($null -ne $value) { | |
| if ($value -is [string]) { | |
| if ($value -match $pathValueRegex) { | |
| $removeValue = $true | |
| } | |
| } | |
| elseif ($value -is [string[]]) { | |
| foreach ($s in $value) { | |
| if ($s -match $pathValueRegex) { | |
| $removeValue = $true | |
| break | |
| } | |
| } | |
| } | |
| } | |
| if ($removeValue) { | |
| Write-Info "Removing IDA registry history value: $($_.Name)\$valueName" | |
| Remove-ItemProperty -LiteralPath $_.PSPath -Name $valueName -Force -ErrorAction SilentlyContinue | |
| } | |
| } | |
| } | |
| } | |
| catch { | |
| } | |
| } | |
| } | |
| function Clear-VisualStudioRegistryTraces { | |
| param([string]$HiveRoot) | |
| if ($SkipVisualStudio) { | |
| return | |
| } | |
| Write-Info "Cleaning Visual Studio registry MRU traces." | |
| $roots = @( | |
| (Join-Path $HiveRoot "Software\Microsoft\VisualStudio"), | |
| (Join-Path $HiveRoot "Software\Microsoft\VSCommon"), | |
| (Join-Path $HiveRoot "Software\Microsoft\VisualStudio\Launcher") | |
| ) | |
| foreach ($root in $roots) { | |
| if (-not (Test-Path -LiteralPath $root)) { | |
| continue | |
| } | |
| try { | |
| Get-ChildItem -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue | | |
| Where-Object { | |
| $_.Name -match "(?i)MRU|Recent|FileMRU|ProjectMRU|SolutionMRU|LastOpened|OpenProject|OpenFile" | |
| } | | |
| ForEach-Object { | |
| Write-Info "Removing Visual Studio registry MRU key: $($_.Name)" | |
| Remove-Item -LiteralPath $_.PSPath -Recurse -Force -ErrorAction SilentlyContinue | |
| } | |
| } | |
| catch { | |
| } | |
| try { | |
| Get-ChildItem -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue | | |
| ForEach-Object { | |
| $item = Get-Item -LiteralPath $_.PSPath -ErrorAction SilentlyContinue | |
| if ($null -eq $item) { | |
| return | |
| } | |
| foreach ($valueName in $item.GetValueNames()) { | |
| if ($valueName -match "(?i)MRU|Recent|FileMRU|ProjectMRU|SolutionMRU|LastOpened|OpenProject|OpenFile") { | |
| Write-Info "Removing Visual Studio registry MRU value: $($_.Name)\$valueName" | |
| Remove-ItemProperty -LiteralPath $_.PSPath -Name $valueName -Force -ErrorAction SilentlyContinue | |
| } | |
| } | |
| } | |
| } | |
| catch { | |
| } | |
| } | |
| } | |
| function Clear-ChromiumRoot { | |
| param([string]$UserDataRoot) | |
| if ($SkipBrowser) { | |
| return | |
| } | |
| if (-not (Test-Path -LiteralPath $UserDataRoot)) { | |
| return | |
| } | |
| $profileDirs = Get-ChildItem -LiteralPath $UserDataRoot -Directory -Force -ErrorAction SilentlyContinue | | |
| Where-Object { | |
| $_.Name -eq "Default" -or | |
| $_.Name -like "Profile *" -or | |
| $_.Name -eq "Guest Profile" -or | |
| $_.Name -eq "System Profile" | |
| } | |
| foreach ($profile in $profileDirs) { | |
| $p = $profile.FullName | |
| $files = @( | |
| "History", | |
| "History-journal", | |
| "Archived History", | |
| "Archived History-journal", | |
| "Visited Links", | |
| "Top Sites", | |
| "Top Sites-journal", | |
| "Shortcuts", | |
| "Shortcuts-journal", | |
| "Favicons", | |
| "Favicons-journal", | |
| "Cookies", | |
| "Cookies-journal", | |
| "Network\Cookies", | |
| "Network\Cookies-journal", | |
| "Web Data", | |
| "Web Data-journal", | |
| "Login Data", | |
| "Login Data-journal", | |
| "Network Action Predictor", | |
| "Network Action Predictor-journal", | |
| "DownloadMetadata", | |
| "Reporting and NEL", | |
| "Reporting and NEL-journal" | |
| ) | |
| foreach ($file in $files) { | |
| Remove-PathSafe (Join-Path $p $file) | |
| } | |
| $dirs = @( | |
| "Cache", | |
| "Code Cache", | |
| "GPUCache", | |
| "Media Cache", | |
| "DawnCache", | |
| "GrShaderCache", | |
| "ShaderCache", | |
| "Service Worker\CacheStorage", | |
| "Service Worker\Database", | |
| "Session Storage", | |
| "Local Storage", | |
| "IndexedDB", | |
| "File System", | |
| "Sessions", | |
| "Storage", | |
| "blob_storage" | |
| ) | |
| foreach ($dir in $dirs) { | |
| Remove-PathSafe (Join-Path $p $dir) | |
| } | |
| } | |
| } | |
| function Clear-FirefoxState { | |
| param( | |
| [string]$Roaming, | |
| [string]$Local | |
| ) | |
| if ($SkipBrowser) { | |
| return | |
| } | |
| $profileRoots = @( | |
| (Join-Path $Roaming "Mozilla\Firefox\Profiles"), | |
| (Join-Path $Local "Mozilla\Firefox\Profiles") | |
| ) | |
| foreach ($root in $profileRoots) { | |
| if (-not (Test-Path -LiteralPath $root)) { | |
| continue | |
| } | |
| $profiles = Get-ChildItem -LiteralPath $root -Directory -Force -ErrorAction SilentlyContinue | |
| foreach ($profile in $profiles) { | |
| $p = $profile.FullName | |
| $items = @( | |
| "places.sqlite", | |
| "places.sqlite-wal", | |
| "places.sqlite-shm", | |
| "cookies.sqlite", | |
| "cookies.sqlite-wal", | |
| "cookies.sqlite-shm", | |
| "formhistory.sqlite", | |
| "formhistory.sqlite-wal", | |
| "formhistory.sqlite-shm", | |
| "downloads.sqlite", | |
| "sessionstore.jsonlz4", | |
| "sessionstore-backups", | |
| "sessionCheckpoints.json", | |
| "cache2", | |
| "startupCache", | |
| "thumbnails", | |
| "jumpListCache", | |
| "storage\temporary", | |
| "storage\default" | |
| ) | |
| foreach ($item in $items) { | |
| Remove-PathSafe (Join-Path $p $item) | |
| } | |
| } | |
| } | |
| } | |
| function Clear-WindowsExplorerState { | |
| param( | |
| [string]$Roaming, | |
| [string]$Local | |
| ) | |
| $recent = Join-Path $Roaming "Microsoft\Windows\Recent" | |
| $automaticDestinations = Join-Path $recent "AutomaticDestinations" | |
| $customDestinations = Join-Path $recent "CustomDestinations" | |
| Clear-FolderContents $recent | |
| New-Item -ItemType Directory -Path $automaticDestinations -Force -ErrorAction SilentlyContinue | Out-Null | |
| New-Item -ItemType Directory -Path $customDestinations -Force -ErrorAction SilentlyContinue | Out-Null | |
| Remove-PathSafe (Join-Path $Local "Microsoft\Windows\Explorer\thumbcache_*.db") | |
| Remove-PathSafe (Join-Path $Local "Microsoft\Windows\Explorer\iconcache_*.db") | |
| Remove-PathSafe (Join-Path $Local "Microsoft\Windows\Explorer\ExplorerStartupLog*.etl") | |
| } | |
| function Clear-PowerShellHistoryFiles { | |
| param([string]$Roaming) | |
| $psReadLineDir = Join-Path $Roaming "Microsoft\Windows\PowerShell\PSReadLine" | |
| Remove-PathSafe (Join-Path $psReadLineDir "*history*.txt") | |
| Remove-PathSafe (Join-Path $psReadLineDir "ConsoleHost_history.txt") | |
| } | |
| function Clear-IdaState { | |
| param( | |
| [string]$Roaming, | |
| [string]$UserProfile | |
| ) | |
| if ($SkipIda) { | |
| Write-Warn "Skipping IDA cleanup because -SkipIda was specified." | |
| return | |
| } | |
| Write-Info "Cleaning IDA / Hex-Rays recent opened binary/database history only." | |
| $idaDirs = @( | |
| (Join-Path $Roaming "Hex-Rays\IDA Pro"), | |
| (Join-Path $UserProfile ".idapro") | |
| ) | |
| foreach ($idaDir in $idaDirs) { | |
| if (-not (Test-Path -LiteralPath $idaDir)) { | |
| continue | |
| } | |
| Write-Info "Cleaning IDA user folder: $idaDir" | |
| $protectedNameRegex = '(?i)(\.hexlic$|ida\.hexlic$|\.key$|license|licence|activation)' | |
| Get-ChildItem -LiteralPath $idaDir -Recurse -Force -ErrorAction SilentlyContinue | | |
| Where-Object { | |
| -not $_.PSIsContainer -and | |
| $_.Name -notmatch $protectedNameRegex -and | |
| ( | |
| $_.Name -match '(?i)mru' -or | |
| $_.Name -match '(?i)recent' -or | |
| $_.Name -match '(?i)history' -or | |
| $_.Name -match '(?i)filehist' -or | |
| $_.Name -match '(?i)lastfile' -or | |
| $_.Name -match '(?i)lastdb' | |
| ) | |
| } | | |
| ForEach-Object { | |
| Write-Info "Removing IDA recent/history artifact: $($_.FullName)" | |
| Remove-Item -LiteralPath $_.FullName -Force -ErrorAction SilentlyContinue | |
| } | |
| $textConfigExtensions = @(".cfg", ".ini", ".reg", ".json", ".xml", ".txt") | |
| Get-ChildItem -LiteralPath $idaDir -Recurse -Force -File -ErrorAction SilentlyContinue | | |
| Where-Object { | |
| $_.Name -notmatch $protectedNameRegex -and | |
| $textConfigExtensions -contains $_.Extension.ToLowerInvariant() | |
| } | | |
| ForEach-Object { | |
| $file = $_.FullName | |
| try { | |
| $raw = Get-Content -LiteralPath $file -Raw -ErrorAction Stop | |
| if ([string]::IsNullOrEmpty($raw)) { | |
| return | |
| } | |
| $lines = $raw -split "`r?`n" | |
| $filtered = $lines | Where-Object { | |
| $_ -notmatch '(?i)\b(mru|recent|history|filehist|lastfile|last_file|lastdatabase|last_database|openedfile|opened_file|inputfile|input_file|database_history|file_history)\b' | |
| } | |
| $newRaw = ($filtered -join [Environment]::NewLine) | |
| if ($newRaw -ne $raw) { | |
| Write-Info "Scrubbing IDA MRU lines from: $file" | |
| Set-Content -LiteralPath $file -Value $newRaw -Encoding UTF8 -Force -ErrorAction SilentlyContinue | |
| } | |
| } | |
| catch { | |
| } | |
| } | |
| } | |
| } | |
| function Clear-VisualStudioState { | |
| param( | |
| [string]$Roaming, | |
| [string]$Local, | |
| [string]$UserProfile | |
| ) | |
| if ($SkipVisualStudio) { | |
| Write-Warn "Skipping Visual Studio cleanup because -SkipVisualStudio was specified." | |
| return | |
| } | |
| Write-Info "Cleaning Visual Studio Community / Visual Studio user traces." | |
| $vsRoots = @( | |
| (Join-Path $Local "Microsoft\VisualStudio"), | |
| (Join-Path $Roaming "Microsoft\VisualStudio") | |
| ) | |
| foreach ($vsRoot in $vsRoots) { | |
| if (-not (Test-Path -LiteralPath $vsRoot)) { | |
| continue | |
| } | |
| $instanceDirs = Get-ChildItem -LiteralPath $vsRoot -Directory -Force -ErrorAction SilentlyContinue | | |
| Where-Object { | |
| $_.Name -like "18.0_*" -or | |
| $_.Name -like "17.0_*" -or | |
| $_.Name -like "16.0_*" -or | |
| $_.Name -like "15.0_*" | |
| } | |
| foreach ($dir in $instanceDirs) { | |
| Write-Info "Cleaning Visual Studio instance folder: $($dir.FullName)" | |
| $items = @( | |
| "ApplicationPrivateSettings.xml", | |
| "ApplicationPrivateSettings.lock", | |
| "privateregistry.bin", | |
| "privateregistry.bin.LOG1", | |
| "privateregistry.bin.LOG2", | |
| "privateregistry.bin*.TM.blf", | |
| "privateregistry.bin*.TMContainer*", | |
| "ComponentModelCache", | |
| "Designer", | |
| "Cache", | |
| "ImageLibrary", | |
| "MEFCacheBackup", | |
| "Server", | |
| "WebView2", | |
| "BrowserCache", | |
| "Roslyn", | |
| "TestStore", | |
| "VsHub", | |
| "ActivityLog.xml", | |
| "ActivityLog.xsl", | |
| "*.log", | |
| "*.tmp", | |
| "*.bak" | |
| ) | |
| foreach ($item in $items) { | |
| Remove-PathSafe (Join-Path $dir.FullName $item) | |
| } | |
| } | |
| } | |
| $commonRoots = @( | |
| (Join-Path $Local "Microsoft\VSCommon"), | |
| (Join-Path $Roaming "Microsoft\VSCommon") | |
| ) | |
| foreach ($commonRoot in $commonRoots) { | |
| if (-not (Test-Path -LiteralPath $commonRoot)) { | |
| continue | |
| } | |
| Remove-PathSafe (Join-Path $commonRoot "Cache") | |
| Remove-PathSafe (Join-Path $commonRoot "ConnectedUser") | |
| Remove-PathSafe (Join-Path $commonRoot "IdentityService") | |
| Remove-PathSafe (Join-Path $commonRoot "VSAccountManagement") | |
| Remove-PathSafe (Join-Path $commonRoot "*.log") | |
| Remove-PathSafe (Join-Path $commonRoot "*.tmp") | |
| } | |
| $documents = Join-Path $UserProfile "Documents" | |
| $docVsRoots = @( | |
| (Join-Path $documents "Visual Studio 2026"), | |
| (Join-Path $documents "Visual Studio 2022"), | |
| (Join-Path $documents "Visual Studio 2019"), | |
| (Join-Path $documents "Visual Studio 2017") | |
| ) | |
| foreach ($docRoot in $docVsRoots) { | |
| Remove-PathSafe (Join-Path $docRoot "Backup Files") | |
| } | |
| $workspaceRoots = @( | |
| (Join-Path $UserProfile "source"), | |
| (Join-Path $UserProfile "repos"), | |
| (Join-Path $UserProfile "Documents"), | |
| (Join-Path $UserProfile "Desktop") | |
| ) | |
| foreach ($workspaceRoot in $workspaceRoots) { | |
| if (-not (Test-Path -LiteralPath $workspaceRoot)) { | |
| continue | |
| } | |
| Get-ChildItem -LiteralPath $workspaceRoot -Directory -Force -Recurse -ErrorAction SilentlyContinue | | |
| Where-Object { $_.Name -eq ".vs" } | | |
| ForEach-Object { | |
| Write-Info "Removing Visual Studio solution metadata folder: $($_.FullName)" | |
| Remove-Item -LiteralPath $_.FullName -Recurse -Force -ErrorAction SilentlyContinue | |
| } | |
| } | |
| } | |
| function Clear-UserProfileArtifacts { | |
| param([object]$Profile) | |
| $userProfile = $Profile.LocalPath | |
| $roaming = Join-Path $userProfile "AppData\Roaming" | |
| $local = Join-Path $userProfile "AppData\Local" | |
| Write-Info "Cleaning profile: $userProfile" | |
| Clear-PowerShellHistoryFiles -Roaming $roaming | |
| Clear-WindowsExplorerState -Roaming $roaming -Local $local | |
| if (-not $SkipBrowser) { | |
| $chromiumRoots = @( | |
| (Join-Path $local "Google\Chrome\User Data"), | |
| (Join-Path $local "Microsoft\Edge\User Data"), | |
| (Join-Path $local "Chromium\User Data"), | |
| (Join-Path $local "BraveSoftware\Brave-Browser\User Data"), | |
| (Join-Path $local "Vivaldi\User Data"), | |
| (Join-Path $roaming "Opera Software\Opera Stable"), | |
| (Join-Path $roaming "Opera Software\Opera GX Stable") | |
| ) | |
| foreach ($root in $chromiumRoots) { | |
| Clear-ChromiumRoot -UserDataRoot $root | |
| } | |
| Clear-FirefoxState -Roaming $roaming -Local $local | |
| } | |
| else { | |
| Write-Warn "Skipping browser cleanup because -SkipBrowser was specified." | |
| } | |
| Clear-IdaState -Roaming $roaming -UserProfile $userProfile | |
| Clear-VisualStudioState -Roaming $roaming -Local $local -UserProfile $userProfile | |
| Clear-FolderContents (Join-Path $local "Temp") | |
| } | |
| function Clear-CommonSystemArtifacts { | |
| Write-Info "Cleaning common temporary locations." | |
| Clear-FolderContents "$env:TEMP" | |
| Clear-FolderContents "$env:SystemRoot\Temp" | |
| try { | |
| Clear-RecycleBin -Force -ErrorAction SilentlyContinue | |
| } | |
| catch { | |
| } | |
| } | |
| function Clear-WindowsEventLogs { | |
| if ($SkipEventLogs) { | |
| Write-Warn "Skipping Windows Event Log cleanup because -SkipEventLogs was specified." | |
| return | |
| } | |
| Write-Info "Clearing Windows Event Logs." | |
| try { | |
| $logs = & wevtutil.exe el 2>$null | |
| } | |
| catch { | |
| Write-Warn "Failed to enumerate Windows Event Logs." | |
| return | |
| } | |
| foreach ($log in $logs) { | |
| if ([string]::IsNullOrWhiteSpace($log)) { | |
| continue | |
| } | |
| try { | |
| & wevtutil.exe cl "$log" 2>$null | |
| if ($LASTEXITCODE -eq 0) { | |
| Write-Ok "Cleared event log: $log" | |
| } | |
| else { | |
| Write-Warn "Failed to clear event log: $log" | |
| } | |
| } | |
| catch { | |
| Write-Warn "Exception while clearing event log: $log" | |
| } | |
| } | |
| Write-Ok "Event log cleanup completed." | |
| } | |
| function Invoke-ZeroFreeSpace { | |
| if ($SkipZeroFreeSpace) { | |
| Write-Warn "Skipping free-space zeroing because -SkipZeroFreeSpace was specified." | |
| return | |
| } | |
| Write-Warn "Zeroing free space. This may take a long time inside a VM." | |
| & cipher.exe "/w:$($env:SystemDrive)\" | |
| } | |
| function Restart-Explorer { | |
| if ($NoKillApps) { | |
| return | |
| } | |
| Start-Process explorer.exe -ErrorAction SilentlyContinue | |
| } | |
| Invoke-SelfElevationIfNeeded | |
| Write-Info "Training VM history cleanup started." | |
| Write-Info "CurrentUserOnly: $CurrentUserOnly" | |
| Write-Info "DisableFutureRecent: $DisableFutureRecent" | |
| Write-Info "NoKillApps: $NoKillApps" | |
| Write-Info "SkipBrowser: $SkipBrowser" | |
| Write-Info "SkipIda: $SkipIda" | |
| Write-Info "SkipVisualStudio: $SkipVisualStudio" | |
| Write-Info "SkipEventLogs: $SkipEventLogs" | |
| Write-Info "SkipZeroFreeSpace: $SkipZeroFreeSpace" | |
| Clear-CurrentPowerShellSessionHistory | |
| Stop-TargetApps | |
| $profiles = Get-TargetProfiles | |
| foreach ($profile in $profiles) { | |
| Clear-UserProfileArtifacts -Profile $profile | |
| Invoke-WithUserHive -Profile $profile -Action { | |
| param($HiveRoot) | |
| Clear-UserRegistryTraces -HiveRoot $HiveRoot | |
| Clear-IdaRegistryHistory -HiveRoot $HiveRoot | |
| Clear-VisualStudioRegistryTraces -HiveRoot $HiveRoot | |
| } | |
| } | |
| Clear-CommonSystemArtifacts | |
| Clear-CurrentPowerShellSessionHistory | |
| Clear-WindowsEventLogs | |
| Invoke-ZeroFreeSpace | |
| Restart-Explorer | |
| Write-Ok "Cleanup completed." | |
| Write-Ok "Recommended next step: shut down the VM, then export OVA." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment