Home > Blockchain >  PowerShell copy latest file in recursive folders
PowerShell copy latest file in recursive folders

Time:10-21

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!

CodePudding user response:

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

CodePudding user response:

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"

   }  
  • Related