Patching File System Trees with PowerShell

by Klaus Graefensteiner 28. August 2008 17:59

Introduction

While working on a elaborate PowerShell script I needed to find a solution for the following problem: I wanted to do a string replace operation on a file system path name and have the resulting new string be reflected by the file system. For example the path name "C:\dogs\dog food\my favorites\hot dogs\Who let the dogs out.mp3" should be renamed by replacing "dog" with "cat". The resulting path name would be "C:\cats\cat food\my favorites\hot cats\Who let the cats out.mp3". Sounds easy, but how do I move the folders and files that are referred to in the path to their new locations? And even better, how can I do this recursively? This short blog post demonstrates two possible approaches.

Oak tree in inverse colors

Figure 1: Oak tree in inverse colors

.NET and recursion

This approach takes advantage of .NET file system classes and recursion. The following code snippet that I found on the web shows how it could work. Calling a function recursively to walk down the folder tree and modify folder and file names is in my opinion beautiful and scary. It requires your brain to at least think through the call stack with the eye in an eye. Not everybody has this gift. Besides recursion doesn't scale beyond the size of a thread's call stack.

   1: function Move-Stuff($folder)
   2: {
   3:     foreach($sub in [System.IO.Directory]::GetDirectories($folder))
   4:       {
   5:         Move-Stuff $sub
   6:     }
   7:     $new = $folder.Replace("wackytacky", "rollypolly")
   8:     if(!(Test-Path($new)))
   9:     {
  10:         new-item -path $new -type directory
  11:     }
  12:     foreach($file in [System.IO.Directory]::GetFiles($folder))
  13:     {
  14:         $new = $file.Replace("wackytacky", "rollypolly")
  15:         move-item $file $new
  16:     }
  17: }
  18:  
  19: Move-Stuff "$Home\Desktop\Workbench"
  20:  
  21: #This function leaves the old folder structure

Get-ChildItems and hash tables

Using Get-ChildItems on the other side walks through the folder tree and returns a list of file system objects. I am not sure whether it does it recursively or not, but at least I don't have to get my mind tangled up by thinking about it. This simple PowerShell Cmdlet "Get-ChildItems -recurse" does it for me. The task of renaming the tree is done by first getting a list of file system objects, then creating a hash table for moving files, creating the new folder branches using the New-Item Cmdlet and finally filling a hash table for deleting the old folder branches after the file move operations. After analyzing the file system object list that Get-ChildItems returned, the script takes care of the remaining tasks in batches. First it moves all the files, then it deletes all folders that are not longer needed.

   1: cd $home\desktop
   2: Set-PSDebug -Strict
   3:  
   4: cls
   5:  
   6: $ds = $Null
   7: $ds = Get-ChildItem "Workbench" -Recurse
   8:  
   9: $FolderDeleteList = @{}
  10: $FileMoveList = @{}
  11:  
  12: #Create new folders
  13: $ds | ForEach-Object `
  14: {     
  15:     if($_ -is [System.IO.FileInfo])
  16:     {
  17:         "`"$($_.Name)`" is a File"
  18:         $NewPath = $_.FullName.Replace("wackytacky", "simplepimple")
  19:         $FileMoveList[$_.FullName] = $NewPath
  20:         
  21:     }
  22:     if($_ -is [System.IO.DirectoryInfo])
  23:     {
  24:         "`"$($_.Name)`" is a Directory"
  25:         $NewPath  = $_.FullName.Replace("wackytacky", "simplepimple")
  26:         if (!$(Test-Path $NewPath))
  27:         {
  28:             New-Item -Path $NewPath -type Directory
  29:             $Level = $_.FullName.split("\").Count
  30:             $FolderDeleteList[$_.FullName] = $Level
  31:         }
  32:     }
  33: }
  34:  
  35: "Folders to delete ====================================="
  36: $FolderDeleteList
  37: "Files to move ========================================="
  38: $FileMoveList
  39:  
  40: #Move Files
  41: $FileMoveList.GetEnumerator() `
  42:     | ForEach-Object { Move-Item -Path $_.Key -Destination $_.Value }
  43:     
  44: #Delete Folders
  45: $FolderDeleteList.GetEnumerator() `
  46:     | Sort Value -Descending `
  47:     | Foreach-Object {"Deleting folder: `"$($_.Key)`""; 
  48:       Remove-Item -Path $_.Key -Recurse}

Download

The script files and a sample file system tree branch can be downloaded here: RenameFileSystemBranch.zip

Ausblick

The beauty of the PowerShell architecture is that this script can be used, with tiny adjustments, for any PowerShell Navigation Cmdlet Provider, like the Registry or Certification Provider. This type of command would be a very nice enhancement to the standard set of item cmdlets that ships with PowerShell. Curious for what "elaborate" script I am going to use this procedure? Stay tuned! I just say D I U G.

Tags: , , , ,

.NET Framework | Photoshop | PowerShell

About Klaus Graefensteiner

I like the programming of machines.

Add to Google Reader or Homepage

LinkedIn FacebookTwitter View Klaus Graefensteiner's profile on Technorati
Klaus Graefensteiner

Klaus Graefensteiner
works as developer in Test at Rockwell Automation and is founder of the PowerShell Unit Testing Framework PSUnit. More...

Administration

About

Powered by:
BlogEngine.Net
Version: 1.5.0.7

License:
Creative Commons License

Copyright:
© Copyright 2009, Klaus Graefensteiner.

Disclaimer:
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

Theme design:
This blog theme was designed and is copyrighted 2009 by Klaus Graefensteiner

Rendertime:
Page rendered at 9/9/2010 7:19:30 PM (PST Pacific Standard Time UTC DST -7)