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.
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.