37

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.

Matthew
  • 1,179
  • 2
  • 12
  • 16
  • 1
    possible duplicate of [Recursively renaming files in Powershell](http://stackoverflow.com/questions/7181804/recursively-renaming-files-in-powershell) – Anthony Neace Feb 06 '14 at 18:59
  • 1
    Thank you for pointing me to that thread. However, I'm still having difficulty, even having read the answers of the other question. I'm either getting the file doesn't exist, or the file is being used by another process. – Matthew Feb 06 '14 at 19:21

3 Answers3

66

You were close:

Get-ChildItem -File -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.Name.replace(".mkv.mp4",".mp4")}
Cole9350
  • 5,444
  • 2
  • 34
  • 50
  • Thanks for your answer, but I'm getting the following now: `Move-Item : The process cannot access the file because it is being used by another process. At line:1 char:45 + get-childitem -recurse | foreach { move-item <<<< -literal $_.FullName $_.FullName.replace(".mkv.mp4",".mp4") } + CategoryInfo : WriteError: (D:\The Wire\Season 1:DirectoryInfo) [Move-Item], IOException + FullyQualifiedErrorId : MoveDirectoryItemIOError,Microsoft.PowerShell.Commands.MoveItemCommand` – Matthew Feb 06 '14 at 19:19
  • @Matthew, I believe it was because you were using move-item instead of rename-item, fixed my post – Cole9350 Feb 06 '14 at 19:23
  • That has solved the problem. I was using move-item as there are square brackets in all the filenames and rename-item wasn't working for me earlier. Thanks a million for your help. – Matthew Feb 06 '14 at 19:26
  • `Cannot rename because item at 'Settings.png' does not exist`. It works in the root, but inside a child folder it fails. – The Muffin Man Jul 29 '14 at 03:46
  • @TheMuffinMan Updated to be more specific about paths. – Cole9350 Jul 29 '14 at 13:38
  • When the target file already exists it will fail. For overriding existing file use `Move-Item` as shown in here https://stackoverflow.com/questions/32311875/rename-item-and-override-if-filename-exists – Skorunka František Oct 25 '18 at 09:58
  • @SkorunkaFrantišek or just use the `-force` parameter. – Cole9350 Oct 25 '18 at 13:41
  • @Cole9350 `-force` with `Rename-Item` didn't work for me. Got the same error as without it. – Skorunka František Oct 25 '18 at 14:27
  • If you use -LiteralPath instead of -Path, it will work with paths that have brackets in them – dsmtoday Dec 18 '19 at 19:00
41

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.

Jason Shirk
  • 7,734
  • 2
  • 24
  • 29
  • I tested this in powershell 2.0 and it only works for 1 folder, and doesn't recurse into subfolders... – Cole9350 Feb 06 '14 at 21:40
  • I just updated the snippet to work with V2 - it needed -Include. V3 is a bit smarter and doesn't need -Include. – Jason Shirk Feb 06 '14 at 21:53
  • Sweet, +1 for doing it without a foreach – Cole9350 Feb 07 '14 at 14:20
  • Spent many hours reading posts about foreach etc that didn't work for child folders. This is the only thing that worked. – The Muffin Man Jul 29 '14 at 03:56
  • With the benefit of many years of hindsight: this feature now has a name and is documented: [_delay-bind script blocks_](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_script_blocks#using-delay-bind-script-blocks-with-parameters) – mklement0 Dec 01 '20 at 21:23
0

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$)','' }
YenForYang
  • 2,998
  • 25
  • 22
  • Good to know; note that in _PowerShell [Core] v6+_ long-path support is now enabled _by default_, and using the `\\?\ ` prefix is not only unnecessary but can cause problems. – mklement0 Dec 01 '20 at 21:29
  • Also note that your command uses _both_ pipeline input from `gci` _and_ the `-PSPath` (`-LiteralPath`) argument, which won't work. – mklement0 Dec 01 '20 at 21:31