I'm currently reading a CSV file and creating a folder structure dependent on column values and copying files into these sub-directories. I'm currently struggling to adjust this, so that the file name is amended upon the copy to DotDate_CurrentName (DotDate is a column in the csv).
##Pull the CSV & Create Directories
Echo "Getting root directory for CSV File" #via root variable
#import the csv file and loop through the results
Echo "Importing CSV file"
Import-Csv -Path "$($FilePath)\$($DateStr)_$($TimeStr)_Export.csv" | ForEach-Object {
Echo "Building subpath and fullpath strings"
$subPath = [System.IO.Path]::Combine($_.'ID' + '_' + $_.'First Name' + '_' + $_.'Surname', $_.Area, $_.SubArea, $_.'DotDate')
$fullPath = Join-Path -Path $rootPath -ChildPath $subPath
"Test fullpath and build path from strings"
if (!(Test-Path -Path $fullPath -PathType Container)) {
New-Item -Path $fullPath -ItemType Directory | Out-Null
}
Copy-Item -Path $_.'Document File Path' -Destination $fullPath
}
You do not need to copy then rename; you can do both in the same action. To prepend the DotDate, you can do something like this:
#requires -Version 4
#(Import-Csv -Path "$FilePath${DateStr}_${TimeStr}_Export.csv").ForEach{
$path = [IO.Path]::Combine(
$rootPath,
('{0}_{1}_{2}' -f $_.ID, $_.'First Name', $_.Surname),
$_.Area,
$_.SubArea,
$_.DotDate
)
if (-not (Test-Path -Path $path -PathType Container)) {
New-Item -Path $path -ItemType Directory >$null
}
$copy = [IO.FileInfo]$_.'Document File Path'
$copy | Copy-Item -Destination "$path\$($_.DotDate)_$($copy.Name)"
}
Related
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
Is there others way to write this script in order to achieve the same results more easily?
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
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
}
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)))
I have a small script that can successfully copy all the files from folders and subfolders and append the creation time, but the files in the subfolders do not have the creation time appended to their names.
How can I append the creation date to all files in a folder and the subfolders?
My current script is:
$path = "C:\test1"
$destination = "C:\test2"
Get-ChildItem -path $path | ForEach-Object{
$newname = $_.CreationTime.toString("yyyy-MM-dd") + $_.BaseName +$_.Extension
(Copy-Item -Recurse -Path $_.FullName -Destination ( Join-Path $destination $newname))
}
You were really close, but the -Recurse switch should have been on Get-ChildItem and within the loop you need to make sure the destination subfolder paths exist.
Try
$source = "C:\test1"
$destination = "C:\test2"
Get-ChildItem -Path $source -File -Recurse | ForEach-Object {
# create the new target folderpath for the copy
$targetPath = Join-Path -Path $destination -ChildPath $_.DirectoryName.Substring($source.Length)
# make sure the target path exists, if not create it
$null = New-Item -ItemType Directory -Path $targetPath -Force
# create a new filename with CreationDate prefixed
$newName = '{0:yyy-MM-dd}{1}{2}' -f $_.CreationTime, $_.BaseName, $_.Extension
# copy the file
$_ | Copy-Item -Destination (Join-Path -Path $targetPath -ChildPath $newname) -Force
}
While you could create your own recursive method to copy files and rename them as you go, it would be easier to use Copy-Item recursively and rename the files and folders afterwards:
$Source = "src"
$Destination = "dst"
Copy-Item -Recurse $Source $Destination
foreach ($Item in (Get-ChildItem -Recurse -File $Destination)) {
Rename-Item $Item ($Item.Name + "-" + $Item.CreationTime.toString("yyyy-MM-dd"))
}
I'm writing an archiving script which collecting desired files to an array then adding them to an archive 1 by 1.
I came to a problem when there is DIR1/file.ext and DIR2/file.ext because DIR2's file going to overwrite the previous.
How can I set unique filename or how it's possible to solve it on the fly instead of copying files to a dir with structures then zip the whole dir?
Here is my code:
# GET FILE LIST
$outgoingfiles = Get-ChildItem -Depth 1 -Filter "*.EXT" | Where-Object { $_.DirectoryName -like "*OUTGOING*" }
# Handle if OUTGOING/archive dir is exists
if(-not (Test-Path "OUTGOING/archive")) {
New-Item -Path "OUTGOING/archive" -ItemType Directory
}
# ZIP outgoing files
ForEach ($outgoing in $outgoingfiles) {
Compress-Archive $outgoing.FullName -Update -DestinationPath $zippath
}
Thank you!
I don't think there is a way to tell Compress-Archive to rename files when a file with the same name is already included in the zip.
What you can do is create a temporary folder, copy all files to there and if needed rename them. Then create the zip file using the unique files in that folder.
Finally, remove the temp folder again:
$zippath = 'D:\Test\OutGoing.zip' # path and filename for the output zip file
$rootPath = 'D:\Test' # where the files can be found
# create a temporary folder to uniquely copy the files to
$tempFolder = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ([Guid]::NewGuid().Guid)
$null = New-Item -ItemType Directory -Path $tempFolder
# create a hashtable to store the fileHash already copied
$fileHash = #{}
# get the list of files and copy them to a temporary folder
Get-ChildItem -Path $rootPath -Depth 1 -Filter '*.EXT' -File | Where-Object { $_.DirectoryName -like "*OUTGOING*" } | ForEach-Object {
$count = 1
$newName = $_.Name
# test if the file name is already in the hash and if so, append a counter to the basename
while ($fileHash.ContainsKey($newName)) {
$newName = "{0}({1}){2}" -f $_.BaseName, $count++, $_.Extension
}
# store this file name in the hash and copy the file
$fileHash[$newName] = $true
$newFile = Join-Path -Path $tempFolder -ChildPath $newName
$_ | Copy-Item -Destination $newFile -Force
}
# append '*.*' to the temporary folder name.
$path = Join-Path -Path $tempFolder -ChildPath '*.*'
# next, get the list of files in this temp folder and start archiving
Compress-Archive -Path $path -DestinationPath $zippath -Update
# when done, remove the tempfolder and files
Remove-Item -Path $tempFolder -Force -Recurse
Hope that helps
I would just copy the files along with their parent directories to a destination folder, then zip it up with Compress-Archive. Then you don't have to worry about making filenames unique.
Demo:
$sourceFolder = "C:\\"
$destinationFolder = "C:\\OUTGOING"
# Create destination folder if it doesn't exist
if (-not(Test-Path -Path $destinationFolder -PathType Container))
{
New-Item -Path $destinationFolder -ItemType Directory
}
# Get all .exe files one level deep
$files = Get-ChildItem -Path $sourceFolder -Depth 1 -Filter *.ext
foreach ($file in $files)
{
# Get standalone parent directory e.g. DIR1, DIR2
$parentFolder = Split-Path -Path (Split-Path -Path $file.FullName) -Leaf
# Create destination path with this parent directory
$destination = Join-Path -Path $destinationFolder -ChildPath $parentFolder
# Create destination parent directory if it doesn't exist
if (-not(Test-Path -Path $destination -PathType Container))
{
New-Item -Path $destination -ItemType Directory
}
# Copy file to parent directory in destination
Copy-Item -Path $file.FullName -Destination $destination
}
# Zip up destination folder
# Make sure to pass -Update for redoing compression
Compress-Archive -Path $destinationFolder -DestinationPath "OUTGOING.zip" -Update -CompressionLevel Optimal
I've seen similar questions and used them as a basis for what i'm trying to do here. I have a folder with many files that are named like "Author Name - Book Title.azw".
I'd like to create sub-folders for each author and move all their books into that folder. This is the script i have so far. It successfully creates folder for the Authors, but it chokes on the move-item, says Could not find a part of the path.
$files = Get-ChildItem -file
foreach ($file in $files){
$title = $file.ToString().Split('-')
$author = $title[0]
if (!(Test-Path $author))
{
Write-Output "Creating Folder $author"
New-Item -ItemType Directory -Force -Path $author
}
Write-Output "Moving $file to $author"
Move-Item -Path $file -Destination $author -Force
}
You have to use this:
Get-ChildItem -file | foreach {
$title = $_.ToString().Split('-')
$author = $title[0]
if (!(Test-Path $author))
{
Write-Host "Creating Folder $($author)"
New-Item -ItemType Directory -Force -Path "$author"
}
Write-Host "Moving $($_) to $($author)"
Move-Item -Path "$_" -Destination "$author" -Force
}
You must surround file paths in double quotes. So your code was not working.
From my understanding, you want to move files in a directory to sub folders where the author names are used as the sub folder names. You can try this solution to achieve this.
# Folder which contains files
$Path = "PATH_TO_FILES"
# Make sure path is a directory
if (Test-Path $Path -PathType Container) {
# Go through each file in folder
Get-ChildItem -Path $Path | ForEach-Object {
# Get full path of file
$filePath = $_.FullName
# Extract author
$author = $_.BaseName.Split("-")[0].Trim()
# Create subfolder if it doesn't exist
$subFolderPath = Join-Path -Path $Path -ChildPath $author
if (-not (Test-Path -Path $subFolderPath -PathType Container)) {
New-Item -Path $subFolderPath -ItemType Directory
}
# Move file to subfolder
Move-Item -Path $filePath -Destination $subFolderPath
}
}
I am looking for some help to create a PowerShell script to merge or copy one directory to another that the destination directory has files with the same name as the source.
I need to keep both, the script can append a number to the source file if it has a file of duplicate name in the destination.
Here is a sample script that deals with one file, but I need to set a directory and let it loose recursively on the entire directory.
$SourceFile = "C:\Temp\File.txt"
$DestinationFile = "C:\Temp\NonexistentDirectory\File.txt"
if ((Test-Path $DestinationFile) -eq $false) {
New-Item -ItemType File -Path $DestinationFile -Force
}
Copy-Item -Path $SourceFile -Destination $DestinationFile
try this
$SourceDir = "C:\Temp"
$DestinationDir = "C:\Temp2\NonexistentDirectory"
#create dir if not exists (dont remove if exist)
New-Item -ItemType Directory -Path $DestinationDir -Force
#get list files destination dir
$DestinationFiles=gci $DestinationDir -File
#loop on file source and create newname for copy while name exist already
gci $SourceDir -File | %{
$counter=0
$name=$_.Name
while ($name -in $DestinationFiles.Name)
{
$counter++;
$name="{0}_{1:d6}{2}" -f $_.BaseName, $counter, $_.Extension
}
Copy-Item -Path $_.FullName -Destination "$DestinationDir\$name"
}