Ad Space here

Welcome Guest! To enable all features please Login or Register.

Notification

Icon
Error

Share
Options
Go to last post Go to first unread
Dustin Higgins  
#1 Posted : Thursday, May 14, 2020 5:39:39 PM(UTC)
Dustin Higgins

Rank: Advanced Member

Groups: Registered
Joined: 7/1/2018(UTC)
Posts: 64
_United States

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.

  • Computer Name

  • Folder

  • Size

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

Sponsor
Ad Space here
Rss Feed  Atom Feed
Users browsing this topic
Forum Jump  
You cannot post new topics in this forum.
You cannot reply to topics in this forum.
You cannot delete your posts in this forum.
You cannot edit your posts in this forum.
You cannot create polls in this forum.
You cannot vote in polls in this forum.