Reputation: 5157
I have a very large directory located at D:\Stuff
and I want to create a copy of it at D:\CopyStuff
, but I only want to take files with a certain extension as well as keep the folder structure.
Getting the files I want seems simple enough:
$from = "D:\stuff"
$to = "D:\CopyStuff"
$files = Get-ChildItem -Recurse -Path $from -Include *.config, *.txt, *.ini
However, copying the files and keeping the structure is a bit more challenging. I could use a for-loop, but that seems against the very nature of Powershell. Here https://stackoverflow.com/a/25780745/782880, it suggests to do it this way:
Get-ChildItem -Path $sourceDir | Copy-Item -Destination $targetDir -Recurse -Container
But that copies files to D:\CopyStuff
with no folders, much less my original structure. What am I doing wrong? I'm using Powershell 5.
Upvotes: 1
Views: 3533
Reputation: 17472
try this :
$Source="C:\temp\test1"
$Dest="C:\temp\test2"
$EnableExt=".config", ".txt" , ".ini"
Get-ChildItem $Source -Recurse | % {
$NewPath=$_.FullName.Replace($Source, $Dest)
if ($_.psiscontainer)
{
New-Item -Path $NewPath -ItemType Directory -Force
}
elseif ($_.Extension -in $EnableExt)
{
Copy-Item $_.FullName $NewPath -Force
}
}
Upvotes: 2
Reputation: 437833
Note: The solution below creates analogous target folders only for those source folders that contain files matching the -Include
filter, not for all source folders.
You can get away with a single-pipeline solution by combining Get-ChildItem
-Name
with delay-bind script blocks:
$from = 'D:\stuff'
$to = 'D:\CopyStuff'
Get-ChildItem -Name -Recurse -LiteralPath $from -Include *.config, *.txt, *.ini |
Copy-Item `
-LiteralPath { Join-Path $from $_ } `
-Destination { New-Item -Type Directory -Force (Split-Path (Join-Path $to $_)) }
-Name
emits paths relative to the input directory as strings.
Delay-bind script block { Join-Path $from $_ }
builds the full input file name from each relative input path.
Delay-bind script block { New-Item -Type Directory -Force (Split-Path (Join-Path $to $_)) }
builds the full path of the target directory from the target root path and the relative input path and creates that directory on demand, using a preexisting one if present (-Force
).
Upvotes: 0
Reputation: 771
First of all, Copy-Item
can do it on its own like:
$fromFolder = "C:\Temp\Source"
$toFolder = "C:\Temp\Dest"
Copy-Item -Path $fromFolder -Destination $toFolder -Recurse -Filter *.txt
But, you may not like the result: it will make folder "Source" inside the "Dest" folder, and then copy the structure. I reckon, you need the same files/folders from inside "Source" folder to be copy to the "Dest" folder. Well, it's a bit more complex, but here it is:
$fromFolder = "C:\Temp\Source"
$toFolder = "C:\Temp\Dest"
Get-ChildItem -Path $fromFolder -Directory -Recurse | Select-Object FullName, @{N="NewPath";E={$_.FullName.Replace($fromFolder, $toFolder)}} | ForEach-Object { New-Item -Path $_.NewPath -ItemType "Directory" }
Get-ChildItem -Path $fromFolder -Include "*.txt" -Recurse | Select-Object FullName, @{N="NewPath";E={$_.FullName.Replace($fromFolder, $toFolder)}} | ForEach-Object { Copy-Item -Path $_.FullName -Destination $_.NewPath }
It copies folder structure first, then files.
NB! I do strongly recommend to use absolute paths only. Otherwise, the Replace
method may give unexpected results.
Upvotes: 1