Rename if file already exist in powershell - powershell

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.

Related

copy newer files without keeping folders

I have a folder with a number of subfolders containing files and want to copy all files to the root folder but only overwrite if newer.
In powershell I can do -
Get-ChildItem D:\VaM\Custom\Atom\Person\Morphs\temp2\female -Recurse -file | Copy-Item -Destination D:\VaM\Custom\Atom\Person\Morphs\female
But this will overwrite all files, I only want to overwrite files if the copied file is newer.
robocopy can overwrite only older this but keeps the folder structure.
Try this
$root = 'D:\VaM\Custom\Atom\Person\Morphs\temp2\female'
[bool]$Delete = $false
Get-ChildItem $root -Recurse -File |
Where-Object {$_.DirectoryName -ne $root } | # Do not touch files already seated in root
ForEach-Object {
$rootNameBrother = Get-Item "$root\$($_.Name)" -ea 0
if($rootNameBrother -and $rootNameBrother.LastWriteTime -lt $_.LastWriteTime) {
# RootFile with same name exists and is Older - Copy and override
Copy-Item -Path $_.FullName -Destination $rootNameBrother.FullName -Force
}
elseif ($rootNameBrother -and $rootNameBrother.LastWriteTime -ge $_.LastWriteTime) {
# RootFile with same name exists and is Newer or same Age
# Delete non root File if allowed
if($Delete) { Remove-Item $_.FullName -Force }
}
}
Set...
$Delete = $true
...if you wish to delete non root files that could not be copied because there already was a file with the same name and greater modiefydate in root.
You also can set the
$VerbosePreference = "Continue"
$WhatIfPreference = "Continue"
variables, just to be safe when you execute the script for the first time.
If you wish to delete all empty subfolder, you can run this:
$allFolders =`
Get-ChildItem $root -Recurse -Directory |
ForEach-Object {
# Add now Depth Script Property
$_ | Add-Member -PassThru -Force -MemberType ScriptProperty -Name Depth -Value {
# Get Depth of folder by looping through each letter and counting the backshlashes
(0..($this.FullName.Length - 1) | ForEach {$this.FullName.Substring($_,1)} | Where-Object {$_ -eq "\"}).Count
}
}
# Sort all Folder by new Depth Property annd Loop throught
$allFolders | Sort -Property Depth -Descending |
ForEach-Object {
# if .GetFileSystemInfos() method return null, the folder is empty
if($_.GetFileSystemInfos().Count -eq 0) {
Remove-Item $_.FullName -Force # Remove Folder
}
}
You can do it like this:
$source = 'D:\VaM\Custom\Atom\Person\Morphs\temp2\female'
$destination = 'D:\VaM\Custom\Atom\Person\Morphs\female'
Get-ChildItem -Path $source -Recurse -File | ForEach-Object {
# try and get the existing file in the destination folder
$destFile = Get-Item -Path (Join-Path -Path $destination -ChildPath $_.Name) -ErrorAction SilentlyContinue
if (!$destFile -or $_.LastWriteTime -gt $destFile.LastWriteTime) {
# copy the file if it either did not exist in the destination or if this file is newer
Write-Host "Copying file $($_.Name)"
$_ | Copy-Item -Destination $destination -Force
}
}
I ended up doing this:
Get-ChildItem G:\VaM\Custom\Atom\Person\Morphs\temp2\ -Recurse |
Where-Object { $_.PSIsContainer -eq $true } |
Foreach-Object { robocopy $_.FullName G:\VaM\Custom\Atom\Person\Morphs\female /xo /ndl /np /mt /nfl}
it runs through the directory structure and copys the contents of each directory to the destination but only overwrites older files.

Powershell Find all empty folders and subfolders in a given Folder name

I´m trying to get a
a) list of all empty folders and subfolders if the folder is named "Archiv"
b) I´d like to delete all those empty folders. My current approch doesn´t check the subfolders.
It would be also great if the results would be exportet in a .csv =)
$TopDir = 'C:\Users\User\Test'
$DirToFind = 'Archiv'>$EmptyDirList = #(
Get-ChildItem -LiteralPath $TopDir -Directory -Recurse |
Where-Object {
#[System.IO.Directory]::GetFileSystemEntries($_.FullName).Count -eq 0
$_.GetFileSystemInfos().Count -eq 0 -and
$_.Name -match $DirToFind
}
).FullName
$EmptyDirList
Any ideas how to adjust the code? Thanks in advance
You need to reverse the order in which Get-ChildItem lists the items so you can remove using the deepest nested empty folder first.
$LogFile = 'C:\Users\User\RemovedEmptyFolders.log'
$TopDir = 'C:\Users\User\Test'
# first get a list of all folders below the $TopDir directory that are named 'Archiv' (FullNames only)
$archiveDirs = (Get-ChildItem -LiteralPath $TopDir -Filter 'Archiv' -Recurse -Directory -Force).FullName |
# sort on the FullName.Length property in Descending order to get 'deepest-nesting-first'
Sort-Object -Property Length -Descending
# next, remove all empty subfolders in each of the $archiveDirs
$removed = foreach ($dir in $archiveDirs) {
(Get-ChildItem -LiteralPath $dir -Directory -Force) |
# sort on the FullName.Length property in Descending order to get 'deepest-nesting-first'
Sort-Object #{Expression = {$_.FullName.Length}} -Descending |
ForEach-Object {
# if this folder is empty, remove it and output its FullName for the log
if (#($_.GetFileSystemInfos()).Count -eq 0) {
$_.FullName
Remove-Item -LiteralPath $_.FullName -Force
}
}
# next remove the 'Archiv' folder that is now possibly empty too
if (#(Get-ChildItem -LiteralPath $dir -Force).Count -eq 0) {
# output this folders fullname and delete
$dir
Remove-Item -LiteralPath $dir -Force
}
}
$removed | Set-Content -Path $LogFile -PassThru # write your log file. -PassThru also writes the output on screen
Not sure a CSV is needed, I think a simple text file will suffice as it's just a list.
Anyway, here's (although not the most elegant) a solution which will also delete "nested empty directories". Meaning if a directory only contains empty directorIS, it will also get deleted
$TopDir = "C:\Test" #Top level directory to scan
$EmptyDirListReport = "C:\EmptyDirList.txt" #Text file location to store a file with the list of deleted directorues
if (Test-Path -Path $EmptyDirListReport -PathType Leaf)
{
Remove-Item -Path $EmptyDirListReport -Force
}
$EmptyDirList = ""
Do
{
$EmptyDirList = Get-ChildItem -Path $TopDir -Recurse | Where-Object -FilterScript { $_.PSIsContainer } | Where-Object -FilterScript { ((Get-ChildItem -Path $_.FullName).Count -eq 0) } | Select-Object -ExpandProperty FullName
if ($EmptyDirList)
{
$EmptyDirList | Out-File -FilePath $EmptyDirListReport -Append
$EmptyDirList | Remove-Item -Force
}
} while ($EmptyDirList)
This should do the trick, should works with nested too.
$result=(Get-ChildItem -Filter "Archiv" -Recurse -Directory $topdir | Sort-Object #{Expression = {$_.FullName.Length}} -Descending | ForEach-Object {
if ((Get-ChildItem -Attributes d,h,a $_.fullname).count -eq 0){
$_
rmdir $_.FullName
}
})
$result | select Fullname |ConvertTo-Csv |Out-File $Logfile
You can do this with a one-liner:
> Get-ChildItem -Recurse dir -filter Archiv |
Where-Object {($_ | Get-ChildItem).count -eq 0} |
Remove-Item
Although, for some reason, if you have nested Archiv files like Archiv/Archiv, you need to run the line several times.

Move-Item doesn't move file

I've tried to rename a csv with powershell and then move it automatically to another folder, when there's no file.
Originally, the csv-name looks like this: import_9999_2020-08-13_132238.csv but the part with 9999 can also include just 2 or 3 digits.
My actual Code looks like:
#Import of path and target-path
$path = "\\network-path\subfolder\subfolder\subfolder\subfolder\subfolder1\"
$target_path = "\\network-path\subfolder\subfolder\subfolder\subfolder\subfolder2\"
#endless loop
$a=$true
while($a -eq $true){
$Files = gci $path
$TargetFiles = gci $target_path
#wait 5 minutes if path is empty
if(($Files).Count -eq 0){
sleep -Seconds 300
}
#if path is filled with one or more files
else {
#if file in target-path is processed (from another program)
if(($TargetFiles).count -eq 0){
#rename and move the latest file
get-childitem -path $path -Filter "import_*.csv"|
where-object { -not $_.PSIsContainer } |
sort-object -Property $_.CreationTime |
select-object -last 1 |
Rename-Item -NewName {($_.Name.Substring(0,($_.Name.Length)-22))+".csv"} |
Move-Item -Destination $target_path +"$($_.Name).csv"
}
sleep -Seconds 20
}
}
It works partly and renames the csv, but it doesn't move it to the target-path. The path is correct, i've copied it from the Windows-Explorer.
Any Ideas, why the program doesn't work completely? Thanks
For better readability, I would split the code where you find the original file, rename it and move it to the target path into several lines.
Also, Move-Item can rename the file aswell, so no need to do a Rename-Item first:
$file = Get-ChildItem -Path $path -Filter "import_*.csv" -File |
Sort-Object -Property $_.LastWriteTime |
Select-Object -Last 1
# create the new name for the file.
# or use regex: $newName = '{0}{1}' -f ($file.BaseName -replace '(_\d{4}-\d{2}-\d{2}_\d+)$'), $file.Extension
$newName = '{0}{1}' -f (($file.BaseName -split '_')[0..1] -join '_'), $file.Extension
# now move the file and rename at the same time
$file | Move-Item -Destination (Join-Path -path $target_path -ChildPath $newName)
I changed CreationTime to LastWriteTime to really get the latest file.
I also added the -File switch to the Get-ChildItem cmdlet, because only for old PowerShell versions you need to use where-object { -not $_.PSIsContainer }
If you want to pass it down the pipeline add -Passthru to the Rename-Item cmdlet
Somefile.txt | Rename-Item -NewName {$_.basename + "abc" + $_.ext} | # nothing in the pipeline
Somefile.txt | Rename-Item -NewName {$_.basename + "abc" + $_.ext} -Passthru | # now the new named fileinfo object is in the pipeline, contained in automatic variable $_

Duplicate file name as folder, insert file

I am trying to use Powershell to
scan folder D://Mediafolder for names of media files
create a folder for each media file scanned, with same name
insert each media file in to matching folder name.
I can find no documentation or thread of this, and I am more fluent in Linux than Windows. I've tried many times to piece this together, but to no avail.
Hope this will help :)
This will create a folder for each file with the same name, so if you have a file called xyz.txt, it will create a folder called xyz and move the file to this folder.
$path = "D:\MediaFolder"
$items = Get-ChildItem $path
Foreach ($item in $items)
{
$folderName = $item.name.Split('.')[0]
New-Item "$path\$folderName" -ItemType Directory
Move-Item -Path "$path\$item" -Destination "$path\$foldername"
}
File Sorting based on extension should do the job:
$folder_path = read-host "Enter the folder path without space"
$file = gci $folder_path -Recurse | ? {-not $_.psiscontainer}
$file | group -property extension | % {if(!(test-path(join-path $folder_path -child $_.name.replace('.','')))){new-item -type directory $(join-path $folder_path -child $_.name.replace('.','')).toupper()}}
$file | % { move-item $_.fullname -destination $(join-path $folder_path -child $_.extension.replace(".",""))}
$a = Get-ChildItem $folder_path -recurse | Where-Object {$_.PSIsContainer -eq $True}
$a | Where-Object {$_.GetFiles().Count -eq 0} | Remove-Item -Force
This will iterate over the files in the media_dir and move those with the extensions in media_types to a folder with the same basename. When you are satisfied that the files will be moved to the correct directory, remove the -WhatIf from the Move-Item statement.
PS C:\src\t> type .\ms.ps1
$media_dir = 'C:\src\t\media'
$new_dir = 'C:\src\t\newmedia'
$media_types = #('.mp3', '.mp4', '.jpeg')
Get-ChildItem -Path $media_dir |
ForEach-Object {
$base_name = $_.BaseName
if ($media_types -contains $_.Extension) {
if (-not (Test-Path $new_dir\$base_name)) {
New-Item -Path $new_dir\$base_name -ItemType Directory | Out-Null
}
Move-Item $_.FullName $new_dir\$base_name -WhatIf
}
}

Powershell compare-object

I trying to make a script which compare two directory ( source, destination) and if there are a difference on destination, copy files from source to destination.
The problem is that I don't know how copy the tree of files too.
Example:
$s = "C:\source\client"
$t = "C:\destination\client"
$target = Get-ChildItem $t -Recurse
$source = get-childitem $s -Recurse
Compare-Object $source $target -Property Name , Length |
Where-Object { $_.SideIndicator -eq '<=' } |
foreach-object -process{
copy-item $_.FullName -destination $t
}
If I have a file in source ( C:\source\client\bin\file.txt) and not in the destination folder, how is the code to copy the file in C:\destination\client\bin\file.txt ?
Thanks.
I am in the process of testing this more. From what i can see the logic of your code is sound.
Compare-Object $source $target -Property Name , Length |
Where-Object { $_.SideIndicator -eq '<=' } | Select-Object -ExpandProperty inputobject |
foreach-object -process{
copy-item $_.FullName -destination $t
}
Once you have the compare done pipe the results after the Where in Select-Object -ExpandProperty inputobject to extract the File item so that you can see the FullName property
copy-item has a -recurse parameter that will let you specify the root of a directory and then copy everything below it
copy-item c:\test d:\test -recurse -force
Edit:
The problem is for repeated tasks you can't stop it from trying to overwrite everything. You can add -force to make it do it, but it is not very efficient.
Alternatively (and probably a better and simpler way to go about this) you could call robocopy with the /mir switch
Thanks for sharing. Here is what I have done with everything I searched to compare MD5 and then copy only newly added and different files.
With [Compare contents of two folders using PowerShell Get-FileHash] from http://almoselhy.azurewebsites.net/2014/12/compare-contents-of-two-folders-using-powershell-get-filehash/
$LeftFolder = "D:\YFMS_Target"
$RightFolder = "D:\YFMS_Copy"
$LeftSideHash = #(Get-ChildItem $LeftFolder -Recurse | Get-FileHash -Algorithm MD5| select #{Label="Path";Expression={$_.Path.Replace($LeftFolder,"")}},Hash)
$RightSideHash = #(Get-ChildItem $RightFolder -Recurse | Get-FileHash -Algorithm MD5| select #{Label="Path";Expression={$_.Path.Replace($RightFolder,"")}},Hash)
robocopy $LeftFolder $RightFolder /e /xf *
Write-Host "robocopy LastExitCode: $LastExitCode"
if ($LastExitCode -gt 7) { exit $LastExitCode } else { $global:LastExitCode = $null }
Compare-Object $LeftSideHash $RightSideHash -Property Path,Hash | Where-Object { $_.SideIndicator -eq '<=' } | foreach { Copy-Item -LiteralPath (Join-Path $LeftFolder $_.Path) -Destination (Join-Path $RightFolder $_.Path) -verbose}