I need help with rearranging and renaming a bunch of files in PowerShell.
I want to change from:
YYYY_Project name_City_Category.jpg
to
YYYY_Category_Project name_City.jpg
The years, categories, project names and cities are of course all different.
Please be gentle, I'm new to PowerShell and regex.
Assuming we have an item like this:
get-item '.\YYYY_Project name_City_Category.jpg'
Directory: C:\temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 6/8/2020 10:12 AM 8 YYYY_Project name_City_Category.jpg
This is a FileInfo object which has a number of properties, one of which is BaseName which gives us the filename without extension.
PS> $file = get-item '.\YYYY_Project name_City_Category.jpg'
PS> $File.BaseName
YYYY_Project name_City_Category
We can call the .Split() method on the BaseName property to split on every instance of the _ underscore character, like so:
PS> $File.BaseName.Split('_')
YYYY
Project name
City
Category
We can then assign those to variables like this:
$FileSegments = $File.BaseName.Split('_')
$YearPart = $FileSegments[0]
$ProjPart = $FileSegments[1]
$CityPart = $FileSegments[2]
$CatgPart = $FileSegments[3]
We can then reassemble them in our desired order like this:
$newName = $YearPart + "_" + $CatgPart + "_" + $ProjPart + "_" + $CityPart + $file.Extension
write-host $newName
YYYY_Category_Project name_City.jpg
So, you'd put it all together like this. If you like the results, remove -WhatIf
$files = Get-ChildItem C:\PathTo\Your\Directory\*.jpg
ForEach ($file in $files){
$FileSegments = $File.BaseName.Split('_')
$YearPart = $FileSegments[0]
$ProjPart = $FileSegments[1]
$CityPart = $FileSegments[2]
$CatgPart = $FileSegments[3]
$newName = $YearPart + "_" + $CatgPart + "_" + $ProjPart + "_" + $CityPart + $file.Extension
write-host $newName
Rename-Item -Path $file.FullName -NewName $newName -WhatIf
}
You don't need a complex Regex for this, just some understanding of how to get the files in a folder using Get-ChildItem and how Rename-Item works.
Something like this should do it
$sourcePath = 'Folder\To\Where\The\Files\Are'
Get-ChildItem -Path $sourcePath -Filter '*.jpg' -File | Rename-Item -NewName {
# split the file's BaseName into 4 pieces at the underscore
$year, $project, $city, $category = $_.BaseName -split '_', 4
# use the -f Format operator to stitch the parts together in a new order
# see https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_operators?view=powershell-5.1#format-operator--f
'{0}_{1}_{2}_{3}{4}' -f $year, $category, $project, $city, $_.Extension
}
One liner version of the split and join idea:
dir *_*_*_*.jpg | ren -new {((($_.basename -split '_')[0,3,1,2]) -join '_') +'.jpg'} -whatif
What if: Performing the operation "Rename File" on target
"Item: C:\Users\admin\foo\YYYY_Project name_City_Category.jpg
Destination: C:\Users\admin\foo\YYYY_Category_Project name_City.jpg".
Related
is there a way to bulk rename items such that a folder with the items arranged in order would have their name changed into numbers with zero padding regardless of extension?
for example, a folder with files named:
file1.jpg
file2.jpg
file3.jpg
file4.png
file5.png
file6.png
file7.png
file8.jpg
file9.jpg
file10.mp4
would end up like this:
01.jpg
02.jpg
03.jpg
04.png
05.png
06.png
07.png
08.jpg
09.jpg
10.mp4
i had a script i found somewhere that can rename files in alphabetical order. however, it seems to only accepts conventionally bulk renamed files (done by selecting all the files, and renaming them such that they read "file (1).jpg" etc), which messes up the ordering when dealing with differing file extensions. it also doesn't seem to rename files with variations in their file names. here is what the code looked like:
Get-ChildItem -Path C:\Directory -Filter file* | % {
$matched = $_.BaseName -match "\((?<number>\d+)\)"
if (-not $matched) {break;}
[int]$number = $Matches["number"]
Rename-Item -Path $_.FullName -NewName "$($number.ToString("000"))$($_.Extension)"
}
If your intent is to rename the files based on the ending digits of their BaseName you can use Get-ChildItem in combination with Where-Object for filtering them and then pipe this result to Rename-Item using a delay-bind script block.
Needles to say, this code does not handle file collision. If there is more than one file with the same ending digits and the same extension this will error out.
Get-ChildItem -Filter file* | Where-Object { $_.BaseName -match '\d+$' } |
Rename-Item -NewName {
$basename = '{0:00}' -f [int][regex]::Match($_.BaseName, '\d+$').Value
$basename + $_.Extension
}
To test the code you can use the following:
#'
file1.jpg
file2.jpg
file3.jpg
file4.png
file5.png
file6.png
file7.png
file8.jpg
file9.jpg
file10.mp4
'# -split '\r?\n' -as [System.IO.FileInfo[]] | ForEach-Object {
$basename = '{0:00}' -f [int][regex]::Match($_.BaseName, '\d+$').Value
$basename + $_.Extension
}
You could just use the number of files found in the folder to create the appropriate 'numbering' format for renaming them.
$files = (Get-ChildItem -Path 'D:\Test' -File) | Sort-Object Name
# depending on the number of files, create a formating template
# to get the number of leading zeros correct.
# example: 645 files would create this format: '{0:000}{1}'
$format = '{0:' + '0' * ($files.Count).ToString().Length + '}{1}'
# a counter for the index number
$index = 1
# now loop over the files and rename them
foreach ($file in $files) {
$file | Rename-Item -NewName ($format -f $index++, $file.Extension) -WhatIf
}
The -WhatIf switch is a safety measure. With this, no file gets actually renamed, you will only see in the console what WOULD happen. Once you are content with that, remove the -WhatIf switch from the code and run again to rename all your files in the folder
I need help to create a command in PowerShell to rename a sequence of files with this format:
001.jpg
001_1.jpg
002.jpg
002_1.jpg
003.jpg
003_1.jpg
into a new sequence that can start with a number such as 9612449, but keeping intact the suffixes, so new sequence would be:
9612449.jpg
9612449_1.jpg
9612450.jpg
9612450_1.jpg
9612451.jpg
9612451_1.jpg
Assuming that 9612449 is an offset to be added to the existing numbers that make up the first _-separated token or all of the base file names:
# Simulate a set of input files.
$files = [System.IO.FileInfo[]] (
'001.jpg',
'001_1.jpg',
'002.jpg',
'002_1.jpg',
'003.jpg',
'003_1.jpg'
)
# The number to offset the existing numbers by.
$offset = 9612449 - 1
# Process each file and apply the offset.
$files | ForEach-Object {
# Split the base name into the number and the suffix.
$num, $suffix = $_.BaseName -split '(?=_)', 2
# Construct and output the new name, with the offset applied.
'{0}{1}{2}' -f ($offset + $num), $suffix, $_.Extension
}
The above yields the output shown in your question.
Applied to a real file-renaming operation, you'd do something like:
Get-ChildItem -LiteralPath . -Filter *.jpg |
Rename-Item -NewName {
$num, $suffix = $_.BaseName -split '(?=_)', 2
'{0}{1}{2}' -f ($offset + $num), $suffix, $_.Extension
} -WhatIf
Note: The -WhatIf common parameter in the command above previews the operation. Remove -WhatIf once you're sure the operation will do what you want.
I am working on inserting the space before every capitalized characters to rename the word documents in power shell
I tried to insert the space before first character for every file in this folder using the following script in power shell:
Could you pls let me know how to write a script to insert and iterate ?
#Target: the files names will appear as shown here: 220519ColdWaterMeters[enter image description here][1] , change all of the file names by adding a spaces in the file names so it will read as “220519 Cold Water Meters”
previous code
PS C:\Users\B> $source_path = "C:\Temp"
PS C:\Users\B> $filter = "*.doc"
PS C:\Users\B> $new_prefix = " "
PS C:\Users\B> $files = Get-ChildItem -Path $source_path -Filter $filter
PS C:\Users\B> # Process each file and add the $new_prefix to the filenames
>> ForEach ($file in $files) {
>>
>> $old_file_name = $file.Name
>> $new_full_name = "$($file.DirectoryName)" + "\" + "$($new_prefix)" + "$($old_file_name)"
>>
>> # Rename the file (perhaps first with the -WhatIf parameter?)
>> # Rename-Item $file.FullName -NewName $new_full_name -WhatIf
>> Rename-Item $file.FullName -NewName $new_full_name
>>
>> } # ForEach $file
Really, having spaces in filenames, folder names, filed names, property names, is a prescription for unneeded headaches in coding later. Doing so, can/will cause quoting complexities.
Yet, if you really want to do this, try this approach.
'220519ColdWaterMeters' -csplit '(?=[A-Z])' -ne '' -join ' '
# Results
<#
220519 Cold Water Meters
#>
You can use -creplace with delay-bind scriptblock in one pipeline like this.
$source_path = "C:\Temp"
$filter = "*.doc"
$new_prefix = " "
Get-ChildItem -Path $source_path -Filter $filter |
Rename-Item -NewName {($_.basename -creplace '(?=[A-Z])',"$new_prefix") + $_.Extension}
Just in case the extension had a capital letter I targeted just the basename and then added the extension back.
I'm trying to rename files that match values in column one of a csv adding the value in column 3 to the beginning of the file name leaving the rest of the file name intact. Here is what I have so far. I cant seem to figure out the Rename-Item.
# Common Paths
$PathRoot = "C:\Temp\somefiles" #Where the files are to rename
# Get csv file
$ClientAccounts = Import-CSV -path "\\server\some\path\to\csv\file.csv"
# Get each file and rename it
ForEach($row in $ClientAccounts)
{
$CurrentClientTaxId = $row[-1].TaxId
$CurrentClientName = $row[-1].ClientName
#loop through files
$FileExists = Test-Path -Path "$PathTotal\*$CurrentClientLB_Number*" #See if there is a file.
If ($FileExists -eq $true) #The file does exist.
{
#ReName File
Rename-Item -Path $PathRoot -NewName {$CurrentClientName + " " + $_.name}
}
}
Lets suppose your CSV file looks similar to this:
"LB_Number","TaxId","ClientName"
"987654","12345","Microsoft"
"321456","91234","Apple"
"741852","81234","HP"
Column 1 has the portion of the existing file name to match
Column 3 has the client name you want to prepend to the file name
Then your function could be something like this:
# Common Paths
$PathRoot = "C:\Temp\somefiles" # Where the files are to rename
# Get csv file
$ClientAccounts = Import-CSV -path "\\server\some\path\to\csv\file.csv"
# Loop through all clients in the CSV
foreach($client in $ClientAccounts) {
$CurrentClientLB_Number = $client.LB_Number
$CurrentClientTaxId = $client.TaxId # unused...??
$CurrentClientName = $client.ClientName
# get the file(s) using wildcards (there can be more than one)
# and rename them
Get-ChildItem -Path "$PathRoot\*$CurrentClientLB_Number*" -File | ForEach-Object {
$_ | Rename-Item -NewName ($CurrentClientName + " " + $_.Name)
}
# Curly braces work also, although this is not very common practice:
# Get-ChildItem -Path "$PathRoot\*$CurrentClientLB_Number*" -File |
# Rename-Item -NewName { ($CurrentClientName + " " + $_.Name) }
}
I use the -File parameter with Get-ChildItem so the function will only return files; not directories. If you are using PowerShell version 2.0, you need to replace that with | Where-Object { !$_.PSIsContainer }.
I need to organize many folders, this folders must have some kind of format in their names. I need to rename them and put them like this:
parent folder 2nd name + "_" + 2 digits + "_" + folder name
This needs to happen with every folder where the .ps1 is executed.
Here are some better explanations, here we have some folders:
00_00_Master folder
+- 00_01_Activities
`- 00_02_Schedules
+- 02_00_dir1
`- 02_01_dir2
As you can see, the subfolders inside 00_02_Schedules start with the 2nd "name".
Etc, with all folders (never files). Now for a better explanation, I added (it will happen a lot) 3 more folders inside 00_02_Schedules. It should be like this (before and after executing .ps1)
Before:
...
`- 00_02 Schedules
+- 02_00_dir1
+- 02_01_dir2
+- dir3
+- 12345dir4
`- 01_01_dir5
After:
...
`- 00_02 Schedules
+- 02_00_dir1
+- 02_01_dir2
+- 02_02_dir3
+- 02_03_dir4
`- 02_04_dir5
I will try to explain a little (I put all the code I had, even when I think some is useless now)
$var1 = 0
$var2 = 0
# It always has to work in the same place, never with a fixed path like
# C:\Users\etc\etc
$arr1 = Get-ChildItem -Path .\ -Directory
$arr2 = New-Object System.Collections.ArrayList
[string]$dir = Get-Location
$ruta = $dir | Split-Path -Leaf
# Did these 2 things because I needed the folder's name, I didn't know how
# to do it better
# I think we can say this following code is useless now, I think I tried to do
# an array of arrays so I could cleave the name
foreach ($Name in $arr1) {
[void]$arr2.Add($Name)
}
foreach ($Directory in $arr1) {
if ($Directory -match '^[0-9].*') {
Rename-Item \$Directory -NewName { [string]($_.Name).Substring(5) }
# This is the part I've being having most problems with. I may say the
# code works...as long a folder's name doesn't start with a number,
# because it enters inside the loop and many error logs pops out. Also
# it renames the folders previously renamed like 00_01_00_01, when it's
# supposed to delete before rename them.
# Also, I think it won't work if the numeric extension isn't equal to 5.
}
}
$arr3 = $ruta -split '_'
$foldername = $arr3[1]
# Works, It splits and give me the second "piece" between the 2 "_"
foreach ($Directory in $arr1) {
Rename-Item "$Directory" -NewName $("$foldername" + "_" + "$var1" + "$var2" + "_" + $Directory.Name)
# I think it should have been: (I put it that way before)
# $("$foldername" + "_" + "$var2" + "$var1" + "_" + $Directory.Name)
# but if I did that way, it renamed them like 10,20,30 instead of 01,02,03 etc.
$var++
if ($var1 = 9) {
$var1 = 0
$var2++
# I suppose is not fancy, but in the result output I can see 2 numbers, not 1
}
}
One more thing, is it possible to transform this (fixed) code to work in all folders in a recursive way? (would save a lot of work and executions).
With this initial situation:
> tree
└───00_00_Master folder
├──00_01_Activities
└──00_02_Schedules
├──01_01_dir5
├──02_00_dir1
├──02_01_dir2
├──12345dir4
└──dir3
This script (adapt the Set-Location to fit your environment):
## Q:\Test\2017\08\18\SO_45750567.ps1
Function Test-DirSchema ($DirName) {
# assume parent is already processed and complies with schema
$ThisDir = GI $DirName
# Get parent's second number for our first
$First = $ThisDir.Parent.Name.Split('_')[1]
If ($ThisDir.Name -Match "^$($First)_\d{2}_.*") {
"{0} : {1} complies with schema" -f $First,$ThisDir.Name
} else {
$Highest = (gci "$($ThisDir.Parent)\$($First)_??_*"|Sort|Select -Last 1).Name
$Second = [int]($Highest -Replace('^\d{2}_(\d{2})_.*','$1'))
$Second = ($Second + 1).ToString('00')
"{0} : {1} does not comply with schema" -F $First,$ThisDir.Name
$NewName = "{0}_{1}_{2}" -F $First, $Second,$ThisDir.Name.TrimStart('0123456789_')
"new: {0}" -F $NewName
$ThisDir | Rename-Item -NewName $NewName
}
"----"
}
Set-Location "A:\00_00_Master folder"
Get-ChildItem "[0-9][0-9]_*" -Rec |
Where-Object {$_.PSIsContainer} |
ForEach-Object {Test-DirSchema $_.FullName}
with this output:
> Q:\Test\2017\08\18\SO_45750567.ps1
02 : 01_01_dir5 does not comply with schema
new: 02_02_dir5
----
02 : 02_00_dir1 complies with schema
----
02 : 02_01_dir2 complies with schema
----
02 : 12345dir4 does not comply with schema
new: 02_03_dir4
----
02 : dir3 does not comply with schema
new: 02_04_dir3
----
Will have this end situation:
> tree
└───00_00_Master folder
├──00_01_Activities
└──00_02_Schedules
├──02_00_dir1
├──02_01_dir2
├──02_02_dir5
├──02_03_dir4
└──02_04_dir3
The order is, due to different sorting, not exactly yours.
If there is no previous number it will start with 01.
Get-ChildItem -Directory -Recurse | ForEach { #Get All Directories
$a = 0 #Reset Indexing to 01
Get-ChildItem $_.FullName -File | ForEach {
$ParentFolderSecond = ($_.Directory.Parent.Name -split '_')[1] #Second part of folder's parent
$Number = $a++ #Number incrementing by 1
$FolderName = $_.Directory.Name #folder's name
"$($ParentFolderSecond)_$($Number.ToString("00"))_$($FolderName)$($_.Extension)" #put this into a rename-item if it works OK.
}
}
Does this match your specs?
it should work recursively as is but I think i'm missing something in the parent folder part.
it won't rename anything, just spit out a list of what the file names 'will' be.
I would probably do something like this:
Get-ChildItem -Recurse -Directory | Where-Object {
# find schedule directories below the current working directory
$_.Name -match '^\d{2}_(\d{2})_Schedules$'
} | ForEach-Object {
$prefix = $matches[1]
$pattern = "^${prefix}_(\d{2})_"
# enumerate subfolders of schedule directory
$folders = Get-ChildItem $_.DirectoryName -Directory
# find highest existing index of folders that already conform to the
# name schema
$lastindex = $folders |
Where-Object { $_.Name -match $pattern } |
ForEach-Object { [int]$matches[1] } |
Sort-Object |
Select-Object -Last 1
$nextindex = if ($lastindex) { $lastindex + 1 } else { 0 }
# rename folders that don't conform to the name schema starting with the
# index after the highest existing index (or 0 if there was none), remove
# digits and underscores from beginning of folder name
$folders | Where-Object {
$_.Name -notmatch $pattern
} | Rename-Item -NewName {
$_.Name -replace '^\d(?:[_\d]*)', "${prefix}_${script:nextindex}_"
${script:nextindex}++
}
}