0

I'm copying files to a USB utilizing the path of the USB (assuming one drive plugged in) rather than the drive letter. I need to do it this way since I'm using this to clone 52 USBs at once.

This is where the prefix for the path is found:

$usb = Get-Volume | where {$_.DriveType -eq "Removable" }
$usb.Path

Which when concatenated with the rest of the file name becomes something like:

\\?\Volume{df4d4042-9133-11e9-9a6b-48a472473e67}\RDC_2.1.1_ALL.dmg

When I call Copy-Item outside of a job everything works and copies properly. When I call Copy-Item within a PowerShell job it fails and complains about the path. This doesn't make any sense to me.

Can someone tell me why my function copyFilesSync works properly, while copyFiles doesn't work?

For simplicity, I have cut the script back to just the elements that reproduce this issue. There is no practical difference between $filestoCopyJSON1 and $filestoCopyJSON2. Swapping the actual files doesn't make a difference. I just needed two file names so I could run the two functions back-to-back and they were not copying the same file:

$filestoCopyJSON1 = '[
    {
        "FromFile":  "C:\\dev\\VMware-player-12.1.1-3770994.exe",
        "ToFile":  "\\\\?\\Volume{df4d4042-9133-11e9-9a6b-48a472473e67}\\VMware-player-12.1.1-3770994.exe"
    }
]'

$filestoCopyJSON2 = '[
    {
        "FromFile":  "C:\\dev\\RDC_2.1.1_ALL.dmg",
        "ToFile":  "\\\\?\\Volume{df4d4042-9133-11e9-9a6b-48a472473e67}\\RDC_2.1.1_ALL.dmg"
    }
]'

function copyFiles($filestoCopy){
    Write-Host "Copying $($filestoCopy.Count)"
    $copyblock = {
        $copy = $args[0]
        Write-Host "$($copy.FromFile)" "$($copy.ToFile)"
        Copy-Item "$($copy.FromFile)" "$($copy.ToFile)"
        $resultHash = Get-FileHash -LiteralPath "$($copy.ToFile)"
    }

    foreach ($filetoCopy in $filestoCopy) {
        Start-Job -ScriptBlock $copyblock -ArgumentList @($filetoCopy)
    }

    Write-Host "Waiting on copying..."
    Get-Job | Wait-Job
    Write-Host "Files Copied"
}

function copyFilesSync($filestoCopy){
    foreach ($filetoCopy in $filestoCopy) {
        Copy-Item "$($filetoCopy.FromFile)" "$($filetoCopy.ToFile)"
        $resultHash = Get-FileHash -LiteralPath "$($filetoCopy.ToFile)"
    }
}

Remove-Job -Name *
Write-Host "Sync File Copy"

copyFilesSync -filestoCopy $(ConvertFrom-JSON -InputObject $filestoCopyJSON1)

Write-Host "Async File Copy"
copyFiles -filestoCopy $(ConvertFrom-JSON -InputObject $filestoCopyJSON2)
Receive-Job -Name *

This results in the following output:

PS C:\WINDOWS\system32> C:\dev\Test.ps1
Sync File Copy
Async File Copy
Copying 1

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command
--     ----            -------------   -----         -----------     --------             -------
207    Job207          BackgroundJob   Running       True            localhost            ...
Waiting on copying...
207    Job207          BackgroundJob   Completed     True            localhost            ...
Files Copied
C:\dev\RDC_2.1.1_ALL.dmg \\?\Volume{df4d4042-9133-11e9-9a6b-48a472473e67}\RDC_2.1.1_ALL.dmg
Illegal characters in path.
    + CategoryInfo          : NotSpecified: (:) [Copy-Item], ArgumentException
    + FullyQualifiedErrorId : System.ArgumentException,Microsoft.PowerShell.Commands.CopyItemCommand
    + PSComputerName        : localhost

The first file from the Copy-Item that's not in a job completes successfully. The async Copy-Item run through the job fails with the errors as have been seen.

Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
Doug
  • 6,446
  • 9
  • 74
  • 107

1 Answers1

0

I believe this is a bug in Powershell Jobs or Copy-Item. I'm not really sure which. I was able to bypass this by utilizing Background Runspaces instead of Jobs. As a result the following async code runs just fine:

     function copyFiles($filestoCopy){
        Write-Host "Copying $($filestoCopy.Count)" 
        $copyblock = {
            $filestoCopy = $args[0]
            foreach($filetoCopy in $filestoCopy){
                Copy-Item "$($filetoCopy.FromFile)" "$($filetoCopy.ToFile)"
                $resultHash = Get-FileHash -LiteralPath "$($filetoCopy.ToFile)"

                if($filetoCopy.FromFileHash -ne $resultHash.hash){
                    Write-Host "File $($copy.ToFile) Failed.. trying again" 
                    Copy-Item "$($filetoCopy.FromFile)" "$($filetoCopy.ToFile)"
                }    
            }
        }

        $newPowerShell = [PowerShell]::Create().AddScript($copyblock).AddArgument($filestoCopy)
        $job = $newPowerShell.BeginInvoke()


        Write-Host "Waiting on copying..." 
        While (-Not $job.IsCompleted) {}
        $result = $newPowerShell.EndInvoke($job)
        $newPowerShell.Dispose()
        Write-Host "Files Copied" 
    }
Doug
  • 6,446
  • 9
  • 74
  • 107