Find Files in Subdirectories with Specific Text and Rename - powershell

From root folder, I am trying to search all files in subdirectories with "-poster" and rename those files to only poster without changing the file extension. All these files have other text in front of the "-poster" that I want to remove, and all the files are .jpgs.
Would a command such as this work:
Get-ChildItem -filter *-poster* -recurse | ForEach {Rename-Item $_ -NewName "poster.jpg"}
I haven't tried it, as don't want to risk changing other files.

Use the -WhatIf common parameter to preview an operation without actually performing it:
Get-ChildItem -Filter *-poster* -Recurse |
Rename-Item -NewName poster.jpg -WhatIf
Note:
-WhatIf does not test actual runtime conditions.
That is, if a poster.jpg file already exists in the target directory, the Rename-Item call will fail once -WhatIf is removed.
To handle this case, you have the following options, depending on your use case:
Allow the failure, if you don't expect a preexisting file.
Ignore the failure, if a preexisting file implies that no action is needed: add -ErrorAction Ignore to the Rename-Item call.
Blindly overwrite the existing file, by adding -Force to the Rename-Item call.
Create a file with a different name, such as by appending a sequence number, using a delay-bind script block - see below.
Get-ChildItem -Filter *-poster* -Recurse |
Rename-Item -NewName {
$newBaseName = 'poster.jpg'
$newBaseName = [IO.Path]::GetFileNameWithoutExtension($newName)
$newExtension = [IO.Path]::GetExtension($newName)
$suffix = ''
if ([array] $preexisting = Get-ChildItem -File -LiteralPath $_.DirectoryName -Filter "$newBaseName*$newExtension")) {
$highestNumSoFar = [int[]] ($preexisting.BaseName -replace '^.*\D') | Sort-Object -Descending | Select-Object -First 1
$suffix = $highestNumSoFar + 1
}
$newBaseName + $suffix + $newExtension
} -WhatIf

Related

How to rename multiple files in powershell from parent folder?

Hello I would like to know is there a way to replace multiple filenames from parent folder?
I was able to rename multiple files using command below, but I have to access each folder first to rename multiple files.
dir .\* -include ('*.mp4','*.srt') | Rename-Item -NewName { $_.Name -replace '\[','' -replace '\]','' }
I was trying to replace dir .\* to dir .\**\* to select from parent folder but didn't work.
What am I missing?
You can do that by adding the -Recurse switch to Get-ChildItem (dir is alias to Get-ChildItem).
When searching for files that contain square brackets, you need to use the -LiteralPath parameter.
Also, be aware that there is a snag if you pipe the results from that directly to Rename-Item..
When doing so, Get-ChildItem may pick up the already processed files again, wasting time of course, so to prevent that you can either do:
$rootFolder = 'X:\WhereTheFilesAre'
(Get-ChildItem -LiteralPath $rootFolder -Include '*.mp4','*.srt' -File -Recurse) |
Rename-Item -NewName { $_.Name -replace '\[|]' }
or
$rootFolder = 'X:\WhereTheFilesAre'
$files = Get-ChildItem -LiteralPath $rootFolder -Include '*.mp4','*.srt' -File -Recurse
foreach ($file in $files) {
$file | Rename-Item -NewName { $file.Name -replace '\[|]' }
}
In additional to marsze's concise answer and realizing this isn't really a performance question:
-Include is much slower than -Filter because it filters after retrieving from the file system. Whereas, -Filter has the file system do the heavy lifting. In essence -Filter is a nuanced version of moving filtering operations left in the command/pipeline to improve performance. However, -Filter doesn't take multiple values! That said, you may still be able to exploit this characteristic using a loop, something like:
'*.mp4','*.srt' |
ForEach-Object{
Get-ChildItem .\ -Filter $_ -File
} |
Rename-Item -NewName { $_.Name -replace '\[|\]' }
-File works even though you are using -Recurse and may carry a modest performance improvement. I also shortened the -replace to 1 operation which can only help further.
What you probably want is the -Recurse switch:
dir "the folder" -Recurse -Include ('*.mp4','*.srt')
Note that this will recurse all levels of subdirectories.
I just found out I could use dir . to select all files (with specific file types), including files in subdirectories.
dir . -Recurse -Include ('*.srt', '*.mp4') | Rename-Item -NewName { $_.Name -replace '\[|\]','' }

Skipping files with a given prefix when renaming

I have the following script for renaming a bunch of files in a directory, adding the name of the directory to the start of them:
$s = "Y:\Teknisk Arkiv\Likeretter 111-Å1\E2_Kretsskjema\02_Likeretter styring\3AJJ000302-222"
Get-ChildItem -Path $s -Exclude $_.Directory.Name* | rename-item -NewName { $_.Directory.Name + '_' + $_.Name }
Before running the script the files in the folder looks something like this
after like this
As you can see it does more or less what I want, except that -exclude $_.DirectoryName* doesn't prevent files which already have the foldername as a prefix from being renamed. What am I doing wrong here?
$_ in a pipeline is only defined inside a script block used in a non-initial pipeline segment, where it refers to the input object at hand, so in your Get-ChildItem command it is effectively undefined.
Even if $_.Directory.Name did have a value, $_.Directory.Name* wouldn't work as expected, because it would be passed as 2 arguments (you'd have to use "$($_.Directory.Name)*" or ($_.Directory.Name + '*').
You instead want to extract the directory name from the $s input path, which you can do with Split-Path -Leaf, and then append '*'.
In order for -Exclude to be effective, the input path must end in \*, because -Include and -Exclude filters - perhaps surprisingly - operate on the leaf component of the -Path argument, not on the child paths (unless -Recurse is also specified).
To put it all together:
Get-Item -Path $s\* -Exclude ((Split-Path -Leaf $s) + '*') |
Rename-Item -NewName { $_.Directory.Name + '_' + $_.Name }
I've switched to Get-Item, since \* is now being used to enumerate the children, but Get-ChildItem would work too.
The $_ is only valid when it is used on the right-side of a pipeline meaning when you have a collection of items and "pipe" them through the "$_" would represent the current item.
Since the directory name you want excluded is static you can just hardcode it and use as your exclude filter.
$s = "Y:\Teknisk Arkiv\Likeretter 111-Å1\E2_Kretsskjema\02_Likeretter styring\3AJJ000302-222"
$exclude_filter = "3AJJ000302-222*"
Get-ChildItem -Path $s -Exclude $exclude_filter | rename-item -NewName { $_.Directory.Name + '_' + $_.Name }
Also try to use "-whatif" with rename-item so you know what will happen before it happens.
$_ represents the currently processed item, what requires a ForEach-Object or a scriptblock inside a pipe, not present at the begin of your command.
Solution make the path a FileInfoObject and use -Exclude
$s = Get-Item "Y:\Teknisk Arkiv\Likeretter 111-Å1\E2_Kretsskjema\02_Likeretter styring\3AJJ000302-222"
Get-ChildItem -Path $s -Exclude "$($s.Name)*"|Rename-Item -NewName {$_.Directory.Name+'_'+$_.Name}
solution use a Where-Object to filter files already starting with the directory name
Get-ChildItem -Path $s | Where-Object {$_.Directory.Name -notlike "$($_.Name)*"} |
Rename-Item -NewName { $_.Directory.Name + '_' + $_.Name }
Solution use the RegEx based -replace operator to prepend the directory name and use a negative lookahead assertion to exclude files which already have it.
Get-ChildItem -Path $s |
Rename-Item -NewName {$x=$_.Directory.Name;$_.Name -replace "^(?!$x)",$x}

Grandparent Folder Name To File Name

I have a script that runs when I specify the exact directory of c:\script\19\ the problem is, have other folders in the c:\script such as 18, 17, 16. The script I have is appending the 19 in front of all of the files. How do I get this to look at the grandparent of the file it's renaming and append it? An example of how it's working is files like this:
c:\script\18\00000001\Plans.txt
c:\script\19\00001234\Plans.txt
c:\script\17\00005678\App.txt
But my script is renaming the files like this
c:\script\18\00000001\19-0001 Plans.txt
c:\script\19\00001234\19-1234 Plans.txt
c:\script\17\00005678\19-5678 App.txt
My script is this:
$filepath = Get-ChildItem "C:script\" -Recurse |
ForEach-Object {
$parent = $_.Parent
$grandparent = $_.fullname | Split-Path -Parent | Split-Path -Parent | Split-Path -Leaf
}
Get-ChildItem "C:\Script\" –recurse –file |
Where-Object {$_.Name –notmatch ‘[0-9][0-9]-[0-9]’} |
rename-item -NewName {$grandparent + '-' + $_.Directory.Name.SubString($_.Directory.Name.length -4, 4) + ' ' + $_.Name}
The simplest solution is to combine string-splitting with the -split operator with a delay-bind script block (which you've tried to use):
Get-ChildItem C:\Script –Recurse –File -Exclude [0-9][0-9]-[0-9]* |
Rename-Item -NewName {
# Split the full path into its components.
$names = $_.FullName -split '\\'
# Compose the new file name from the relevant components and output it.
'{0}-{1} {2}' -f $names[-3], $names[-2].Substring($names[-2].Length-4), $_.Name
} -WhatIf
-WhatIf previews the renaming operation; remove it to perform actual renaming.
Note how -Exclude is used with a wildcard expression directly with Get-ChildItem to exclude files that already have the target name format.
The main reason your original didn't work is that you calculated single, static
$parent and $grandparent values, instead of deriving the input path-specific values from each input path.
Additionally, your $grandparent calculation was needlessly complicated; Gert Jan Kraaijeveld's helpful answer shows a simpler way.
To get the grandparent of a $file object:
$file.Directory.Parent
The parent directory of a file is the 'Directory' member of the file object.
The parent directory of a directory is the 'Parent' member of the directory object.
It is not hard, but confusing it sure is...
Edit
You asked for my solution:
Get-ChildItem C:\script -Recurse -File | ForEach-Object {
$parent = $_.Directory.Name
$grandparent = $_.Directory.Parent.Name
Rename-Item $_.FullName -NewName "$grandparent-$($parent.Substring($parent.length-4,4)) $($_.name)"
}
I used the -file parameter of Get-ChildItem to get only files from the folder structure. I'm not sure that suits in your situation

Rename all sub elements with incremental number

I'm new in powershell, I have to execute the following statement recursively:
Get-ChildItem | ForEach-Object {Rename-Item $_ -NewName ("new_filename{0}.smali" -f $nr++)}
In other words I have to rename all the .smali files in the subdirectories with any other name different from the current one (keeping the .smali extension).
Get-ChildItem -Path FolderPath -Filter *.smali -Recurse | ForEach-Object{$_ | Rename-Item -NewName ($.BaseName + StringToAdd + $.Extension) -WhatIf}
Copy the above line as is... Replace the values for FolderPath and StringToAdd and give it a try. If everything looks fine then execute it by removing the -Whatif switch.
dir is an alias for Get-ChildItem which has a -Recursive parameter.
$nr = 0; Get-ChildItem -Recurse -Filter "*.smali" | Rename-Item -NewName "new_filename$($nr++).smali"
Things to note:
DIR is an alias for Get-ChildItem. Get-ChildItem is more Powershell-y
Your command only looked at the top level directory, so I added the -recurse
Your command would rename all files, not just the "*.smali", so I added the filter.

error from Powershell when try to rename with sequential prefix *recursively*

I have no problem adding sequential prefixes to filenames. The following works great on the top directory in question.
$path="E:\path\newtest1"
$count=4000
Get-ChildItem $path -recurse | Where-Object {!$_.PSIsContainer -and $_.Name -NotMatch '^\d{4}\s+'} | ForEach -Process {Rename-Item $_ -NewName ("$count " + $_.name -f $count++) -whatif}
BUT if there are files in subfolders within the top directory, these are all completely missed. Whatif reports that for any deeper file it "does not exist".
I have tried the following, based on looking at some pages on other recursion problems, but as you can probably guess I have no clue what it is doing. Whatif shows that it does at least pickup and rename all the files. But the following does it too much and makes multiple copies of each file with each number:
$path="E:\path\newtest1"
$count=4000
Get-ChildItem -recurse | ForEach-Object { Get-ChildItem $path | Rename-item -NewName ("$count " + $_.Basename -f $count++) -whatif}
Really keen to get some guidance on how to get the first of these two snippets to work to find all files in all subdirectories and rename them with sequential number prepended.
Try it like so:
Get-ChildItem $path -recurse -file | Where Name -NotMatch '^\d{4}\s+' |
Rename-Item -NewName {"{0} $($_.name)" -f $count++} -whatif
When you supply $_ as an argument (not a pipeline object), that gets assigned to the Path parameter which is of type string. PowerShell tries to convert that FileInfo object to a string but unfortunately the "ToString()" representation of files in nested folders is just the filename and not the full path. You can see this by executing:
Get-ChildItem $path -recurse -file | Where Name -NotMatch '^\d{4}\s+' | ForEach {"$_"}
The solution is either to A) pipe the object into Rename-Item or B) use the FullName property e.g. Rename-Item -LiteralPath $_.FullName ....