Naiem Ayoub
Naiem Ayoub

Reputation: 29

renaming multiple folders (works with errors!)

I'm new to PowerShell, and have written the following script, using ISE, trying to rename a bunch of folders in the same directory, that strictly have the date format 20201130 into 2020-11-30 (simply adding dashes) the script kinda works, but I have 3 problems with it.

1- when it ran it threw a bunch of errors relating to Rename-Item the error:

Rename-Item : Source and destination path must be different.
At line:13 char:25
+ ... ChildItem | Rename-Item -NewName { $_.Name -replace $file, $MyNewName ...
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : WriteError: (D:\Google Drive...Term 2\testings:String 
   ) [Rename-Item], IOException
    + FullyQualifiedErrorId : RenameItemIOError,Microsoft.PowerShell.Commands.Renam 
   eItemCommand

2- when I tested it with a folder named (234567890) it still worked although the lenght of this folder is not eq to 8!

3- when the above renaming happened the resulting name was "1234-56-7890" which is strange since $MyNewName should end with 2 characters! $file.Substring( 6 , 2 )

the script:

$FileNameArr_All = (Get-ChildItem).Name
$i = 0

foreach($file in $FileNameArr_All){
    
    if ( ($file -match "^\d+$") -and ($file.Length -eq 8) ){
    
        [string] $MyNewName = $file.Substring( 0 , 4 )+"-"+$file.Substring( 4 , 2 )+"-"+$file.Substring( 6 , 2 )
        
        #the actual renamining of the item, where the error is:
        Get-ChildItem | Rename-Item -NewName { $_.Name -replace $file, $MyNewName }

        $message = "the item " + $file + " has been renamed : " + $MyNewName
        Write-Output $message
        $i ++

        Write-Output $file.Length
    }
}
if ($i -eq 0){
    Write-Output "No mathcing items found."
}
else{
    $message2 = "total of items affected: " + $i
    Write-Output $message2
}

I know that's a lot but its my first post, and my first script, I appreciate helping with any of the points.
Many thanks :)

Upvotes: 2

Views: 152

Answers (4)

mklement0
mklement0

Reputation: 439367

There's good information in the existing answers, but let me boil it down conceptually and offer a more streamlined solution:

  • You're looping over all directory names, individually, yet you're trying to rename all items in each loop iteration, due to using just Get-ChildItem - no arguments - as input to Rename-Item:

    • Get-ChildItem | Rename-Item ... sends all files and subdirectories to Rename-Item; you're effectively trying to rename all of them to their existing names (see next bullet point), which is a quiet no-op for files, but triggers the Source and destination path must be different error you saw for directories.
  • $MyNewName is the complete new file name, so there is no reason to use -replace on the original directory name: since $MyNewName isn't a substring of the existing names, $_.Name -replace $file, $MyNewName is effectively a no-op - the existing $_.Name is passed through.

    • If you were renaming a single item, just passing $MyNewName - not inside a delay-bind script block ({ ... }) - would be enough.

However, you can streamline your solution to use a single pipeline, comprising a Get-ChildItem call that uses a wildcard expression to match the directories of interest, and Rename-Item with a delay-bind script block to insert the - chars. in the names of the matching items via the regex-based -replace operator:

# Create two sample directories
$null = New-Item -Force -Type Directory '12345678', '87654321'

Get-ChildItem -Directory -Path ('[0-9]' * 8) |
  Rename-Item -NewName { $_.Name -replace '(.{4})(.{2})(.{2})', '$1-$2-$3'  } -Verbose

The above performs the desired renaming and, due to use of the common -Verbose parameter, prints the following (line breaks for readability):

VERBOSE: Performing the operation "Rename Directory" on target "Item: ...\12345678 
         Destination: ...\1234-56-78".       
VERBOSE: Performing the operation "Rename Directory" on target "Item: ...\87654321
         Destination: ...\8765-43-21".

Note:

  • If you want to preview the renaming operation, use the common -WhatIf parameter with Rename-Item.

  • If you need to know whether any files matched and were therefore renamed, capture the Get-ChildItem call in a variable first:

    $files = Get-ChildItem -Directory -Path ('[0-9]' * 8)
    
    $files | Rename-Item -NewName { $_.Name -replace '(.{4})(.{2})(.{2})', '$1-$2-$3'  } -Verbose
    
    Write-Verbose -Verbose $(if ($files) { "$($files.Count) directories renamed." } else { 'No matching directories found.' })
    

Upvotes: 2

Buxmaniak
Buxmaniak

Reputation: 478

This script works a little bit more reliable, because it use the retun objects of "Get-ChildItem" and not only the file/folder names.

$i = 0
Get-ChildItem -Directory "C:\UserData\Test" | ForEach-Object {
    if (($_.Name) -match "^\d{8}$") {
        [System.String]$NewFolderName = ($_.Name).Insert(6, "-").Insert(4, "-")
        Rename-Item -Path ($_.FullName) -NewName $NewFolderName
        Write-Output ("the item {0} ({1}) has been renamed : {2}" -f  ($_.Name), (($_.Name).Length), $NewFolderName)
        $i++
    }
}

if ($i) {
    Write-Output ("total of items affected: {0}" -f $i)
} else{
    Write-Output "No matching items found."
}

Upvotes: 0

Theo
Theo

Reputation: 61168

You can have the regex -match in the Where-Object clause check the foldername for having exactly 8 digits and rename only those:

$i = 0
Get-ChildItem -Path 'D:\Test' -Directory | 
    Where-Object { $_.Name -match '^\d{8}$' } | 
    ForEach-Object {
        $newName = $_.Name -replace '(\d{4})(\d{2})(\d{2})', '$1-$2-$3'
        Write-Host "Renaming folder $($_.FullName) to: $newName"
        $_ | Rename-Item -NewName $newName -WhatIf
        $i++
    }

if ($i) {
    Write-Host "Total of items affected: $i"
}
else {
    Write-Host "No matching folder names found"
}

Here, the -WhatIf switch makes sure you only get to see what would happen. Nothing actually gets renamed yet. If you are satisfied with that output, remove the -WhatIf switch from the Rename-Item cmdlet.

Also, the regex -replace makes creating the new name format a lot easier than using Substring() multiple times, by capturing the digits in 3 diferent 'backreferences' and output them with dashed in between.

If you don't care about the Write-Host messages at any point, the code can be shortened to

Get-ChildItem -Path 'D:\Test' -Directory | 
    Where-Object { $_.Name -match '^\d{8}$' } | 
    Rename-Item -NewName {$_.Name -replace '(\d{4})(\d{2})(\d{2})', '$1-$2-$3'} -WhatIf

Upvotes: 1

Buxmaniak
Buxmaniak

Reputation: 478

instead of ...

Get-ChildItem | Rename-Item -NewName { $_.Name -replace $file, $MyNewName }

try ...

Rename-Item -Name $file -NewName $MyNewName

Upvotes: 1

Related Questions