How to use PowerShell copy-item and keep structure

Powershell

Powershell Problem Overview


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.

Powershell Solutions


Solution 1 - Powershell

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

Solution 2 - Powershell

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.

Solution 3 - Powershell

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

Solution 4 - Powershell

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

Solution 5 - Powershell

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.

Solution 6 - Powershell

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/

Solution 7 - Powershell

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>>

Solution 8 - Powershell

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

Solution 9 - Powershell

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.

Solution 10 - Powershell

$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.

Solution 11 - Powershell

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"

Solution 12 - Powershell

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
}

Solution 13 - Powershell

$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
}

Solution 14 - Powershell

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

Solution 15 - Powershell

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSylvainView Question on Stackoverflow
Solution 1 - PowershellThomas MView Answer on Stackoverflow
Solution 2 - PowershellSentientView Answer on Stackoverflow
Solution 3 - PowershellworkabyteView Answer on Stackoverflow
Solution 4 - PowershellJoeyView Answer on Stackoverflow
Solution 5 - PowershellAdam CavinessView Answer on Stackoverflow
Solution 6 - PowershellRafael CostaView Answer on Stackoverflow
Solution 7 - PowershellmjsrView Answer on Stackoverflow
Solution 8 - Powershellmikhail-tView Answer on Stackoverflow
Solution 9 - PowershellgladiatorView Answer on Stackoverflow
Solution 10 - Powershelljeremias kühnisView Answer on Stackoverflow
Solution 11 - Powershellklaas-janView Answer on Stackoverflow
Solution 12 - PowershellJimView Answer on Stackoverflow
Solution 13 - PowershellHimanshuView Answer on Stackoverflow
Solution 14 - PowershellDarrel K.View Answer on Stackoverflow
Solution 15 - PowershelluseromView Answer on Stackoverflow