Training
Get a free hour of SANS training

Experience SANS training through course previews.

Learn More
Learning Paths
Can't find what you are looking for?

Let us help.

Contact us
Resources
Join the SANS Community

Become a member for instant access to our free resources.

Sign Up
For Organizations
Interested in developing a training plan to fit your organization’s needs?

We're here to help.

Contact Us
Talk with an expert

Month of PowerShell - Renaming Groups of Files

Have you ever had to rename hundreds of files ... manually? 😱 Let's look at how to automate a massive file-rename task using PowerShell.

Authored byJoshua Wright
Joshua Wright

#monthofpowershell

Using a loop to rename a group of files is one of the things that I'm often thankful for. Sure, if I have 20, 40, 100 files, I could rename them all manually, but that's some tedious work.

~/Desktop/Images> Get-ChildItem | Select-Object -Property Name

Name
----
DSC_4745.jpg
DSC_4791.jpg
DSC_4830.jpg
DSC_4947.jpg
DSC_4973.jpg
DSC_5035.jpg
DSC_5043.jpg
DSC_5187.jpg
DSC_5217.jpg
DSC_5296.jpg
DSC_5312.jpg

I have 11 files here (I didn't count them; I used code.Count[/code] with the grouping operator). These images are from a photo shoot I did with my nephew for his senior photos, all cropped to be printed at 5x7 aspect ratio. Since I'm also printing a different set cropped for 8x10 aspect ratio, I want to make sure I keep the files sorted correctly. To help me remember which files are the different crop aspect ratios, I want to add [code]Crop-5x7-[/code] to the beginning of each of these files.

At first, this seemed like a simple task:

~/Desktop/Images> Get-ChildItem | foreach { Rename-Item -Path $_.Name -NewName "Crop-5x7-$_.Name" }
Rename-Item: Cannot rename the specified target, because it represents a path or device name.
Rename-Item: Cannot rename the specified target, because it represents a path or device name.
Rename-Item: Cannot rename the specified target, because it represents a path or device name.
Rename-Item: Cannot rename the specified target, because it represents a path or device name.
Rename-Item: Cannot rename the specified target, because it represents a path or device name.
Rename-Item: Cannot rename the specified target, because it represents a path or device name.
Rename-Item: Cannot rename the specified target, because it represents a path or device name.
Rename-Item: Cannot rename the specified target, because it represents a path or device name.
Rename-Item: Cannot rename the specified target, because it represents a path or device name.
Rename-Item: Cannot rename the specified target, because it represents a path or device name.
Rename-Item: Cannot rename the specified target, because it represents a path or device name.

To troubleshoot, I did another loop, this time displaying the [code]$_.Name[/code] property for each file with [code]Write-Host[/code]:

~/Desktop/Images> Get-ChildItem | foreach { Write-Host "Crop-5x7-$_.Name" }
Crop-5x7-/Users/jwright/Desktop/Images/DSC_4745.jpg.Name
Crop-5x7-/Users/jwright/Desktop/Images/DSC_4791.jpg.Name
Crop-5x7-/Users/jwright/Desktop/Images/DSC_4830.jpg.Name
Crop-5x7-/Users/jwright/Desktop/Images/DSC_4947.jpg.Name
Crop-5x7-/Users/jwright/Desktop/Images/DSC_4973.jpg.Name
Crop-5x7-/Users/jwright/Desktop/Images/DSC_5035.jpg.Name
Crop-5x7-/Users/jwright/Desktop/Images/DSC_5043.jpg.Name
Crop-5x7-/Users/jwright/Desktop/Images/DSC_5187.jpg.Name
Crop-5x7-/Users/jwright/Desktop/Images/DSC_5217.jpg.Name
Crop-5x7-/Users/jwright/Desktop/Images/DSC_5296.jpg.Name
Crop-5x7-/Users/jwright/Desktop/Images/DSC_5312.jpg.Name

In the [code]-NewName[/code] argument I specified [code]"Crop-5x7-$.Name"[/code], thinking that PowerShell would expand the variable [code]\(_.Name[/code] as part of the new string. However, PowerShell expanded [code]\)[/code] first (which resolved to [code]/Users/jwright/Desktop/Images/DSC_4745.jpg[/code]), then added the prefix [code]Crop-5x7-[/code] and treated [code].Name[/code] as a suffix.

Subexpressions to the Rescue

What I want is for PowerShell to expand the [code]$_.Name[/code] property first, then add the prefix [code]Crop-5x7-[/code] to the beginning of the file name. To force PowerShell to expand the string in this manner, I need to use a subexpression.

In PowerShell, subexpressions are specified using a leading dollar sign, then parenthesis: [code]$()[/code]. A subexpression tells PowerShell to execute this first and then treat the result like a variable:

~/Desktop/Images> Get-ChildItem | foreach { Write-Host "Crop-5x7-$($_.Name)" }
Crop-5x7-DSC_4745.jpg
Crop-5x7-DSC_4791.jpg
Crop-5x7-DSC_4830.jpg
Crop-5x7-DSC_4947.jpg
Crop-5x7-DSC_4973.jpg
Crop-5x7-DSC_5035.jpg
Crop-5x7-DSC_5043.jpg
Crop-5x7-DSC_5187.jpg
Crop-5x7-DSC_5217.jpg
Crop-5x7-DSC_5296.jpg
Crop-5x7-DSC_5312.jpg

Perfect! The subexpression is evaluated first, and then the result is incorporated as a string into the file name with the specified prefix. Incorporating that into the [code]Rename-Item[/code] loop gives me this:

~/Desktop/Images> Get-ChildItem | foreach { Rename-Item -Path $_.Name -NewName "Crop-5x7-$($_.Name)" }
~/Desktop/Images> Get-ChildItem | Select-Object -Property Name

Name
----
Crop-5x7-DSC_4745.jpg
Crop-5x7-DSC_4791.jpg
Crop-5x7-DSC_4830.jpg
Crop-5x7-DSC_4947.jpg
Crop-5x7-DSC_4973.jpg
Crop-5x7-DSC_5035.jpg
Crop-5x7-DSC_5043.jpg
Crop-5x7-DSC_5187.jpg
Crop-5x7-DSC_5217.jpg
Crop-5x7-DSC_5296.jpg
Crop-5x7-DSC_5312.jpg

There and Back Again

After printing the images, I wanted to rename them back to the original [code]DSC_NNNN.jpg[/code] file names again. I could have re-exported them from Lightroom, but I took the opportunity to experiment more with using PowerShell to rename groups of files.

Strings in PowerShell have a method called [code]Replace()[/code]. We can look at the methods using an empty string and [code]Get-Member[/code]:

~/Desktop/Images> '' | Get-Member -MemberType Method | Select-String 'Replace\('

string Replace(string oldValue, string newValue, bool ignoreCase, cultureinfo culture), string Replace(string oldValue, string
newValue, System.StringComparison comparisonType), string Replace(char oldChar, char newChar), string Replace(string oldValue, string
newValue)

Using [code]Replace()[/code], I can remove the [code]Crop-5x7-[/code] part of the file names to rename back to the original names. First, let's test it out to make sure we get what we need:

~/Desktop/Images> Get-ChildItem | foreach { Write-Host "$_.Name.Replace('Crop-5x7-','')" }
/Users/jwright/Desktop/Images/Crop-5x7-DSC_4745.jpg.Name.Replace('Crop-5x7-','')
/Users/jwright/Desktop/Images/Crop-5x7-DSC_4791.jpg.Name.Replace('Crop-5x7-','')
/Users/jwright/Desktop/Images/Crop-5x7-DSC_4830.jpg.Name.Replace('Crop-5x7-','')
/Users/jwright/Desktop/Images/Crop-5x7-DSC_4947.jpg.Name.Replace('Crop-5x7-','')
/Users/jwright/Desktop/Images/Crop-5x7-DSC_4973.jpg.Name.Replace('Crop-5x7-','')
/Users/jwright/Desktop/Images/Crop-5x7-DSC_5035.jpg.Name.Replace('Crop-5x7-','')
/Users/jwright/Desktop/Images/Crop-5x7-DSC_5043.jpg.Name.Replace('Crop-5x7-','')
/Users/jwright/Desktop/Images/Crop-5x7-DSC_5187.jpg.Name.Replace('Crop-5x7-','')
/Users/jwright/Desktop/Images/Crop-5x7-DSC_5217.jpg.Name.Replace('Crop-5x7-','')
/Users/jwright/Desktop/Images/Crop-5x7-DSC_5296.jpg.Name.Replace('Crop-5x7-','')
/Users/jwright/Desktop/Images/Crop-5x7-DSC_5312.jpg.Name.Replace('Crop-5x7-','')

We need to use the subexpression operator again here, to get PowerShell to evaluate the [code]$_.Name.Replace()[/code] statement, returning the modified file name as a string:

~/Desktop/Images> Get-ChildItem | foreach { Write-Host "$($_.Name.Replace('Crop-5x7-',''))" }
DSC_4745.jpg
DSC_4791.jpg
DSC_4830.jpg
DSC_4947.jpg
DSC_4973.jpg
DSC_5035.jpg
DSC_5043.jpg
DSC_5187.jpg
DSC_5217.jpg
DSC_5296.jpg
DSC_5312.jpg

Perfect! By adding [code]$([/code] to the beginning of the file name, and a trailing [code])[/code] to the end, we can apply the subexpression operator, producing the desired file name. Next, we can use [code]Rename-Item[/code] in the [code]foreach[/code] loop to rename the files:

~/Desktop/Images> Get-ChildItem | foreach { Rename-Item -Path $_.Name -NewName $($_.Name.Replace('Crop-5x7-',''))  }
~/Desktop/Images> Get-ChildItem | Select-Object -Property Name

Name
----
DSC_4745.jpg
DSC_4791.jpg
DSC_4830.jpg
DSC_4947.jpg
DSC_4973.jpg
DSC_5035.jpg
DSC_5043.jpg
DSC_5187.jpg
DSC_5217.jpg
DSC_5296.jpg
DSC_5312.jpg

Conclusion

In this article we looked at the practical skill of using PowerShell to rename a collection of files. First we looked at renaming several files to include a prefix string, and how we need to use the PowerShell subexpression operator [code]$()[/code] to expand the desired properties when referencing the file names with [code]Rename-Item[/code]. Next we looked at returning the files to their original names, using the subexpression operator again but also with the string method [code]Replace()[/code] to strip the leading file name prefixes.

With PowerShell, and some understanding of how expressions are evaluated, it's not too difficult to automate the process of renaming files. Hopefully you'll remember this article and swoop in and help someone less fortunate, next time you see them manually renaming hundreds of files.

-Joshua Wright

Return to Getting Started With PowerShell


Joshua Wright is the author of SANS SEC504: Hacker Tools, Techniques, and Incident Handling, a faculty fellow for the SANS Institute, and a senior technical director at Counter Hack.

Month of PowerShell - Renaming Groups of Files | SANS Institute