1

Renaming files from a CSV

I am trying to write a script to reorganize files in a folder structure using csv as index file but I can´t figure out in how to solve the Rename-Item error.

Questions

  1. Is there others way to write this script in order to achieve the same results more easily?
  2. How to pass the right parameters to Rename-Item?

My csv file template

folderName                          newName                                      oldName          
----------                          -------                                      -------          
01 Course Overview                  01_Course_Overview                           1280x720.mp4     
02 Introduction to PowerShell       01_Introduction to PowerShell                1280x720 (1).mp4 
02 Introduction to PowerShell       02_Who Is This Course For?                   1280x720 (2).mp4 
02 Introduction to PowerShell       03_What Is PowerShell?                       1280x720 (3).mp4 
02 Introduction to PowerShell       04_Windows PowerShell and PowerShell 7       1280x720 (4).mp4 

PowerShell Script

$csv = Import-Csv '.\index.csv' -Delimiter ';'
$newFolders = $csv.folderName | Sort-Object -Unique
$listFolders = Get-ChildItem -Directory | Select-Object Name
$listFiles = Get-ChildItem | Where {$_.extension -eq ".mp4"}

ForEach ($a in $newFolders){
    
    If ($listFolders.Name -contains $a){
        Write-Host "The Folder $a exist"
    }
    else{
        New-Item -Path $pwd.Path -Name $a -Type Directory | Out-Null
        Write-Host "The folder $a has been created"
    }

}

ForEach ($b in $csv){
    
    If ($listFiles.Name -contains $b.oldName){
        Write-Host "File $($b.oldName) exist"
        Write-Host "Renaming file to: "$($b.newName)"
        
        #Rename-Item $($b.oldName) -NewName $($b.newName)
        #Write-Host "Moving file to: "$($b.folderName)"
        #Move-Item .\$($b.newName) -Destination .\$($b.folderName)
    }
    else{
        Write-Host "File $($b.oldName) doesn't exist" `n
    }
    
}

Error when executin Rename-Item

No D:\Downloads\Pluralsight\_PowerShell_Essentials\01_Powershell_Getting_Started\Temp\indexfiles.ps1:30 caractere:9
+         Rename-Item $($b.oldName) -NewName $($b.newName)
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (D:\Downloads\Pl...280x720 (2).mp4:String) [Rename-Item], ArgumentException
    + FullyQualifiedErrorId : RenameItemArgumentError,Microsoft.PowerShell.Commands.RenameItemCommand
chrispaiva
  • 13
  • 2

2 Answers2

0

Here you have an example of how this can be done, this is mainly a test case, it will create the files as you show us on the CSV and move them to the new folders based on the folderName column.

The code will look for the files on the current directory, before testing it with the real files, Set-Location (cd) to that folder.

If you're not sure if the code will work you can add a -WhatIf switch to Rename-Item and Move-Item.

Note, I have removed ? from the newName column since it's an invalid character on Windows. See this answer for more details.

# Go to a temporary folder for testing
Set-Location path/to/temporaryfolder/here

# Here you would use:
# $csv = Import-Csv path/to/csv.csv
$csv = @'
folderName                          newName                                      oldName
01 Course Overview                  01_Course_Overview                           1280x720.mp4
02 Introduction to PowerShell       01_Introduction to PowerShell                1280x720 (1).mp4
02 Introduction to PowerShell       02_Who Is This Course For                    1280x720 (2).mp4
02 Introduction to PowerShell       03_What Is PowerShell                        1280x720 (3).mp4
02 Introduction to PowerShell       04_Windows PowerShell and PowerShell 7       1280x720 (4).mp4
'@ -replace '  +',',' | ConvertFrom-Csv

# Create test files, this part is only for testing the code
$csv.foreach({ New-Item $_.oldName -ItemType File })

foreach($line in $csv)
{
    if(-not (Test-Path $line.folderName))
    {
        # Create the Folder if it does not exist
        New-Item $line.folderName -ItemType Directory -Verbose
    }
    Rename-Item -LiteralPath $line.oldName -NewName $line.newName
    Move-Item -LiteralPath $line.newName -Destination $line.folderName
}
Santiago Squarzon
  • 41,465
  • 5
  • 14
  • 37
  • @chrispaiva I'm guessing this could be because your files could have invalid characters in their name. See my update, using `-LiteralPath` instead of `-Path`. Hope it works – Santiago Squarzon Jan 18 '22 at 02:02
  • 1
    Really nice and easier approach Santiago, but I am still getting some erros and I guess it is because filename has special caracters in the name. MODO DETALHADO: Realizando a operação "Renomear Arquivo" no destino "Item: D:\Downloads\Pluralsight\_PowerShell_Essentials\01_Powershell_Getting_Started\Teste\1280x720 (3).mp4 Destino: **D:\Downloads\Pluralsight\_PowerShell_Essentials\01_Powershell_Getting_Started\Teste\03_What Is PowerShell?".** – chrispaiva Jan 18 '22 at 02:04
  • @chrispaiva see my comment above and the update using `-LiteralPath`. It should work with it – Santiago Squarzon Jan 18 '22 at 02:05
  • Even with -Literalpah I got errors, but is because special caracters like **?** I need to correct it inside csv or replace it in powershell and then update csv to have the names without those caracters. Thanks for your prompt response and help. – chrispaiva Jan 18 '22 at 02:24
  • @chrispaiva yes `?` is an invalid character and I would definitely not recommend to use it on files or folders. See this answer for the list of invalid chars: https://stackoverflow.com/a/31976060/15339544. I'm quite sure that if you remove them from your CSV the script should work fine, as long as the original files don't have special characters too – Santiago Squarzon Jan 18 '22 at 02:31
0

If I understand correctly, your real CSV file contains folder and/or file names with characters that are invalid like the ?.

To fix that, you can choose to remove those characters from the CSV file first, OR make sure you remove them before creating a folder or renaming a file.
For both options, you can use this small helper function:

function Remove-InvalidNameChars {
    param(
        [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
        [String]$Name,

        [ValidateSet('File', 'Path')]
        [string]$Type ='File'
    )

    if ($Type -eq 'File') {
        $invalidChars = [IO.Path]::GetInvalidFileNameChars() -join ''
    }
    else {
        $invalidChars = [IO.Path]::GetInvalidPathChars() -join ''
    } 
    # build a regex string from the invalid characters
    $removeThese = "[{0}]" -f [RegEx]::Escape($invalidChars)
    # output the name with invalid characters removed
    $Name -replace $removeThese
}

Method 1: remove the invalid characters from the CSV file and use cleaned-up data:

$sourcePath = 'D:\Test'
$csvFile    = Join-Path -Path $sourcePath -ChildPath 'index.csv'
$csvData    = Import-Csv -Path $csvFile -Delimiter ';'
foreach ($item in $csvData) {
    $item.folderName = Remove-InvalidNameChars -Name $item.folderName -Type Path
    $item.newName    = Remove-InvalidNameChars -Name $item.newName -Type File
}
$csvData | Export-Csv -Path $csvFile -Delimiter ';' -Force  # rewrite the CSV file if you like

# now use the cleaned-up data in $csvData for the rest of the code:
foreach ($item in $csvData) {
    # create the output folder if this does not already exist
    $targetPath = Join-Path -Path $sourcePath -ChildPath $item.folderName
    $null = New-Item -Path $targetPath -ItemType Directory -Force
    # move and rename the file if found
    $sourceFile = Join-Path -Path $sourcePath -ChildPath $item.oldName
    if (Test-Path -Path $sourceFile -PathType Leaf) {
        $targetFile = Join-Path -Path $targetPath -ChildPath $item.newName
        Move-Item -Path $sourceFile -Destination $targetFile
    }
}

Method 2: leave the csv data as-is and make sure you remove invalid characters while renaming/moving:

$sourcePath = 'D:\Test'
$csvFile    = Join-Path -Path $sourcePath -ChildPath 'index.csv'
$csvData    = Import-Csv -Path $csvFile -Delimiter ';'
foreach ($item in $csvData) {
    # create the output folder if this does not already exist
    $targetPath = Join-Path -Path $sourcePath -ChildPath (Remove-InvalidNameChars -Name $item.folderName -Type Path)
    $null = New-Item -Path $targetPath -ItemType Directory -Force
    # move and rename the file if found
    $sourceFile = Join-Path -Path $sourcePath -ChildPath (Remove-InvalidNameChars -Name $item.oldName -Type File)
    if (Test-Path -Path $sourceFile -PathType Leaf) {
        $targetFile = Join-Path -Path $targetPath -ChildPath (Remove-InvalidNameChars -Name $item.newName -Type File)
        Move-Item -Path $sourceFile -Destination $targetFile
    }
}

Note that Move-Item can move a file to a new destination and rename it at the same time, so you do not need Rename-Item


P.S. I noticed in your example CSV there are no extensions to the newName filenames..
If that is the case in real life, you need to add these aswell.

For that change the Move-Item line to:

Move-Item -Path $sourceFile -Destination ([IO.Path]::ChangeExtension($targetFile, [IO.Path]::GetExtension($sourceFile)))
Theo
  • 57,719
  • 8
  • 24
  • 41