Search code examples
powershellfile-renamesequential

Powershell Renumbering Files in Bulk Without Replacing the Original Name


In Powershell I am trying to rename ~2,500 files. However, some needed to be deleted as they were duplicates. Now 2,400 remain, but these files are not numbered sequentially (ie DATE_Photo001_NAME, DATE_Photo003_NAME)

I have been using the following to do the dates and the photo #'s in one pass, however, these photos span multiple dates and I'm just trying to streamline it.

ls |
  Rename-Item -NewName {$_.name -replace "Photo???",("Photo{0:d3}" -f  {1},$nr++)}

This keeps throwing me an error because the "???" is naturally invalid in a file name. I thought that the placeholder would work the same as the command prompt which I used before diving into Powershell. Any help is welcome and if there is an easier way to do this let me know!


Solution

  • Make the regex you pass to -replace match a run of digits (\d+) only if preceded by _Photo and succeeded by _, and replace those digits with the new sequence number:

    $nr = @{ Value = 1 }
    Get-ChildItem -File |
      Rename-Item -NewName { 
        $_.Name -replace '(?<=_Photo)\d+(?=_)', ($nr.Value++).ToString('D3') 
      } -WhatIf
    

    Note: The -WhatIf common parameter in the command above previews the operation. Remove -WhatIf and re-execute once you're sure the operation will do what you want.

    Note:

    • An aux. hashtable (@{ Value = 1 }) is used to contain the sequence number, which is necessary for technical reasons: delay-bind script blocks run in a child scope of the caller - see this answer for background information.

    • The -File switch passed to Get-ChildItem (one of whose built-in aliases on Windows is ls) limits processing to only files, not also directories.

    • The regex passed to -replace uses positive lookaround assertions ((?<=...) and (?=...)), which ensure that patterns of interest precede and succeed the run of digits but without becoming part of what is captured.

    • The desired formatting string, D3 is passed to a .ToString() method call directly on the sequence number; the -f operator-based alternative would be:

      ('{0:D3}' -f $nr.Value++)