powershellloopsrename-item-cmdletforeach-object

Rename files in a foreach loop with Powershell - Strange behaviour


I am renaming the files of a directory. The way to rename them is to add a letter to the beginning of the file name, depending on the option a user chooses ("F" or "O") A possible solution to this exercise is the following:

$Path="C:\app\SandBox\" 
    Get-ChildItem -Path $Path -Filter *.xlsx | ForEach-Object {
        $opt = Read-Host "Do you want modify (F) this file or not (O)?"
        $name=$_.name
        $PathFinal=$Path+$name
        if ($opt -eq "F") {
           $newName="F"+$name
           Rename-Item -NewName $newName -Path $PathFinal
            } 
        if ($opt -eq "O") { 
           $newName="F"+$name
           Rename-Item -NewName $newName -Path $PathFinal
            } 
        }

The loop iterates as many times as there are files in the directory. However, when I try to change the code as follows:

 $Path="C:\app\SandBox\" 
    Get-ChildItem -Path $Path -Filter *.xlsx | ForEach-Object {
        $name=$_.name
        $opt = Read-Host "$name - Do you want modify (F) this file or not (O)?"
        if ($opt -eq "O") {
            $name=$_.Name
            Rename-Item -NewName "O$name" -Path $Path$name
            } 
        if ($opt -eq "F") { 
            $name=$_.Name
            Rename-Item -NewName "F$name" -Path $Path$name
            } 
        }

It turns out that, in some cases, the loop iterates one more time! If I have two files in the folder, sometimes the loop oterates three. Which may be due?

enter image description here

It should iterate twice, but iterate three. I can't think of what it could be due to, since when channeling it should pass only two files.


Solution

  • It should iterate twice, but iterate three. I can't think of what it could be due to, since when channeling it should pass only two files.

    Get-ChildItem works against the file system by asking for the first file matching a given filter, and then it continues asking the OS "what's the next matching file name after fileX", until the OS says "no more files".

    In this case, A.xlsx is renamed to FA.xlsx on the first iteration, and the OS is thus able to answer the question after the next iteration: "what's the next matching file name after B.xlsx" with "Next file is FA.xlsx".

    To enumerate only the files already in the directory when you start the script, place the call to Get-ChildItem in a subexpression or nested pipeline:

    $(Get-ChildItem -Path $Path -Filter *.xlsx) | ForEach-Object { ... }
    

    This will force PowerShell to wait for Get-ChildItem to finish executing before sending the first output item to ForEach-Object - and since Get-ChildItem is already "done" by the time the renaming starts, you don't risk seeing the same file multiple times.