Learn about Windows PowerShell
-- EC
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:
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-DirSizeFunction 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:
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:\Users\edwils> C:\data\ScriptingGuys\HSG_8_17_09\ScriptingGamesBeginnerEvent8.ps1Folder Size of Folder ------ -------------- C:\data 64.55 GigaBytes C:\data\VM 28.60 GigaBytes C:\data\vmzip 9.43 GigaBytes C:\data\DELL_Data 5.84 GigaBytes C:\data\DELL_Data\data 5.83 GigaBytes
EC, I hope the ScriptingGamesBeginnerEvent8.ps1 script will help you to plan your migration to Windows 7. Make sure you make two copies when you back up your data (just to be on the safe side). You might also consider synchronizing your most vital data with Live Mesh. It is free and pretty cool. I use it to synchronize my scripts between machines. Tojo2000, thanks for participating in the 2009 Summer Scripting Games and writing an awesome function.
If you want to be the first to know what is happening on the Script Center, follow us on Twitter or on Facebook. If you need assistance with a script, you can post questions on the Official Scripting Guys Forum, or send an e-mail to scripter@microsoft.com. The 2009 Summer Scripting Games wrap-up will continue tomorrow. Until then, keep on scripting.
Ed Wilson and Craig Liebendorfer, Scripting Guys