diff --git a/Powershell/Invoke-DiskSpaceReport.ps1 b/Powershell/Invoke-DiskSpaceReport.ps1 new file mode 100644 index 0000000..2cef1c3 --- /dev/null +++ b/Powershell/Invoke-DiskSpaceReport.ps1 @@ -0,0 +1,837 @@ +#Requires -RunAsAdministrator +<# +.SYNOPSIS + C: Drive Disk Space Report — generates a self-contained HTML report for stakeholders. + +.DESCRIPTION + Scans the C: drive across all common space consumers and produces a polished, + printable HTML report. No cleanup is performed. Safe to run at any time. + +.NOTES + Supports: Windows Server 2016, 2019, 2022, 2025 + Run as: Administrator + Output: HTML file on the Desktop (or path specified by -OutputPath) + Author: SBCIT +#> + +param( + [string]$OutputPath = "$env:USERPROFILE\Desktop", + [string]$ClientName = "" +) + +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 Get-SeverityClass { + param([long]$Bytes) + if ($Bytes -ge 1GB) { return "sev-critical" } + if ($Bytes -ge 200MB) { return "sev-warning" } + if ($Bytes -ge 10MB) { return "sev-minor" } + return "sev-ok" +} + +function Get-SeverityLabel { + param([long]$Bytes) + if ($Bytes -ge 1GB) { return "High" } + if ($Bytes -ge 200MB) { return "Medium" } + if ($Bytes -ge 10MB) { return "Low" } + return "Minimal" +} + +# ───────────────────────────────────────────── +# CONSOLE PROGRESS +# ───────────────────────────────────────────── + +Write-Host "" +Write-Host " SBCIT // Disk Space Report Generator" -ForegroundColor DarkCyan +Write-Host " Scanning $env:COMPUTERNAME — please wait...`n" -ForegroundColor Gray + +# ───────────────────────────────────────────── +# DRIVE STATE +# ───────────────────────────────────────────── + +$drive = Get-PSDrive C +$driveTotal = $drive.Used + $drive.Free +$driveFree = $drive.Free +$driveUsed = $drive.Used +$freePct = [math]::Round(($driveFree / $driveTotal) * 100, 1) +$usedPct = [math]::Round(($driveUsed / $driveTotal) * 100, 1) +$totalGB = [math]::Round($driveTotal / 1GB, 2) +$usedGB = [math]::Round($driveUsed / 1GB, 2) +$freeGB = [math]::Round($driveFree / 1GB, 2) + +$driveStatus = if ($freePct -lt 10) { "Critical" } elseif ($freePct -lt 20) { "Low" } else { "Healthy" } +$driveStatusClass = if ($freePct -lt 10) { "sev-critical" } elseif ($freePct -lt 20) { "sev-warning" } else { "sev-ok" } + +# OS Version +$osInfo = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue +$osName = if ($osInfo) { $osInfo.Caption } else { "Windows Server" } +$osBuild = if ($osInfo) { "Build $($osInfo.BuildNumber)" } else { "" } + +# Uptime +$uptime = "" +if ($osInfo -and $osInfo.LastBootUpTime) { + $span = New-TimeSpan -Start $osInfo.LastBootUpTime -End (Get-Date) + $uptime = "$($span.Days)d $($span.Hours)h $($span.Minutes)m" +} + +# ───────────────────────────────────────────── +# SCAN +# ───────────────────────────────────────────── + +$findings = [System.Collections.Generic.List[PSCustomObject]]::new() + +function Add-Finding { + param( + [string]$Category, + [string]$Label, + [string]$Path, + [long]$Size, + [string]$Description, + [string]$Note = "" + ) + $findings.Add([PSCustomObject]@{ + Category = $Category + Label = $Label + Path = $Path + Size = $Size + Description = $Description + Note = $Note + }) +} + +# Temp Files +Add-Finding "Temporary Files" "Windows Temp Folder" "C:\Windows\Temp" (Get-FolderSize "C:\Windows\Temp") "System-wide temporary files created by Windows and applications." "" +Add-Finding "Temporary Files" "System Temp ($env:TEMP)" $env:TEMP (Get-FolderSize $env:TEMP) "Temporary files for the current user session." "" +Add-Finding "Temporary Files" "User AppData Temp" "C:\Users\$env:USERNAME\AppData\Local\Temp" (Get-FolderSize "C:\Users\$env:USERNAME\AppData\Local\Temp") "Per-user application temporary data." "" + +# Windows Update +Add-Finding "Windows Update" "Update Download Cache" "C:\Windows\SoftwareDistribution\Download" (Get-FolderSize "C:\Windows\SoftwareDistribution\Download") "Cached Windows Update downloads. Can be safely cleared when no updates are pending." "" +Add-Finding "Windows Update" "WinSxS Component Backups" "C:\Windows\WinSxS\Backup" (Get-FolderSize "C:\Windows\WinSxS\Backup") "Old component backups retained after Windows updates." "" + +# System Logs +$cbsSize = Get-FolderSize "C:\Windows\Logs\CBS" +Add-Finding "System Logs" "Component-Based Servicing Logs" "C:\Windows\Logs\CBS" $cbsSize "Logs generated during Windows component servicing and updates." "" + +$evtxSize = 0 +try { + $evtxSize = (Get-ChildItem "C:\Windows\System32\winevt\Logs" -Filter "*.evtx" -File -ErrorAction SilentlyContinue | + Measure-Object -Property Length -Sum).Sum +} catch {} +Add-Finding "System Logs" "Windows Event Logs" "C:\Windows\System32\winevt\Logs" $evtxSize "Application, System, and Security event logs." "Backed up to Zipline before clearing." + +# Error Reporting +Add-Finding "Error Reporting" "WER Report Queue" "C:\ProgramData\Microsoft\Windows\WER\ReportQueue" (Get-FolderSize "C:\ProgramData\Microsoft\Windows\WER\ReportQueue") "Pending Windows Error Reports awaiting upload to Microsoft." "" +Add-Finding "Error Reporting" "WER Report Archive" "C:\ProgramData\Microsoft\Windows\WER\ReportArchive" (Get-FolderSize "C:\ProgramData\Microsoft\Windows\WER\ReportArchive") "Previously sent Windows Error Reports retained locally." "" +Add-Finding "Error Reporting" "User Error Reports" "C:\Users\$env:USERNAME\AppData\Local\Microsoft\Windows\WER" (Get-FolderSize "C:\Users\$env:USERNAME\AppData\Local\Microsoft\Windows\WER") "Per-user application crash reports." "" + +# Crash Dumps +$miniDump = Get-FolderSize "C:\Windows\Minidump" +$memDump = if (Test-Path "C:\Windows\MEMORY.DMP") { (Get-Item "C:\Windows\MEMORY.DMP").Length } else { 0 } +Add-Finding "Crash Dumps" "Minidump Files" "C:\Windows\Minidump" $miniDump "Small memory dumps generated when the system encounters a critical error." "" +Add-Finding "Crash Dumps" "Full Memory Dump" "C:\Windows\MEMORY.DMP" $memDump "Complete RAM snapshot from last system crash. Large file — only present after a BSOD." "" + +# Delivery Optimisation +Add-Finding "Delivery Optimisation" "DO Peer Cache" "C:\Windows\SoftwareDistribution\DeliveryOptimization" (Get-FolderSize "C:\Windows\SoftwareDistribution\DeliveryOptimization") "Windows Update peer-to-peer delivery cache." "" +Add-Finding "Delivery Optimisation" "DO Network Service Cache" "C:\Windows\ServiceProfiles\NetworkService\AppData\Local\Microsoft\Windows\DeliveryOptimization" (Get-FolderSize "C:\Windows\ServiceProfiles\NetworkService\AppData\Local\Microsoft\Windows\DeliveryOptimization") "Delivery Optimisation metadata cache." "" + +# IIS Logs +foreach ($iisPath in @("C:\inetpub\logs\LogFiles","C:\Windows\System32\LogFiles\W3SVC1")) { + $sz = Get-FolderSize $iisPath + if ($sz -gt 0) { + Add-Finding "IIS Web Server Logs" "IIS Logs ($iisPath)" $iisPath $sz "Internet Information Services access and error logs." "Cleanup removes files older than 30 days." + } +} + +# Recycle Bin +Add-Finding "Recycle Bin" "C: Recycle Bin" 'C:\$Recycle.Bin' (Get-RecycleBinSize) "Files deleted by users but not yet permanently removed." "" + +# Cache +Add-Finding "Cache" "Thumbnail Cache" "C:\Users\$env:USERNAME\AppData\Local\Microsoft\Windows\Explorer" (Get-FolderSize "C:\Users\$env:USERNAME\AppData\Local\Microsoft\Windows\Explorer") "Cached image thumbnails. Rebuilds automatically when browsing folders." "" +Add-Finding "Cache" "Font Cache" "C:\Windows\ServiceProfiles\LocalService\AppData\Local\FontCache" (Get-FolderSize "C:\Windows\ServiceProfiles\LocalService\AppData\Local\FontCache") "Font rendering cache. Rebuilds automatically on next boot." "" + +# Installer Cache +$installerCache = Get-FolderSize "C:\Windows\Installer\`$PatchCache`$" +Add-Finding "Installer Cache" "MSI Patch Cache" "C:\Windows\Installer\`$PatchCache`$" $installerCache "Cached MSI installer patches. Clearing may affect application repair." "⚠ Verify before removing." + +# Old Profiles +$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-Finding "User Profiles" "Profile: $($prof.Name)" $prof.FullName $sz "User profile folder for an account that may no longer be active." "⚠ Verify account status before removing." +} + +# Legacy +Add-Finding "Legacy" "Downloaded Program Files" "C:\Windows\Downloaded Program Files" (Get-FolderSize "C:\Windows\Downloaded Program Files") "Legacy ActiveX and Java components. Generally safe to clear on modern servers." "" + +# Filter zero-size findings +$findings = $findings | Where-Object { $_.Size -gt 0 } + +# ───────────────────────────────────────────── +# DERIVED STATS +# ───────────────────────────────────────────── + +$totalRecoverable = ($findings | Measure-Object -Property Size -Sum).Sum +$recoverablePct = if ($driveTotal -gt 0) { [math]::Round(($totalRecoverable / $driveTotal) * 100, 1) } else { 0 } +$projectedFreeGB = [math]::Round(($driveFree + $totalRecoverable) / 1GB, 2) +$projectedFreePct = [math]::Round((($driveFree + $totalRecoverable) / $driveTotal) * 100, 1) + +$categoryGroups = $findings | Group-Object Category | Sort-Object { ($_.Group | Measure-Object -Property Size -Sum).Sum } -Descending +$timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' +$fileStamp = Get-Date -Format 'yyyyMMdd-HHmmss' +$reportFile = Join-Path $OutputPath "$($env:COMPUTERNAME)_DiskReport_$fileStamp.html" +$reportTitle = if ($ClientName) { "$ClientName — $($env:COMPUTERNAME)" } else { $env:COMPUTERNAME } + +# ───────────────────────────────────────────── +# BUILD HTML +# ───────────────────────────────────────────── + +$rowsHtml = [System.Text.StringBuilder]::new() +foreach ($group in $categoryGroups) { + $groupTotal = ($group.Group | Measure-Object -Property Size -Sum).Sum + $isFirst = $true + $rowCount = $group.Group.Count + foreach ($item in ($group.Group | Sort-Object Size -Descending)) { + $sev = Get-SeverityClass $item.Size + $sevLabel = Get-SeverityLabel $item.Size + $sizeStr = Format-Size $item.Size + $noteHtml = if ($item.Note) { "$([System.Web.HttpUtility]::HtmlEncode($item.Note))" } else { "" } + if ($isFirst) { + $catCell = "$([System.Web.HttpUtility]::HtmlEncode($group.Name))$(Format-Size $groupTotal)" + $isFirst = $false + } else { + $catCell = "" + } + [void]$rowsHtml.AppendLine(" + + $catCell + $([System.Web.HttpUtility]::HtmlEncode($item.Label))
$([System.Web.HttpUtility]::HtmlEncode($item.Path)) + $([System.Web.HttpUtility]::HtmlEncode($item.Description)) $noteHtml + $sizeStr + $sevLabel + ") + } +} + +$barUsed = [math]::Round($usedPct) +$barUsedColor = if ($usedPct -gt 90) { "#e05252" } elseif ($usedPct -gt 75) { "#e09a3a" } else { "#4a7fb5" } + +$html = @" + + + + + +Disk Space Report — $reportTitle + + + +
+ + +
+
+ +
+

Disk Space Report

+
$reportTitle — C: Drive Analysis
+
+
+
+
Host  $($env:COMPUTERNAME)
+
OS  $osName $osBuild
+
Uptime  $uptime
+
Generated  $timestamp
+
Prepared by  SBCIT
+
+
+ + +
+
+
Drive Status
+
$driveStatus
+
$freePct% free of $totalGB GB total
+ $driveStatus +
+
+
Currently Free
+
$freeGB GB
+
$freePct% of total capacity
+
+
+
Recoverable Space
+
$(Format-Size $totalRecoverable)
+
$recoverablePct% of total capacity
+
+
+
Projected Free (After)
+
$projectedFreeGB GB
+
$projectedFreePct% if all items cleaned
+
+
+ + +
+
+
C: Drive Capacity
+
+ $usedGB GB used + $(Format-Size $totalRecoverable) recoverable + $freeGB GB free + $totalGB GB total +
+
+
+
+
+
+
+
+ 0 GB + ↥ $(Format-Size $totalRecoverable) recoverable + $totalGB GB +
+
+ + +
+ Impact: +
High ≥ 1 GB
+
Medium 200 MB – 1 GB
+
Low 10 MB – 200 MB
+
Minimal < 10 MB
+
+ + +
Findings — All Scanned Categories
+
+ + + + + + + + + + + + $($rowsHtml.ToString()) + +
CategoryItemDescriptionSizeImpact
+
+ + +
Recommendations
+
+

Suggested Actions

+ +
+ + + + +
+ + +"@ + +# ───────────────────────────────────────────── +# WRITE FILE +# ───────────────────────────────────────────── + +# Need HttpUtility for HTML encoding — load System.Web +Add-Type -AssemblyName System.Web -ErrorAction SilentlyContinue + +$html | Out-File -FilePath $reportFile -Encoding UTF8 + +Write-Host "" +Write-Host " ✔ Report generated:" -ForegroundColor Green +Write-Host " $reportFile" -ForegroundColor Cyan +Write-Host "" +Write-Host (" Total Recoverable : {0}" -f (Format-Size $totalRecoverable)) -ForegroundColor Yellow +Write-Host (" Current Free : $freeGB GB ($freePct%)") -ForegroundColor Gray +Write-Host (" Projected Free : $projectedFreeGB GB ($projectedFreePct%)") -ForegroundColor Gray +Write-Host "" + +# Open in default browser +try { + Start-Process $reportFile +} catch {} \ No newline at end of file