<# .SYNOPSIS Clears old temp files from the usual places and can run the built-in Disk Cleanup (cleanmgr) unattended. .DESCRIPTION Removes files older than -OlderThanDays from the Windows temp folder and user temp folders (yours, or every profile with -AllProfiles). Files that are locked are skipped and counted, not forced. With -RunCleanMgr it registers a set of Disk Cleanup handlers under a sageset number and runs cleanmgr /sagerun against it. Returns a summary per location, and -WhatIf shows how much would go without deleting anything. .PARAMETER OlderThanDays Only delete files that haven't been written to in this many days. Default: 7. .PARAMETER AllProfiles Clean the temp folder of every local profile, not just yours. Needs to run elevated. .PARAMETER RunCleanMgr Also run Disk Cleanup silently with the handlers in -CleanMgrHandlers. Needs to run elevated. .PARAMETER CleanMgrHandlers Disk Cleanup handler names (the key names under ...\Explorer\VolumeCaches). .PARAMETER SageSet The sageset number to store the handler selection under. Default: 4242. .EXAMPLE .\Invoke-DiskCleanup.ps1 -WhatIf .EXAMPLE .\Invoke-DiskCleanup.ps1 -AllProfiles -OlderThanDays 14 -RunCleanMgr #> [CmdletBinding(SupportsShouldProcess)] param( [ValidateRange(1, 3650)] [int]$OlderThanDays = 7, [switch]$AllProfiles, [switch]$RunCleanMgr, [string[]]$CleanMgrHandlers = @('Temporary Files', 'Thumbnail Cache', 'Update Cleanup', 'Delivery Optimization Files', 'Windows Error Reporting Files', 'Temporary Setup Files'), [ValidateRange(0, 9999)] [int]$SageSet = 4242 ) function Get-FreeGB { $disk = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DeviceID='$($env:SystemDrive)'" [math]::Round($disk.FreeSpace / 1GB, 2) } $cutoff = (Get-Date).AddDays(-$OlderThanDays) $freeBefore = Get-FreeGB $locations = @(Join-Path $env:SystemRoot 'Temp') if ($AllProfiles) { $locations += Get-CimInstance -ClassName Win32_UserProfile -Filter 'Special = FALSE' | ForEach-Object { Join-Path $_.LocalPath 'AppData\Local\Temp' } } else { $locations += [System.IO.Path]::GetTempPath().TrimEnd('\') } foreach ($location in $locations | Select-Object -Unique) { if (-not (Test-Path -LiteralPath $location)) { continue } $old = @(Get-ChildItem -LiteralPath $location -File -Recurse -Force -ErrorAction SilentlyContinue | Where-Object { $_.LastWriteTime -lt $cutoff }) $bytes = ($old | Measure-Object Length -Sum).Sum $summary = [pscustomobject]@{ Location = $location FilesFound = $old.Count FilesRemoved = 0 SkippedInUse = 0 MBFreed = 0 MBFound = [math]::Round($bytes / 1MB, 1) } if ($old.Count -and $PSCmdlet.ShouldProcess($location, "Delete $($old.Count) file(s), $($summary.MBFound) MB, older than $OlderThanDays days")) { $freed = 0L foreach ($file in $old) { try { Remove-Item -LiteralPath $file.FullName -Force -Confirm:$false -ErrorAction Stop $summary.FilesRemoved++ $freed += $file.Length } catch { # Almost always "in use by another process". That's fine; it's somebody's live temp file. $summary.SkippedInUse++ } } $summary.MBFreed = [math]::Round($freed / 1MB, 1) } $summary } if ($RunCleanMgr) { $cacheRoot = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches' $flagName = 'StateFlags{0:D4}' -f $SageSet if ($PSCmdlet.ShouldProcess("Disk Cleanup handlers: $($CleanMgrHandlers -join ', ')", "Run cleanmgr /sagerun:$SageSet")) { foreach ($handler in $CleanMgrHandlers) { $key = Join-Path $cacheRoot $handler if (Test-Path -LiteralPath $key) { # 2 = "checked" for this sageset. Other sagesets are left alone. Set-ItemProperty -LiteralPath $key -Name $flagName -Value 2 -Type DWord -Confirm:$false } else { Write-Warning "Disk Cleanup handler '$handler' doesn't exist on this version of Windows. Skipping it." } } Write-Verbose "Running cleanmgr /sagerun:$SageSet. This can take a while." Start-Process -FilePath (Join-Path $env:SystemRoot 'System32\cleanmgr.exe') -ArgumentList "/sagerun:$SageSet" -Wait -WindowStyle Hidden } } Write-Host ("Free space on {0} {1} GB before, {2} GB now." -f $env:SystemDrive, $freeBefore, (Get-FreeGB))