Batch Rename by Pattern With Rename-Item
The workhorse for renaming in PowerShell is Get-ChildItem piped into Rename-Item. Get-ChildItem lists the files, the pipeline hands each one to Rename-Item, and the -NewName parameter decides what the new name should be. The trick that makes it powerful is that -NewName accepts a script block, so you can compute a name per file rather than pass a single fixed string. Inside that block, $_ is the current file, which gives you $_.Name, $_.BaseName, $_.Extension, and the full path to work with.
The simplest job is adding a prefix. Something like Get-ChildItem *.pdf | Rename-Item -NewName { "Invoice_" + $_.Name } walks every PDF in the folder and prepends Invoice_ to each existing name. The block runs once per file, so every file keeps its own tail and just gains the prefix.
Zero-padded sequential numbering needs a counter you increment yourself. Declare $i = 1 first, then run Get-ChildItem *.pdf | Rename-Item -NewName { $name = 'Invoice_{0:D3}{1}' -f $script:i, $_.Extension; $script:i++; $name }. The {0:D3} format specifier pads the number to three digits, so you get Invoice_001.pdf, Invoice_002.pdf, and so on, and the $script:i++ bumps the counter after each file. Sorting the Get-ChildItem output first (pipe it through Sort-Object Name) keeps the numbering predictable.
Reformatting text that's already in the name is a job for -replace, which takes a regular expression. To turn a date written as 20250314 into 2025-03-14 inside each filename you can run Get-ChildItem *.pdf | Rename-Item -NewName { $_.Name -replace '(\d{4})(\d{2})(\d{2})', '$1-$2-$3' }. The capture groups grab the year, month, and day, and the replacement string stitches them back with hyphens. Because -replace is regex, you can reorder, strip, or normalize almost any pattern that already exists in the old name.
Changing extensions is the same shape: Get-ChildItem *.jpeg | Rename-Item -NewName { $_.Name -replace '\.jpeg$', '.jpg' } rewrites the tail on every match. And you never have to fly blind. Add -WhatIf to any Rename-Item and PowerShell prints exactly what it would do without touching a single file, which is the single most important habit when you're about to rename hundreds of files at once. Add -Verbose to see each rename as it happens on the real run.
Two more flags matter for bulk work. -Recurse on Get-ChildItem walks subfolders so you can rename an entire tree in one pass, and -Filter (or a trailing Where-Object clause) narrows the set before anything gets renamed. For example Get-ChildItem -Recurse -Filter *.pdf | Where-Object { $_.Length -gt 0 } skips zero-byte files across every subfolder. Between the script block, -replace, a counter, and -WhatIf, PowerShell handles essentially any rename where the new name can be built from the old name, the extension, or file metadata.
CSV-Driven Rename (One-to-One Mapping)
Pattern rename gives every file a name derived from the same rule. But sometimes each file needs its own distinct name that no pattern can generate, and you already know what those names should be. That's the case for a CSV-driven rename, where you supply the mapping yourself and PowerShell just executes it.
Build a two-column CSV with a header row of OldName,NewName, then one row per file: scan001.pdf,Acme_Invoice_2025-03-14.pdf on the next line, scan002.pdf,Globex_Invoice_2025-03-15.pdf below that, and so on. Save it as rename-map.csv in the folder. Then drive it with Import-Csv piped into a ForEach-Object that calls Rename-Item once per row: Import-Csv rename-map.csv | ForEach-Object { Rename-Item -Path $_.OldName -NewName $_.NewName -WhatIf }. Import-Csv turns each row into an object whose properties are the column headers, so $_.OldName and $_.NewName pull the two values straight out.
Keep the -WhatIf on the first pass to preview the whole mapping, confirm the pairs line up, then remove it to commit. PowerShell is honest about the edge cases here rather than silent. If a row's OldName points at a file that isn't in the folder, Rename-Item raises an error for that row and moves on to the next, so one bad line doesn't halt the batch (add -ErrorAction SilentlyContinue if you want the errors suppressed). And if a NewName already exists on disk, Rename-Item refuses the operation and reports it rather than overwriting the existing file, which protects you from silently clobbering data.
This is how PowerShell gives every file a different, meaningful name without ever reading a document: you supply the names, the script applies them at scale. The catch is exactly that phrase. You have to build the mapping, which means you already had to know that scan001.pdf is the Acme invoice. When you don't, because the only place that fact lives is inside the file, the CSV approach can't help you author the mapping. Which is the wall.
Where PowerShell Hits the Content Wall
Everything so far reads two things: the filename and file metadata. Rename-Item never opens the document. You can push one step further with a module. Load a .NET PDF library into your session (a PdfPig or iTextSharp style assembly) or shell out to an external tool like pdftotext, and PowerShell can pull the text LAYER out of a born-digital PDF. Once you have the page text as a string, you're back on familiar ground: run a regex over it to grab the invoice number or the date, and feed that into Rename-Item -NewName. For born-digital PDFs, that genuinely works, and it's a reasonable amount of code.
The wall is scans. A scanned PDF is not text wearing a PDF wrapper, it's an image of a page. There is no text layer to extract, so pdftotext or a PDF library returns an empty string no matter how clean the scan looks to your eye. The invoice number is right there on screen, and every extraction call comes back with nothing, because to the parser the page is a picture.
The only way through is optical character recognition. To read a scan, your script has to call an OCR engine, either a local one like Tesseract or a cloud OCR API, hand it the page image, get text back, and only then run your regex. That's a legitimate technique, but notice what just happened to the scope. You're no longer writing a rename one-liner. You're building and maintaining an OCR pipeline: installing and configuring the engine, converting PDF pages to images at the right resolution, cleaning up OCR noise, and writing per-vendor regex because every invoice layout puts the number somewhere different. It works, but it is real, ongoing engineering, and it comes with none of the safety rails you'd want. There is no preview and no undo unless you build them yourself, so a regex that grabs the wrong field renames the whole batch wrong in a single pass. The deeper scanned-document workflow, and why scans are their own category of problem, lives on the hub page on how to rename scanned PDF files.
PowerShell Rename: What Works and Where It Stops
| Task | PowerShell approach | Works on scanned PDFs? |
|---|
| Pattern, prefix, or sequential number rename | Get-ChildItem piped to Rename-Item -NewName (script block plus counter) | No content needed |
| Regex reformat of text already in the name | Rename-Item -NewName with a -replace regex | N/A (works on the filename, not the file) |
| One-to-one mapping, a distinct name per file | Import-Csv piped to Rename-Item, you supply the names | Yes, but only because you supply the names |
| Content-based name from a born-digital PDF | Text-extraction module or pdftotext, then regex into Rename-Item | Yes for born-digital only (there is a text layer) |
| Content-based name from a scanned PDF | Must call an OCR engine (Tesseract or a cloud OCR API) from the script | Only if you wire up OCR yourself |