I have the following PowerShell script that puts all .jpg files found in its subfolders into a 'Scans' folder and renames these .jpg files to match the name of their respective parent directory (=subfolder). When multiple .jpg files exist the renaming process will automatically add a number to the filename, like File_1,jpg and File_2.jpg.
So before the script is run a folder may look like this:
Parent Directory from which script is executed
|
Subfolder (containing .jpg files and the 'Scans' subfolder)
|
Scans (=folder)
Photo.jpg
Picture.jpg
Shot(1).jpg
Shot(2).jpg
Once the script has run the folder looks like this:
Parent Directory from which script is executed
|
Subfolder (containing the 'Scans' subfolder)
|
Scans (=folder containing supposed to contain all .jpg files)
|
Subfolder_1.jpg
Subfolder_2.jpg
Shot(1) (=file moved but extension stripped)
Shot(2) (=file not moved and extension stripped)
So the .jpg files containing parenthesis 'Shot(1).jpg' and 'Shot(2).jpg' are not properly renamed and moved to the 'Scans' folder. In fact their '.jpg' extension is stripped off.
The script works fine as long as the .jpg files do not contain any parentheses (()), as in
'Shot(1).jpg' and 'Shot(2).jpg' → when multiple .jpg files exist
'Pics(2001).jpg' → when the subfolder name contains parentheses as in 'Pics(2001)', the .jpg is correctly renamed, but then contains parentheses and the script would fail again when run a second time.
I have read about escaping special characters in other threads but have not been able to implement a solution into the script below. Does anybody here have a solution so that the parentheses are not causing any issues when moving and renaming these .jpg files?
$path = Split-Path -parent $MyInvocation.MyCommand.Definition
function renamePhotos {
# Loop through all directories
$dirs = dir $path -Recurse | Where { $_.psIsContainer -eq $true }
foreach ($dir In $dirs) {
$i = 1
$newdir = $dir.parent.name + "_"
$images = Get-ChildItem -Path $dir.fullname -Filter *.jpg -Recurse
foreach ($image In $images) {
$split = $image.name.split(".jpg")
$replace = $split[0] -Replace $split[0],($newdir + $i + ".jpg")
$image_string = $image.fullname.ToString().Trim()
Rename-Item "$image_string" "$replace"
$i++
Move-Item -Path $dir\*.jpg -Destination $dir\Scans
}
}
}
# RUN SCRIPT
renamePhotos
"SCRIPT FINISHED"
The -replace operator does a regular expression replacement. If you want to use it you need to escape special characters in your source string:
-replace [regex]::Escape($split[0]), "$newdir$i.jpg"
However, you don't need that in the first place. Simply use the appropriate properties of the FileInfo objects:
$images = Get-ChildItem -Path $dir.fullname -Filter *.jpg -Recurse
foreach ($image in $images) {
$newname = $newdir + $i + $image.Extension
Rename-Item $image.FullName $newname
$i++
}
Move-Item -Path $dir\*.jpg -Destination $dir\Scans
Or simply move the items to the new location with their new name without renaming them first:
$images = Get-ChildItem -Path $dir.fullname -Filter *.jpg -Recurse
foreach ($image in $images) {
$newname = $newdir + $i + $image.Extension
Move-Item $image.FullName "$dir\Scans\$newname"
$i++
}
Related
I've tried looking this up but have got nowhere so far and I'm on a time limit.
Let's say I have three files that have similar clones in multiple folders:
(folder1)
image1.png
image2.png
image3.png
(folder2)
image1.png
image2.png
image3.png
I want to rename these using cmd prompt, powershell, or using a .bat to:
(folder1)
B-Sign.png
B-Gauge.png
B-Cup.png
(folder2)
G-Sign.png
G-Gauge.png
G-Cup.png
I intend to run the commands for each folder as only the front of the name is different. I want something simple.
rename-item *.png B-Sign.png
rename-item *.png B-Gauge.png
When it needs a different prefix I would just find and replace the prefix with the new one using ctrl+H in notepad.
Problem is I can't figure out, in any of these, how to automatically cycle to the next file in the folder instead of changing all of the files' names at once. Any one can help?
Yes that's very easy in PowerShell:
Get-ChildItem "C:\temp\Folder1" |
Rename-Item -NewName { "B-" + $_.Name }
The new name is a result of an Expression that Rename-Item knows to evaluate. Because the information is sent down the pipeline the Rename-Item command is run once per file, resulting in names like your example.
Also you can use the filter parameter as you described with either of the below:
Get-ChildItem "C:\temp\Folder1\*.png" |
Rename-Item -NewName { "B-" + $_.Name }
I prefer to filter using the -Filter parameter:
Get-ChildItem "C:\temp\Folder1" -Filter *.png |
Rename-Item -NewName { "B-" + $_.Name }
You can store the folder in a variable too:
$Folder = "C:\temp\Folder1"
Get-ChildItem $Folder -Filter *.png |
Rename-Item -NewName { "B-" + $_.Name }
Update:
Per comments here's an example to correlate a list of prefixes with the renames you want to do:
$Prefix = 'B-'
$NewNames =
#(
'Sign'
'Guage'
'Cup'
)
# If New names are stored ina file simply do:
# $NewNames = Get-Content <FilePath>
$Folder = "C:\temp\Test_10-30-20"
$Files = Get-ChildItem $Folder -Filter *.png
For( $i = 0; $i -lt $Files.Count; ++$i )
{
$CurrentFile = $Files[$i]
$NewName = $Prefix + $NewNames[$i] + $CurrentFile.Extension
Rename-Item $CurrentFile.FullName -NewName $NewName
}
I have a a bunch of language folders present in a directory under E:\Data\ like hu-hu, de-de etc.. on the other hand i have a bunch of file names in G:\ that contain the part of folder name for e.g.
amd64.de-de_OCR.cab,amd64.handwriting.de-de.cab
I need to copy all matching file names based on the foldername
for e.g. de-de should copy all matching files in G:\ i.e. both amd64.de-de_OCR.cab,amd64.handwriting.de-de.cab
This is the code i have so far but it is not copying over the files, and i am not sure how to proceed next, any help is appreciated.
$listfoldername = Get-ChildItem -Path "E:\Data" -Recurse -Directory -Force -ErrorAction SilentlyContinue | Select-Object Name
$destfolder = Get-ChildItem -Path "E:\Data" -Recurse -Directory -Force -ErrorAction SilentlyContinue | Select-Object FullName
$filename = Get-ChildItem -file G:\
if($filename -like $listfoldername)
{
Copy-Item -Path $filename -Destination $destfolder
}
There's a few issues with your code
The main issue with your code is that you are trying to use the -like operator to compare two objects (your object containing the directories you wish to move files to, and the object containing the files.
What you need to do is loop through each file and directory, one by one, to determine if the directory name (e.g. "hu-hu" is found in the filename (e.g. amd64.hu-hu_OCR.cab)
You'll want to use the wildcard indicator "*" with the -like operator (e.g. "*hu-hu*")
This below code snippet should do the trick. I tested using the file and folder names you've provided.
"G:" contains the folders:
de-de
hu-hu
us-us (note, I added this to make sure the code did not match this directory)
"E:\Data" contains the files
amd64.de-de_OCR.cab
amd64.handwriting.de-de.cab
amd64.handwritinghu-hu.cab
amd64.hu-hu_OCR.cab
$FileDirectory = "G:" # Change to "G:\", the trailing slash breaks syntax highlight on SO
$DataDirectory = "E:\Data"
$listfoldername = Get-ChildItem -Path "$DataDirectory" -Recurse -Directory -Force -ErrorAction SilentlyContinue | Select-Object Name
$filename = Get-ChildItem -file "$FileDirectory"
#Loop through each file one at a time
foreach ($file in $filename) {
# Then, loop through each folder one at a time
foreach ($folder in $listfoldername) {
# Set the current filename and listfoldername to variables for later -like operator
$FileString = $file.Name
$FolderString = $folder.Name
# If the current file "is like" the current folder name
if($FileString -like "*$FolderString*")
{
# Set the name of the current folder to a variable
$DataFolder = $folder.Name
Copy-Item -Path "$FileDirectory\$FileString" -Destination "$DataDirectory\$DataFolder"
} else {
Write-Output ("$FolderString pattern not found in $FileString")
}
}
}
I think you should start off by getting a list of possible language target folders. Then loop over the path where the files are, filtering their names to have at least the dash in it and next test if any of the language target folders matches the filename.
Something like this:
$langFolder = 'E:\Data'
$fileFolder = 'G:\' #'# dummy comment to fix syntax highlighting in SO
# get a list of the language folders
# if the languages folder has multiple subdirectories to include, add -Recurse here
$targetFolders = Get-ChildItem -Path $langFolder -Directory
# get a list of FileInfo objects for the files in the G:\ path
# if you need to search subdirectories aswell, add -Recurse here
$files = Get-ChildItem -Path $fileFolder -File -Filter '*-*.*'
foreach($file in $files) {
# check if a language name matches the file name
foreach($folder in $targetFolders) {
if ($file.BaseName -like "*$($folder.Name)*") {
# we have found a matching language target directory
$file | Copy-Item -Destination $folder.FullName
break # exit this folder foreach loop and get on with the next file
}
}
}
P.S. If all the files are .cab files you could speed up by setting the Filter to '*-*.cab' in line $files = Get-ChildItem ...
I have a powershell script that uses a control file to archive files:
# Takes the information from $controlfile and moves the files to the archive folder**
foreach ($line in get-content $controlfile)
{
$file = $controlfile
$split = $line.split("|")
$archive_path = $split[0]
$source_path = $split[1]
$basename = $split[2]
$extention = $split[3]
$daystoleave = $split[4]
# move the files to the archive folder
Get-ChildItem $source_path -Filter "$basename*" -Force -Recurse | Where-Object {$_.LastWriteTime -lt (Get-Date).AddDays($daystoleave)} | Move-Item -Destination $archive_path
Get-ChildItem $archive_path -Filter "$basename*" -Force -Recurse | % { rename-item –path $_.Fullname –Newname (("$basename") + ("_") + ($_.LastWriteTime.toString("MM.dd.yy_HH.mm.ss")) + (".$extension")) }
}
Control file example:
\server\Apps\Archive-Requests\|\server\Apps\requests\|?||0
\server\Apps\Archive-Requests\|\server\Apps\requests\|*||0
Above examples don't work because of the ? or * character in the file name split of the control file.
\server\Apps\Archives\Archive_Daily_Reports\|\apps\FICS\Daily Reports\|audit|fics|0
Above example works because "audit" is the beginning of the file name.
This works fine when I want to capture and use the first part of the original file name (ex. SAD123.txt - capture SAD|add LastWriteTime date to name|add extension|leave some file(s) in original folder ($daystoleave).
But if I'd like to capture either the entire original name or a middle portion of the name using wildcard characters, I receive "Illegal characters in path." and nothing changes to the end result filename. Is there a wildcard I can use other than * or ? in the control file.
Is it possible to use a wildcard in the control file?
I have an exported CSV file with the below table
"Directory","BaseName"
"E:\Movie Files\Movie (2000)","Movie"
"E:\Movie Files\Movie (2001)","Movie 2"
My code is very close to what I want and that is basically rename the cover.jpg in each folder to the basename of the movie and add -poster.jpg after it, e.g. Movie-poster.jpg, Movie 2-poster.jpg.
It works for the first folder but every folder after this uses the same basename from the first folder. It doesn't recurse for every folder within the root location like I thought it would. So for Movie (2001) I end up having Movie-poster.jpg, not Movie 2-poster.jpg.
This is the code that's not quite there:
$lines = Import-Csv 'E:\Output.csv'
foreach ($line in $lines) {
Get-ChildItem -Path .\* -File -Filter '*Cover*' -Recurse |
Rename-Item -NewName ($line.BaseName + '-poster.jpg')
}
Expected results as mentioned above is that each cover.jpg in each folder and sub-folders will be renamed to match the basename according to their locations.
You end up with all covers being renamed to Movie-poster.jpg because your code renames all covers under the current working directory (.) in the first iteration of your loop, so there are no files with the string "Cover" in their name left when the loop goes into the second iteration.
Process the directories from your CSV instead of processing the current working directory for each of them, and use the current object from the pipeline ($_) for renaming the files. For the latter you also must replace the parentheses with curly brackets.
Change this:
foreach ($line in $lines) {
Get-ChildItem -Path .\* -File -Filter '*Cover*' -Recurse |
Rename-Item -NewName ($line.BaseName + '-poster.jpg')
}
into this:
foreach ($line in $lines) {
Get-ChildItem -Path $line.Directory -File -Filter '*Cover*' -Recurse |
Rename-Item -NewName {$_.BaseName + '-poster.jpg'}
}
Thank you Ansgar for the comment. It didn't work in my instance, what it did was rename ALL the files (including those that already had the correct naming convention) to be cover-poster.jpg however the code did point me in the right direction with regards to $line.Directory.
The code that worked for me in the end was this;
# Renames Cover.jpg to match BaseName file with basename-poster.jpg
$lines = Import-Csv 'E:\Output.csv'
ForEach ($line in $lines) {
Get-ChildItem -Path $line.Directory -file -filter '*Cover*' -Recurse | Rename-Item -NewName {$line.BaseName + '-poster.jpg'}
}
I have 10K documents in a directory with this type of naming convention:
1050_14447_Letter Extension.pdf, 1333_14444_Letter.docx, etc...
I tried using this script to remove all characters before the 2nd underscore (including the 2nd underscore):
Get-ChildItem -Filter *.pdf | Rename-Item -NewName {$_.Name -replace '^[0-9_]+'}
This worked, but revealed there would be duplicate file names.
Wondering if there is some way a script can create a subfolder based on the filename (minus the extension)? So there would be 10K subfolders in my main folder. Each subfolder would just have the one file.
This should work. It creates a new folder for each item, then moves it, renaming it in the process.
gci | ? {!$_.PSIsContainer} | % {New-Item ".\$($_.BaseName)" -Type Directory; Move-Item $_ ".\$($_.BaseName)\$($_.Name -replace '^[0-9_]+')"}
Note that if there are two files with the same name, but different extensions, you'll see an error when trying to create the directory, but both files will wind up in the same folder.
Alternately, if you want something more readable to save in a script, this is functionally identical:
$files = Get-ChildItem | Where-Object {!$_.PSIsContainer}
foreach ($file in $files)
{
$pathName = ".\" + $file.BaseName
New-Item $pathName -Type Directory
$newFileName = $pathName + "\" + ($file.Name -replace '^[0-9_]+')
Move-Item $file $newFileName
}