76
$from = "\\something\1 XLS\2010_04_22\*"
$to =  "c:\out\1 XLS\2010_04_22\"
copy-item $from $to -Recurse 

This works if c:\out\1 XLS\2010_04_22\ does exist . Is it possible with a single command to create c:\out\1 XLS\2010_04_22\ if it doesn't exist?

ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122
MicMit
  • 2,372
  • 5
  • 28
  • 41
  • Does this need to be using a powershell cmdlet, or are you okay with using xcopy which is available in a powershell prompt, but is not a powershell cmdlet? – Shiraz Jan 04 '23 at 16:34

11 Answers11

119

In PowerShell 2.0, it is still not possible to get the Copy-Item cmdlet to create the destination folder, you'll need code like this:

$destinationFolder = "C:\My Stuff\Subdir"

if (!(Test-Path -path $destinationFolder)) {New-Item $destinationFolder -Type Directory}
Copy-Item "\\server1\Upgrade.exe" -Destination $destinationFolder

If you use -Recurse in the Copy-Item it will create all the subfolders of the source structure in the destination but it won't create the actual destination folder, even with -Force.

FrinkTheBrave
  • 3,894
  • 10
  • 46
  • 55
  • Thanks for this observation. I got some inconsistent copy actions; sometimes it copied a subfolder and sometimes it did not. After making sure the destination folder existed this inconsistency was gone. – Joost van der Griendt Nov 29 '16 at 07:31
  • 2
    I also encounter the wrong behavior when the target folder does not exist – vicancy Apr 26 '17 at 01:39
  • 2
    Thanks. This was the answer I was looking for. To bad there not switch to create destination folder. – jdawiz Feb 01 '19 at 18:08
86

Yes, add the -Force parameter.

copy-item $from $to -Recurse -Force
Ben
  • 54,723
  • 49
  • 178
  • 224
Shay Levy
  • 121,444
  • 32
  • 184
  • 206
  • 5
    PowerShell is so easy when one knows it :) – stej Apr 23 '10 at 08:41
  • 87
    That works for entire directories, but what if I'm just copying individual files (so I can have a status)? How do you copy c:\test1\file.txt to c:\test2\file.txt when c:\test2 does not exist? That's what I really need to know how to do. Thanks, Gary – Gary Evans Dec 16 '10 at 16:46
  • 51
    I ran into this same problem and solved it by calling New-Item first: `New-Item -Force $to` `Copy-Item -Force $from $to` – rjschnorenberg Apr 26 '12 at 19:52
20

In PowerShell 3 and above I use the Copy-Item with New-Item.

copy-item -Path $file -Destination (new-item -type directory -force ("C:\Folder\sub\sub\" + $newSub)) -force -ea 0

I haven't tried it in ver 2.

mjudd
  • 221
  • 2
  • 4
3
  $filelist | % {
    $file = $_
    mkdir -force (Split-Path $dest) | Out-Null
    cp $file $dest
  } 
majkinetor
  • 8,730
  • 9
  • 54
  • 72
3
function Copy-File ([System.String] $sourceFile, [System.String] $destinationFile, [Switch] $overWrite) {

    if ($sourceFile -notlike "filesystem::*") {
        $sourceFile = "filesystem::$sourceFile" 
    }

    if ($destinationFile -notlike "filesystem::*") {
        $destinationFile = "filesystem::$destinationFile" 
    }

    $destinationFolder = $destinationFile.Replace($destinationFile.Split("\")[-1],"")

    if (!(Test-Path -path $destinationFolder)) {
        New-Item $destinationFolder -Type Directory
    }

    try {
        Copy-Item -Path $sourceFile -Destination $destinationFile -Recurse -Force
        Return $true 
    } catch [System.IO.IOException] {
        # If overwrite enabled, then delete the item from the destination, and try again:
        if ($overWrite) {
            try {
                Remove-Item -Path $destinationFile -Recurse -Force        
                Copy-Item -Path $sourceFile -Destination $destinationFile -Recurse -Force 
                Return $true
            } catch {
                Write-Error -Message "[Copy-File] Overwrite error occurred!`n$_" -ErrorAction SilentlyContinue
                #$PSCmdlet.WriteError($Global:Error[0])
                Return $false
            }
        } else {
            Write-Error -Message "[Copy-File] File already exists!" -ErrorAction SilentlyContinue
            #$PSCmdlet.WriteError($Global:Error[0])
            Return $false
        }
    } catch {
        Write-Error -Message "[Copy-File] File move failed!`n$_" -ErrorAction SilentlyContinue
        #$PSCmdlet.WriteError($Global:Error[0]) 
        Return $false
    } 
}
peter.hrasko.sk
  • 4,043
  • 2
  • 19
  • 34
Bart
  • 31
  • 1
  • 1
    Welcome to Stack Overflow! Thank you for this code snippet, which may provide some immediate help. A proper explanation [would greatly improve](//meta.stackexchange.com/q/114762) its educational value by showing *why* this is a good solution to the problem, and would make it more useful to future readers with similar, but not identical, questions. Please [edit] your answer to add explanation, and give an indication of what limitations and assumptions apply. – Toby Speight Jun 02 '17 at 08:33
3

Here's an example that worked for me. I had a list of about 500 specific files in a text file, contained in about 100 different folders, that I was supposed to copy over to a backup location in case those files were needed later. The text file contained full path and file name, one per line. In my case, I wanted to strip off the Drive letter and first sub-folder name from each file name. I wanted to copy all these files to a similar folder structure under another root destination folder I specified. I hope other users find this helpful.

# Copy list of files (full path + file name) in a txt file to a new destination, creating folder structure for each file before copy
$rootDestFolder = "F:\DestinationFolderName"
$sourceFiles = Get-Content C:\temp\filelist.txt
foreach($sourceFile in $sourceFiles){
    $filesplit = $sourceFile.split("\")
    $splitcount = $filesplit.count
    # This example strips the drive letter & first folder ( ex: E:\Subfolder\ ) but appends the rest of the path to the rootDestFolder
    $destFile = $rootDestFolder + "\" + $($($sourceFile.split("\")[2..$splitcount]) -join "\")
    # Output List of source and dest 
    Write-Host ""
    Write-Host "===$sourceFile===" -ForegroundColor Green
    Write-Host "+++$destFile+++"
    # Create path and file, if they do not already exist
    $destPath = Split-Path $destFile
    If(!(Test-Path $destPath)) { New-Item $destPath -Type Directory }
    If(!(Test-Path $destFile)) { Copy-Item $sourceFile $destFile }
}
3
$file | Copy-Item -Destination (New-Item -Force -Type Directory -Path $directoryName)
Hashbrown
  • 12,091
  • 8
  • 72
  • 95
2

My favorite is to use the .Net [IO.DirectoryInfo] class, which takes care of some of the logic. I actually use this for a lot of similar scripting challenges. It has a .Create() method that creates directories that don't exist, without errors if they do.

Since this is still a two step problem, I use the foreach alias to keep it simple. For single files:

[IO.DirectoryInfo]$to |% {$_.create(); cp $from $_}

As far as your multi file/directory match, I would use RoboCopy over xcopy. Remove the "*" from your from and just use:

RoboCopy.exe $from $to *

You can still add the /r (Recurse), /e (Recurse including Empty), and there are 50 other useful switches.

Edit: Looking back at this it is terse, but not very readable if you are not using the code often. Usually I have it split into two, like so:

([IO.DirectoryInfo]$to).Create()
cp $from $to

Also, DirectoryInfo is the type of the Parent property of FileInfo, so if your $to is a file, you can use them together:

([IO.FileInfo]$to).Parent.Create()
cp $from $to
Michael Erickson
  • 3,881
  • 2
  • 20
  • 16
1

I have stumbled here twice, and this last time was a unique situation and even though I ditch using copy-item I wanted to post the solution I used.

Had a list of nothing but files with the full path and in majority of the case the files have no extensions. the -Recurse -Force option would not work for me so I ditched copy-item function and fell back to something like below using xcopy as I still wanted to keep it a one liner. Initially I tied with Robocopy but it is apparently looking for a file extension and since many of mine had no extension it considered it a directory.

$filelist = @("C:\Somepath\test\location\here\file","C:\Somepath\test\location\here2\file2")

$filelist | % { echo f | xcopy $_  $($_.Replace("somepath", "somepath_NEW")) }

Hope it helps someone.

ATek
  • 815
  • 2
  • 8
  • 20
0

I modified @FrinkTheBrave 's answer to also create sub-directories, and resolve relative paths:

$src_root = Resolve-Path("./source/path/")
$target_root = Resolve-Path("./target/path/")
$glob_filter = "*.*"
Get-ChildItem -path $src_root -filter $glob_filter -recurse | 
  ForEach-Object {
        $target_filename=($_.FullName -replace [regex]::escape($src_root),$target_root) 
        $target_path=Split-Path $target_filename
        if (!(Test-Path -Path $target_path)) {
            New-Item $target_path -ItemType Directory
        }
        Copy-Item $_.FullName -destination $target_filename
    }
Prophet Lamb
  • 530
  • 3
  • 17
0

I have used this before:

new-item -path 'd:\path\to\destination' -name 'folder' && robocopy '.\source\folder\path\or\file' 'd:\path\to\destination\folder' /e

the double ampersand ( && ) works for a command prompt and PowerShell. It allows two separate commands to be run consecutively.

R S
  • 11
  • 4