I have a series of .pdf files where the name contains an old file number, and I'd like to convert it to the new file number. However, I only want to change the file number portion, I don't want to rename the whole document. There also isn't consistency in the document names, so I can't just put the entire path in a .csv and do them that way.
For example, based on the following .csv:
oldname,newname
A12345,A98765
A00645,A39502
The following files:
A12345 Termination Letter.pdf
A00645 Correspondence.pdf
Would become:
A98765 Termination Letter.pdf
A39502 Correspondence
I currently use the following script to remove or replace single words in the names of files in a given directory (in this example, I replace - Copy with nothing):
$path = "C:\path"
$filter = '*.pdf'
get-childitem -path $path -filter $filter |
rename-item -newname {$_.name -replace '- Copy',''}
I'm trying to work off this to build something that will work the same way, but pull the replacement words from a .csv so I can do multiple replacements at once. I'm not sure if I'm just not using the right syntax in my searching, but I keep finding ways to rename whole documents, change words in a .csv, etc. I'm essentially just looking to do a find and replace.
A way to do it is using a regex OR pattern where you have all the oldname
and then you use replacement with a match evaluator or replacement with a scriptblock (same thing just that the latter only works in newer versions of PowerShell) where you can get the newname
from a give match (.Value
).
A little example of how the logic goes:
# this is a dictionary where you have `oldname` and `newname`
$map = @{
A12345 = 'A98765'
A00645 = 'A39502'
}
# the OR pattern
$replacePattern = [regex]::new(
'A12345|A00645',
[System.Text.RegularExpressions.RegexOptions] 'Compiled, IgnoreCase')
# the replacement logic
'A12345 Termination Letter.pdf', 'A00645 Correspondence.pdf' |
ForEach-Object { $replacePattern.Replace($_, { $map[$args[0].Value] }) }
# Outputs:
# A98765 Termination Letter.pdf
# A39502 Correspondence.pdf
In the actual code, the $map
is constructed dynamically but the rest of the code is similar, however there should be a check for if the regex pattern matches the file name otherwise you would be trying to rename a file with the same name and that fails in older versions:
# this dynamically creates dictionary with `oldname` and `newname`
$map = @{}
Import-Csv 'the\path\toMyRenamingFiles.csv' |
ForEach-Object { $map[$_.oldname] = $_.newname }
# this creates an OR pattern like 'A12345|A00645|...|...|etc'
$replacePattern = [regex]::new(
$map.Keys.ForEach({ [regex]::Escape($_) }) -join '|',
[System.Text.RegularExpressions.RegexOptions] 'Compiled, IgnoreCase')
$path = 'C:\path'
$filter = '*.pdf'
Get-ChildItem -Path $path -Filter $filter |
Where-Object { $replacePattern.IsMatch($_.Name) } |
Rename-Item -NewName { $replacePattern.Replace($_.Name, { $map[$args[0].Value] }) }