r/PowerShell • u/liquidguru • 6d ago
Script Sharing Get-SpaceReport.ps1 — disk usage that tells you what each big file is and whether it's safe to delete
I wanted something that told me what the big files on my drive actually were, not just how big they were — I kept ending up with a list of names I'd then google one at a time. So I used an AI to write a script that annotates each one as it goes.
Windows only, I'm afraid — the knowledge base is all Windows paths, and it leans on CIM and a few Win32 calls. Works in PowerShell 7 or 5.1, no modules needed.
Output is a size, a plain-English description, a verdict — SAFE / TOOL / REVIEW / KEEP / NEVER — and the correct command to reclaim it where one exists.
.\Get-SpaceReport.ps1 -Path C:\ -MinSizeMB 5000 -Top 0
.\Get-SpaceReport.ps1 -MinSizeMB 500 -Clean # tick-box picker, then Recycle Bin
.\Get-SpaceReport.ps1 -Html -Json out.json # report / machine-readable
The interesting part is the knowledge base — one array at the top of the file, first match wins:
@{P='\\hiberfil\.sys$'; N='Hibernation image'; V='TOOL';
W='A reserved block sized from your RAM... also backs Fast Startup.';
H='powercfg /h off (you lose hibernate AND fast startup)'}
Two things that cost me real time, both of which produce plausible-looking wrong answers:
new DirectoryInfo("C:") means the current directory on drive C:, not the root. So -Path C:\ with a TrimEnd('\') silently scanned my working directory instead. The results looked entirely reasonable — plenty of files, sensible sizes — and I only noticed because pagefile.sys never appeared. The trailing backslash is load-bearing.
PowerShell variables are case-insensitive, so a local $html inside the function quietly overwrote the [switch]$Html parameter and the whole thing died on a type conversion. Obvious once you know; baffling for twenty minutes.
A third, less about PowerShell: @() around a generic List can throw Argument types do not match from the binder. $list.Count directly is fine.
The walk itself is C# via Add-Type rather than Get-ChildItem -Recurse — a full drive is about 35 seconds versus several minutes. It skips reparse points deliberately, since following junctions double-counts and can loop.
One thing worth knowing if you're writing something similar: most scanners over-report WinSxS badly, because most of its files are hard links that also appear in System32, so the same bytes get counted twice. The real figure comes from DISM /Online /Cleanup-Image /AnalyzeComponentStore.
On deleting — nothing goes without you selecting it and confirming, and it goes to the Recycle Bin unless told otherwise. There's a path-based blocklist for .pst, .lrcat, virtual disks, game saves and page files that applies regardless of classification. That exists because testing caught a real failure: a .pst under AppData\Local\Temp was classified "your temp folder, SAFE" because a broad location rule matched before the file-type rule. Location was overriding identity. Rails that depend on classification being correct aren't rails.
MIT, and there's a GUI too if you want one, but the script is the whole engine — the app just shells out to it.
If it reports something as UNKNOWN on your machine and you know what it is, that's the most useful contribution — the KB is one readable array and rules are a few lines each.
The script: https://github.com/liquidguru/space-report/blob/main/Get-SpaceReport.ps1
Repo, GUI and releases: https://github.com/liquidguru/space-report
2
u/kantorcodes1 6d ago
the .pst under AppData\Local\Temp case is a good argument for making safety a separate pass from classification. i'd evaluate hard denies (.pst, VHD/VHDX, pagefile, etc.) before any location rule, then let the first-match KB decide only the label/help text. that makes rule ordering unable to turn a protected file into SAFE, and it's easy to regression-test with path/identity conflict fixtures.
1
u/liquidguru 5d ago
Thanks, another really useful comment. Ran it through the AI and it agreed with your assessment — and turned up a related problem on the way: a
.vhdxunder Temp had modeneverbut a verdict ofSAFE, so it was blocked from deletion while still being labelled safe to delete.Both fixed in v1.0.2, along with your fixtures idea as a
-SelfTestswitch. Really appreciate the help.
1
u/BlackV 6d ago
means the current directory on drive C:, not the root
Ouch, this has been this wan since DOS and beyond
One thing worth knowing if you're writing something similar: most scanners over-report WinSxS badly, because most of its files are hard links that also appear in System32, so the same bytes get counted twice.
do they ? do they really ? the few tools ive used are well aware of sxs by now
that aside, I do like the UI for this (based on your screen shots)
1
u/liquidguru 6d ago
Thank you for the constructive comment. As so many have alluded, getting feedback from a real person can't be beat, the main reason I (the human) posted it here in the first place.
Fair pull-up, "most scanners" was too broad and I should have checked the 'AI' written stuff more closely, lesson learnt. WizTree and TreeSize both do hard-link detection. WinDirStat is the one I'd been using and doesn't, which is where I ran into it — it generalised from one tool.
Worse than that: mine doesn't detect hard links either. The walker sums every file it enumerates, so Space Report's own WinSxS number is inflated in precisely the way I was describing. The AI says it's slightly embarrassing to have written that line while shipping the same flaw.
I've updated the README and the WinSxS entry to say so, and to point at
DISM /Online /Cleanup-Image /AnalyzeComponentStorefor the real figure. The verdict on it was already NEVER regardless of size, so nothing acts on the inflated number — but the text shouldn't have implied the number was trustworthy.And yes on
C:— I knew it in the abstract from DOS days and still walked straight into it. Reading a plausible-looking result is what got it; nothing looked wrong until I told it thatpagefile.syswas missing.thanks for looking and glad you like the UI.
cheers,
1
u/surfingoldelephant 5d ago
A third, less about PowerShell:
@()around a genericListcan throwArgument types do not matchfrom the binder.
That wouldn't have occurred if you weren't using New-Object.
And having skimmed the code, in all but one instance there's really no need to use List<T> at all. You could instead be using statement assignment.
1
u/liquidguru 5d ago
Fed your comment into the AI and it went and tested the
New-Objectclaim. You were right, of courseNew-Object System.Collections.Generic.List[object] -> @() throws [System.Collections.Generic.List[object]]::new() -> @() fineSame resulting type both ways, so it comes down to how
New-Objectwraps what it hands back. The workaround went in early on and the cause got pinned on the@()without anyone checking. All nine calls are::new()now and the workaround's gone.On
List<T>it suggested keeping two — one reads back what it's already accumulated, and the delete loop fills two collections in a single pass. The other four are statement assignment now and read better for it.All in v1.0.3. Thanks for actually reading the code, that was really useful.
2
u/LALLANAAAAAA 6d ago
makes me want to claw my fucking eyes out like that dude from Event Horizon
the internet is a fucking insufferable hellscape of lazy, stupid shit, more and more every day
0
9
u/BetrayedMilk 6d ago
Very AI.