a1a17e81a1
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
414 lines
14 KiB
PowerShell
414 lines
14 KiB
PowerShell
#Requires -Version 5.1
|
|
|
|
#########################################################################################
|
|
#### windows-disk-usage-reporter.ps1 - Find what's consuming disk space on Windows ####
|
|
#### Scans volumes, ranks largest directories and files, flags old data ####
|
|
#### ####
|
|
#### Author: Phil Connor ####
|
|
#### Contact: contact@mylinux.work ####
|
|
#### License: MIT ####
|
|
#### Version 1.00 ####
|
|
#### ####
|
|
#### Usage: ####
|
|
#### .\windows-disk-usage-reporter.ps1 ####
|
|
#### .\windows-disk-usage-reporter.ps1 -Path C:\Users ####
|
|
#### .\windows-disk-usage-reporter.ps1 -TopN 50 -MinSizeMB 100 ####
|
|
#### .\windows-disk-usage-reporter.ps1 -Json ####
|
|
#### ####
|
|
#########################################################################################
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[string]$Path = "C:\",
|
|
[int]$TopN = 20,
|
|
[int]$MinSizeMB = 1,
|
|
[int]$MaxDepth = 3,
|
|
[int]$AgeWarnDays = 90,
|
|
[switch]$Json,
|
|
[switch]$NoColor
|
|
)
|
|
|
|
$Version = "1.00"
|
|
$MinSizeBytes = [int64]$MinSizeMB * 1048576
|
|
$AgeCutoff = (Get-Date).AddDays(-$AgeWarnDays)
|
|
$Separator = [string]::new([char]0x2500, 88)
|
|
|
|
# ============================================================================
|
|
# HELPERS
|
|
# ============================================================================
|
|
|
|
function Format-FileSize {
|
|
param([int64]$Bytes)
|
|
if ($Bytes -ge 1TB) { return "{0:N2} TB" -f ($Bytes / 1TB) }
|
|
if ($Bytes -ge 1GB) { return "{0:N2} GB" -f ($Bytes / 1GB) }
|
|
if ($Bytes -ge 1MB) { return "{0:N1} MB" -f ($Bytes / 1MB) }
|
|
if ($Bytes -ge 1KB) { return "{0:N1} KB" -f ($Bytes / 1KB) }
|
|
return "$Bytes B"
|
|
}
|
|
|
|
function Write-ColorLine {
|
|
param(
|
|
[string]$Text,
|
|
[ConsoleColor]$Color = [ConsoleColor]::Gray
|
|
)
|
|
if ($NoColor) {
|
|
Write-Output $Text
|
|
} else {
|
|
Write-Host $Text -ForegroundColor $Color
|
|
}
|
|
}
|
|
|
|
function Write-Header {
|
|
param([string]$Title)
|
|
Write-ColorLine ""
|
|
Write-ColorLine ("=" * 56) Cyan
|
|
Write-ColorLine " $Title" Cyan
|
|
Write-ColorLine ("=" * 56) Cyan
|
|
Write-ColorLine ""
|
|
}
|
|
|
|
function Get-DirectorySizeRecursive {
|
|
param(
|
|
[string]$DirPath,
|
|
[int]$CurrentDepth,
|
|
[int]$Limit
|
|
)
|
|
if ($CurrentDepth -gt $Limit) { return @() }
|
|
|
|
$results = [System.Collections.Generic.List[PSCustomObject]]::new()
|
|
|
|
try {
|
|
$items = Get-ChildItem -LiteralPath $DirPath -Directory -ErrorAction SilentlyContinue
|
|
} catch {
|
|
return @()
|
|
}
|
|
|
|
foreach ($dir in $items) {
|
|
try {
|
|
$files = Get-ChildItem -LiteralPath $dir.FullName -Recurse -File -ErrorAction SilentlyContinue
|
|
$totalSize = ($files | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
|
|
if ($null -eq $totalSize) { $totalSize = 0 }
|
|
|
|
$results.Add([PSCustomObject]@{
|
|
Path = $dir.FullName
|
|
SizeBytes = [int64]$totalSize
|
|
})
|
|
} catch {
|
|
continue
|
|
}
|
|
|
|
if ($CurrentDepth -lt $Limit) {
|
|
$children = Get-DirectorySizeRecursive -DirPath $dir.FullName -CurrentDepth ($CurrentDepth + 1) -Limit $Limit
|
|
foreach ($child in $children) {
|
|
$results.Add($child)
|
|
}
|
|
}
|
|
}
|
|
|
|
return $results
|
|
}
|
|
|
|
# ============================================================================
|
|
# VOLUME OVERVIEW
|
|
# ============================================================================
|
|
|
|
function Get-VolumeOverview {
|
|
Write-Header "Volume Overview"
|
|
|
|
$headerLine = " {0,-12} {1,-20} {2,10} {3,10} {4,6}" -f "Drive", "Label", "Size", "Free", "Used%"
|
|
Write-ColorLine $headerLine White
|
|
Write-ColorLine " $Separator"
|
|
|
|
$volumes = @()
|
|
try {
|
|
$disks = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3" -ErrorAction SilentlyContinue
|
|
} catch {
|
|
$disks = Get-WmiObject -Class Win32_LogicalDisk -Filter "DriveType=3" -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
foreach ($disk in $disks) {
|
|
$total = [int64]$disk.Size
|
|
$free = [int64]$disk.FreeSpace
|
|
if ($total -eq 0) { continue }
|
|
$used = $total - $free
|
|
$pct = [math]::Round(($used / $total) * 100, 1)
|
|
|
|
$color = [ConsoleColor]::Green
|
|
if ($pct -ge 90) { $color = [ConsoleColor]::Red }
|
|
elseif ($pct -ge 80) { $color = [ConsoleColor]::Yellow }
|
|
|
|
$line = " {0,-12} {1,-20} {2,10} {3,10} {4,5}%" -f $disk.DeviceID, $disk.VolumeName, (Format-FileSize $total), (Format-FileSize $free), $pct
|
|
Write-ColorLine $line $color
|
|
|
|
$volumes += [PSCustomObject]@{
|
|
Drive = $disk.DeviceID
|
|
Label = $disk.VolumeName
|
|
SizeBytes = $total
|
|
FreeBytes = $free
|
|
UsedPct = $pct
|
|
}
|
|
}
|
|
|
|
return $volumes
|
|
}
|
|
|
|
# ============================================================================
|
|
# TOP DIRECTORIES BY SIZE
|
|
# ============================================================================
|
|
|
|
function Get-TopDirectories {
|
|
Write-Header "Top $TopN Directories by Size"
|
|
|
|
$headerLine = " {0,4} {1,-60} {2,10}" -f "#", "Directory", "Size"
|
|
Write-ColorLine $headerLine White
|
|
Write-ColorLine " $Separator"
|
|
|
|
$allDirs = Get-DirectorySizeRecursive -DirPath $Path -CurrentDepth 1 -Limit $MaxDepth
|
|
$topDirs = $allDirs | Sort-Object SizeBytes -Descending | Select-Object -First $TopN
|
|
|
|
$rank = 0
|
|
foreach ($dir in $topDirs) {
|
|
$rank++
|
|
$color = [ConsoleColor]::Gray
|
|
if ($dir.SizeBytes -ge 10GB) { $color = [ConsoleColor]::Red }
|
|
elseif ($dir.SizeBytes -ge 1GB) { $color = [ConsoleColor]::Yellow }
|
|
|
|
$displayPath = $dir.Path
|
|
if ($displayPath.Length -gt 58) { $displayPath = "..." + $displayPath.Substring($displayPath.Length - 55) }
|
|
|
|
$line = " {0,4} {1,-60} {2,10}" -f $rank, $displayPath, (Format-FileSize $dir.SizeBytes)
|
|
Write-ColorLine $line $color
|
|
}
|
|
|
|
return $topDirs
|
|
}
|
|
|
|
# ============================================================================
|
|
# TOP FILES BY SIZE
|
|
# ============================================================================
|
|
|
|
function Get-TopFiles {
|
|
Write-Header "Top $TopN Files by Size"
|
|
|
|
$headerLine = " {0,4} {1,-60} {2,10}" -f "#", "File", "Size"
|
|
Write-ColorLine $headerLine White
|
|
Write-ColorLine " $Separator"
|
|
|
|
$files = Get-ChildItem -LiteralPath $Path -Recurse -File -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.Length -ge $MinSizeBytes } |
|
|
Sort-Object Length -Descending |
|
|
Select-Object -First $TopN
|
|
|
|
$rank = 0
|
|
$result = @()
|
|
foreach ($file in $files) {
|
|
$rank++
|
|
$color = [ConsoleColor]::Gray
|
|
if ($file.Length -ge 1GB) { $color = [ConsoleColor]::Red }
|
|
elseif ($file.Length -ge 100MB) { $color = [ConsoleColor]::Yellow }
|
|
|
|
$displayPath = $file.FullName
|
|
if ($displayPath.Length -gt 58) { $displayPath = "..." + $displayPath.Substring($displayPath.Length - 55) }
|
|
|
|
$line = " {0,4} {1,-60} {2,10}" -f $rank, $displayPath, (Format-FileSize $file.Length)
|
|
Write-ColorLine $line $color
|
|
|
|
$result += [PSCustomObject]@{
|
|
Path = $file.FullName
|
|
SizeBytes = $file.Length
|
|
}
|
|
}
|
|
|
|
return $result
|
|
}
|
|
|
|
# ============================================================================
|
|
# OLD LARGE FILES
|
|
# ============================================================================
|
|
|
|
function Get-OldLargeFiles {
|
|
Write-Header "Old Large Files (> ${MinSizeMB} MB, older than $AgeWarnDays days)"
|
|
|
|
$headerLine = " {0,4} {1,-50} {2,10} {3,12}" -f "#", "File", "Size", "Last Modified"
|
|
Write-ColorLine $headerLine White
|
|
Write-ColorLine " $Separator"
|
|
|
|
$files = Get-ChildItem -LiteralPath $Path -Recurse -File -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.Length -ge $MinSizeBytes -and $_.LastWriteTime -lt $AgeCutoff } |
|
|
Sort-Object Length -Descending |
|
|
Select-Object -First $TopN
|
|
|
|
if (-not $files -or $files.Count -eq 0) {
|
|
Write-ColorLine " No files found matching criteria."
|
|
return @()
|
|
}
|
|
|
|
$rank = 0
|
|
$result = @()
|
|
foreach ($file in $files) {
|
|
$rank++
|
|
$displayPath = $file.FullName
|
|
if ($displayPath.Length -gt 48) { $displayPath = "..." + $displayPath.Substring($displayPath.Length - 45) }
|
|
|
|
$line = " {0,4} {1,-50} {2,10} {3,12}" -f $rank, $displayPath, (Format-FileSize $file.Length), $file.LastWriteTime.ToString("yyyy-MM-dd")
|
|
Write-ColorLine $line Yellow
|
|
|
|
$result += [PSCustomObject]@{
|
|
Path = $file.FullName
|
|
SizeBytes = $file.Length
|
|
LastModified = $file.LastWriteTime.ToString("yyyy-MM-dd")
|
|
}
|
|
}
|
|
|
|
return $result
|
|
}
|
|
|
|
# ============================================================================
|
|
# SUMMARY
|
|
# ============================================================================
|
|
|
|
function Write-Summary {
|
|
param(
|
|
[int64]$TotalScanned,
|
|
[array]$OldFiles
|
|
)
|
|
|
|
Write-Header "Summary"
|
|
|
|
$oldCount = 0
|
|
$oldBytes = [int64]0
|
|
if ($OldFiles) {
|
|
$oldCount = $OldFiles.Count
|
|
$oldBytes = ($OldFiles | Measure-Object -Property SizeBytes -Sum).Sum
|
|
if ($null -eq $oldBytes) { $oldBytes = 0 }
|
|
}
|
|
|
|
Write-ColorLine (" Scan path: $Path")
|
|
Write-ColorLine (" Total scanned: $(Format-FileSize $TotalScanned)")
|
|
Write-ColorLine (" Min file size: ${MinSizeMB} MB")
|
|
Write-ColorLine (" Age threshold: $AgeWarnDays days")
|
|
Write-ColorLine ""
|
|
Write-ColorLine (" Old large files: $oldCount files")
|
|
Write-ColorLine (" Reclaimable space: $(Format-FileSize $oldBytes)") Yellow
|
|
Write-ColorLine ""
|
|
|
|
if ($oldBytes -gt 0) {
|
|
Write-ColorLine " -> Review old files above - candidates for cleanup or archival" Yellow
|
|
} else {
|
|
Write-ColorLine " OK - No old large files found" Green
|
|
}
|
|
Write-ColorLine ""
|
|
}
|
|
|
|
# ============================================================================
|
|
# JSON OUTPUT
|
|
# ============================================================================
|
|
|
|
function Get-JsonReport {
|
|
param(
|
|
[array]$Volumes,
|
|
[array]$TopDirs,
|
|
[array]$TopFilesList,
|
|
[array]$OldFiles,
|
|
[int64]$TotalScanned
|
|
)
|
|
|
|
$oldBytes = [int64]0
|
|
if ($OldFiles) {
|
|
$oldBytes = ($OldFiles | Measure-Object -Property SizeBytes -Sum -ErrorAction SilentlyContinue).Sum
|
|
if ($null -eq $oldBytes) { $oldBytes = 0 }
|
|
}
|
|
|
|
$report = [PSCustomObject]@{
|
|
scan_path = $Path
|
|
timestamp = (Get-Date -Format "yyyy-MM-ddTHH:mm:ssZ")
|
|
min_size_mb = $MinSizeMB
|
|
age_warn_days = $AgeWarnDays
|
|
max_depth = $MaxDepth
|
|
volumes = @($Volumes)
|
|
top_directories = @($TopDirs)
|
|
top_files = @($TopFilesList)
|
|
old_large_files = @($OldFiles)
|
|
summary = [PSCustomObject]@{
|
|
total_scanned_bytes = $TotalScanned
|
|
old_file_count = if ($OldFiles) { $OldFiles.Count } else { 0 }
|
|
reclaimable_bytes = $oldBytes
|
|
}
|
|
}
|
|
|
|
return $report | ConvertTo-Json -Depth 5
|
|
}
|
|
|
|
# ============================================================================
|
|
# MAIN
|
|
# ============================================================================
|
|
|
|
if (-not (Test-Path -LiteralPath $Path)) {
|
|
Write-Error "Path does not exist: $Path"
|
|
exit 1
|
|
}
|
|
|
|
# Calculate total size of scanned path
|
|
$totalScanned = [int64]0
|
|
try {
|
|
$allFiles = Get-ChildItem -LiteralPath $Path -Recurse -File -ErrorAction SilentlyContinue
|
|
$totalScanned = ($allFiles | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
|
|
if ($null -eq $totalScanned) { $totalScanned = 0 }
|
|
} catch {
|
|
$totalScanned = 0
|
|
}
|
|
|
|
if ($Json) {
|
|
# Collect data silently for JSON - redirect console output
|
|
$volumes = @()
|
|
try {
|
|
$disks = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3" -ErrorAction SilentlyContinue
|
|
} catch {
|
|
$disks = Get-WmiObject -Class Win32_LogicalDisk -Filter "DriveType=3" -ErrorAction SilentlyContinue
|
|
}
|
|
foreach ($disk in $disks) {
|
|
$total = [int64]$disk.Size
|
|
$free = [int64]$disk.FreeSpace
|
|
if ($total -eq 0) { continue }
|
|
$used = $total - $free
|
|
$pct = [math]::Round(($used / $total) * 100, 1)
|
|
$volumes += [PSCustomObject]@{
|
|
Drive = $disk.DeviceID
|
|
Label = $disk.VolumeName
|
|
SizeBytes = $total
|
|
FreeBytes = $free
|
|
UsedPct = $pct
|
|
}
|
|
}
|
|
|
|
$topDirs = Get-DirectorySizeRecursive -DirPath $Path -CurrentDepth 1 -Limit $MaxDepth |
|
|
Sort-Object SizeBytes -Descending | Select-Object -First $TopN
|
|
|
|
$topFilesList = Get-ChildItem -LiteralPath $Path -Recurse -File -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.Length -ge $MinSizeBytes } |
|
|
Sort-Object Length -Descending |
|
|
Select-Object -First $TopN |
|
|
ForEach-Object { [PSCustomObject]@{ Path = $_.FullName; SizeBytes = $_.Length } }
|
|
|
|
$oldFiles = Get-ChildItem -LiteralPath $Path -Recurse -File -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.Length -ge $MinSizeBytes -and $_.LastWriteTime -lt $AgeCutoff } |
|
|
Sort-Object Length -Descending |
|
|
Select-Object -First $TopN |
|
|
ForEach-Object { [PSCustomObject]@{ Path = $_.FullName; SizeBytes = $_.Length; LastModified = $_.LastWriteTime.ToString("yyyy-MM-dd") } }
|
|
|
|
Get-JsonReport -Volumes $volumes -TopDirs $topDirs -TopFilesList $topFilesList -OldFiles $oldFiles -TotalScanned $totalScanned
|
|
exit 0
|
|
}
|
|
|
|
# Interactive output
|
|
Write-ColorLine ""
|
|
Write-ColorLine "Disk Usage Report" White
|
|
Write-ColorLine ("{0} - Scanning: {1}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"), $Path)
|
|
|
|
$volumes = Get-VolumeOverview
|
|
$topDirs = Get-TopDirectories
|
|
$topFiles = Get-TopFiles
|
|
$oldFiles = Get-OldLargeFiles
|
|
|
|
Write-Summary -TotalScanned $totalScanned -OldFiles $oldFiles
|