0

Using PowerShell, I'm trying to pipe a file list from my local drive so Copy-Items can copy them to a network folder. The following statement does find the files I need, and copies them. However, all path information is lost and they all end up in the same folder. I want each file to be saved in relative path fashion at the destination with its file path from the local disk.

So for example "c:\users\user_name\Documents\file_1.txt" should be copied to "\\server\share\my_folder\users\user_name\Documents\file_1.txt"

My searches have proved fruitless. Can this statement be modified to achieve that?

Get-ChildItem -Path c:\users\user_name -r | ? {!($_.psiscontainer) -AND $_.lastwritetime -gt (get-date).date} | %{Copy-Item -Path $_.fullname -destination "\\server\share\my_folder" -Recurse -Force -Container}

EDIT:

It seems like Copy-Item should be able to do this. The following command works:

Copy-Item -Path c:\users\user_name\Documents -Destination "\\server\share\my_folder" -recurse -Force

This command copies all files, sub-folders and files in the sub-folders found in and under c:\users\user_name\Documents. It then recreates the relative directory structure on the network share. It seems like the $_.fullname parameter from the first command is not being treated in a similar fashion. I'm most certainly not piping file list in the expected manner to Copy-Item. Any other advice?

timlash
  • 89
  • 2
  • 8
  • I would recommend you take a look at `robocopy` instead – Bill_Stewart Aug 18 '20 at 21:31
  • Does this answer your question? [Get relative path of files in sub-folders from the current directory](https://stackoverflow.com/questions/10972589/get-relative-path-of-files-in-sub-folders-from-the-current-directory) – iRon Aug 19 '20 at 10:11
  • @iRon - That does not answer my question. I have the relative path info, I'm trying to recreate that structure on the network file share. Below Doug Maurer indicates there is no way for the Copy-Item command to accomplish that. So, I guess I have to roll my own functionality that was present in MS-DOS 6.0. Disappointing. – timlash Aug 19 '20 at 12:55

3 Answers3

0

There are a few things we should go over. First, If you just want files you can drop the !$_.psiscontainer check and just add -file to the Get-Childitem command. Next, if you intend to copy files that are older that Get-Date, the comparison needs to be -lt. As for creating the directory structure in the destination, you'd need to make it yourself. You can achieve it by stripping c: off the path and using the *-Path cmdlets like this.

$source      = 'c:\users\user_name\Documents'
$destination = '\\server\share\my_folder'

Get-ChildItem -Path $source -File -Recurse |
    Where-Object {$_.lastwritetime -lt (get-date).date} |
        ForEach-Object {
            $destdir = Join-Path $destination (Split-Path $_.FullName -Parent).Substring(2)
            
            If(-not(Test-Path $destdir))
            {
                Write-Host Making directory $destdir -ForegroundColor Cyan
                $null = New-Item -Path $destdir -ItemType Directory
            }
            
            Write-Host Copying file $_.name to $destdir -ForegroundColor Green
            Copy-Item $_.FullName -Destination $destdir
        }

However as Bill_Stewart said robocopy or another tool may be a better fit. Robocopy would surely be much faster so if you need this to process a lot of data, I'd direct my effort in that direction.

Doug Maurer
  • 8,090
  • 3
  • 12
  • 13
  • No, I don't want files older than Get-Date. What about my question suggested that my boolean comparison was opposite of what I wrote? My question indicated that I was returning the files I was after AND copying them. They were just not being copied as I had hoped. I included !$_.psiscontainer since I'm trying to copy files and folders. So, taking the implication of your re-worked question and Bill_Stewart's comment from above, the answer to my question appears to be "No". The Copy-Item portion of my statement can't be reworked to re-create the file paths. – timlash Aug 19 '20 at 13:03
  • So you want files made in the future that don’t exist yet. Also, !$_.psiscontainer means not a directory so you’re filtering those out. – Doug Maurer Aug 19 '20 at 14:28
  • 1) This will be run at the end of the day and pick up files that were created/updated the same day. I'm trying to create an incremental back-up strategy. As I say, I'm finding the files I need. 2) Not a directory - I don't want to pipe an updated directory to Copy-Item since that will result in a full directory copy. This command works in that it lists the files I want to copy: Get-ChildItem -Path c:\users\tlash -r | ? {!($_.psiscontainer) -AND $_.lastwritetime -gt (get-date).date}. I just can't get the path to copy. – timlash Aug 19 '20 at 14:48
  • I do believe you’re correct about copy item. If you were doing a folder recursively it would create the sub directories, but for each file I don’t know of a way other than what I’ve suggested. – Doug Maurer Aug 19 '20 at 15:04
0

As mentioned in the noted using Set-Location (see: Get relative path of files in sub-folders from the current directory):

Set-Location c:\users\user_name
Get-ChildItem -Recurse |
  Where-Object { !($_.psiscontainer) -AND $_.lastwritetime -gt (get-date).date } |
    Foreach-Object {
        $Destination = Join-Path \\server\share\my_folder ("$_" | Resolve-Path -Relative)
        New-Item -ItemType File -Path $destination -Force
        Copy-Item -Path "$_" -Destination $Destination -Force
    }
iRon
  • 20,463
  • 10
  • 53
  • 79
  • that one-liner did not work. It does return a proper file list, but the Copy-Item component still fails: "ObjectNotFound: (c:\users\user_name\test.txt:string) [Resilve-Path], ItemNotFoundException. test.txt is in Documents. – timlash Aug 19 '20 at 17:45
  • Sorry, you will need to remove the last `-Recurse` and `-Container` switches as that is already defined by the `Get-ChildItem` and `Where-Object` cmdlets (I have update the answer). – iRon Aug 19 '20 at 18:10
  • Same error. When I use this == Set-Location c:\users\user_name; Get-ChildItem -Recurse | ? { !($_.psiscontainer) -AND $_.lastwritetime -gt (get-date).date } | Resolve-Path -Relative == I get: .\Documents\test.txt. However, the Copy-Item is still trying to copy c:\users\user_name\test.txt which does not exist. Copy-Items can't be made to recognize the path being passed. – timlash Aug 19 '20 at 18:20
  • Okay, this works - Set-Location c:\users\user_name; Get-ChildItem -Recurse | ? { !($_.psiscontainer) -AND $_.lastwritetime -gt (get-date).date } | % {Copy-Item $_.fullname -destination (Join-Path '\\server\share\my_folder' ($_ | Resolve-Path -Relative)) -Force } BUT ONLY IF THE DESTINATION FOLDER "Documents" ALREADY EXISTS. So close! – timlash Aug 19 '20 at 18:30
  • This "folder" issue is in fact described at [Should Copy-Item create the destination directory structure?](https://stackoverflow.com/a/7523632/1701026). I incorporated this in the answer. I did removed the oneliner as is no longer clear what it does but you might simply concatenate the statements with a semicolon (`;`) if you do need a one liner (you can also just paste multiple lines into your IDE). – iRon Aug 20 '20 at 07:21
0

Okay, this was quite the challenge, but I finally found a mechanism. Thanks to @iRob for pointing me to a potential solution. I could not get his code to create the relative paths at the destination, but was able to extend his concept. I ended up adding in a New-Item command (found via Powershell 2 copy-item which creates a folder if doesn't exist) and a Split-Path (found here Copy a list of files to a directory). The Split-Path sliced off the file name from the joined path, then that path was created via the New-Item command. Finally the Copy-Item had a place to copy the file. So in that sense @Doug Maurer was also right that Copy-Item would not do this itself. Some extra preparation was need before the Copy-Item command could successfully run.

Set-Location c:\users\user_name; Get-ChildItem -Recurse | ? { !($_.psiscontainer) -AND $_.lastwritetime -gt (get-date).date } | % {Copy-Item $_.fullname -destination (New-Item -type directory -force ((Join-Path '\\server\share\my_folder' ($_ | Resolve-Path -Relative)) | Split-Path -Parent)) -Force}

IMHO something that was trivial in MS-DOS 6.0 (xcopy /D:08-20-2020 /S c:\users\user_name*.* \server\share\my_folder) should not be so difficult in PowerShell. Seems like there should be an easier way. Now that I think of it, probably should have stuck to xcopy instead of diving down this rabbit hole. PowerShell just seems so shiny.

timlash
  • 89
  • 2
  • 8