0

I have this simple cmdlet that correctly copies files and folders to a second directory:

Copy-Item -Path 'G:\xyz\Test\A' -Recurse -Destination 'G:\xyz\Test\B\'

However I am unable to tweak it to only copy the latest file in each folder within its folder (i.e. also copying the folder structure). I have written the following, but this doesn't copy folder names and does not go down all the hierarchies of sub-folders.

Get-ChildItem -Path 'G:\xyz\Test\A' -Directory | ForEach-Object {
    Get-ChildItem -Path 'G:\xyz\Test\A' -File -Recurse |
     Sort-Object LastWriteTime | Select-Object -Last 1 | 
    Copy-Item -Destination 'G:\xyz\Test\B\'
}

Could someone please identify my errors!

2 Answers 2

1

A slightly different approach of collecting the directories first then iterating through them to retrieve the files.

If ( -not (Test-Path -Path "G:\Test\B")) {
  $Null = New-Item -ItemType "Directory" "G:\Test\B"
}

Get-ChildItem -Path 'G:\Test\A' -Directory -Recurse | 
   
   #Sort to insure dirs are created shortest to longest
   Sort-Object FullName |
   ForEach-Object {
     $DestPath = $($_.FullName).Replace("`\Test`\A","`\Test\`B")
     If ( -not (Test-Path -Path "$DestPath")) {
      $Null =  New-Item -ItemType "Directory" "$DestPath"
     }
     Get-ChildItem -Path "$($_.FullName)" -File |
     Sort-Object LastWriteTime |
     Select-Object -Last 1 | 
     Copy-Item -Destination "$DestPath"

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

2 Comments

With many folders, would "collecting the directories first then iterating through them" be a bit risky in terms of memory?
Well you'd need a lot of very longdirectories
1

First error is that you don't pass the current directory from the outer Get-ChildItem to the inner one. The inner one currently always loops over the same sub directory.

Also, when you copy individual files in a Get-ChildItem pipeline, you have to build up the destination path on your own, in order to keep the relative source directory structure:

$source = 'G:\xyz\Test\A'
$destination = 'G:\xyz\Test\B\'

# Set base directory for outer Get-ChildItem and Resolve-Path -Relative
Push-Location $source  
try {
    Get-ChildItem -Directory | ForEach-Object {
        Get-ChildItem -Path $_.Fullname -File -Recurse |
            Sort-Object LastWriteTime | Select-Object -Last 1 | 
            ForEach-Object {
                # Make the current file path relative to $source
                $relativePath = Resolve-Path $_.Fullname -Relative

                # Build up the full destination file path
                $destinationFullPath = Join-Path $destination $relativePath
                
                # Create destination directory if not already exists (-force)
                $null = New-Item (Split-Path $destinationFullPath -Parent) -ItemType Directory -Force

                # Copy the current file
                Copy-Item -Path $_.Fullname -Destination $destinationFullPath
            }
    }
}
finally {
    Pop-Location  # Restore the current directory
}
  • Resolve-Path -Relative makes the given path relative to the current directory. In order to get paths relative to the source directory, we set the current directory to the source directory path using Push-Location.
  • The try and finally blocks make sure the current directory is restored even in case of a script-terminating error (exception).

1 Comment

Well you'd need a lot of very long directories to cause memory problems. A single GB of memory will hold approximately 6,500 paths of 160 bytes each.

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.