Copy-Item with overwrite? - powershell

Here is a section of code from a larger script. The goal is to recurse through a source directory, then copy all the files it finds into a destination directory, sorted into subdirectories by file extension. It works great the first time I run it. If I run it again, instead of overwriting existing files, it fails with this error on each file that already exists in the destination:
Copy-Item : Cannot overwrite the item with itself
I try, whenever possible, to write scripts that are idempotent but I havn't been able to figure this one out. I would prefer not to add a timestamp to the destination file's name; I'd hate to end up with thirty versions of the exact same file. Is there a way to do this without extra logic to check for a file's existance and delete it if it's already there?
## Parameters for source and destination directories.
$Source = "C:\Temp"
$Destination = "C:\Temp\Sorted"
# Build list of files to sort.
$Files = Get-ChildItem -Path $Source -Recurse | Where-Object { !$_.PSIsContainer }
# Copy the files in the list to destination folder, sorted in subfolders by extension.
foreach ($File in $Files) {
$Extension = $File.Extension.Replace(".","")
$ExtDestDir = "$Destination\$Extension"
# Check to see if the folder exists, if not create it
$Exists = Test-Path $ExtDestDir
if (!$Exists) {
# Create the directory because it doesn't exist
New-Item -Path $ExtDestDir -ItemType "Directory" | Out-Null
}
# Copy the file
Write-Host "Copying $File to $ExtDestDir"
Copy-Item -Path $File.FullName -Destination $ExtDestDir -Force
}

$Source = "C:\Temp"
$Destination = "C:\Temp\Sorted"
You are trying to copy files from a source directory to a sub directory of that source directory. The first time it works because that directory is empty. The second time it doesn't because you are enumerating files of that sub directory too and thus attempt to copy files over themselves.
If you really need to copy the files into a sub directory of the source directory, you have to exclude the destination directory from enumeration like this:
$Files = Get-ChildItem -Path $Source -Directory |
Where-Object { $_.FullName -ne $Destination } |
Get-ChildItem -File -Recurse
Using a second Get-ChildItem call at the beginning, which only enumerates first-level directories, is much faster than filtering the output of the Get-ChildItem -Recurse call, which would needlessly process each file of the destination directory.

Related

Powershell: Find Folders with (Name) and Foreach Copy to Location Preserve Directory Structure

Got another multi-step process I'm looking to streamline. Basically, I'm looking to build a Powershell script to do three things:
Get-Childitem to look for folders with a specific name (we'll call it NAME1 as a placeholder)
For each folder it finds that has the name, I want it to output the full directory to a TXT file (so that in the end I wind up with a text file that has a list of the results it found, with their full paths; so if it finds folders with "NAME1" in five different subdirectories of the folder I give it, I want the full path beginning with the drive letter and ending with "NAME1")
Then I want it to take the list from the TXT file, and copy each file path to another drive and preserve directory structure
So basically, if it searches and finds this:
D:\TEST1\NAME1
D:\TEST7\NAME1
D:\TEST8\NAME1\
That's what I want to appear in the text file.
Then what I want it to do is to go through each line in the text file and plug the value into a Copy-Item (I'm thinking the source directory would get assigned to a variable), so that when it's all said and done, on the second drive I wind up with this:
E:\BACKUP\TEST1\NAME1
E:\BACKUP\TEST7\NAME1
E:\BACKUP\TEST8\NAME1\
So in short, I'm looking for a Get-Childitem that can define a series of paths, which Copy-Item can then use to back them up elsewhere.
I already have one way to do this, but the problem is it seems to copy everything every time, and since one of these drives is an SSD I only want to copy what's new/changed each time (not to mention that would save time when I need to run a backup):
$source = "C:\"
$target = "E:\BACKUP\"
$search = "NAME1"
$source_regex = [regex]::escape($source)
(gci $source -recurse | where {-not ($_.psiscontainer)} | select -expand fullname) -match "\\$search\\" |
foreach {
$file_dest = ($_ | split-path -parent) -replace $source_regex,$target
if (-not (test-path $file_dest)){mkdir $file_dest}
copy-item $_ -Destination $file_dest -force -verbose
}
If there's a way to do this that wouldn't require writing out a TXT file each time I'd be all for that, but I don't know a way to do this the way I'm looking for except a Copy-Item.
I'd be very grateful for any help I can get with this. Thanks all!
If I understand correctly, you want to copy all folders with a certain name, keeping the original folder structure in the destination path and copy only files that are newer than what is in the destination already.
Try
$source = 'C:\'
$target = 'E:\BACKUP\'
$search = 'NAME1'
# -ErrorAction SilentlyContinue because in the C:\ disk you are bound to get Access Denied on some paths
Get-ChildItem -Path $source -Directory -Recurse -Filter $search -ErrorAction SilentlyContinue | ForEach-Object {
# construct the destination folder path
$dest = Join-Path -Path $target -ChildPath $_.FullName.Substring($source.Length)
# copy the folder including its files and subfolders (but not empty subfolders)
# for more switches see https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/robocopy
robocopy $_.FullName $dest  /XO /S /R:0
}
If you don't want console output of robocopy you can silence it by appending 2>&1, so neither stdout nor stderr is echoed
If you want to keep a file after this with both the source paths and the destinations, I'd suggest doing
$source = 'C:\'
$target = 'E:\BACKUP\'
$search = 'NAME1'
$output = [System.Collections.Generic.List[object]]::new()
# -ErrorAction SilentlyContinue because in the C:\ disk you are bound to get Access Denied on some paths
Get-ChildItem -Path $source -Directory -Recurse -Filter $search -ErrorAction SilentlyContinue | ForEach-Object {
# construct the destination folder path
$dest = Join-Path -Path $target -ChildPath $_.FullName.Substring($source.Length)
# add an object to the output list
$output.Add([PsCustomObject]#{Source = $_.FullName; Destination = $dest })
# copy the folder including its files and subfolders (but not empty subfolders)
# for more switches see https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/robocopy
robocopy $_.FullName $dest  /XO /S /R:0
}
# write the output to csv file
$output | Export-Csv -Path 'E:\backup.csv' -NoTypeInformation

Copy files from one folder to many via Powershell

I need to copy the files from one folder to many. Here's an example of my directory structure:
\\files\CA1\Files\Files
CA = state code
1 = office in that state
I want to copy all files from a source folder into the last files folder. The last files folder in that directory structure above is the destination. The script just needs to cycle through all of the directories with that state code and copy the new files into \\files\CA*\files\FILES\ folder. For instance, I want to copy all files from c:\documents into all folders that are for CA, regardless of the office number. Here's what I have so far:
$source = 'C:\Documents'
$destination = (Get-ChildItem -Path \\files\CA*\Files\Files -Recurse -Directory)
foreach ($dir in $destination){
Get-ChildItem $dir.Fullname | ForEach-Object {
$_.FullName
#Copy-Item -Path $Source -Destination $_ -Force -Recurse -WhatIf
}}

Place files with prefix in folder with number

I have batch changed multiple files that all start with a prefix of a folder where I need them in.
The files are located on another location, like a folder on the desktop.
For example:
101AA0001.dat
101AA0002.dat
102AA0001.dat
102AA0002.dat
The destination folder will for example be:
C:\destfolder\101\ or C:\destfolder\102\
Files starting with 101 need to go in the 101 folder and the files starting with 102 go to folder 102.
I can find some scripts that creates the folder based on the filename. But in this situation the folders already exist. I also know for sure the files don't exist, so I don't have to overwrite files or something.
I guess it is easy for the people that know PowerShell very well, but I don't know how to do this. Can someone please help me? This can save me a lot of time.
I have tried to move the files with the following rule:
Move-Item -Path C:\Users\Username\Desktop\test*.dat -Destination C:\Users\Username\Desktop\test2\ -include "*.dat"
But it copies the whole folder except for the files.
You can do that quite easily with code like below:
$sourceFolder = Join-Path -Path $env:USERPROFILE -ChildPath 'Desktop'
$destination = 'C:\destfolder'
Get-ChildItem -Path $sourceFolder -File -Filter '*.dat' | ForEach-Object {
$targetFolder = Join-Path -Path $destination -ChildPath $_.Name.Substring(0, 3)
# if the target folder does not exist yet, create it
if (!(Test-Path -Path $targetFolder -PathType Container)) {
$null = New-Item -Path $targetFolder -ItemType Directory
}
$_ | Move-Item -Destination $targetFolder -WhatIf
}
The -WhatIf switch shows what would happen in the console without actually performing the move. If you are satisfied with what is output, remove that switch.
This will take all files that end in ".dat" from the $Source folder into a subfolder inside the $DestinationRoot named for the first three characters of the ".dat" file.
$Source = "C:\Users\Username\Desktop"
$DestinationRoot = "C:\Users\Username\Desktop\test2"
$Filelist = Get-ChildItem -Path $Source -Filter "*.dat" -File
foreach ($File in $Filelist){ $DestinationFolder = $File.Name.Substring(0,3)
$FinalPath = "$DestinationRoot\$DestinationFolder"
Move-Item -Path $File.Fullname -Destination $FinalPath -Whatif }
Remove the -Whatif when you're ready to run it for real.
This doesn't handle folder creation and should error out if the file already exists in the target location so it won't accidentally overwrite anything.

Copying Folders with Wildcards

I am trying to copy a whole bunch of files using Powershell, from one directory to another on my computer.
I used Get-ChildItem C:\Users\Tom\Google Drive\My Files\*\Assessment 1\* to identify that this was the path that I wanted to copy too, and I know about Copy-Item, but I want to maintain parts of the path name when copied.
Example:
If I copy from C:\Users\Tom\Google Drive\My Files\Cool Stuff\Assessment 1\*
I want the files to go to a folder that is created called C:\Users\Tom\Archive\Cool Stuff\Assessment 1
Whereas if I copy from C:\Users\Tom\Google Drive\My Files\New Stuff\Assessment 1\*
I want the files to go to a folder that is created called C:\Users\Tom\Archive\New Stuff\Assessment 1
You could use the Get-ChildItem cmdlet to recursively find all Assessment 1 folders within your base directory and then remove the base path using -replace to finally copy the items using the Copy-Item cmdlet:
$baseDir = 'C:\Users\Tom\Google Drive\My Files\'
$destination = 'C:\Users\Tom\Archive\'
Get-ChildItem $baseDir -directory -Filter 'Assessment 1' -Recurse | ForEach-Object {
$newPath = Join-Path $destination ($_.FullName -replace [regex]::Escape($baseDir))
Copy-Item $_.FullName $newPath -Force -Recurse
}

Powershell Get-ChildItem -recurse doesn't get all items

I'm working on a powershell script erase certain files from a folder, and move the rest into predefined subfolders.
My structure looks like this
Main
(Contains a bunch of pdb and dll files)
-- _publish
--Website
(Contains a web.config, two other .config files and a global.asax file)
-- bin
(Contains a pdb and dll file)
-- JS
-- Pages
-- Resources
I want to remove all pdb, config and asax files from the entire file structure before I start moving them. To which I use:
$pdbfiles = Get-ChildItem "$executingScriptDirectory\*.pdb" -recurse
foreach ($file in $pdbfiles) {
Remove-Item $file
}
And so on for all filetypes I need removed. It works great except for a pdb file located in the bin folder of the website. And for the ASAX file in the website folder. For some reason they get ignored by the Get-ChildItem recurse search.
Is this caused by the depth of the items within the resursive structure? Or is it something else? How can I fix it, so it removes ALL files as specified.
EDIT: I have tried adding -force - But it changed nothing
ANSWER: The following worked:
$include = #("*.asax","*.pdb","*.config")
$removefiles = Get-ChildItem "$executingScriptDirectory\*" -recurse -force -include $include
foreach ($file in $removefiles) {
if ($file.Name -ne "Web.config") {
Remove-Item $file
}
}
Get-ChildItem -path <yourpath> -recurse -Include *.pdb
You can also use the pipe for remove:
Get-ChildItem -path <yourpath> -recurse -Include *.pdb | rm