Matthew
Matthew

Reputation: 1289

Recursively renaming files with Powershell

I currently have a line to batch rename files in a folder that I am currently in.

dir | foreach { move-item -literal $_ $_.name.replace(".mkv.mp4",".mp4") }

This code works perfectly for whatever directory I'm currently in, but what I want is to run a script from a parent folder which contains 11 child-folders. I can accomplish my task by navigating to each folder individually, but I'd rather run the script once and be done with it.

I tried the following:

get-childitem -recurse | foreach { move-item -literal $_ $_.name.replace(".mkv.mp4",".mp4") }

Can anyone please point me in the right direction here? I'm not very familiar with Powershell at all, but it suited my needs in this instance.

Upvotes: 38

Views: 59857

Answers (3)

YenForYang
YenForYang

Reputation: 3284

Note that if you are still having issue with errors like Cannot rename because item at '...' does not exist., you may be working with some super long paths and/or paths with 'specially-interpreted' characters like square brackets (i.e. [ ]).

For such scenarios, use -LiteralPath/-PSPath along with the special prefix \\?\ (for UNC paths you will want to use the prefix \\?\UNC\) for paths up to 32k characters. I also suggest filtering early (with Get-ChildItem) for improved performance (less Rename-Item calls are better).


$path = 'C:\Users\Richard\Downloads\[Long Path] THE PATH TO HAPPINESS (NOT CLICKBAIT)\...etc., etc.'
# -s is an alias for -Recurse
# -File for files only
# gci, dir, and ls are all aliases for Get-ChildItem
#   Note that among the 3, only `gci` is ReadOnly.
gci -s -PSPath $path -File -Filter "*.mkv.mp4" |
    # ren, rni are both aliases for Rename-Item
    #   Note that among the 2, only `rni` is ReadOnly.
    # -wi is for -WhatIf (a dry run basically). Remove this to actually do stuff.
    # I used -replace for regex (for excluding those super rare cases)
  rni -wi -PSPath { "\\?\$($_.FullName)" } -NewName { $_.Name -replace '\.mkv(?=\.mp4$)','' }

Upvotes: 0

Cole9350
Cole9350

Reputation: 5560

You were close:

Get-ChildItem -File -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace(".mkv.mp4",".mp4")}

Upvotes: 72

Jason Shirk
Jason Shirk

Reputation: 8019

There is a not well-known feature that was designed for exactly this scenario. Briefly, you can do something like:

Get-ChildItem -Recurse -Include *.ps1 | Rename-Item -NewName { $_.Name.replace(".ps1",".ps1.bak") }

This avoids using ForEach-Object by passing a scriptblock for the parameter NewName. PowerShell is smart enough to evaluate the scriptblock for each object that gets piped, setting $_ just like it would with ForEach-Object.

Upvotes: 41

Related Questions