Shore Up
A stack of paper folders on a desk, each with a handwritten label; a hand crossing out one word on every label and writing a new word above it, with one folder set aside untouched.
Windows

Batch-Rename Files by Pattern with a Windows Batch Script

Ketan Aagja7 min read
No ratings yet

Before you run this

This is a cmd.exe batch file that renames files in one folder by find-and-replace: it takes every file matching a mask (say *.txt), swaps a substring in the filename (say draftfinal), and renames it. Its purpose is bulk cleanup of filenames — removing a stray word, fixing a prefix, changing a spelling — without you clicking through Explorer.

  • Privileges: No administrator rights are needed as long as you own the files and the folder isn't a protected system location. Renaming files under C:\Windows, C:\Program Files, or another user's profile would need an elevated prompt, and you should not be doing that anyway. Run it as your normal user.
  • Test first: Read the script before you run it. Point it at a copy of a folder — not the real one — the first time. The version below ships with a dry-run switch turned on by default, so it prints what it would rename and touches nothing until you deliberately flip it off. Use that.
  • This changes data, and there is no automatic undo. A batch ren does not go into the Explorer "Undo" history — pressing Ctrl+Z in a window later will not reverse it. The rename is done when it's done. It is reversible by hand only if the change is clean (see the Undo section), so treat the first real run with the same care you'd give any bulk operation.
  • Collisions: ren will not silently overwrite an existing file — if the target name already exists it errors out. The script checks for that and skips, rather than clobbering. Substring matching in cmd is case-insensitive, so Draft, draft, and DRAFT all match; know that going in.

Assumed environment: Windows 10 or Windows 11, run from cmd.exe (the classic Command Prompt), operating on a single folder, non-recursively. If you'd rather do this in PowerShell, the standard path there is Get-ChildItem piped to Rename-Item -WhatIf — that's a different guide; I'm doing the batch-file way here.

The simplest case: change an extension

If all you want is to change the extension on a batch of files, you don't need a script at all. The built-in ren command takes wildcards:

ren *.jpeg *.jpg

That renames every .jpeg in the current directory to .jpg. It's the right tool when the pattern is a clean extension swap. It gets unreliable the moment you try to do partial-name surgery with wildcards, though — cmd's wildcard rename has genuinely surprising edge behaviour. For anything more than an extension change, use the loop below.

The script: find-and-replace in filenames

Save this as rename-pattern.bat. Edit the five variables at the top, then run it.

@echo off
setlocal enabledelayedexpansion

rem === Settings you edit ===
set "TARGETDIR=C:\path\to\files"
set "FILEMASK=*.txt"
set "FIND=draft"
set "REPLACE=final"
set "DRYRUN=1"          rem 1 = preview only, 0 = actually rename

rem === Do not edit below this line ===
pushd "%TARGETDIR%" || (echo Cannot open "%TARGETDIR%" & exit /b 1)

for %%F in (%FILEMASK%) do (
    set "OLD=%%~nxF"
    rem substring replace: !OLD! with FIND swapped for REPLACE
    set "NEW=!OLD:%FIND%=%REPLACE%!"
    if not "!OLD!"=="!NEW!" (
        if "%DRYRUN%"=="1" (
            echo [DRY RUN] "!OLD!"  --^>  "!NEW!"
        ) else if exist "!NEW!" (
            echo SKIP    : "!NEW!" already exists, leaving "!OLD!" alone
        ) else (
            ren "!OLD!" "!NEW!"
            echo RENAMED : "!OLD!"  --^>  "!NEW!"
        )
    )
)

popd
endlocal

What the non-obvious lines do:

  • setlocal enabledelayedexpansion lets me read a variable I just set inside the same loop iteration, using !OLD! instead of %OLD%. Without it, the loop would use the value from before the loop started — a classic cmd trap.
  • %%~nxF is the file's name plus extension with any path stripped. In a batch file the loop variable takes %%F; on the command line directly it would be %F.
  • set "NEW=!OLD:%FIND%=%REPLACE%!" is the substitution. cmd's %var:old=new% replaces every occurrence of old in the string, not just the first. So FIND=a, REPLACE=b on banana gives bbnbnb. Keep your FIND string specific.
  • The if not "!OLD!"=="!NEW!" guard means files that don't contain the pattern are ignored, so the output only lists what actually changed.
  • pushd/popd move into and back out of the target folder so ren operates there. ren cannot move a file between folders — it renames in place only.

Run it

Open Command Prompt, cd to wherever you saved it, and:

rename-pattern.bat

With DRYRUN=1 it prints lines like:

[DRY RUN] "q1-draft-report.txt"  --> "q1-final-report.txt"
[DRY RUN] "draft-notes.txt"  --> "final-notes.txt"

Read that list. If — and only if — every proposed rename is correct, edit the file, set DRYRUN=0, and run it again to commit.

Common variations

  • Add or remove a prefix: to strip a leading IMG_, set FIND=IMG_ and REPLACE= (empty). To add a prefix you can't do it with this replace logic; you'd rename to PREFIX_!OLD! instead — that's a one-line change to the ren line, ren "!OLD!" "PREFIX_!OLD!", but test it on a copy because it has its own collision cases.
  • Recurse into subfolders: swap the loop for for /r "%TARGETDIR%" %%F in (%FILEMASK%) do (...) and use ren "%%F" "!NEW!" with the full path. Recursion multiplies the blast radius, so dry-run it hard.
  • Case sensitivity: cmd's substring replace is case-insensitive and there's no switch to change that. If you need case-sensitive matching, this isn't the right tool — do it in PowerShell.

Verify it worked

List the folder and confirm the names are what you expect:

dir /b "C:\path\to\files"

/b gives bare filenames, one per line, which is easy to eyeball or pipe into a file for a before/after comparison:

dir /b "C:\path\to\files" > after.txt

If you captured a dir /b > before.txt beforehand, fc before.txt after.txt shows exactly what changed.

Undo

There is no automatic rollback. If the change was clean — a simple, unique substring swap with no skipped collisions — you can reverse it by running the same script with FIND and REPLACE swapped. For the example above, set FIND=final and REPLACE=draft, dry-run it, and confirm before committing.

That reversal is only safe when nothing was skipped and the replacement string didn't already occur in the original names. If the output during the real run showed any SKIP lines, or the pattern was ambiguous, do not trust a blind reverse — restore those files from your copy or backup instead. This is exactly why the first run should be against a copy of the folder.

For the official reference on ren, for, and variable substring substitution, see the Windows Commands documentation on Microsoft Learn (search "ren command" and "for command").

Written by
Ketan Aagja

Runs enterprise networks and security for a living, and writes Shore Up to turn two decades of hands-on Linux, Windows and mail-server work into guides you can actually use.

More about the author →

Was this article helpful?

Tap a star — no sign-in needed.

Be the first to rate this article.

Automate Temp and Log Cleanup with a Batch File

This guide builds a Windows batch file that deletes old log files from a folder you name and temporary files from a temp folder, based on how many days ago each file was last modified. Its purpose is to reclaim disk space on a schedule without you having to remember to do it.

9 min read

Bulk-Rename and Reorganise Files by Date with Bash

I keep ending up with directories that are one flat dumping ground — exported reports, scans, camera dumps — hundreds of files with no order. This guide builds a small bash script that reads each file's modification date, renames it with a YYYY-MM-DD_ prefix, and moves it into DEST/YYYY/MM/ folders. It is safe by default: it prints what it would do and moves nothing until you explicitly tell it to.

8 min read

Automate Moving AD Users Between OUs From a CSV

This procedure reads a CSV of user accounts and their destination OUs, then moves each account to its target OU with PowerShell's Move-ADObject . Moving a user changes its distinguished name (DN). That matters because anything scoped by DN or OU — Group Policy links, delegated permissions, and OU-based filters — will start or stop applying to the account the moment it moves. The move itself does not delete the account or its group memberships, and it is reversible if you know where the account came from — which is why the script below records the original OU of every user before it touches anything.

8 min read

Automate Expiring AD Password Notifications with PowerShell

This script reads every enabled AD user's password-expiry date, works out who expires within a window you set (say, the next 14 days), and emails each of those users a reminder. It reads Active Directory and sends mail — it does not change a single account, reset a password, or alter a policy. That makes it low-risk, but it can still misfire loudly: point it at the whole domain with a bad window and you can email hundreds of people at once, so treat the first live run as the dangerous part.

9 min read