Up another PowerShell Tree

A few weeks ago I posted a PowerShell script that created a directory tree, like the TREE command from the CMD shell. Then while testing something new in PrimalForms, I realized I had a literal form object, a tree view, that I could use. So I put together a short script that creates a graphical tree for a given directory, along with the file counts and sizes from my original scripts.  As a bonus, because I now had a graphical interface I could do things such as display empty folders in a different color. The script not only gives you a handy tool but also serves as a demonstration on how to use a TreeView control.

treeform

The script, TreeForm.ps1, will default to the %TEMP% directory, but you can specify a path parameter from the command prompt:

PS C:\scripts\> .\treeform.ps1 -path c:\users\jeff\documents\sapien

Once the script has been launched, all you need to do is type in a new directory path and click the Build Tree button. The script will expand the first level of folders from the root. You can expand and collapse folders by clicking the +/- symbols. Or use the Expand All toggle button. The button text will change depending on the tree’s state.

$Toggle=
{
    if ($blnExpandAll)
      {
      $root.Collapse()
      $btnControl.text="Expand All"
      $blnExpandAll=$False
      }
   else
      {
       $root.ExpandAll()
       $btnControl.text="Collapse All"
       $blnExpandAll=$True
      }

    #select the top node
    $treeview1.TopNode=$root
    $form1.Refresh()
}

There is a IsExpanded boolean property you can use to determine if a given node is expanded or not, but I defined my own variable since I wanted to track the state of the entire tree.

The script creates a basic Windows form, including a TreeView control.

$form1 = New-Object System.Windows.Forms.Form
$statusBar1 = New-Object System.Windows.Forms.StatusBar
$btnUpdate = New-Object System.Windows.Forms.Button
$btnControl = New-Object System.Windows.Forms.Button
$txtPath = New-Object System.Windows.Forms.TextBox
$treeView1 = New-Object System.Windows.Forms.TreeView
$InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState

When the form is shown, the script runs the BuildTree script block which first removes any existing tree nodes.

$BuildTree=
{
    #clear existing nodes if any
    if ($root)
    {
        $treeview1.Nodes.remove($root)
        $form1.Refresh()
    }

    $path=$txtPath.Text

    Function Get-SubFolder {
        Param([string]$path,
              [system.Windows.Forms.TreeNode]$parent )

        $statusbar1.Text="Analyzing $path"
 
        $subdirs = dir $path | where {$_.GetType() -match "directoryInfo"}
        if ($subdirs)
        {
            foreach ($subdir in $subdirs) {
                $subfiles=dir $subdir.fullname | where {$_.GetType() -match "FileInfo"}
                $stats=$subfiles | Measure-Object -sum length
                [int64]$subcount=$stats.count
                [int64]$subsize=$stats.sum

                $leaf=("{0} ({1} files {2} KB)" -f ($subdir.name,$subcount,("{0:N2}" -f ($subsize/1KB))))

                $node = New-Object System.Windows.Forms.TreeNode
                $node.Text=$leaf
                $node.name=$subdir.name
                #Write-Host ("Adding {0} to {1}" -f $node.name,$parent.name)
                $parent.nodes.add($node) | Out-Null

                Get-SubFolder  -path $subdir.fullname -parent $node
            } #end foreach
        } #end if
 
    if ($parent.Nodes.count -eq 0 -and $subsize -eq 0)
     {
         #empty directory so change font color
         $parent.ForeColor=[System.Drawing.Color]::FromArgb(255,192,0,192)
     }
 } #end Get-Subfolder function

    #Turn off the error pipeline
    $ErrorActionPreference="SilentlyContinue"

    if ((Get-Item $path).exists)
    {
        $data=dir $path
        $statusbar1.Text="Analyzing $path"
        $files=$data | where {$_.GetType() -match "FileInfo"}
        [int64]$count=$files.count
        [int64]$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)))

        $root = New-Object System.Windows.Forms.TreeNode
        $root.text = $leaf
        $root.Name = $path

        $treeView1.Nodes.Add($root)|Out-Null
        #enumerate child folders
        Get-SubFolder -path $path -parent $root
        $statusbar1.Text="Ready"

    }
    else
    {
        $statusbar1.text= "Failed to find $path"
    }

    #expand the root node
    $root.Expand()
    $blnExpandAll=$False
    #select the top node
    $treeview1.TopNode=$root
    #set the toggle button
    $btnControl.text="Expand All"
} #end Build Tree

Most of the code to retrieve folder information is the same as in TREE.PS1. What’s new is that I first create the root node in my tree view by creating a System.Windows.Forms.TreeNode object.

#write the tree root
$leaf=("{0} ({1} files {2} KB)" -f $path,$count,("{0:N2}" -f ($size/1KB)))

$root = New-Object System.Windows.Forms.TreeNode
$root.text = $leaf
$root.Name = $path

$treeView1.Nodes.Add($root) | Out-Null

The Text property is what gets displayed in the form.I don’t really need the Name property but I felt I should show it anyway. This node object is then added to the TreeView control’s Nodes collection. Now I have to get build the child nodes by calling my Get-Subfolder function.

#enumerate child folders
Get-SubFolder -path $path -parent $root

This function is also basically unchanged from Tree.ps1 so I won’t go into the details. One difference though is that I pass the parent Node object as a parameter. This is so that when I get my folder information I can create a new node object and add it to the parent node’s Nodes collection.

$leaf=("{0} ({1} files {2} KB)" -f ($subdir.name,$subcount,("{0:N2}" -f ($subsize/1KB))))
 
node = New-Object System.Windows.Forms.TreeNode
node.Text=$leaf
node.name=$subdir.name
# Write-Host ("Adding {0} to {1}" -f $node.name,$parent.name)
$parent.nodes.add($node) | Out-Null
 
Get-SubFolder  -path $subdir.fullname -parent $node

After adding the node I recursively call the Get-Subfolders function again. The last step in the function is to check if there are any subfolders or files within the parent node. If there are no folders or the subfolder size is 0, then I’ll display the parent node text, using the ForeColor property (or I could have used the BackColor property), to a different color.

if ($parent.Nodes.count -eq 0 -and $subsize -eq 0)
  {
      #empty directory so change font color
      $parent.ForeColor=[System.Drawing.Color]::FromArgb(255,192,0,192)
  }

This gives me a nice visual clue for empty directories. You might want to display a node say in red if the size exceeds a certain value.

There is much more that you can do with the TreeView and TreeNode objects but hopefully this will get you started. If you need a copy of PrimalForms, you can download it for free from http://www.primaltools.com/communitytools/.

Download an updated zip file with the Primalform form and PowerShell script here.

If you need help with the script or your own PowerShell project, please drop by the forums at ScriptingAnswers.com.