How can I batch rename files in powershell using the following code:
$nr=1;Get-ChildItem -Filter *.jpg |
Rename-Item -Newname {"PPPPPPP_{0:d3}.jpg" -f $global:nr++}
where PPPPPPP is the name of parent folder containing these files.
Expected Output :
PPPPPPP_001.jpg
PPPPPPP_002.jpg
PPPPPPP_003.jpg
Files are located in C:\USER\MAIN\BLABLABLA\PPPPPPP folder.
Get the parent directory's name via
$_.Directory.Name
inside the script block.Cast the
$nr
variable to[ref]
so that you can modify its value directly in the caller's scope (via.Value
), which is preferable to using scope modifier$global:
- see below.-WhatIf
previews the renaming operation; remove it, once you're confident that the command will perform as intended.Optional reading: Modifying the caller's variables in a delay-bind script block or calculated property:
The reason you couldn't just use
$nr++
in your script block in order to increment the sequence number directly is:Delay-bind script blocks (such as the one passed to
Rename-Item -NewName
) and script blocks in calculated properties run in a child scope.Where-Object
andForEach-Object
, which run directly in the caller's scope.It is unclear whether that difference in behavior is intentional.
Therefore, attempting to modify the caller's variables instead creates a block-local variable that goes out of scope in every iteration, so that the next iteration again sees the original value:
Using a calculated property as an example:
While you can use a scope modifier such as
$global:
or$script:
to explicitly reference a variable in a parent scope, these are absolute scope references that may not work as intended: Case in point: if you move your code into a script,$global:nr
no longer refers to the variable created with$nr = 1
.Quick aside: Creating global variables should generally be avoided, given that they linger in the current session, even after a script exits.
The robust approach is to use a
Get-Variable -Scope 1
call to robustly refer to the immediate parent scope:While this technique is robust, it is also cumbersome and verbose.
But you could improve the efficiency as follows:
$nr = 1; $nrVar = Get-Variable nr; 1..2 | Select-Object { '#' + $nrVar.Value++ }
Using the
[ref]
type offers a more concise alternative, though the solution is a bit obscure:Casting a variable to
[ref]
returns an object whose.Value
property can access - and modify - that variable's value. Note that since$nr
isn't being assigned to at that point, it is indeed the caller's$nr
variable that is referenced.Another, slightly different way:
EDIT: modified due to mklement0s hint.
To get the parent name, use
.Directory.Name
as another parameter for the format operatorIf the output looks OK remove the -WhatIf
This will only work while there are no overlapping ranges doing the rename, in that case you should probaply use a temporary extension.
Sample output on my German locale Windows