tnpir4002
tnpir4002

Reputation: 83

Powershell: Find Folders with (Name) and Foreach Copy to Location Preserve Directory Structure

Got another multi-step process I'm looking to streamline. Basically, I'm looking to build a Powershell script to do three things:

  1. Get-Childitem to look for folders with a specific name (we'll call it NAME1 as a placeholder)
  2. For each folder it finds that has the name, I want it to output the full directory to a TXT file (so that in the end I wind up with a text file that has a list of the results it found, with their full paths; so if it finds folders with "NAME1" in five different subdirectories of the folder I give it, I want the full path beginning with the drive letter and ending with "NAME1")
  3. Then I want it to take the list from the TXT file, and copy each file path to another drive and preserve directory structure

So basically, if it searches and finds this:

D:\TEST1\NAME1
D:\TEST7\NAME1
D:\TEST8\NAME1\

That's what I want to appear in the text file.

Then what I want it to do is to go through each line in the text file and plug the value into a Copy-Item (I'm thinking the source directory would get assigned to a variable), so that when it's all said and done, on the second drive I wind up with this:

E:\BACKUP\TEST1\NAME1
E:\BACKUP\TEST7\NAME1
E:\BACKUP\TEST8\NAME1\

So in short, I'm looking for a Get-Childitem that can define a series of paths, which Copy-Item can then use to back them up elsewhere.

I already have one way to do this, but the problem is it seems to copy everything every time, and since one of these drives is an SSD I only want to copy what's new/changed each time (not to mention that would save time when I need to run a backup):

$source = "C:\"
$target = "E:\BACKUP\"
$search = "NAME1"
$source_regex = [regex]::escape($source)
(gci $source -recurse | where {-not ($_.psiscontainer)} | select -expand fullname) -match "\\$search\\" |
foreach { 

$file_dest = ($_ | split-path -parent) -replace $source_regex,$target

if (-not (test-path $file_dest)){mkdir $file_dest}
copy-item $_ -Destination $file_dest -force -verbose
}

If there's a way to do this that wouldn't require writing out a TXT file each time I'd be all for that, but I don't know a way to do this the way I'm looking for except a Copy-Item.

I'd be very grateful for any help I can get with this. Thanks all!

Upvotes: 0

Views: 1499

Answers (1)

Theo
Theo

Reputation: 61208

If I understand correctly, you want to copy all folders with a certain name, keeping the original folder structure in the destination path and copy only files that are newer than what is in the destination already.

Try

$source = 'C:\'
$target = 'E:\BACKUP\'
$search = 'NAME1'

# -ErrorAction SilentlyContinue because in the C:\ disk you are bound to get Access Denied on some paths
Get-ChildItem -Path $source -Directory -Recurse -Filter $search -ErrorAction SilentlyContinue | ForEach-Object {
    # construct the destination folder path
    $dest = Join-Path -Path $target -ChildPath $_.FullName.Substring($source.Length)
    # copy the folder including its files and subfolders (but not empty subfolders)
    # for more switches see https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/robocopy
    robocopy $_.FullName $dest  /XO /S /R:0
}

If you don't want console output of robocopy you can silence it by appending 2>&1, so neither stdout nor stderr is echoed

If you want to keep a file after this with both the source paths and the destinations, I'd suggest doing

$source = 'C:\'
$target = 'E:\BACKUP\'
$search = 'NAME1'
$output = [System.Collections.Generic.List[object]]::new()

# -ErrorAction SilentlyContinue because in the C:\ disk you are bound to get Access Denied on some paths
Get-ChildItem -Path $source -Directory -Recurse -Filter $search -ErrorAction SilentlyContinue | ForEach-Object {
    # construct the destination folder path
    $dest = Join-Path -Path $target -ChildPath $_.FullName.Substring($source.Length)
    # add an object to the output list
    $output.Add([PsCustomObject]@{Source = $_.FullName; Destination = $dest })
    # copy the folder including its files and subfolders (but not empty subfolders)
    # for more switches see https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/robocopy
    robocopy $_.FullName $dest  /XO /S /R:0
}
# write the output to csv file
$output | Export-Csv -Path 'E:\backup.csv' -NoTypeInformation

Upvotes: 1

Related Questions