PowerShell Tree

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.