Bulk-renaming files can cause some headache: maybe you'd like to nicely rename all your pictures with a keyword and an incrementing counter, or you'd want to clean up log files and assign them a standard name based on its creation date. It's easy to do that for one file, and with our latest tip, we illustrated how you can as just as easily do this for hundreds of files, too. Heck, did we get a lot of feedback, both additional questions and great suggestions, so let's check out what other options you have to bulk-rename files.
Creating Filenames with an Incrementing Counter
In our original tip, we wanted to rename the content of a folder filled with picture files. Each picture file was supposed to get a new file name like "pictureX", where "X" was an incrementing counter. Here is the logic:
$global:i = 1
dir c:\pictures\ -Filter *.jpg |
Rename-Item -NewName {
"picture_$i.jpg"; $global:i++
}
There are two things to note here: First, we need a global variable $i which holds the incrementing counter. It needs to be global because we want to remember its setting and increment it with each new file. By default, variables are valid only within their "territory" (scriptblock) and below, so if the counter variable was not global, the scriptblock would always start with a new variable.
Second, note how Rename-Item accepts a scriptblock. Instead of assigning a static file name, a scriptblock can dynamically create the filename for you. In our example code, the new filename consists of a fixed prefix "picture_" and the counter variable. Pretty nice! But it gets better.
Taking Advantage of $_
Jacques Barathon pointed out that the scriptblock that you submit to Rename-Item of course has a $_ automatic variable. This is true for most scriptblocks that you submit to cmdlets. $_ always represents the current object you are working with.
That's cool because now you can easily use the existing file name and manipulate it. Here is what Jacques did: he wanted to replace a single word in a filename. Let's check out how he did this:
dir *.docx |
Rename-Item -NewName {$_.Name.Replace(‘chapter’, ‘chapitre’)}
Whew! That was easy! Since $_ is referring to the current file, he accessed the original filename through $_.Name. The result is the filename, and it is a string data type. So he could then use the string method Replace() to replace a word in that filename. The result was the new filename where 'chapter' was replaced by 'chapitre'. This result was then used by Rename-Item to rename the file.
The Sky Is The Limit
Of course, $_ gives you complete access to all file properties, so you can basically access and use all file properties to construct the new filename. Let's say you wanted to replace a filename by a timestamp based on the creation date. Here is some code that does it:
$code = {
$timestamp = Get-Date $_.CreationTime -format 'yyyyMMddHHmmssff'
$extension = $_.Extension
$timestamp.$extension
}
dir c:\pictures | Rename-Item -NewName $code
Note how we placed the code (scriptblock) into an extra variable $code to keep the code clean.
While you are playing with this sample, you'll definitely run into issues. When doing bulk operations, you always have to take all kinds of edge cases into consideration. What if there are two files with the very same creation date? Since no two files can have the same name, Rename-Item would fail at the second file and not continue. Bummer.
Happily, you are not bound to any limits. Your scriptblock can contain as much logic as you like. So here is a more advanced approach that takes care of duplicates. It first creates the new filename, then checks if a file with that name already exists, and if so adds a counter to the filename.
$code = {
$timestamp = Get-Date $_.CreationTime -format 'yyyyMMddHHmmssff'
$extension = $_.Extension
$counter = 0
while ($true) {
$filename = '{0}{1}{2}' -f $timestamp, $( if ($counter) { "-$counter" } else { '' }) , $extension
$filepath = Join-Path (Split-Path $_.FullName) $filename
if (Test-Path $filepath) {
$counter++
} else {
$filename
break
}
}
}
dir c:\pictures | Rename-Item -NewName $code
You can reuse this logic for all events where you cannot be sure that the filename created by your scriptblock is unique.
That's the scoop for today. Hope to see you next week,
Tobias
Microsoft MVP PowerShell Germany
P.S.
If you live in Germany or other parts of Europe and your company would like to set up a truly great PowerShell training, just contact me! I regularly train mid- to large-size companies. Trainings are always a blast with tons of real-world-examples and solutions. Here's how to get in touch with me: tobias.weltner@scriptinternals.de
Posted
Sep 12 2011, 10:49 AM
by
Tobias