Rank: Advanced Member
Groups: Registered
Joined: 7/1/2018(UTC) Posts: 64 Thanks: 1 times Was thanked: 6 time(s) in 6 post(s)
|
"Sizer"
Disk space has always been a problem for the 20 years that I have spent in the IT field, and it doesn't seem to be going away anytime soon. The crux of the problem is not necessarily the size of the disks; the operating systems, applications, and user projects just keep getting bigger. It really only takes one application, one user, or one system log file to run a disk right out of space. Servers and workstations share the same issue. Having enough disk space to operate is one of the most important proactive items that an administrator can monitor. Please see my article on LinkedIn about disk space. Even if you have a list of machines that are low on disk space, as trivial as it might sound, how does one efficiently locate the big files / directories?
Introducing the "Sizer".
Discussion about Sizer and Quick Demo on YouTube
The Sizer utilizes Robocopy.exe to quickly list the size of folders. With Sizer, you can start at the root of the C:\ drive and quickly drill down to locate problem areas. There are many posts that illustrate how to list folder sizes with Robocopy.exe. I give them all credit. Robocopy.exe is freaking awesome! Using that method, along with capturing and sizing subfolders is really the key to quickly finding where the disk space is being taken.
I also used PowerShell, a Process with a Timeout only to start the Robocopy.exe process and capture the StdOut/StdErr streams.
Post Sizer, there is no more manually checking common problem areas. Sizer will quickly tell you exactly where the problem areas are, and that equals efficiency.
Here is the code (Don't forget Start-ProcessWaitTimeout mentioned above):
All of the code goes in a single ps1 file IE: "Sizer.ps1". The parameters must be first.
Parameters for the script:
Code:param (
[Parameter(Mandatory=$true)]
[string]$Dir = $NULL,
[switch]$MB = $FALSE,
[switch]$GB = $FALSE,
[switch]$Auto = $FALSE,
[switch]$CSV = $FALSE,
[switch]$OBJECT = $FALSE
)
Function to get a directory list using the Get-ChildItem command:
Code:function Get-DirectoryList
{
<#
.SYNOPSIS
Funtion to get a list of directory names using the Get-ChildItem command
.DESCRIPTION
Funtion to get a list of directory names using the Get-ChildItem command
#>
param (
[Parameter(Mandatory=$true)]
[string]$Directory = $null
)
# Create the Object
$Obj = [PSCustomObject]@{
Directory = $Directory
DirList = New-Object System.Collections.Generic.List[string]
}
# Add a trailing backslash if it is not there
# This will handle the case of C: and default it to the root
if ($Directory.EndsWith("\") -eq $false) {
$Directory = "$($Directory)\"
}
$Directories = Get-ChildItem -Force -Directory "$($Directory)*"
# Add the Directories to the list
foreach ($i in $Directories) {
# Don't want links
if ($i.Attributes -notmatch "ReparsePoint") {
$Obj.DirList.Add($i.Name)
}
}
return $Obj
}
Function that uses Robocopy to get folder sizes:
Code:function Get-RobocopyDirectory
{
<#
.SYNOPSIS
Function that utilizes Robocopy to get the size of directories in the directory that is passed to it
.DESCRIPTION
Function that utilizes Robocopy to get the size of directories in the directory that is passed to it
#>
param (
[Parameter(Mandatory=$true)]
[string]$Directory = $null,
[switch]$IgnoreSubDirs = $false
)
# Create the Object
$Obj = [PSCustomObject]@{
Directory = $Directory
DirList = New-Object System.Collections.Generic.List[string]
Cmd = $null
Size = $null
RawSize = $null
}
$Directory = $Directory.TrimEnd("\")
if ($Directory.EndsWith(":") -eq $true) { $Directory = "$($Directory)\\" }
# Setup the command
$Cmd = "C:\Windows\System32\robocopy.exe"
# Setup the arguments
if ($IgnoreSubDirs -eq $false) { $CmdArgs = @("""$($Directory)""","""NULL""","/l","/e","/njh","/nfl","/xj","/r:0","/w:0","/bytes") }
elseif ($IgnoreSubDirs -eq $true) { $CmdArgs = @("""$($Directory)""","""NULL""","/l","/njh","/nfl","/xj","/r:0","/w:0","/bytes") }
$Obj.Cmd = Start-ProcessWaitTimeout -Computer $null -CmdLine $cmd -CmdLineArgs $CmdArgs -Timeout 180
# Add a trailing backslash to the directory (so it matches Robocopy output)
if ($Directory.EndsWith("\") -eq $false) { $Directory = "$($Directory)\" }
# Loop through the StdOut
foreach ($i in $Obj.Cmd.ProcessStdOut.Split("`n")) {
# If it is a directory, get the name from the Robocopy output and add it to the list if it isn't there
if ($i -match ".*\sDir\s+\d+\s+(?<Directory>.*)\s+") {
if ($Matches.Directory -ne $Directory) {
$RelativeSubDir = $Matches.Directory.Replace($Directory,"")
$SubDir = $RelativeSubDir.SubString(0,$RelativeSubDir.IndexOf("\"))
if ($Obj.DirList.Contains($SubDir) -eq $false) {
$Obj.DirList.Add($SubDir)
}
}
}
# Get the size and round to 2 places
if ($i -match "Bytes\s:\s+(?<Size>\d+)\s.*") {
$Obj.RawSize = $Matches.Size
if ($MB -or ($Auto -and ([math]::Round(($Matches.Size / 1MB),2) -lt 1024))) {
$Obj.Size = [math]::Round(($Matches.Size / 1MB),2).ToString("0.00").PadRight(9," ") + "MB"
}
elseif ($GB -or ($Auto -and ([math]::Round(($Matches.Size / 1MB),2) -ge 1024))) {
$Obj.Size = [math]::Round(($Matches.Size / 1GB),2).ToString("0.00").PadRight(9," ") + "GB"
}
}
}
# Return the Object
Return $Obj
}
Main:
Code:#################################################
#
# Main
#
#################################################
# Input validation: Only one of the 3 input flags (MB, GB, Auto) can be set at a time
if (([bool]$MB + [bool]$GB + [bool]$Auto) -gt 1) {
Write-Host "Invalid Options. -MB, -GB, and -Auto cannot be combined with each other" -ForegroundColor Red
EXIT 2
}
elseif (([bool]$MB + [bool]$GB + [bool]$Auto) -eq 0) { $Auto = $True }
# Just for the root, use Get-ChildItem to get a list of the directories. It is a lot faster than using Robocopy.
# Matching on IE: (C: or C:\) or IE: (\\computer\c$ or \\computer\c$\)
if ($Dir -match "[A-Z]:\\*\Z" -or $Dir -match "\A\\\\.*\\[A-Z]\$\\*\Z") {
# Get a list of directories at the root
$DirObj = Get-DirectoryList -Directory $Dir
} else {
# Not the root, just size the directory (that will get a list of directories also)
$DirObj = Get-RobocopyDirectory -Directory "$($Dir)"
}
# Get longest directory name for display purposes
$LongestDir = 0
foreach ($i IN $DirObj.DirList) { IF (("$($Dir.TrimEnd("\"))\$($i)").length -gt $LongestDir) {$LongestDir = ("$($Dir.TrimEnd("\"))\$($i)").length} }
if ($Dir.length -gt $LongestDir) {$LongestDir = $Dir.Length}
$Width = $LongestDir + 5
$Lines = "".PadRight(($Width + 14), "-")
$ToBecomeObject = New-Object System.Collections.Generic.List[object]
# Write visual header if normal mode, and csv header if CSV mode
if ((!($CSV)) -and (!($OBJECT))) {
Write-Host $Lines
Write-Host " SubFolder Size(s):"
Write-Host $Lines
}
elseif ($OBJECT) { $ToBecomeObject.Add("Directory,Size_MB,Size_GB") }
else { "Directory,Size_MB,Size_GB" }
# Size the directories
$TotalRawSize = 0
foreach ($i in $DirObj.DirList) {
$RoboObj = Get-RobocopyDirectory -Directory "$($Dir.TrimEnd("\"))\$($i)"
$TotalRawSize += $RoboObj.RawSize
if ((!($CSV)) -and (!($OBJECT))) { Write-Host " $($RoboObj.Directory.PadRight($Width," ")) $($RoboObj.Size)" -ForegroundColor Cyan }
elseif ($OBJECT) { $ToBecomeObject.Add("$($RoboObj.Directory),$([math]::Round(($RoboObj.RawSize / 1MB),2).ToString("0.00")),$([math]::Round(($RoboObj.RawSize / 1GB),2).ToString("0.00"))") }
else { "$($RoboObj.Directory),$([math]::Round(($RoboObj.RawSize / 1MB),2).ToString("0.00")),$([math]::Round(($RoboObj.RawSize / 1GB),2).ToString("0.00"))" }
}
if ((!($CSV)) -and (!($OBJECT))) {
Write-Host $Lines
Write-Host " Total Size:"
Write-Host $Lines
}
# Get the final size of the whole $Dir
$WholeDirObj = Get-RobocopyDirectory -Directory "$($Dir)" -IgnoreSubDirs
$TotalRawSize += $WholeDirObj.RawSize
# Format TotalSize based on input switches
if ($MB -or ($Auto -and ([math]::Round(($TotalRawSize / 1MB),2) -lt 1024))) {
$TotalSize = [math]::Round(($TotalRawSize / 1MB),2).ToString("0.00").PadRight(9," ") + "MB"
}
elseif ($GB -or ($Auto -and ([math]::Round(($TotalRawSize / 1MB),2) -ge 1024))) {
$TotalSize = [math]::Round(($TotalRawSize / 1GB),2).ToString("0.00").PadRight(9," ") + "GB"
}
if ((!($CSV)) -and (!($OBJECT))) {
Write-Host " $($WholeDirObj.Directory.PadRight($Width," ")) $($TotalSize)" -ForegroundColor Cyan
Write-Host $Lines
Write-Host ""
}
elseif ($OBJECT) {
$ToBecomeObject.Add("$($WholeDirObj.Directory),$([math]::Round(($TotalRawSize / 1MB),2).ToString("0.00")),$([math]::Round(($TotalRawSize / 1GB),2).ToString("0.00"))")
$SizeObj = ConvertFrom-Csv ($ToBecomeObject)
$SizeObj | % { $_.Size_MB = [double]$_.Size_MB }
$SizeObj | % { $_.Size_GB = [double]$_.Size_GB }
$SizeObj
}
else { "$($WholeDirObj.Directory),$([math]::Round(($TotalRawSize / 1MB),2).ToString("0.00")),$([math]::Round(($TotalRawSize / 1GB),2).ToString("0.00"))" }
Follow Dustin Higgins on Twitter and Instagram
Edited by user Wednesday, December 23, 2020 7:12:20 PM(UTC)
| Reason: Not specified
|