Bulk File/Folder Renaming Advice

Hello,

I’ve got another thread here on matching a value in one array with a value in another array.

The matched value is then stored in a new array, which is then used to serve as the original file/folder name and lined up with another array containing the new names for bulk renaming.

My first test was done against just 5 sample folders, and both arrays were identical in order.

# Match OldName to FullName (matt-bloomfield)
$arrNew = $arrFullNames | Where-Object {
  $arrOldNames -contains ($_ | Split-Path -Leaf)
}

However, I’m hesitant on running this same solution against a list of over 1,700 folders. Reason being is that Windows and Excel sort data differently.

$arrOldNames & $arrNewNames were matched in Excel and copy/pasted into a text file
$arrFullNames was created using PowerShell:

# Get ALL Folders (Array List)
$srcPath = '\\mynas\ar_books'
$arrOldNames = New-Object -TypeName System.Collections.ArrayList
$arrOldNames.AddRange(@(Get-ChildItem -Path $srcPath -Directory -Recurse -Force -ErrorAction SilentlyContinue | % { $_.FullName }))

My question:

Am I going about this right way, or is there a better (POSH) way to do the bulk renaming by matching values and then renaming the file/folder without having to create a new array?

For example, I’ll still need to use both Excel and PowerShell to create the values for the arrays due to the contents: Unique book quiz numbers and later some personal media files; movies and shows. So accuracy is critical.

What I’m thinking is that I need to adjust my original method a bit. Instead of creating a new array, perhaps I can just match values in both OldName and NewName arrays and then use those two values to rename the file or folder immediately.

The reason for this is just in case the matching winds up being two different indexes:

 $arrOldName[23] may be matched to $arrNewName[35]

Any advice to steer me in the right direction would be greatly appreciated.

To guarantee the correct names, I would suggest you have the old names and new names in a CSV file.

OldName,NewName
\\mynas\ar_books\old_name_1,\\mynas\ar_books\new_name_1
\\mynas\ar_books\old_name_2,\\mynas\ar_books\new_name_2

Then you can just do:

$csv = Import-CSV .\fileNames.csv
foreach ($row in $csv) {
    Rename-Item -Path $row.OldName -NewName $row.NewName
}
1 Like

Maybe I just need another cup of coffee, but I’m not really following your process…

After looking at your code in that other thread, it seems like this is what you’re doing:

  1. (arrFullNames) You have a list of full path names for some set of folders. I assume that this list contains more folder names than you have in your second list.
  2. (arrOldNames) Then you have a list of folder names without the path. Again, I’m assuming this list is usually smaller than arrFullNames
  3. You need the full path name for each of the folders in arrOldNames, so you are matching a folder name in arrOldNames to the corresponding full path name in arrFullNames.
  4. Once you have the new list of full path names (arrNew), you are renaming each of those folders based on yet another list of full path names. (arrNewNames)

Am I missing something?

If all of that is correct, then I’ve got a few comments/concerns:

  1. The renaming is just renaming whatever is in index 0 on arrNew with whatever is in index 0 in arrNewNames. There is no verification that you are renaming the folder you really want to rename.
  2. How are you generating arrFullNames and arrOldNames? Hopefully there is a simpler way of pulling that data so that you don’t have to create 2 lists and then compare them.
  3. If you intend to match the old folder name to a new one in arrNewNames, we might be able to do all of it in one loop. We’d have to understand the process and purpose a little better, though.
1 Like

Hello Matt,

Thank you for the recommendation. I’m not sure I can use that method at this time unless I first match the NewNames to the FullNames. My NewNames are just the names of the folder (in Excel), while the FullNames include the full path to the current folder name in the file system pulled using PowerShell.

In my previous exercise, I was using two small lists of only 5 books that I manually lined up.

I also realize that my previous attempts were more complicated than necessary. I need to figure out how to use POSH to perform the matching and then rename the folder. I also need to accept that I will not get all to match on the first run. Some I’ll need to address manually.

I’ve changed my naming methods over the last year a bit:

Electricity (Murray)
Electricity (Murray) (110459)
Electricity (Murray) (110459) - LG 4.1

If the first two naming conventions exist, then they need to be renamed using the 3rd naming convention.

I revisited Excel this morning and found one method to match using wild cards. It’s not 100%, but I’ll take what I can get and then test your method against it.

However, before I can attempt any actually renaming, I wanted to first output the OldNames and NewNames side by side in PowerShell.

I used $row.OldNames in your loop, but I couldn’t get the NewNames to output on screen like:

$row.OldNames & $row.NewNames

Thanks again Matt.

Hello masterofthehat,

To answer the first part of your question (1-4)… I’d say you outlined my process better than I did.

For the 2nd part:

This concerned me too when I recalled that Excel and Windows sorts data differently when it comes to numbers. There’s a way to force it, but I don’t think I can trust it.

FullNames are pulled using POSH from my NAS, and NewNames are generated in my Excel tracker. My Excel is used to manage all the book quizzes.

This is what I’m hoping to achieve. I was thinking about this last night. As I mentioned to @matt-bloomfield earlier today, 'I also realize that my previous attempts were more complicated than necessary’. However, I just realized that I didn’t fully understand the purpose of Split-Path -Leaf until just now.

I’ll try and list them in a logical order:

  1. I’ll generate the NewNames from Excel to a text file; NewNames.txt

  2. POSH will get all folders recursively into an array from the NAS to speed up the matching process: $arrFullNames
    – I’m currently using my example in top post, but I’ve been having trouble with excluding certain folders and its contents: Reference Books (Collection) and IT (Collection).

  3. POSH will match the current folder name in $arrFullNames to $NewNames
    – using split-path -leaf … ?

  4. If a match is found, it will rename the folder

  5. If a NewName is not matched it will go to a log file: NotMatched.txt

Out-File "$srcPath\NotMatched.txt"

The log file will help me address them on a more manual basis. I already know which ones will wind up in there. Books part of a numbered ordered series; I’m not sure I want to go down the RegEx road right now… :slight_smile:

FullName Example: "\\mynas\ar_books\Electricity (Murray)"
NewName Example: Electricity (Murray) (110459) - LG 4.1

Thanks…

UPDATE 1:

So far, I’ve made a little progress with STEP 3 from my earlier post:

$arrFullNames = @(Get-Content "$srcPath\FullNames.txt")
$arrNewNames = @(Get-Content "$srcPath\NewNames.txt")

# Create OldNames Array from FullNames Array
$arrOldNames = $arrFullNames | ForEach-Object {
   Split-Path $_ -Leaf
}
$arrOldNames.Count

As expected, my count was identical to the input array: 1,766
I outputted the results to a file to take a quick look:

$arrOldNames | Out-file $srcPath\OldNames.txt

Some trouble figuring out how to work with the arrays, so this basic example helped out:

$NewName = "Apples (Murray) (113175) - LG 3.5"
$NewName -like "*Apples (Murray) (113175)*" # NewName -like OldName

… which resulted in this…

# Match OldNames with NewNames
$MatchedNames = $arrOldNames | Where-Object {
  $arrNewNames -like "*$_*"
}
$MatchedNames.Count

This count needed to be compared with the NewName. Not a match, but I’m looking through both lists now.
I should note that this $MatchedNames array was only created at this phase in order to compare the list manually.

963 = $NewNames.Count
834 = $MatchedNames.Count

I was expecting them to be the same…

Now I just need to figure out how to output the matches side by side; something similar to:

Write-Host "$OldName matches $NewName"

This will help with validation before the final script is run to perform the actual renaming…

I’m lost with all these posts. Let’s go right back to basics.

Please provide some examples of what the names currently are and what you want them to be.

What do full_names.txt, old_names_5.txt, and new_names_5.txt actually look like?

1 Like

Hello Matt,

Sorry about the confusion. I was trying my best to work through it on my own and wanted to share my progress.

Those ‘_5’ files were just used for a sample run against 5 folders that I created manually - in perfect order.

I’m basically working with only two files for now, but that’s only based on my lack of knowledge on the best way to achieve my results.

I learned last night that POSH uses singular references, so I’ve switched from Names to Name. I doubt I’ll adhere to it entirely, but I’ll see how it goes… :wink:

FullName.txt contains all of the folder paths from \\mynas\ar_books:

\\mynas\ar_books\First Science (ABDO)\Electricity (Murray)

NewName.txt contains the new folder names. This list is generated in my Excel Tracker and then pasted into that file.

Electricity (Murray) (110459) - LG 4.1

I understand that both of these lists will not be in the exact same order.

Your suggested Split-Path $_ -Leaf will help isolate the OldName…

Electricity (Murray)

… from the FullName…

\\mynas\ar_books\First Science (ABDO)\Electricity (Murray)

The OldName Electricity (Murray) is compared with NewName Electricity (Murray) (110459) - LG 4.1 to find a match.

Let me know if you need any additional information.

Thanks, Matt.

I have made some assumptions, but this should work:

# contents of fullName.txt
\\mynas\ar_books\First Science (ABDO)\Electricity (Murray)
\\mynas\ar_books\First Science (ABDO)\Water (Spring)
\\mynas\ar_books\First Science (ABDO)\Vegetables (Potato)
\\mynas\ar_books\Computer Science\PowerShell (Jones)
\\mynas\ar_books\Pseudoscience\Astrology (Leo)
\\mynas\ar_books\Aperture Science\Cake (Lie)

# contents of newName.txt
PowerShell (Jones) (123456) - LG 9.1
Water (Spring) (120359) - LG 2.3
Astrology (Leo) (987654) - LG 2
Vegetables (Potato) (190529) - LG 0.1
Cake (Lie) (123123) - LG 13.5
Electricity (Murray) (110459) - LG 1.1
$newNameList = Get-Content E:\Temp\newName.txt
$fullNameList = Get-Content E:\Temp\fullName.txt

foreach ($fullName in $fullNameList) {
    
    $oldFolderName = $fullName | Split-Path -Leaf
    $newfolderName = $newNameList | Where-Object {$_ -like "$oldFolderName*"}
    $newFullName = ($fullName | Split-Path -Parent) | Join-Path -ChildPath $newfolderName 

    [PSCustomObject] @{
        oldName = $fullName
        newName = $newFullName        
    } | Export-Csv E:\Temp\folderList.csv -Append -NoTypeInformation
}
1 Like

What @matt-bloomfield wrote up may do all that you need to do, but I tend to complicate things… Here’s what I came up with:

$arrNewNames = Import-Csv -path C:\temp\NewNames.csv

$arrGoodMatch = @()
$arrMultiMatch = @()
$arrNoMatch = @()
$arrFullNames | ForEach-Object {
    $oldFullPath = $_.FullName

    $fileItem = Get-Item $oldFullPath
    $newFileName = $(($arrNewNames | Where-Object NewName -like "*$($fileItem.BaseName)*").NewName)

    if ($newFileName) {
        # At least one matching filename was found
        if ($newFileName -is [String]) {
            # Only one matching filename was found
            $newFullPath = "$($fileItem.Directory)\$($newFileName)$($fileItem.Extension)"
            $arrGoodMatch += [PSCustomObject]@{
                NewName = $newFullPath
                OldName = $oldFullPath
            }
            #Rename-Item -Path $oldFullPath -NewName $newFullPath
        }
        elseif ($newFileName -is [Array]) {
            # More than one filename was found
            $newFileName | ForEach-Object {
                $arrMultiMatch += [PSCustomObject]@{
                    NewName = $_
                    OldName = $oldFullPath
                }
            }
        }
    }
    else {
        # No matching filename was found
        $arrNoMatch += $oldFullPath
    }
}
Write-Host "Good Matches"
$arrGoodMatch
Write-Host "--------------------"
Write-Host "Multiple Matches"
$arrMultiMatch
Write-Host "--------------------"
Write-Host "No Matches"
$arrNoMatch

It also makes several assumptions, but it handles getting multiple matches and getting no matches. I’m sure it needs some more work, but I’ve got to get to a meeting!

Keep working on your project. I don’t think they are ever “final”…

1 Like

Hello Matt,

Thanks for taking the time to share this with me.

I tested it out and really liked how they were scrambled on the INPUT but lined up perfectly on the OUTPUT. :+1:

I did have to make just a minor change to prevent the NewName from using the full path; POSH only wants the name of the folder with a RENAME.

I’m overdue for a haircut and don’t want to wind up on a Bigfoot sighting list… so I’ll share what I did when I return home.

Thanks again, and best regards.

Just got your post - Thank You - and will test it out when I get back. Gotta get a haircut and pick up my kids…

Ain’t that the truth!

I’ll update you shortly…

I finally had an opportunity to test your code…

The only adjustements I had to make it run:

Note: My NewName is just the folder name, like: Electricity (Murray)

Changed from to:

<# from #> $arrNewNames = Import-Csv -path C:\temp\NewNames.csv
<# to #>   $arrNewNames = Get-Content $srcPath\NewName.txt

and removed the $($fileItem.Extension) as only Folders are being renamed.

<# from #> $newFullPath = "$($fileItem.Directory)\$($newFileName)$($fileItem.Extension)"
<# to #>   $newFullPath = "$($fileItem.Directory)\$($newFileName)"

Populated $arrFullNames

$arrFullNames = Get-Content $srcPath\FullName.txt

Commented .FullName out as FullName array contains the FullName already.

$arrFullNames | ForEach-Object {
    $oldFullPath = $_#.FullName

I got the following output running it against some test folders:

Good Matches
--------------------
Multiple Matches
--------------------
No Matches
D:\_TEMP\Test_Rename\First Science (ABDO)\Electricity (Murray)
D:\_TEMP\Test_Rename\First Science (ABDO)\Water (Spring)
D:\_TEMP\Test_Rename\First Science (ABDO)\Vegetables (Potato)
D:\_TEMP\Test_Rename\Computer Science\PowerShell (Jones)
D:\_TEMP\Test_Rename\Pseudoscience\Astrology (Leo)
D:\_TEMP\Test_Rename\Aperture Science\Cake (Lie)

I tried playing around with a few additional changes, but I just couldn’t figure out what values are being passed through the loop; specifically the $newFileName. That one I can’t figure out.

If helps, this is what my INPUT data would look like:

FullName.txt contains all of the folder paths to the CURRENT folder names.
\\mynas\ar_books\First Science (ABDO)\Electricity (Murray)

NewName.txt contains only the new folder names; no paths.
Electricity (Murray) (110459) - LG 4.1

Thanks again…

UPDATE:

Hi Matt,

Here’s what I did earlier before I had to run…

I was able to view the output to confirm proper matching against the sample folders.

I made a few minor changes and ran the Rename command against the sample folders and all were renamed correctly.

$path = 'D:\_TEMP\Test_Rename'
$newNameList = Get-Content $path\NewNames.txt
$fullNameList = Get-Content $path\FullNames.txt

foreach ($fullName in $fullNameList) {
    
    $oldFolderName = $fullName | Split-Path -Leaf
    $newfolderName = $newNameList | Where-Object {$_ -like "$oldFolderName*"}
    #$newFullName = ($fullName | Split-Path -Parent) #| Join-Path -ChildPath $newfolderName 

    [PSCustomObject] @{
        oldName = $fullName
        #newName = $newFullName
        newName = $newfolderName
    } #| Export-Csv $path\folderList.csv -Append -NoTypeInformation
    
    Rename-Item -Path $fullName -NewName $newfolderName #-WhatIf
}

I ran the Export-Csv against my actual dataset and looked at the results.
Only one thing caught my attention and that was three folders: Disney, Science and Soccer were all being matched to System.Object[]

These folders just happened to be either a Collection or Series and only contains subfolders.

At first I thought it was because they are single word folders, but I have a few more that didn’t get matched… which is good.

I’ll rename them tomorrow and run my test again and see what the results. So far, so good.

Thanks again, Matt.

On my code, I think you’re biggest problem is that I used CSV files, which allowed me to provide a column header to the data, and you’re just using Get-Content, which doesn’t assign any kind of header. The column headers in the CSV turn into attributes of the objects in the new array.

$oldFullPath = $_.FullName

The “.FullName” references an attribute of the object in arrFullNames. In my testing, I actually used Get-ChildItem to pull my list of folders, and only pulled one attribute:

$arrFullNames = Get-ChildItem -Recurse -Folder -Path C:\temp\books | Select-Object FullName

$newFileName = $(($arrNewNames | Where-Object NewName -like "*$($fileItem.BaseName)*").NewName)

The “NewName” in the Where-Object line and the “.NewName” at the end reference an attribute of the objects in arrNewNames. I set that up by creating a one column CSV with the “NewName” header:

NewName
Electricity (Murray) (110459) - LG 1.1
Water (Spring) (120359) - LG 2.3

I do it that way because it is simpler to reference the lines of the file (for me)…

Also, on your problem with Matt’s code, the output of “System.Object” means that you tried to write an array to that single cell. That means it’s matching to more than 1 folder name.

1 Like

Hello Charles,

Sorry for the late reply. Kind of set this process on the back burner for the time being.

I didin’t realize you were using headers in your code. I was thinking the .newname was stripping out the new foldername from a full path.

Thanks for sharing this with me. Once I get more time, I’ll start looking into how to output multiple matches to a log file.

I’ll also revisit your solution as well using your original code to generate the csv file against my target directory and then importing the CSV file to identify any matches.

Thanks again and best regards…

1 Like