20

The behaviour I would expect from the code below is:

  1. Grab a list of the files in the source directory.

  2. Loop through and copy each file to the backup destination, only if it does not already exist.

    if (!(Test-Path C:\Folder\Destination)) {
       New-Item -ItemType Directory -Force -Path C:\Folder\Destination
    }
    
    $originalfiles = Get-ChildItem -Path  "C:\Folder\Source"
    $originalfiles
    
    foreach ($file in $originalfiles) {
        Write-Host
        Write-Host File Name: -ForegroundColor DarkYellow
        Write-Host $file.Name
        Write-Host File Path: -ForegroundColor DarkYellow
        Write-Host $file.FullName
    
        $src = $file.FullName
        $dest = "C:\Folder\Destination\$($file.Name)"
    
        Copy-Item $src $dest
    }
    

I would have thought that the Copy-Item cmdlet defaults to NOT overwrite, unless you specify the -Force flag. This is the behaviour I have seen in the past when I originally encountered situations where I did want to overwrite.

Also, I thought it may be the introduction of the foreach loop but I tried the copy command, on it's own, with hardcoded paths for a single file, and it is still the same.

Should I restart my IDE, or is it a mistake I have overlooked?

TylerH
  • 20,799
  • 66
  • 75
  • 101
Supernatix
  • 465
  • 2
  • 4
  • 14

6 Answers6

28

When in doubt, read the documentation.

-Force

Indicates that this cmdlet copies items that can't otherwise be changed, such as copying over a read-only file or alias.

The default behavior for Copy-Item is to replace existing items. The -Force switch is only to enforce replacement if for instance the destination file has the readonly attribute set.

You can use -Confirm to get prompted before Copy-Item performs the operation, or you can use -WhatIf to see what the cmdlet would do.

TylerH
  • 20,799
  • 66
  • 75
  • 101
Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
  • The wording of the documentation quote actually suggests that `-Force` affects the _copied_ (_source_) item, not the _destination_ item. Are you saying -Force applies to the destination item here? If so, we can file a pull request for that documentation to be updated. – TylerH Mar 03 '22 at 22:00
17

Old one, but I found a short expression that does that:

Copy-Item (Join-Path $dir_from "*") $dir_to -Exclude (Get-ChildItem $dir_to)

It copies all files from $dir_from to $dir_to but not (the -Exclude part) the files with names that are already in $dir_to

motto
  • 171
  • 1
  • 2
  • This is the only one that has worked for me in terms of copying and overwriting existing files with the same names. For those not familiar with Powershell, $dir_from and $dir_to should be defined in quotes. For example, $dir_to = "C:\users\..." – Sarah Oct 17 '19 at 03:39
  • 5
    This is useless if someone wants to use `-recurse`. – Appleoddity Oct 31 '19 at 15:24
5

It seems to be the expected behaviour of Copy-Item to copy an item to the destination even if it already exists in destination. I suggest to test, if the destination file path exists and only copy the file if it does not yet exist.

$destinationPath = 'C:\tryout\destination';
if (!(Test-Path $destinationPath)) 
{
   New-Item -ItemType Directory -Force -Path $destinationPath;
}

$sourcePath = 'C:\tryout\source';
$originalfiles = Get-ChildItem -Path $sourcePath;
$originalfiles;

foreach ($file in $originalfiles) 
{
    Write-Host;
    Write-Host File Name: -ForegroundColor DarkYellow;
    Write-Host $file.Name;
    Write-Host File Path: -ForegroundColor DarkYellow;
    Write-Host $file.FullName;

    $src = $file.FullName;
    $dest = "C:\tryout\destination\$($file.Name)";

    if (!(Test-Path $dest))
    {
        Copy-Item $src -Destination $dest;
    }
}
rufer7
  • 3,369
  • 3
  • 22
  • 28
3

Here is a version of your code to copy files recursively only when they don't exist in target location. It creates subfolders if needed. It doesn't create empty folders; if you need them, remove -File from Get-ChildItem.

$srcDirPath = "C:\SourceDir"
$destDirPath = "C:\DestinationDir"
# Get list of files from source dir recursively. It doesn't list hidden files by default 
# Remove where{} if you don't need to exclude files by name, in this sample I exclude all README.md in any folders
$fileList = Get-ChildItem -Path $srcDirPath -File -Recurse | where{$_.Name -ne 'README.md'}
# get source dir path just in case to exactly match Get-ChildItem paths
$fullSrcDirPath = (Get-Item -Path $srcDirPath).FullName
foreach($file in $fileList)
{
    # build full destination file path
    $destFilePath = $destDirPath + $file.FullName.Remove(0, $fullSrcDirPath.Length)
    # proceed only if file doesn't exist in destination
    if(-not (Test-Path "$destFilePath"))
    {
        # Copy-Item can't create subfolders, so create subfolder if it doesn't exist
        # Get full destination folder path for current file
        $curDestDir = Split-Path $destFilePath -Parent
        if(-not (Test-Path "$curDestDir"))
        {
            # create subfolder(s)
            # $res here suppresses output
            $res = New-Item -Path "$curDestDir" -ItemType "directory"
        }
        Copy-Item -Path $file.FullName -Destination "$destFilePath"
    }
}
TylerH
  • 20,799
  • 66
  • 75
  • 101
Vasiliy Zverev
  • 622
  • 5
  • 10
1

Couldn't edit @Vasiliy Zverev answer. I made it a function and offered exclude list based on relative file pattern. What is good (relative to the approved answer) that it does it recursively.

function CpNoClobber ($srcDirPath,$destDirPath,$exclude)
{
    $fileList = Get-ChildItem -Path $srcDirPath  -File -Recurse 
# get source dir path just in case to exactly match Get-ChildItem paths
        $fullSrcDirPath = (Get-Item -Path $srcDirPath).FullName
        foreach($file in $fileList)
        {
            $rel= $file.FullName.Remove(0, $fullSrcDirPath.Length+1)
            $toexc= $exclude | %{ $rel -like  $_ } | Select-Object -First 1
            if ($toexc)
            {
                continue
            }
# build full destination file path
            $destFilePath = $destDirPath + $file.FullName.Remove(0, $fullSrcDirPath.Length)
# proceed only if file doesn't exist in destination
                if(-not (Test-Path "$destFilePath"))
                {
# Copy-Item can't create subfolders, so create subfolder if it doesn't exist
# Get full destination folder path for current file
                    $curDestDir = Split-Path $destFilePath -Parent
                        if(-not (Test-Path "$curDestDir"))
                        {
# create subfolder(s)
# $res here suppresses output
                            $res = New-Item -Path "$curDestDir" -ItemType "directory"
                        }
                    Copy-Item -Path $file.FullName -Destination "$destFilePath"
                    echo $file.FullName
                }
        }

}

Usage example:

CpNoClobber c:\temp c:\temp2 @("test\*")
Eyal
  • 53
  • 6
0

Something quick if you're worried about overwriting:

function mycopy { 
  if (! (test-path $args[1])) { copy $args[0] $args[1] } else { 'file exists' } 
}
js2010
  • 23,033
  • 6
  • 64
  • 66
  • 1
    This answer would benefit from an explanation of what the code does and how it solves OP's problem. – TylerH Mar 03 '22 at 22:05