60

I have a directory structure that looks like this:

C:\folderA\folderB\folderC\client1\f1\files
C:\folderA\folderB\folderC\client1\f2\files
C:\folderA\folderB\folderC\client2\f1\files
C:\folderA\folderB\folderC\client2\f2\files
C:\folderA\folderB\folderC\client3\f1\files
C:\folderA\folderB\folderC\client4\f2\files

I want to copy the content of the f1 folders in C:\tmp\ to get this

C:\tmp\client1\f1\files
C:\tmp\client2\f1\files
C:\tmp\client3\f1\files

I tried this:

Copy-Item -recur -path: "*/f1/" -destination: C:\tmp\

But it copies the contents without copying the structure correctly.

0

16 Answers 16

45

In PowerShell version 3.0 and newer this is simply done this way:

Get-ChildItem -Path $sourceDir | Copy-Item -Destination $targetDir -Recurse -Container

Reference: Get-ChildItem

Sign up to request clarification or add additional context in comments.

6 Comments

What does the -container flag do?
Note that in this example, $targetDir must already exist. In my testing I found that when $targetDir does not exist, the files will be copied but the directory structure will be flattened.
bwerks is correct. No idea why this received any votes.
this is not working, i just tryed and the structure is just flattened as said in bwerks comment
|
29

PowerShell:

$sourceDir = 'c:\folderA\folderB\folderC\client1\f1'
$targetDir = ' c:\tmp\'

Get-ChildItem $sourceDir -filter "*" -recurse | `
    foreach{
        $targetFile = $targetDir + $_.FullName.SubString($sourceDir.Length);
        New-Item -ItemType File -Path $targetFile -Force;
        Copy-Item $_.FullName -destination $targetFile
    }

Note:

  • The -filter "*" does not do anything. It is just here to illustrate if you want to copy some specific files. E.g. all *.config files.
  • Still have to call a New-Item with -Force to actually create the folder structure.

8 Comments

I don't understand, is this powershell? Or a .bat script? Sorry I am new to NT
FWIW, this fails if you put a trailing directory separator on $sourceDir.
Works for PS 2.0 and PS 4.0 ? Mabye any differences in cmdlets.
The code above tries to create files of every folder. A fast fix would be to exclude the folders like so: foreach{ if( $_.Attributes -neq 'Directory' ){ $targetFile =... } }
@Sentient It keeps the folder structure - I was a bit unclear. My code excludes making files out of folders; it still iterates the files and folders are created as necessary. Come to think of it - empty folders are not created then.
|
22

I have been digging around and found a lot of solutions to this issue, all being some alteration, not just a straight copy-item command. Granted, some of these questions predate PowerShell 3.0 so the answers are not wrong, but using PowerShell 3.0 I was finally able to accomplish this using the -Container switch for Copy-Item:

Copy-Item $from $to -Recurse -Container

This was the test I ran, no errors and the destination folder represented the same folder structure:

New-Item -ItemType dir -Name test_copy
New-Item -ItemType dir -Name test_copy\folder1
New-Item -ItemType file -Name test_copy\folder1\test.txt

# NOTE: with no \ at the end of the destination, the file
#       is created in the root of the destination, and
#       it does not create the folder1 container
#Copy-Item D:\tmp\test_copy\* D:\tmp\test_copy2 -Recurse -Container

# If the destination does not exist, this created the
# matching folder structure and file with no errors
Copy-Item D:\tmp\test_copy\* D:\tmp\test_copy2\ -Recurse -Container

5 Comments

I noticed that if you want to copy one directory to another directory, then you need to end both paths with a trailing '\'. Otherwise it copies the files to the root of that target directory. Strange home MS does things.
you mean the content of one dir to the content of another?
Perfect answer!
I tried this and if the destination container does not exist, this will only transfer the files without folders which contradicts what the documentation says for the Copy-Item. The only solution I have found is to create the target folder before performing the Copy-Item cmdlet.
I too found that if the destination folder structure did not exist, it would only create the first subdirectory that didn't already exist and put all the files in there. If ran agian, it would create the next level and again dump all the files in there. Very strange. But create the destination directory structure first and it works well, like this: Get-ChildItem -Path $(srcDirectory) -Directory -Recurse | ForEach-Object { New-Item -ItemType Directory -Path $(dstDirectory) -Name $_ } Copy-Item -Path $(srcDirectory)/* -Destination $(dstDirectory) -Recurse -Container -Force
13

Use xcopy or robocopy, both of which have been designed for exactly that purpose. Assuming your paths are only filesystem paths, of course.

5 Comments

I know but I'd like to do that in powershell because that's a part of a bigger script and I'm going to pipe the output of copy (using -PassThru) to other commands.
you can use cmd /c xcopy to use xcopy from powershell
@ZacharyYates: You can use xcopy to use xcopy from PowerShell. It's not a cmd built-in, so there is no need to use cmd /c here.
few answers below speak to a -container switch, worth taking a look at. copy-item -container will do just that without all the extra work native to powershell.
@ZacharyYates: unfortunately, xcopy fails where the full path of a file exceeds 256 chars and stops (i.e. no subsequent files are copied).
7

Since I spent time finding a more straightforward way that wouldn't involve piping or inline scripting:

Copy-Item -Path $sourceDir -Destination $destinationDir -Recurse -Container -Verbose

One can also supply a -Filter argument if a condition for the copy is demanded.

Works with PowerShell 5.1.18362.752.

Source: https://devblogs.microsoft.com/scripting/powertip-use-powershell-to-copy-items-and-retain-folder-structure/

2 Comments

This appears to be the same solution previously provided by @workabyte.
this is more clear than other solutions.
7

If you want to copy a folder structure correctly with PowerShell, do it like so:

$sourceDir = 'C:\source_directory'
$targetDir = 'C:\target_directory'

Get-ChildItem $sourceDir -Recurse | % {
   $dest = $targetDir + $_.FullName.SubString($sourceDir.Length)

   If (!($dest.Contains('.')) -and !(Test-Path $dest))
   {
        mkdir $dest
   }

   Copy-Item $_.FullName -Destination $dest -Force
}

This accounts for creating directories and just copying the files. Of course you'll need to modify the Contains() call above if your folders contain periods or add a filter if you want to search for "f1" as you mentioned.

1 Comment

This seems the most useful PS answer as it takes care of creating folders that do not exist, which is a common requirement and many other answers are ignoring. To be more explicit on the destination folder you can use: $destFile = $processingPath + $_.FullName.SubString($importPath.Length) $destFolder = Split-Path $destFile if (!(Test-Path $destFolder)) { mkdir $destFolder }
4

The Container switch (to Copy-Item) maintain the folder structure. Enjoy.

testing>> tree

Folder PATH listing
Volume serial number is 12D3-1A3F
C:.
├───client1
│   ├───f1
│   │   └───files
│   └───f2
│       └───files
├───client2
│   ├───f1
│   │   └───files
│   └───f2
│       └───files
├───client3
│   └───f1
│       └───files
└───client4
    └───f2
        └───files

testing>> ls client* | % {$subdir = (Join-Path $_.fullname f1); $dest = (Join-Path temp ($_
.name +"\f1")); if(test-path ($subdir)){ Copy-Item $subdir $dest -recurse -container -force}}

testing>> tree

Folder PATH listing
Volume serial number is 12D3-1A3F
C:.
├───client1
│   ├───f1
│   │   └───files
│   └───f2
│       └───files
├───client2
│   ├───f1
│   │   └───files
│   └───f2
│       └───files
├───client3
│   └───f1
│       └───files
├───client4
│   └───f2
│       └───files
└───temp
    ├───client1
    │   └───f1
    │       └───files
    ├───client2
    │   └───f1
    │       └───files
    └───client3
        └───f1
            └───files

testing>>

1 Comment

though your answer is correct its not clear, may want to add a very simple example call at the top to help clarify. I read the answer but it seemed convoluted so i moved on looking for another solution (coming back i see that its the same solution i found but not as clear as it could be).
2

I needed to do the same thing, so I found this command:

XCopy souce_path destination_path /E /C /I /F /R /Y

And in your case:

XCopy c:\folderA\folderB\folderC c:\tmp /E /C /I /F /R /Y

And if you need to exclude some items, create text file with a list of exclusions. E.g.:

Create text file 'exclude.txt' on drive C:\ and add this in it:

.svn
.git

And your copy command will look like this now:

XCopy c:\folderA\folderB\folderC c:\tmp /EXCLUDE:c:\exclude.txt /E /C /I /F /R /Y

2 Comments

This was what I was looking for thanks. I also used the /w and /S to copy sub directories and to skip empty folders
While this works its kind of clunky that you need to name the specific folders/files to include. Providing a pattern match of some kind would make it better.
2

Here is a slight variation of the main question where the source file is relative and only a subset of files need to be copied with folder structure. This scenario can happen if you run git diff on a repo and have a subset of changes. E.g.

src/folder1/folder2/change1.txt      
src/folder3/change2.txt   
->   
c:\temp

Code

# omitting foreach($file in $files)
$file = 'src/folder1/folder2/change1.txt'
$tempPath = 'c:\temp'

# Build destination path
$destination = Join-Path $tempPath -ChildPath (Split-Path $file)

# Copy and ensure destination exists
Copy-Item -Path $file -Destination (New-Item -Path $destination -Type Directory -Force)

Results in
c:\temp\src\folder1\folder2\change1.txt
c:\temp\src\folder3\change2.txt

The main techniques in script are evaluating folder structure and ensuring it is created with New-Item command

Comments

1
$source ="c:\"
$destination="c:\tmp"
sl $source
md $destination
ls "." -rec -Filter *.zip | %{
$subfolder=($_.FullName)
$pathtrim=($subfolder -split [regex]::escape([system.io.path])::directoryseperatorchar)[-3] # update -2,-3 here to match output, we need 'client1\f1\files' here 
echo $pathtrim
$finaldest=Join-Path -Path $destination -ChildPath $pathtrim
cp $source $finaldest -Verbose
}

Comments

0
$sourceDir = 'C:\source_directory'
$targetDir = 'C:\target_directory'

Get-ChildItem $sourceDir -Recurse | % {
   $dest = $targetDir + $_.FullName.SubString($sourceDir.Length)

   If (!($dest.Contains('.')) -and !(Test-Path $dest))
   {
        mkdir $dest
   }

   Copy-Item $_.FullName -Destination $dest -Force
}

This code works, especially if you use Get-ChildItem in connection with a filter/where-object method.

However, there is one minor error with this code: By the "IF" statement and the following code "mkdir" the folder on the $targetDir will be created...afterwards the command "copy-item" creates the same folder within the folder just created by "mkdir" command.


Here is an example of how it worked for me with a "Where-Object" function. You can simply omit the IF statement.

$Sourcefolder= "C:\temp1"
$Targetfolder= "C:\temp2"


$query = Get-ChildItem $Sourcefolder -Recurse | Where-Object {$_.LastWriteTime -gt [datetime]::Now.AddDays(-1)}
$query | % {
    $dest = $Targetfolder + $_.FullName.SubString($Sourcefolder.Length)
    Copy-Item $_.FullName -Destination $dest -Force
}

Make sure that the paths are not indicated with an "\" at the end.

Comments

0

The below worked for me

@echo off
    setlocal enableextensions disabledelayedexpansion

    set "target=e:\backup"

    for /f "usebackq delims=" %%a in ("TextFile.txt") do (
        md "%target%%%~pa" 2>nul
        copy /y "%%a" "%target%%%~pa"
    )

For each line (file) inside the list, create, under the target folder, the same path indicated in the read line (%%~pa is the path of the element referenced by %%a). Then, copy the read file to the target folder.

1 Comment

I doubt this will work in powershell - it is a batch file
0

Forget Copy-Item, it never does what you expect, Invoke-Expression invokes a commandline command and allows for using powershell variables. I use the good-old xcopy command with /E so directories are copied (also empty ones) and /Y to suppress prompting and confirm to overwrite.

$sourceDir = "c:\source\*"
$targetDir = "c:\destination\"
Invoke-Expression "xcopy $sourceDir $targetDir /E /Y"

3 Comments

Please don't post only code as answer, but also provide an explanation what your code does and how it solves the problem of the question. Answers with an explanation are usually more helpful and of better quality, and are more likely to attract upvotes
This is the solution I chose and it worked. If you don't want to copy empty folders, replace /E with /S
The author's original question also requires to filter parent folders that contain specifically named child folders, which this does not address.
0

Generally, Copy-Item will do this for you as long as the target folder already exists. The documentation does not match what I have validated through testing. A trailing slash in the destination path does not resolve this. When copying a folder hierarchy, you must use -Recurse. The Container parameter is defaulted to true, you can leave that off.

In your question you are filtering the list of files to copy based on a named subfolder. While this works for the Path property of Copy-Item, the problem is that the target folder ( client* ) do not exist, which is why the files are placed in the root of the target folder. The majority of the other answers do not address this scenario specifically and thus do not answer the question asked. To achieve your solution this will take two steps:

  1. Select the files you want to copy
  2. Copy the selected files to the destination folder while ensuring the destination folder exists
$source = 'C:\FolderA\FolderB\FolderC'
$dest = 'C:\tmp'
# Only need the full name
$files = Get-ChildItem -Path $source -Recurse -File | Where-Object { $_.FullName -match '^.+\\client\d\\f1\\.+\..+$' } | ForEach-Object { $_.FullName }

# Iterate through the list copying files
foreach( $file in $files ) {
    $destFile = $file.Replace( $source, $dest )
    $destFolder = Split-Path -Path $destFile -Parent

    # Make sure the destination folder for the file exists
    if ( -not ( Test-Path -Path $destFolder ) ) {
        New-Item -Path ( Split-Path -Path $destFolder -Parent ) -Name ( Split-Path -Path $destFolder -Leaf ) -ItemType Directory -Force
    }
    
    Copy-Item -Path $file -Destination $destFolder
}

Comments

0

I have created the following function that will copy all files from a directory keep the folder structure. You can then specify the start index where the folder structure should start.

    function Copy-FileKeepPath {

    param (
        $filter,$FileToCopy,$des,$startIndex
    )
    Get-ChildItem -Path $FileToCopy -Filter $filter -Recurse -File | ForEach-Object {
        $fileName = $_.FullName
        #Remove the first part to ignore from the path.
        $newdes=Join-Path -Path $des -ChildPath $fileName.Substring($startIndex)
        $folder=Split-Path -Path $newdes -Parent
        $err=0
    
        #check if folder exists"
        $void=Get-Item $folder -ErrorVariable err  -ErrorAction SilentlyContinue
        if($err.Count -ne 0){
          #create when it doesn't
          $void=New-Item -Path $folder -ItemType Directory -Force -Verbose
        }

        $void=Copy-Item -Path $fileName -destination $newdes -Recurse -Container -Force -Verbose
    }
}

Use it as follows:

Copy-FileKeepPath -FileToCopy 'C:\folderA\folderB\folderC\client1\f1\' -des "C:\tmp" -filter * -startIndex "C:\folderA\folderB\folderC\".Length

Comments

-3

I had a similar requirement where I wanted to share only library files (different platform and build types). I was about to write my PowerShell script, that I realized it can be done less than a minute using the "SyncBackFree" tool. It worked as expected.

1> Create Profile

2> Select Source and Destination Folder

3> Click "Choose sub-directories and files

4> Click left side "Change Filter"

5> Add file extension ("*.lib") and "*\" (for folder structure)

enter image description here

2 Comments

Although this (almost) does what the OP asks (almost because it doesn't use powershell) this would be useless in a CI/CD pipeline using an automated deployment tool, such as Octopus...
Downvoted as it has nothing to do with powershell tag

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.