Skip to content

Instantly share code, notes, and snippets.

@aaaddress1
Created May 9, 2026 17:29
Show Gist options
  • Select an option

  • Save aaaddress1/739a087ba3bf5cfb4aab78ab0d7c19fc to your computer and use it in GitHub Desktop.

Select an option

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.
# 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