Edit: Heavily modified script for speed. Bulk of script is now running Compiled C# Code.
Been resolving some problems at work lately with respect to full disks. One of our charters is to manage the ConfigMgr cache sizes on each machine to ensure that the packages we need to get replicated, actually get replicated out to the right machines at the right time.
But we’ve been getting some feedback about one 3rd party SCCM caching tool failing in some scenarios. Was it really the 3rd party tool failing, or some other factor?
Well we looked at the problem and found:
- Machines with a modest 120GB SSD Drive (most machines have a more robust 250GB SSD)
- Configuration Manager Application Install packages that are around 10-5GB (yowza!)
- Users who leave too much… crap laying around their desktop.
- And several other factors that have contributed to disks getting full.
Golly, when I try to install an application package that requires 12GB to install, and there is only 10GB free, it fails.
Um… yea…
I wanted to get some data for machines that are full: What is using up the disk space? But it’s a little painful searching around a disk for directories that are larger than they should be.
Options
One of my favorite tools is “WinDirStat” which produces a great graphical representation of a disk, allowing you to visualize what directories are taking up the most space, and which files are the largest. http://windirstat.net
Additionally I also like the “du.exe” tool from SysInternals. https://live.sysinternals.com/du.exe
I wrap it up in a custom batch script file
@%~dps0du.exe -l 1 -q -accepteula %*
and it produces output that looks like:
PS C:\Users> dudir 263,122 C:\Users\Administrator 1,541 C:\Users\Default 7,473,508 C:\Users\keith 4,173 C:\Users\Public 7,742,345 C:\Users Files: 27330 Directories: 5703 Size: 7,928,161,747 bytes Size on disk: 7,913,269,465 bytes
Cool, however, I wanted something that I could run remotely, and that would give me just the most interesting directories, say everything over 1GB, or something configurable like that.
So a tool was born.
Tool
The script will enumerate through all files on a local machine and return the totals. Along the way we can add in rules to “Group” interesting directories and output the results.
So, say we want to know if there are any folders under “c:\program files (x86)\Adobe\*” that are larger than 1GB. For the most part, we don’t care about Adobe Reader, since it’s under 1GB, but everything else would be interesting. Stuff like that.
We have a default set of rules built into the script, but you can pass a new set of rules into the script using a *.csv file ( I use excel )
Folder | SizeMB |
c:\* | 500 |
C:\$Recycle.Bin | 100 |
c:\Program Files | 0 |
C:\Program Files\* | 1000 |
C:\Program Files (x86) | 0 |
C:\Program Files (x86)\Adobe\* | 1000 |
C:\Program Files (x86)\* | 1000 |
C:\ProgramData\* | 1000 |
C:\ProgramData | 0 |
C:\Windows | 0 |
C:\Windows\* | 1000 |
c:\users | 0 |
C:\Users\* | 100 |
C:\Users\*\* | 500 |
C:\Users\*\AppData\Local\Microsoft\* | 1000 |
C:\Users\*\AppData\Local\* | 400 |
Example output:
The machine isn’t too interesting (it’s my home machine not work machine)
I’m still looking into tweaks and other things to modify in the rules to make the output more interesting.
- Should I exclude \windows\System32 directories under X size?
- etc…
If you have feedback, let me know
Script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.SYNOPSIS | |
Report on Disk Hogs | |
.DESCRIPTION | |
Returns a list of the largest directories in use on the local machine | |
.NOTES | |
Copyright Keith Garner, All rights reserved. | |
Really Updated for Windows 7 and Optimized for !!!SPEED!!! | |
.PARAMETER Path | |
Start of the search, usually c:\ | |
.PARAMETER IncludeManifest | |
Include basic info about the memory, OS, and Disk in the manifest | |
.PARAMETER OutFile | |
CLIXML file used to store results | |
Location of a custom rules *.csv file, otherwise use the default table | |
.LINK | |
https://keithga.wordpress.com | |
#> | |
[cmdletbinding()] | |
param( | |
$path = 'c:\', | |
[switch] $IncludeManifest, | |
$OutFile | |
) | |
########################################################### | |
$WatchList = @( | |
@{ Folder = 'c:\'; SizeMB = '0' } | |
@{ Folder = 'c:\*'; SizeMB = '500' } | |
@{ Folder = 'C:\$Recycle.Bin'; SizeMB = '100' } | |
@{ Folder = 'c:\Program Files'; SizeMB = '0' } | |
@{ Folder = 'C:\Program Files\*'; SizeMB = '1000' } | |
@{ Folder = 'C:\Program Files (x86)'; SizeMB = '0' } | |
@{ Folder = 'C:\Program Files (x86)\Adobe\*'; SizeMB = '1000' } | |
@{ Folder = 'C:\Program Files (x86)\*'; SizeMB = '1000' } | |
@{ Folder = 'C:\ProgramData\*'; SizeMB = '1000' } | |
@{ Folder = 'C:\ProgramData'; SizeMB = '0' } | |
@{ Folder = 'C:\Windows'; SizeMB = '0' } | |
@{ Folder = 'C:\Windows\*'; SizeMB = '1000' } | |
@{ Folder = 'c:\users'; SizeMB = '0' } | |
@{ Folder = 'C:\Users\*'; SizeMB = '100' } | |
@{ Folder = 'C:\Users\*\*'; SizeMB = '500' } | |
@{ Folder = 'C:\Users\*\AppData\Local\Microsoft\*'; SizeMB = '1000' } | |
@{ Folder = 'C:\Users\*\AppData\Local\*'; SizeMB = '400' } | |
) | |
########################################################### | |
Add-Type –TypeDefinition @" | |
public class EnumFolder | |
{ | |
public static System.Collections.Generic.Dictionary<string, long> ListDir(string Path, System.Collections.Generic.Dictionary<string, long> ControlList) | |
{ | |
System.Collections.Generic.Dictionary<string, long> Results = new System.Collections.Generic.Dictionary<string, long>(); | |
System.IO.DirectoryInfo Root = new System.IO.DirectoryInfo(Path); | |
ListDirRecursive(Root, Results, ControlList); | |
return Results; | |
} | |
private static long ListDirRecursive | |
( | |
System.IO.DirectoryInfo Path, | |
System.Collections.Generic.Dictionary<string, long> Results, | |
System.Collections.Generic.Dictionary<string, long> ControlList | |
) | |
{ | |
try | |
{ | |
long Total = 0; | |
foreach (System.IO.DirectoryInfo Directory in Path.GetDirectories()) | |
if ((Directory.Attributes & System.IO.FileAttributes.ReparsePoint) == 0) | |
Total += ListDirRecursive(Directory, Results, ControlList); | |
foreach (System.IO.FileInfo file in Path.GetFiles()) | |
{ | |
if ((file.Attributes & System.IO.FileAttributes.ReparsePoint) == 0) | |
{ | |
if (ControlList.ContainsKey(file.FullName)) | |
{ | |
if ((ControlList[file.FullName] * 1024 * 1024) < file.Length) | |
{ | |
Results.Add(file.FullName, file.Length); | |
} | |
else | |
{ | |
Total += file.Length; | |
} | |
} | |
else | |
{ | |
Total += file.Length; | |
} | |
} | |
} | |
if (ControlList.ContainsKey(Path.FullName)) | |
{ | |
if ((ControlList[Path.FullName] * 1024 * 1024) < Total) | |
{ | |
Results.Add(Path.FullName, Total); | |
Total = 0; | |
} | |
} | |
return Total; | |
} | |
catch | |
{ | |
return 0; | |
} | |
} | |
} | |
"@ | |
########################################################### | |
$start = [datetime]::Now | |
$ControlList = new-object –TypeName 'System.Collections.Generic.Dictionary[String,int64]' | |
foreach ( $Item in $WatchList ) { | |
if ( $item.Folder.EndsWith('*') ) { | |
get-childitem $Item.Folder.TrimEnd('*') –force –ErrorAction SilentlyContinue | | |
ForEach-Object { | |
$_.FullName.Substring(0,1).ToLower() + $_.FullName.Substring(1) | |
} | | |
Where-Object { -not $ControlList.ContainsKey( $_ ) } | | |
foreach-object { $ControlList.Add($_,0 + $Item.SizeMB) } | |
} | |
else { | |
get-item $Item.Folder –force –ErrorAction SilentlyContinue | | |
ForEach-Object { | |
$_.FullName.Substring(0,1).ToLower() + $_.FullName.Substring(1) | |
} | | |
Where-Object { -not $ControlList.ContainsKey( $_ ) } | | |
foreach-object { $ControlList.Add($_,0 + $Item.SizeMB) } | |
} | |
} | |
$ControlList.Keys | write-verbose | |
################### | |
$Results = [EnumFolder]::ListDir($Path.ToLower(), $ControlList ) | |
$Results | write-output | |
([datetime]::now – $Start).TotalSeconds | Write-verbose | |
################### | |
if ( $OutFile ) { | |
new-item –ItemType Directory –Path ( split-path $OutFile ) –ErrorAction SilentlyContinue | Out-Null | |
if ( $IncludeManifest ) { | |
@{ | |
OS = GWMI Win32_OPeratingSystem | Select OSarchitecture,OSLanguage,InstallDate,Version | |
Mem = GWMI Win32_PhysicalMemory | Select Capacity | |
Vol = GWMI Win32_LogicalDisk –Filter "DeviceID='$($path.Substring(0,1))`:'" | Select Size,FreeSpace,VolumeName | |
Data = $Results | |
} | Export-Clixml –Path $OutFile | |
} | |
else { | |
$Results | Export-Clixml –Path $OutFile | |
} | |
} |