I want to get the duplicates from a folder structure and copy all of them to a single folder, while renaming (so they don't overwrite). I would like the first file from a duplicates group to be copied with it's original name, and for the rest to add "_X" at the end of the name.
I wrote a code that almost works, but at some point it just overwrites the first file copied. Only one file is being overwritten, the rest are renamed and copied like intended.
Get-ChildItem $SourcePath -Recurse -File -Force | Group-Object -Property Name | Where-Object {$_.Count -gt 1} | Select-Object -ExpandProperty Group |
ForEach-Object {
$SourceFile = $_.FullName
$FileName = $($_.BaseName + $_.Extension)
$DestFileName = Join-Path -Path $DestinationPath -ChildPath $FileName
if (Test-Path -Path $DestFileName) {
$DestinationFile = "$DestinationPath\" + $_.BaseName + "_" + $i + $_.Extension
$i+=1
} else {
$DestinationFile = $DestFileName
}
Copy-Item -Path $SourceFile -Destination $DestinationFile
}
I don't see the actual problem but you could rewrite the code without using Test-Path. Remove Select-Object -ExpandProperty Group too, then iterate over each group's elements. Increment a counter and append it to all file names except the first one.
Get-ChildItem $SourcePath -Recurse -File -Force | Group-Object -Property Name | Where-Object Count -gt 1 |
ForEach-Object {
$i = 0
foreach( $dupe in $_.Group ) {
$SourceFile = $dupe.FullName
$DestinationFile = Join-Path -Path $DestinationPath -ChildPath $dupe.BaseName
if( $i -gt 0 ) { $DestinationFile += "_$i" }
$DestinationFile += $dupe.Extension
Copy-Item -Path $SourceFile -Destination $DestinationFile
$i++
}
}
Related
I am trying to copy all files recursively from a:\ to b:\, except those whose metadata is present in a:\list.txt. The list.txt pattern is LastWriteTimeYYYY-MM-DD HH:MM:SS,size,.fileextension, for example:
2001-01-31 23:59:59,12345,.doc
2001-01-31 23:59:59,12345,.txt
2001-01-31 23:59:00,456,.csv
...so any and all files, anywhere in the a:\ dir tree, matching these metadata should not be copied.
I seem to be having trouble with the Where-Object in order to exclude the items on the list.txt, but copy everything else:
$Source = "C:\a"
$Target = "C:\b"
$List = Import-Csv list.txt -Header LastWriteTime,Size,Name
$Hash = #{}
ForEach ($Row in $List){
$Key = ("{0},{1},.{2}" -F $Row.LastWriteTime,$Row.Size,$Row.Name.Split('.')[-1].ToLower())
IF (!($Hash[$Key])) {$Hash.Add($Key,$Row.Name)}
}
$Hash | Format-Table -Auto
Get-Childitem -Path $Source -Recurse -File | Where-Object {$Hash -eq $Hash[$Key]}| ForEach-Object {$Key = ("{0},{1},{2}" -F ($_.LastWriteTime).ToString('yyyy-MM-dd HH:mm:ss'),$_.Length,$_.Extension.ToLower())
#$Key
If ($Hash[$Key]){
$Destination = $_.FullName -Replace "^$([RegEx]::Escape($Source))","$Target"
If (!(Test-Path (Split-Path $Destination))){MD (Split-Path $Destination)|Out-Null}
$_ | Copy-Item -Destination $Destination
}
}
I propose you a simplification of your code :
$Source = "C:\a\"
$Target = "C:\b\"
New-Item -ItemType Directory $Target -Force | Out-Null
$List = Import-Csv list.txt -Header LastWriteTime,Length,Extension
Get-Childitem $Source -Recurse -File | %{
$File=$_
$exist=$List | where {$_.LastWriteTime -eq $File.LastWriteTime.ToString('yyyy-MM-dd HH:mm:ss') -and $_.Length -eq $File.Length -and $_.Extension -eq $File.Extension} | select -first 1
if ($exist -ne $null) {continue}
New-Item -ItemType Directory $File.DirectoryName.Replace($Source, $Target) -Force | Out-Null
Copy-Item $File.FullName $File.FullName.Replace($Source, $Target) -Force
}
I need a script to move 1 old file to another directory.
I have a script but is not good for me:
$path = "C:*.*"
$Destination = "C:*.*"
Foreach($file in (Get-ChildItem $path)) {
If($file.LastWriteTime -gt (Get-Date).adddays(-1).date) {
Move-Item -Path $file.fullname -Destination $Destination
}
}
I need only 1 file, and Oldest every day.
Please help, Thanks
If you want only one file then you don't need a foreach loop there. You can try something like this using the Sort-Object and the Select-Object with the -Descending parameter.
Presently I have sorted that with the CreationTime and you can always choose the first element of the resultant using the -First.
$path = "C:*.*" ;
$Destination = "C:*.*" ;
$file= Get-ChildItem $path | select name,lastwritetime,CreationTime | sort-object -property CreationTime -Descending | Select-Object -First 1 ;
If($file.LastWriteTime -gt (Get-Date).adddays(-1).date) {
Move-Item -Path $file.fullname -Destination $Destination
}
Hope it helps.
try Something like this
$path = "C:\temp" ;
$Destination = "C:\temp\olddir\" ;
Get-ChildItem $path -file -rec |
where {$_.LastWriteTime -le (Get-Date).adddays(-10).date} |
Select-Object -First 1 |
Move-Item -Destination "$Destination"
I am using this code I found on this site in a script to copy PST files and rename the duplicate. My question and the problem I am having with it is that when it renames the .pst it continues to increment the number.
For example, if it finds a file named "test.pst" it will copy it as is. If it finds another file also named "test.pst", it will copy it and rename it "test-1.pst" which is fine. However, if it finds two files named "test2.pst" it will copy the first one as "test2.pst" and copy and rename the second one to "test2-2.pst" instead of "test2-1.pst".
Do you have any suggestions on how I can modify my code so that it will start numbering each new duplicate file with 1 (test3-1.pst, test4-1.pst, etc)?
$csv = import-csv .\test.csv
foreach ($line in $csv) {
New-Item c:\new-pst\$($line.username) -type directory
$dest = "c:\new-pst\$($line.username)"
$i=1
Get-ChildItem -Path $line.path -Filter *.pst -Recurse | ForEach-Object {
$nextName = Join-Path -Path $dest -ChildPath $_.name
while(Test-Path -Path $nextName)
{
$nextName = Join-Path $dest ($_.BaseName + "_$i" + $_.Extension)
$i++
}
$_ | copy-Item -Destination $nextName -verbose
}
}
You'll need to reset the counter:
$csv = import-csv .\test.csv
foreach ($line in $csv) {
New-Item c:\new-pst\$($line.username) -type directory
$dest = "c:\new-pst\$($line.username)"
Get-ChildItem -Path $line.path -Filter *.pst -Recurse | ForEach-Object {
$i=1 # Note the position of the initializer
$nextName = Join-Path -Path $dest -ChildPath $_.name
while(Test-Path -Path $nextName)
{
$nextName = Join-Path $dest ($_.BaseName + "_$i" + $_.Extension)
$i++
}
$_ | copy-Item -Destination $nextName -verbose
}
}
Moving my comment to an answer. You need to move the $i = 1 line to inside your ForEach loop as such:
$csv = import-csv .\test.csv
foreach ($line in $csv) {
New-Item c:\new-pst\$($line.username) -type directory
$dest = "c:\new-pst\$($line.username)"
Get-ChildItem -Path $line.path -Filter *.pst -Recurse | ForEach-Object {
$i=1
$nextName = Join-Path -Path $dest -ChildPath $_.name
while(Test-Path -Path $nextName)
{
$nextName = Join-Path $dest ($_.BaseName + "_$i" + $_.Extension)
$i++
}
$_ | copy-Item -Destination $nextName -verbose
}
}
I created ps script to copy only files in the folder structure- recursive
cp $source.Text -Recurse -Container:$false -destination $destination.Text
$dirs = gci $destination.Text -directory -recurse | Where { (gci $_.fullName).count -eq 0 } | select -expandproperty FullName
$dirs | Foreach-Object { Remove-Item $_ }
it is working fine. but the problem i have files in the same names. it is not copying duplicated files. i need to rename if file already exist
source:
folderA--> xxx.txt,yyy.txt,
folderB-->xxx.txt,yyy.txt,zzz.txt,
folderc-->xxx.txt
destination (requirement)
xxx.txt
xxx1.txt
xxx2.txt
yyy.txt
yyy1.txt
zzz.txt
Here a solution where I use the Group-Object cmdlet to group all items by the filename. I then iterate over each group and if the group contains more then one file, I append _$ito it where $i starts by one and gets incremented:
$source = $source.Text
$destination = $destination.Text
Get-ChildItem $source -File -Recurse | Group-Object Name | ForEach-Object {
if ($_.Count -gt 1) { # rename duplicated files
$_.Group | ForEach-Object -Begin {$i = 1} -Process {
$newFileName = $_.Name -replace '(.*)\.(.*)', "`$1_$i.`$2"
$i++
Copy-Item -Path $_.FullName -Destination (Join-Path $destination $newFileName)
}
}
else # the filename is unique, just copy it.
{
$_.Group | Copy-Item -Destination $destination
}
}
Note:
You may change the -File to -Container:$false if your PowerShell version doesn't support it. Also note that the script doesn't look into the destination folder whether a file with the same name already exist.
$path = "c:\folder a"
$destFolder = "C:\"
$subFolder = "\folder c\folder d\"
$file = "file.txt"
$dir = Get-ChildItem $path | select -first 10 | Sort-Object -Property CreationTime
[array]::Reverse($dir)
$dir | format-table FullName
$fullPath = #()
ForEach ($i in $dir) {
$fullPath += $i + $subFolder + $file
}
$i = 0
while ($i -lt $fullPath.Count) {
$exists = Test-Path $fullPath[$i]
if ($exists){
Copy-Item -Path $fullPath[$i] -Destination $destFolder
break
}
$i++
}
having trouble getting &fullpath to work
edit:
&fullpath displays all the folder in the directory then adds the subfolder+file at the end.
I want it to take the 1 file path at a time from &dir and add the subfolder+file
sorry if I haven't explained it very well.
Im a total beginner at this kind of stuff
I think what you need is this:
$path = "c:\temp"
$destFolder = "C:\"
$subFolder = "\folder c\folder d\"
$file = "file.txt"
$childItems = Get-ChildItem $path | select -first 10 | Sort-Object -Property CreationTime -Descending
forEach ($item in $childItems)
{
$fullPath = Join-Path -Path $item.FullName -ChildPath "$subFolder$file"
if (Test-Path -Path $fullPath)
{
Copy-Item -Path $fullPath -Destination $destFolder
}
}