Hey, Scripting Guy! Can I Determine Which Folders Are Using the Most Space on My Computer?

ScriptingGuy1

 

Hey, Scripting Guy! Question

Hey Scripting Guy! I have a laptop that I bought last year with Windows Vista installed on it. I love it. However, I have been reading all the positive reviews surrounding Windows 7, and I think I am going to install Windows 7 as soon as it is available. In fact, I have already pre-ordered it. The thing is that I do not like to do upgrades—I have never trusted them because I upgraded my computer at work from Windows 3.11 to Windows 95 when it came out, and I had nothing but problems until I did the wipe and reload process. In preparation for upgrading my laptop to Windows 7, I am trying to find all of my data. The laptop has a respectable 150 GB hard drive, but it is nearly full. I need help identifying which folders are eating up all of my disk space. I have a 200 GB portable drive I will use to back up my data from the laptop, but I do not want to have a nice “new” laptop that is sucking fumes in the disk space department. Can you help me?

— EC

Hey, Scripting Guy! Answer

Hello EC,

The ink blue sky hung down heavy upon the whiskey bottle strewn alley leading to the 100-year-old brownstone. A shadow slowly slithered up the wall and a sense of foreboding crept up my spine like a 70-year-old man navigating the narrow stairs to the top of the Statue of Liberty. A clinking sound cut through the smog-laden air like a dull butter knife attempting to part a bagel. It seems like only last year when my new Windows Vista laptop arrived via the early evening overnight delivery service. Disk space has been gobbled up like Inky, Blinky, Pinky, and Clyde consuming dots. (Microsoft Scripting Guy Ed Wilson here. If you have friended me on Facebook, you know that after I finished writing the recent article for TechNet Magazine, I also finished reading a Mickey Spillane novel. I was just trying to get into the mood to solve the mystery of the missing disk space.) The cool thing is we have the tools to solve the mystery.

This week we will be reviewing some of the scripts that were submitted during the recently held 2009 Summer Scripting Games. The description of the 2009 Summer Scripting Games details all of the events. Each of the events was answered by a globally recognized expert in the field. There were some cool prizes and winners were recognized from around the world. Additionally, just like at the “real Olympics” because there was a lot going on, an “if you get lost page” was created. Communication with participants was maintained via Twitter, Facebook, and a special forum. (The special forum has been taken down, but Twitter and Facebook are still used to communicate with Hey, Scripting Guy! fans). We will be focusing on solutions that used Windows PowerShell. We have several good introduction to Windows PowerShell Hey, Scripting Guy! articles that you will find helpful.

The Beginner Event 8 was the pole vault in the 2009 Summer Scripting Games, and the task was to write a script that identifies which folders are consuming the most disk space. Tojo2000 submitted a function for the solution to the Beginner Event 8. The Windows PowerShell 2.0 function is seen here.

(Note: The original Tojo2000 function from PoshCode has a problem with the help tags when it is run on the RTM version of Windows PowerShell 2.0. This is because the returnvalue tag changed the name to outputs after the community technology preview 3 version. I have corrected it here.)

Get-DirSizeFunction.ps1

function Get-DirSize {
<#
.Synopsis
  Gets a list of directories and sizes.
.Description
  This function recursively walks the directory tree and returns the size of
  each directory found.
.Parameter path
  The path of the root folder to start scanning.
.Example
  (Get-DirSize $env:userprofile | sort Size)[-2]
  Get the largest folder under the user profile.
.Example
  Get-DirSize -path “c:data” | Sort-Object -Property size -Descending
  Displays folders and sub folders from c:data in descending size order
.Outputs
  [PSObject]
.Notes
 NAME:  Get-DirSize
 AUTHOR: ToJo2000
 LASTEDIT: 8/12/2009
 KEYWORDS: 2009 Summer Scripting Games, Beginner Event 8, Get-ChildItem, Files and Folders
.Link
 Http://www.ScriptingGuys.com
#Requires -Version 2.0
#>
  param([Parameter(Mandatory = $true, ValueFromPipeline = $true)][string]$path)
  BEGIN {}
 
  PROCESS{
    $size = 0
    $folders = @()
 
    foreach ($file in (Get-ChildItem $path -Force -ea SilentlyContinue)) {
      if ($file.PSIsContainer) {
        $subfolders = @(Get-DirSize $file.FullName)
        $size += $subfolders[-1].Size
        $folders += $subfolders
      } else {
        $size += $file.Length
      }
    }
 
    $object = New-Object -TypeName PSObject
    $object | Add-Member -MemberType NoteProperty -Name Folder `
                         -Value (Get-Item $path).FullName
    $object | Add-Member -MemberType NoteProperty -Name Size -Value $size
    $folders += $object
    Write-Output $folders
  }
 
  END {}
} # end function Get-DirSize

The nice thing about functions is that you have a lot of flexibility in how you use them. The problem with a function is you have to load it somehow before you can use it. This is not a problem, but it is an extra step that most VBScripters are not used to having to perform. Perhaps the easiest thing to do with a function is to save it into a separate file, and then include it into your Windows PowerShell console session. To include the function into your Windows PowerShell session, you precede the path to the file with a period. You can then work with the function in the same manner you would with a cmdlet. (In fact, the early name for Windows PowerShell 2.0 advanced functions was “script cmdlets.”) Once the function is loaded into the Windows PowerShell console, you can use the Get-Help cmdlet to get help from the function. You can use the function directly or even pipe the output from the function into another cmdlet such as the Sort-Object cmdlet. One thing that is great about Windows PowerShell 2.0 and functions is that tab expansion even works when using them interactively. This is seen here:

Image of working with Get-DirSize

 

In addition to working with functions directly from within the Windows PowerShell console, you have the option to include the function in your Windows PowerShell profile, in a function library, or a module (modules are a Windows PowerShell 2.0 feature). Of course, you can also put functions into a script, and that is what I decided to do.

The ScriptingGamesBeginnerEvent8.ps1 Windows PowerShell script uses the Tojo2000 Get-DirSize function and adds command-line parameters, sorting, and formatting features. Because Tojo2000 had already incorporated Windows PowerShell 2.0 help tags into his script (which will not work on Windows PowerShell 1.0), I decided to go ahead and add additional help to the script and to my number-formatting function. It is very likely that the bulk of your computers are still running Windows PowerShell 1.0. It is a best practice when using a feature from Windows PowerShell 2.0 to include the requires version 2.0 tag. If you are not sure what those features are, I would recommend that for any script you write using Windows PowerShell 2.0 you either test extensively on Windows PowerShell 1.0 or you include the requires version 2.0 tag in all of your scripts. The syntax for this command is seen here:

#requires –version 2.0

The complete ScriptingGamesBeginnerEvent8.ps1 script is seen here.

ScriptingGamesBeginnerEvent8.ps1

<#
   .Synopsis
    Lists the five largest folders and their size
   .Description
    This script lists the five largest folders and their size
   .Example
    ScriptingGamesBeginnerEvent8.ps1 -path C:fso -first 3
    Returns the three largest folders from within the C:fso parent directory
   .Inputs
    [string]
   .OutPuts
    [string]
   .Notes
    NAME:  ScriptingGamesBeginnerEvent8.ps1
    AUTHOR: Ed Wilson
    LASTEDIT: 5/20/2009
    KEYWORDS: 2009 SUmmer Scripting Games, Beginner Event 8, Get-ChildItem, Files and Folders
   .Link
     Http://www.ScriptingGuys.com
#Requires -Version 2.0
#>
Param(
 [string]$path = “c:data1”,
 [int]$first = 5
)# end param

# *** Begin Functions ***
function Get-DirSize {
<#
.Synopsis
  Gets a list of directories and sizes.
.Description
  This function recursively walks the directory tree and returns the size of
  each directory found.
.Parameter path
  The path of the root folder to start scanning.
.Example
  (Get-DirSize $env:userprofile | sort Size)[-2]
  Get the largest folder under the user profile.
.Example
  Get-DirSize -path “c:data” | Sort-Object -Property size -Descending
  Displays folders and sub folders from c:data in descending size order
.Outputs
  [PSObject]
.Notes
 NAME:  Get-DirSize
 AUTHOR: ToJo2000
 LASTEDIT: 8/12/2009
 KEYWORDS: 2009 Summer Scripting Games, Beginner Event 8, Get-ChildItem, Files and Folders
.Link
 Http://www.ScriptingGuys.com
#Requires -Version 2.0
#>
  param([Parameter(Mandatory = $true, ValueFromPipeline = $true)][string]$path)
  BEGIN {}
 
  PROCESS{
    $size = 0
    $folders = @()
 
    foreach ($file in (Get-ChildItem $path -Force -ea SilentlyContinue)) {
      if ($file.PSIsContainer) {
        $subfolders = @(Get-DirSize $file.FullName)
        $size += $subfolders[-1].Size
        $folders += $subfolders
      } else {
        $size += $file.Length
      }
    }
 
    $object = New-Object -TypeName PSObject
    $object | Add-Member -MemberType NoteProperty -Name Folder `
                         -Value (Get-Item $path).FullName
    $object | Add-Member -MemberType NoteProperty -Name Size -Value $size
    $folders += $object
    Write-Output $folders
  }
 
  END {}
} # end function Get-DirSize

Function Get-FormattedNumber($size)
{
 <#
   .Synopsis
    Formats a number into Gig, Meg, or Kilo
   .Description
    This function will format a number that is passed in bytes into
    Gigabytes, Megabytes, or Kilobytes as necessary. It displays two
    decimal places and the appropriate string qualifier. It should
    be used only to format string output. This function does not maintain
    technical precision, but rounds to the nearest two decimal places.
   .Example
    Get-FormattedNumber -size 1025
    Displays 1.00 KiloBytes
   .Example
    Get-FormattedNumber -size 1026
    Displays 9.79 MegaBytes
   .Example
    Get-FormattedNumber -size 10261024
    Displays 1.00 KiloBytes
   .Inputs
    [int32]
   .OutPuts
    [string]
   .Notes
    NAME:  Get-FormattedNumber
    AUTHOR: Ed Wilson
    LASTEDIT: 8/12/2009
    KEYWORDS: Format number, admin constants, if/elseif/else,
    Dot Net framework format specifier, .NET Framework
   .Link
     Http://www.ScriptingGuys.com
#Requires -Version 2.0
#>
  IF($size -ge 1GB)
   {
      “{0:n2}” -f  ($size / 1GB) + ” GigaBytes”
   }
 ELSEIF($size -ge 1MB)
    {
      “{0:n2}” -f  ($size / 1MB) + ” MegaBytes”
    }
 ELSE
    {
      “{0:n2}” -f  ($size / 1KB) + ” KiloBytes”
    }
} #end function Get-FormattedNumber

 # *** Entry Point to Script ***
 
 if(-not(Test-Path -Path $path))
   {
     Write-Host -ForegroundColor red “Unable to locate $path”
     Help $MyInvocation.InvocationName -full
     exit
   }
 Get-DirSize -path $path |
 Sort-Object -Property size -Descending |
 Select-Object -Property folder, size -First $first |
 Format-Table -Property Folder,
  @{ Label=”Size of Folder” ; Expression = {Get-FormattedNumber($_.size)} }

The entry point to the ScriptingGamesBeginnerEvent8.ps1 script uses the Test-Path cmdlet to determine if the path that is supplied to the script exists. If it does not exist, a message is displayed on the screen that states the path cannot be located. The help for the script is then displayed on the console, and the script exits. This is seen here:

Image of error message displayed when path does not exist

 

 

After the directory is determined to exist, the Get-DirSize function is called where the Get-ChildItem cmdlet is used to retrieve directory size information. After the directory sizes are retrieved, a custom PSObject is created and returned to the calling code. The PSObjects are piped to the Sort-Object cmdlet where the sizes are sorted and to the Select-Object cmdlet where the largest folders are selected. This is seen here:

Get-DirSize -path $path |
 Sort-Object -Property size -Descending |
 Select-Object -Property folder, size -First $first |

Because the size of the folders is returned in bytes, I decided to write a function that would convert the sizes to something a bit more readable. In the Get-FormattedNumber function, the size of the number is used to determine whether the number will be converted to kilobytes, megabytes, or gigabytes. The newly formatted number is returned to the Format-Table cmdlet as a custom property. This is seen here:

Format-Table -Property Folder,
  @{ Label=”Size of Folder” ; Expression = {Get-FormattedNumber($_.size)} }

When the script is run, the following output is displayed:

PS C:Usersedwils> C:dataScriptingGuysHSG_8_17_09ScriptingGamesBeginnerEvent8.ps1

Folder                                                                                Size of Folder                                                                      
——                                                                                ————–                                                                      
C:data                                &nbs