Powershell file rename syntax - powershell

I'm trying to rename all the files in a given directory so that they have the sequential format 001a.jpg, 001b.jpg, 002a.jpg etc.
I have tried:
$c=1
get-childitem * | foreach {
if($c/2=[int]($c/2)){
rename-item -NewName {($c-1) + 'b.jpg'} else rename-item - NewName {$c + 'a.jpg'}}$c++} - whatif
But I am getting "the assignment operator is not valid".
The = in the third line is obviously not supposed to be an assignment operator. I just want to check whether the counter is on an even number. I found -ne for not equal to but PS didn't seem to like the -.
I am not sure about the syntax of the new names but the parser doesn't get that far ATM.
Grateful for any pointers.
Edit: I've just realised the numbers would skip but I can fix that once I understand the problem with what I already have.

As commented, if you do not leave spaces around the -eq or -ne operators, PowerShell will interpret the dash as belonging to the math, so
if($c/2-ne[int]($c/2))
Will fail.
Also, to test if a number is odd, it is probably quicker to do if ($c % 2) or if ($c -band 1).
Use -not to test for even numbers.

Several things you need to know (some are just recommendations):
Every if-else branch requires curly brackets
If you do not input the items to Rename-Item via the pipeline, you have to specify the path
If you specify the path for Rename-Item, you cannot use a script block for -NewName
You cannot use + if the left operand is int and the right is string, because it would be interpreted as addition and requires two numbers.
You cannot use the -WhatIf switch on the whole construct, but only individual commands
It will suffice to use the command only once, and use the if-else only to set the new name
You can simplify your condition using the modulus operator % (as explained in other answers and comments)
You can use the -Begin parameter for initialization
The * for Get-ChildItem is not necessary
Here is the updated code:
Get-ChildItem | foreach -Begin { $c = 1 } -Process {
if ($c % 2) {
$newName = "${c}a.jpg"
}
else {
$newName = "$($c-1)b.jpg"
}
Rename-Item -LiteralPath $_.FullName -NewName $newName -WhatIf
$c++
}
Here is an even shorter version, that pipes directly into Rename-Item
$c = 0
Get-ChildItem |
Rename-Item -NewName {if (++$script:c % 2) {"${c}a.jpg"} else {"$($c-1)b.jpg"}} -WhatIf

Related

Using Variables with Directories & Filtering

I'm new to PowerShell, and trying to do something pretty simple (I think). I'm trying to filter down the results of a folder, where I only look at files that start with e02. I tried creating a variable for my folder path, and a variable for the filtered down version. When I get-ChildItem for that filtered down version, it brings back all results. I'm trying to run a loop where I'd rename these files.
File names will be something like e021234, e021235, e021236, I get new files every month with a weird extension I convert to txt. They're always the same couple names, and each file has its own name I'd rename it to. Like e021234 might be Program Alpha.
set-location "C:\MYPATH\SAMPLE\"
$dir = "C:\MYPATH\SAMPLE\"
$dirFiltered= get-childItem $dir | where-Object { $_.baseName -like "e02*" }
get-childItem $dirFiltered |
Foreach-Object {
$name = if ($_.BaseName -eq "e024") {"Four"}
elseif ($_.BaseName -eq "e023") {"Three"}
get-childitem $dirFiltered | rename-item -newname { $name + ".txt"}
}
There are a few things I can see that could use some adjustment.
My first thought on this is to reduce the number of places a script has to be edited when changes are needed. I suggest assigning the working directory variable first.
Next, reduce the number of times information is pulled. The Get-ChildItem cmdlet offers an integrated -Filter parameter which is usually more efficient than gathering all the results and filtering afterward. Since we can grab the filtered list right off the bat, the results can be piped directly to the ForEach block without going through the variable assignment and secondary filtering.
Then, make sure to initialize $name inside the loop so it doesn't accidentally cause issues. This is because $name remains set to the last value it matched in the if/elseif statements after the script runs.
Next, make use of the fact that $name is null so that files that don't match your criteria won't be renamed to ".txt".
Finally, perform the rename operation using the $_ automatic variable representing the current object instead of pulling the information with Get-ChildItem again. The curly braces have also been replaced with parenthesis because of the change in the Rename-Item syntax.
Updated script:
$dir = "C:\MYPATH\SAMPLE\"
Set-Location $dir
Get-ChildItem $dir -Filter "e02*" |
Foreach-Object {
$name = $null #initialize name to prevent interference from previous runs
$name = if ($_.BaseName -eq "e024") {"Four"}
elseif ($_.BaseName -eq "e023") {"Three"}
if ($name -ne $null) {
Rename-Item $_ -NewName ($name + ".txt")
}
}

Rename and Increment Integer File Names

I have a bunch of files with integers that range from 30.pdf to 133.pdf as filenames. What I am trying to do is increment each filename, so 30.pdf should become 31.pdf, ..., and 133.pdf should become 134.pdf.
Does anybody know how I can achieve this?
I know I can loop through the directory with foreach ($f in dir) or display and even sort the filenames with get-childitem | sort-object, but this latter method obviously has issues sorting numerically.
No idea why something so simple is so difficult to figure out. Cannot find this anywhere online...
This should work assuming the BaseName of the files contains only digits as you have shown on your question. First you would need to sort the files descending to avoid collision and after that you can pipe them to Rename-Item using the -NewName script block:
Get-ChildItem path/to/files -File | Sort-Object { [int]$_.BaseName } -Descending |
Rename-Item -NewName {
[int]$n = $_.BaseName; '{0}{1}' -f (++$n), $_.Extension
}
We can take some shortcuts to make this easier since your file names are literally just integers. Start by using this statement to get a collection of integers that represents your files ($files is the output of Get-ChildItems from the parent directory):
$integersFromNames = $files | Select -ExpandProperty BaseName | foreach { $_ -as [int] } | sort ‐Descending
Now you have all of the existing files sorted from largest to smallest. You can use Rename-Item in a foreach loop to get you the rest of the way:
foreach ($oldNumber in $integersFromNames) {
$newNumber = $oldNumber + 1
Rename-Item -Path "$oldNumber.pdf" -NewName "$newNumber.pdf"
}
Please excuse any typos. I'm on my phone. Hopefully the concept comes across clearly.

Need to batch add characters to filenames using Powershell

I have a series of files all named something like:
PRT14_WD_14220000_1.jpg
I need to add two zeroes after the last underscore and before the number so it looks like PRT14_WD_14220000_001.jpg
I've tried"
(dir) | rename-Item -new { $_.name -replace '*_*_*_','*_*_*_00' }
Appreciate any help.
The closest thing to what you attempted would be this. In regex, the wildcard is .*. And the parentheses do grouping to refer to later with the dollar sign numbers.
dir *.jpg | rename-Item -new { $_.name -replace '(.*)_(.*)_(.*)_','$1_$2_$3_00' } -whatif
What if: Performing the operation "Rename File" on target "Item: C:\users\admin\foo\PRT14_WD_14220000_1.jpg Destination: C:\users\admin\foo\PRT14_WD_14220000_001.jpg".
Ok, here's my take when you want the number with max two zeroes padding. $num has to be an integer for the .tostring() method I want.
dir *.jpg | rename-item -newname { $a,$b,$c,$num = $_.basename -split '_'
$num = [int]$num
$a + '_' + $b + "_" + $c + '_' + $num.tostring('000') + '.jpg'
} -whatif
the following presumes your last part of the .BaseName will always need two zeros added to it. what it does ...
fakes getting the fileinfo object that you get from Get-Item/Get-ChildItem
replace that with the appropriate cmdlet. [grin]
splits the .BaseName into parts using the _ as the split target
adds two zeros to the final part from the above split
merges the parts into a $NewBaseName
gets the .FullName and replaces the original BaseName with the $newBaseName
displays that new file name
you will still need to do your rename, but that is pretty direct. [grin]
here's the code ...
# fake getting a file info object
# in real life, use Get-Item or Get-ChildItem
$FileInfo = [System.IO.FileInfo]'PRT14_WD_14220000_1.jpg'
$BNParts = $FileInfo.BaseName.Split('_')
$BNParts[-1] = '00{0}' -f $BNParts[-1]
$NewBasename = $BNParts -join '_'
$NewFileName = $FileInfo.FullName.Replace($FileInfo.BaseName, $NewBaseName)
$NewFileName
output = D:\Data\Scripts\PRT14_WD_14220000_001.jpg
The -replace operator operates on regexes (regular expressions), not wildcard expressons such as * (by itself), which is what you're trying to use.
A conceptually more direct approach is to focus the replacement on the end of the string:
Get-ChildItem | # `dir` is a built-in alias for Get-ChildItem`
Rename-Item -NewName { $_.Name -replace '(?<=_)[^_]+(?=\.)', '00$&' } -WhatIf
Note: The -WhatIf common parameter in the command above previews the operation. Remove -WhatIf once you're sure the operation will do what you want.
(?<=_)[^_]+(?=\.) matches a nonempty run (+) of non-_ chars. ([^_]) preceded by _ ((?<=_) and followed by a literal . ((?=\.)), excluding both the preceding _ and the following . from what is captured by the match ((?<=...) and (?=...) are non-capturing look-around assertions).
In short: This matches and captures the characters after the last _ and before the start of the filename extension.
00$& replaces what was matched with 00, followed by what the match captured ($&).
In a follow-up comment you mention wanting to not just blindly insert 00, but to 0-left-pad the number after the last _ to 3 digits, whatever the number may be.
In PowerShell [Core] 6.1+, this can be achieved as follows:
Get-ChildItem |
Rename-Item -NewName {
$_.Name -replace '(?<=_)[^_]+(?=\.)', { $_.Value.PadLeft(3, '0') }
} -WhatIf
The script block ({ ... }) as the replacement operand receives each match as a Match instance stored in automatic variable $_, whose .Value property contains the captured text.
Calling .PadLeft(3, '0') on that captured text 0-left-pads it to 3 digits and outputs the result, which replaces the regex match at hand.
A quick demonstration:
PS> 'PRT14_WD_14220000_1.jpg' -replace '(?<=_)[^_]+(?=\.)', { $_.Value.PadLeft(3, '0') }
PRT14_WD_14220000_001.jpg # Note how '_1.jpg' was replaced with '_001.jpg'
In earlier PowerShell versions, you must make direct use of the .NET [regex] type's .Replace() method, which fundamentally works the same:
Get-ChildItem |
Rename-Item -NewName {
[regex]::Replace($_.Name, '(?<=_)[^_]+(?=\.)', { param($m) $m.Value.PadLeft(3, '0') })
} -WhatIf

Bulk rename files in powershell and add a count to each filename

I am able to bulk rename files in a directory OR substitute each file name with a count, however I need both combined.
Bulk rename (keep first 2 charachters of original name):
Get-ChildItem * |
Rename-Item -NewName { $_.BaseName.Split('-')[0] + $_.Extension }
(partially hard coded solution, I know!)
Alternatively, add count:
$count = 1
Get-ChildItem * | % { Rename-Item $_ -NewName (‘{0}.xlsx’ -f $count++) }
(I am not even dreaming about trailing 0s)
I tried to combine both things, but to no avail. What am I doing wrong?
My failed attempt:
$count = 1
Get-ChildItem * |
Rename-Item -NewName { $_.BaseName.Split('-')[0] -f $count++ + $_.Extension }
You're misunderstanding how the format operator works. You need a format string with a placeholder ({0}) in order to make that operator work. I would also recommend putting grouping parentheses around that expression, even though that shouldn't be required in this case. Just to be on the safe side.
('foo {0} bar' -f $some_var) + $other_var
With that said, you apparently want to append the value of the counter variable to the fragment from the original file name rather than using that fragment as a format string. For that you can simply concatenate the counter to the fragment, just like you do with the extension.
For the counter to work correctly you also need to specify the correct scope. The way you're using it defines a new variable $counter in the local scope of the scriptblock every time a file is renamed, so the variable $counter in the parent scope is never incremented. Use the script: or global: scope modifier to get the variable you actually intend to use.
$count = 1
Get-ChildItem * |
Rename-Item -NewName { $_.BaseName.Split('-')[0] + $script:count++ + $_.Extension }
If you do want to use the format operator instead of string concatenation (+) you'd use it like this:
$count = 1
Get-ChildItem * |
Rename-Item -NewName { '{0}{1}{2}' -f $_.BaseName.Split('-')[0], $script:count++, $_.Extension }
To have the $count counted up, you have to go with the loop.
$count = 1
Get-ChildItem * | foreach-object { Rename-Item -NewName { $_.BaseName.Split('-')[0] -f
count++ + $_.Extension } }
% is the alias for the foreach-object cmdlet you used in the "add count" approach.
Just added it to you failed attempt.

Retain initial characters in file names, remove all remaining characters using powershell

I have a batch of files with names like: 78887_16667_MR12_SMITH_JOHN_713_1.pdf
I need to retain the first three sets of numbers and remove everything between the third "_" and "_1.pdf".
So this: 78887_16667_MR12_SMITH_JOHN_713_1.pdf
Becomes this: 78887_16667_MR12_1.pdf
Ideally, I'd like to be able to just use the 3rd "_" as the break as the third set of numbers sometimes includes 3 characters, sometimes 4 characters (like the example) and other times, 5 characters.
If I used something like this:
Get-ChildItem Default_*.pdf | Rename-Item -NewName {$_.name -replace...
...and then I'm stuck: can I state that everything from the 3rd "" and the 6th "" should be replaced with "" (nothing)? My understanding that I'd include ".Extension" to also save the extension, too.
You can use the -split operator to split your name into _-separated tokens, extract the tokens of interest, and then join them again with the -join operator:
PS> ('78887_16667_MR12_SMITH_JOHN_713_1.pdf' -split '_')[0..2 + -1] -join '_'
78887_16667_MR12_1.pdf
0..2 extracts the first 3 tokens, and -1 the last one (you could write this array of indices as 0, 1, 2, -1 as well).
Applied in the context of renaming files:
Get-ChildItem -Filter *.pdf | Rename-Item -NewName {
($_.Name -split '_')[0..2 + -1] -join '_'
} -WhatIf
Common parameter -WhatIf previews the rename operation; remove it to perform actual renaming.
mklement0 has given you a good and working answer. Here is another way to do it using a regex.
Get-ChildItem -Filter *.pdf |
ForEach-Object {
if ($_.Name -match '(.*?_.*?_.*?)_.*(_1.*)') {
Rename-Item -Path $_.FullName -NewName $($Matches[1..2] -join '') -WhatIf
}
}