powershellpowershell-7.0powershell-5.1rename-item-cmdlet

Rename-Item fails with 'Cannot rename because item at 'C:\Folder\ Example App Folder ' does not exist


I have a folder that has 100s of subfolders and files within those subfolders that have names with leading and trailing spaces. These folders and files were created using a Node JS app. I need to now remove the leading and trailing spaces from the file names of the folders and files.

I found this script which seems to be built for this purpose.

If I use the function to create files/folders with leading spaces mentioned on the same blog, the script is able to rename those.

However, it does not work with files/folders created using the node JS app. When renaming the folder/file, it fails with -

Rename-Item : Cannot rename because item at 'C:\Folder\ Example App Folder ' does not exist.
At C:\renamefileswithspaces.ps1:25 char:13
+             Rename-Item -LiteralPath $_.fullname -NewName (“{0}{1}{2} ...
+             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [Rename-Item], PSInvalidOperationException
    + FullyQualifiedErrorId : InvalidOperation,Microsoft.PowerShell.Commands.RenameItemCommand

If I run the following cmdlets for just one folder (outside the script), it fails with the same error -

$f = Get-ChildItem -Path 'C:\Folder' -Filter "*Example*"

Rename-Item -LiteralPath $f.FullName -NewName $f.Name.Trim()

Rename-Item : Cannot rename because item at 'C:\Folder\ Example App Folder ' does not exist.
At line:1 char:1
+ Rename-Item -LiteralPath $f.FullName -NewName $f.Name.Trim()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [Rename-Item], PSInvalidOperationException
    + FullyQualifiedErrorId : InvalidOperation,Microsoft.PowerShell.Commands.RenameItemCommand

Why does Rename-Item fail for files/folders created using the app whereas it works for files/folders created using PowerShell?

Edit: Running on Windows Server 2016 Standard. PowerShell version - 5.1.14393.3866

Edit 2: Updated to PowerShell 7.1.4 from here and the issue still exists.


Solution

  • Trailing spaces in file / directory names are problematic on Windows (unless a filename extension is also present, and the trailing spaces are only in the base file name):

    PowerShell, .NET, cmd.exe, and File Explorer:

    While the file-system itself allows such names, it's clearly not a good idea to create them.

    Node.js allows you to create them, but fortunately also allows you to target them later for renaming or deletion.

    Thus, the workaround is to use Node.js for renaming too; e.g.:

    node -e "fs.renameSync(' Example App directory ', 'Example App directory')"
    

    Here's a self-contained example that uses a temporary folder to enumerate all subdirectories and rename them by removing leading and trailing whitespace from their names, if needed:

    # Switch to a temporary directory.
    Push-Location -ea Stop ($tmpDir = (New-Item -Type Directory -Force (Join-Path $env:TEMP/ $PID)).FullName)
    
    try {
    
      # Create a test directory whose name has trailing (and leading) spaces.
      # Note: 
      #   * You cannot do this with PowerShell / cmd.exe commands.
      #   * To delete or rename such a directory, use Node.js too:
      #       node -e "fs.rmdirSync(' Example App directory ')"
      #       node -e "fs.renameSync(' Example App directory ', 'Example App directory')"
      node -e "fs.mkdirSync(' Example App directory ')"
      if ($LASTEXITCODE) { throw }
    
      # Create another directory, but without leading or trailing whitespace.
      $null = New-Item -Type Directory 'Example without surrounding spaces'
    
      # Enumerate all directories and rename them, if needed, 
      # by trimming leading and trailing whitespace.
      # Note: The Get-ChildItem call is enclosed in (...) to guard against renamed directorys
      #       re-entering the enumeration.
      (Get-ChildItem -LiteralPath . -Directory -Filter *) | ForEach-Object {
        # Determine the full path with the name part trimmed.
        $trimmedPath = Join-Path (Split-Path -LiteralPath $_.FullName) ($_.BaseName.Trim() + $_.Extension.Trim())
        if ($trimmedPath -ne $_.FullName) { # Trimming is needed.
          Write-Verbose -vb "Renaming '$($_.FullName)' to '$($trimmedPath)'..."
          # Perform the renaming via Node.js
          node -e ("fs.renameSync('{0}', '{1}')" -f ($_.FullName -replace "[\\']", '\$0'), ($trimmedPath -replace "[\\']", '\$0'))
          if ($LASTEXITCODE) { Write-Error "Renaming '$($_.FullName)' to '$($trimmedPath)' failed with exit code $LASTEXITCODE." }
        }
        else { # Trimming not needed.
          Write-Verbose -vb "Name has no leading or trailing spaces: '$($_.FullName)'"
        }
      }
    
      Write-Verbose -vb "Names after:"
      (Get-ChildItem).Name.ForEach({"[$_]"})
    
    }
    finally {
      # Clean up.
      Pop-Location; Remove-Item $tmpDir -Recurse
    }
    

    Sample output:

    VERBOSE: Renaming 'C:\Users\jdoe\AppData\Local\Temp\3712\ Example App directory ' to 'C:\Users\jdoe\AppData\Local\Temp\3712\Example App directory'...
    VERBOSE: Name has no leading or trailing spaces: 'C:\Users\jdoe\AppData\Local\Temp\3712\Example without surrounding spaces'
    VERBOSE: Names after:
    [Example App directory]
    [Example without surrounding spaces]
    

    Note: