Rank: Advanced Member
Groups: Registered
Joined: 7/1/2018(UTC) Posts: 64 Thanks: 1 times Was thanked: 6 time(s) in 6 post(s)
|
"Super Sizer" The answer is simple. There is not 1 root cause. Collecting data in a method that automates the analysis and resolution is the answer. Step 1. Populate Table 1 with standard information - Computer Name
- C: Drive Size
- C: Drive Used Space
- C: Drive Free Space
- C: Drive Percent Free
Step #1 is the easy part. Many have their own tools for this. Here is mine: Disk Space CIM Query Step 2. Populate Table 2 with the sizes of common problem directories. Common Problem Directories: - Size of all user profiles individually
- Size of the AppData folder for each user profile (identifies application issues)
- Size of the Desktop folder for each user profile
- Size of the Temp folder for each user profile
- Size of the System Temp folder
- Size of the SCCM cache folder
- Size of the Recycle Bin folder for each user
- Easily add other folders
Step #2 is a little bit more difficult. In a nutshell, here is how it works: 1. Gather profile data from Win32_Profiles. 2. Loop through each profile adding the directories to the list. 3. Get the size of each folder in the list (user profiles and system folders) with Robocopy. 4. Return objects with the folders and sizes in GB if they are over a certain size. Here is the code for Step #2 Don't be intimidated by the Start-ProcessWaitTimeout function. That is used to read the StdOut from Robocopy without writing to a file. Code:function Start-ProcessWaitTimeout
{
<#
.SYNOPSIS
Function to start a process and wait until it completes or reaches a specified time limit in seconds
.DESCRIPTION
Function to start a process and wait until it completes or reaches a specified time limit in seconds
#>
[CmdletBinding()]
param (
[Parameter(ValueFromPipelineByPropertyName)]
[string]$Computer,
[Parameter(ValueFromPipelineByPropertyName)]
[string]$CmdLine,
[Parameter(ValueFromPipelineByPropertyName)]
[array]$CmdLineArgs,
[Parameter(Mandatory=$true)]
[int]$Timeout
)
Begin {
# Initialize list to hold the process objects
$ProcessList = New-Object System.Collections.Generic.List[PSObject]
# Establish Functions to Create and Wait for the Process
function New-Process
{
<#
.SYNOPSIS
Create a process
.DESCRIPTION
Create a process
#>
param (
[Parameter(Mandatory=$true)]
[string]$CmdLine,
[Parameter(Mandatory=$true)]
$CmdLineArgs
)
## SET THE PROCESS INFO
$processInfo = New-Object System.Diagnostics.ProcessStartInfo
$processInfo.FileName = $cmdLine
$processInfo.RedirectStandardError = $true
$processInfo.RedirectStandardOutput = $true
$processInfo.UseShellExecute = $false
## SET THE PROCESS
$processInfo.Arguments = $cmdLineArgs
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $processInfo
# Return the Process
Return $process
}
function Wait-ForProcess
{
<#
.SYNOPSIS
Wait for a process to complete.
.DESCRIPTION
Wait for a process to complete.
#>
param (
[Parameter(Mandatory=$true)]
[System.Diagnostics.Process]$Process,
[Parameter(Mandatory=$true)]
[int]$TimeoutSeconds
)
$ProcessComplete = $Process.WaitForExit($TimeoutSeconds * 1000)
if ($ProcessComplete -eq $false)
{
$Process.Kill()
Return -1
} else {
Return 0
}
}
} # End Begin
Process {
# Create the Process Object
$ProcessObj = [PSCustomObject]@{
Computer = $computer
CmdLine = $cmdLine
CmdLineArgs = $cmdLineArgs
Timeout = $timeout
ProcessStartTime = $null
ProcessEndTime = $null
ProcessDuration = $null
ProcessStdOut = $null
ProcessStdErr = $null
ProcessExitCode = $null
ProcessResult = $null
Result = $null
ObjTimeStamp = (get-date)
}
# Create the process
$Process = New-Process -CmdLine $CmdLine -CmdLineArgs $CmdLineArgs
# START THE PROCESS
$Process.Start() | Out-Null
$ProcessObj.ProcessStartTime = $process.StartTime
# READ STD OUT AND ERROR TO END ASYNC SO BUFFER DOESNT FILL AND HANG PROCESS
$stdOut = $process.StandardOutput.ReadToEndAsync()
$stdErr = $process.StandardError.ReadToEndAsync()
# WAIT for the Process to Complete
$WaitProcess = Wait-ForProcess $Process $Timeout
if ($WaitProcess -eq 0) {
$processObj.ProcessResult = $Process.ProcessExitCode
}
if ($WaitProcess -eq -1) {
$processObj.ProcessResult = "Timeout"
}
## SET THE EXIT CODE STD OUT/ERR, END TIME, DURATION
$processObj.ProcessExitCode = $process.ExitCode
$processObj.ProcessStdOut = $stdOut.Result
$processObj.ProcessStdErr = $stdErr.Result
$processObj.ProcessEndTime = $process.ExitTime
$processObj.ProcessDuration = $processObj.ProcessEndTime - $processObj.ProcessStartTime
## RETURN THE PROCESS OBJECT
$ProcessList.Add($processObj)
} End {
# Return the Process List
Return $ProcessList
}
}
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
)
# 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 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,
[switch]$IgnoreSubDirs = $false
)
# Create the Object
$Obj = [PSCustomObject]@{
Directory = $Directory
DirList = New-Object System.Collections.Generic.List[string]
Cmd = $null
Size = $null
Size_MB = $null
Size_MB_String = $null
Size_GB = $null
Size_GB_String = $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
$Obj.Size_MB = [math]::Round(($Matches.Size / 1MB),2)
$Obj.Size_GB = [math]::Round(($Matches.Size / 1GB),2)
$Obj.Size_MB_String = [math]::Round(($Matches.Size / 1MB),2).ToString("0.00").PadRight(9," ") + "MB"
$Obj.Size_GB_String = [math]::Round(($Matches.Size / 1GB),2).ToString("0.00").PadRight(9," ") + "GB"
}
}
# Return the Object
Return $Obj
}
function Size-Directory
{
<#
.SYNOPSIS
Gets the size of a directory
.DESCRIPTION
Gets the size of a directory
#>
param (
[Parameter(Mandatory=$true)]
[string]$Directory
)
# Setup the object
$Obj = [PSCustomObject]@{
Directory = $Directory
DirList = New-Object System.Collections.Generic.List[PSObject]
TotalSize = $null
TotalSize_MB = $null
TotalSize_MB_String = $null
TotalSize_GB = $null
TotalSize_GB_String = $null
}
# 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 ($Directory -match "[A-Z]:\\*\Z" -or $Directory -match "\A\\\\.*\\[A-Z]\$\\*\Z") {
# Get a list of directories at the root
$DirObj = Get-DirectoryList -Directory $Directory
} else {
# Not the root, just size the directory (that will get a list of directories also)
$DirObj = Get-RobocopyDirectory -Directory "$($Directory)"
}
# Size the Directories
$TotalRawSize = 0
foreach ($i in $DirObj.DirList) {
$RoboObj = Get-RobocopyDirectory -Directory "$($Directory.TrimEnd("\"))\$($i)"
$Obj.DirList.Add($RoboObj)
$TotalRawSize += $RoboObj.RawSize
}
# Get the final size of the whole dir
$WholeDirObj = Get-RobocopyDirectory -Directory "$($Directory)" -IgnoreSubDirs
$TotalRawSize += $WholeDirObj.RawSize
# Set the sizes
$Obj.TotalSize = $TotalRawSize
$Obj.TotalSize_MB = [double]([math]::Round(($TotalRawSize / 1MB), 2))
$Obj.TotalSize_MB_String = [math]::Round(($TotalRawSize / 1MB), 2).ToString("0.00").PadRight(9," ") + "MB"
$Obj.TotalSize_GB = [double]([math]::Round(($TotalRawSize / 1GB), 2))
$Obj.TotalSize_GB_String = [math]::Round(($TotalRawSize / 1GB), 2).ToString("0.00").PadRight(9," ") + "GB"
# Return the Object
Return $Obj
}
#####################################################
# MAIN
#####################################################
# Set the Computer name
$Computer = $env:COMPUTERNAME
# Set a threshold to return folder size if bigger than $ThresholdGb
$ThresholdGb = .5
# Setup the folder to size list
$FolderToSizeList = New-Object System.Collections.Generic.list[PSCustomObject]
# Add the machine based folders
$FolderToSizeList.Add([PSCustomObject]@{ Computer = $Computer ; SID = $null ; LastUseTime = $null ; Folder = "C:\`$RECYCLE.BIN" ; Size_GB = $null })
$FolderToSizeList.Add([PSCustomObject]@{ Computer = $Computer ; SID = $null ; LastUseTime = $null ; Folder = "C:\Windows\ccmcache" ; Size_GB = $null })
$FolderToSizeList.Add([PSCustomObject]@{ Computer = $Computer ; SID = $null ; LastUseTime = $null ; Folder = "C:\Windows\Temp" ; Size_GB = $null })
# Get the user profiles
$UserProfiles = Get-CimInstance Win32_UserProfile
# Loop through the profiles from the CIM query
foreach ($i in $UserProfiles) {
# Add it to the list of folders to size
$FolderToSizeList.Add([PSCustomObject]@{ Computer = $Computer ; SID = $i.SID ; LastUseTime = $i.LastUseTime ; Folder = "$($i.LocalPath)" ; Size_GB = $null })
$FolderToSizeList.Add([PSCustomObject]@{ Computer = $Computer ; SID = $i.SID ; LastUseTime = $i.LastUseTime ; Folder = "$($i.LocalPath)\AppData" ; Size_GB = $null })
$FolderToSizeList.Add([PSCustomObject]@{ Computer = $Computer ; SID = $i.SID ; LastUseTime = $i.LastUseTime ; Folder = "$($i.LocalPath)\Documents" ; Size_GB = $null })
$FolderToSizeList.Add([PSCustomObject]@{ Computer = $Computer ; SID = $i.SID ; LastUseTime = $i.LastUseTime ; Folder = "$($i.LocalPath)\Desktop" ; Size_GB = $null })
$FolderToSizeList.Add([PSCustomObject]@{ Computer = $Computer ; SID = $i.SID ; LastUseTime = $i.LastUseTime ; Folder = "$($i.LocalPath)\Local\Temp" ; Size_GB = $null })
$FolderToSizeList.Add([PSCustomObject]@{ Computer = $Computer ; SID = $i.SID ; LastUseTime = $i.LastUseTime ; Folder = "C:\`$RECYCLE.BIN\$($i.SID)" ; Size_GB = $null })
}
# Size the folders
foreach ($i in $FolderToSizeList) {
if (Test-Path $i.Folder) {
Write-Host "Processing: $($i.Folder)" -ForegroundColor Cyan
$FolderSize = Size-Directory -Directory $i.Folder
$i.Size_GB = $FolderSize.TotalSize_GB
# If the folder is bigger than the threshold, return it
if ($i.Size_GB -gt $ThresholdGb) {
$i
}
}
}
Take the data from Step 1 and Step 2, load it into a database. I've been using "An Object-Oriented Approach to PowerShell and SqlServer" for database access. A simple query that joins the 2 tables can show computers low on disk space that have big folders in common problem areas. Don't keep looking in the same places! Did I mention that Robocopy is incredibly fast? Follow Dustin Higgins on Twitter and Instagram Edited by user Thursday, May 14, 2020 9:23:40 PM(UTC)
| Reason: Not specified
|