Of course since I’ve posted a VBScript version of the Tree command, I had to do it in PowerShell. On one hand, this would have been pretty straightforward since PowerShell can use COM objects and I could have simply reused the FileSystemObject. But that didn’t feel right. So I came up with a version that uses cmdlets and PowerShell features like the -f formatting operator. So here is TREE.PS1.
#Tree.ps1
Param ([string]$path=$env:temp,
[switch]$pipeline)
Function Get-SubFolder {
Param([int]$TabLevel=0,[string]$path)
$TabLevel+=1
$subdirs = dir $path | where {$_.GetType() -match "directoryInfo"}
if ($subdirs)
{
foreach ($subdir in $subdirs) {
$subfiles=dir $subdir.fullname | where {$_.GetType() -match "FileInfo"}
[int]$subcount=$subfiles.count
[int]$subsize=($subfiles | Measure-Object -Sum Length).sum
$leaf=("{0}{1}{2}{3} ({4} files {5} KB)" -f `
(" "*$tabLevel),"|",("_"*$TabLevel),$subdir.name,$subcount,("{0:N2}" -f ($subsize/1KB)))
if ($pipeline)
{
write $leaf
}
else
{
Write-Host $leaf -foregroundcolor green
}
Get-SubFolder $TabLevel $subdir.fullname
}
}
$TabLevel-=1
}
#Turn off the error pipeline
$ErrorActionPreference="SilentlyContinue"
if ((Get-Item $path).exists)
{
$data=dir $path
$files=$data | where {$_.GetType() -match "FileInfo"}
[int]$count=$files.count
[int]$size=($files | Measure-Object -Sum Length).sum
#write the tree root
$leaf=("{0} ({1} files {2} KB)" -f $path,$count,("{0:N2}" -f ($size/1KB)))
if ($pipeline)
{
write $leaf
}
else
{
Write-Host $leaf -foregroundcolor green
}
$TabLevel=0
#enumerate child folders
Get-SubFolder -tablevel $tablevel -path $path
}
else
{
Write-Warning "Failed to find $path"
}
Like my original version, it takes a path as a run time parameter and defaults to the %TEMP% directory. You’ll also see I turn off the error pipeline so that the script runs without interruption. I realize there are ways I could have developed the script to be more concise, but I decided to sacrifice conciseness for clarity. I wanted the script to be easier to follow. Once you understand how it works, by all means, revise away.
The main part of the script, assuming the path exists, gets a directory listing and saves it to a variable. This makes it easier and faster to filter out files only, get a count of all the files and measure their total size.
$data=dir $path
$files=$data | where {$_.GetType() -match "FileInfo"}
[int]$count=$files.count
[int]$size=($files | Measure-Object -Sum Length).sum
All of this information becomes part of a the “leaf” I want to display.
#write the tree root
$leaf=("{0} ({1} files {2} KB)" -f $path,$count,("{0:N2}" -f ($size/1KB)))
I decided to try something different in this script. I liked the idea of using Write-Host so I could display the tree in a color, like green. But what if you wanted to save the output? To accomplish that I need to write to the pipeline. So I give you a choice. If you use the -pipeline parameter, the information is written using Write-Object. Otherwise, the script uses the default Write-Host.
if ($pipeline)
{
write $leaf
}
else
{
Write-Host $leaf -foregroundcolor green
}
Now to get the subfolders.
$TabLevel=0
#enumerate child folders
Get-SubFolder -tablevel $tablevel -path $path
The Get-SubFolder function gets a collection of directories within the root of the specified path.
Function Get-SubFolder {
Param([int]$TabLevel=0,[string]$path)
$TabLevel+=1
$subdirs = dir $path | where {$_.GetType() -match "directoryInfo"}
if ($subdirs)
Assuming subfolders were found, each subfolder is analyzed to get all files in it, and the total size of all files.
foreach ($subdir in $subdirs) {
$subfiles=dir $subdir.fullname | where {$_.GetType() -match "FileInfo"}
[int]$subcount=$subfiles.count
[int]$subsize=($subfiles | Measure-Object -Sum Length).sum
Again, this information is part of the “leaf” variable.
$leaf=("{0}{1}{2}{3} ({4} files {5} KB)" -f `
(" "*$tabLevel),"|",("_"*$TabLevel),$subdir.name,$subcount,("{0:N2}" -f ($subsize/1KB)))
The -f operator makes it easy to create a string by replacing each placeholder like {0} with a corresponding value. I end up with something like this:
|_foo2 (2 files 812.00 KB)
As before, $leaf is written either to the pipeline or console. Then I call the function again to recurse through additional subfolders. The output is about the same as with my VBScript version. Personally, I think it looks best if you set your PowerShell window to use Lucida Console instead of the raster fonts.
Download the PowerShell script here.
UPDATE 4/6/2009
I’ve updated the script file and download link. The new version now uses [int64] to handle larger folder sizes. I also fixed a problem where folders with a single file were displaying as having no file.
One thing I have noticed when outputing folder sizes in powershell is that an Int32 is usually to small meaning that you can not have folder sizes above 2 GB or the script will fail. So I usually use Int64 instead which will allow me to have up to 8 exabytes of data.
Terrific observation. I should test with larger data sets.