Reputation: 3987
Is there any way to copy a really large file (from one server to another) in PowerShell AND display its progress?
There are solutions out there to use Write-Progress in conjunction with looping to copy many files and display progress. However I can't seem to find anything that would show progress of a single file.
Any thoughts?
Upvotes: 98
Views: 192408
Reputation: 24922
Starting from Powershell 7.4 Copy-Item
will show the progress status. See the corresponding release note and PR for more information.
Upvotes: 3
Reputation: 526
This is an old post, but I thought it might help others.
The solution with FileStreams is elegant and it works, but very slow.
I think using other programs, like robocopy.exe defeats powershell's purpose.
That was even one of the motivations in the Monad manifest.
So I cracked open the Copy-Item cmdlet from Microsoft.PowerShell.Management, and at the end, it calls CopyFileEx, from kernel32.dll.
On CopyFileEx signature, there's a parameter that accepts a callback to provide progress information.
On pinvoke.net there's a great example on how to marshal this function and the callback into a delegate.
I modified slightly so we can provide the delegate from the PS script itself.
And believe me when I say this.
I was not expecting this to work :D (I literally jumped from the chair).
And it's considerably faster.
Here's the code:
function Copy-File {
[CmdletBinding()]
param (
[Parameter(Mandatory, Position = 0)]
[string]$Path,
[Parameter(Mandatory, Position = 1)]
[string]$Destination
)
$signature = @'
namespace Utilities {
using System;
using System.Runtime.InteropServices;
public class FileSystem {
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CopyFileEx(
string lpExistingFileName,
string lpNewFileName,
CopyProgressRoutine lpProgressRoutine,
IntPtr lpData,
ref Int32 pbCancel,
CopyFileFlags dwCopyFlags
);
delegate CopyProgressResult CopyProgressRoutine(
long TotalFileSize,
long TotalBytesTransferred,
long StreamSize,
long StreamBytesTransferred,
uint dwStreamNumber,
CopyProgressCallbackReason dwCallbackReason,
IntPtr hSourceFile,
IntPtr hDestinationFile,
IntPtr lpData);
int pbCancel;
public enum CopyProgressResult : uint
{
PROGRESS_CONTINUE = 0,
PROGRESS_CANCEL = 1,
PROGRESS_STOP = 2,
PROGRESS_QUIET = 3
}
public enum CopyProgressCallbackReason : uint
{
CALLBACK_CHUNK_FINISHED = 0x00000000,
CALLBACK_STREAM_SWITCH = 0x00000001
}
[Flags]
enum CopyFileFlags : uint
{
COPY_FILE_FAIL_IF_EXISTS = 0x00000001,
COPY_FILE_RESTARTABLE = 0x00000002,
COPY_FILE_OPEN_SOURCE_FOR_WRITE = 0x00000004,
COPY_FILE_ALLOW_DECRYPTED_DESTINATION = 0x00000008
}
public void CopyWithProgress(string oldFile, string newFile, Func<long, long, long, long, uint, CopyProgressCallbackReason, System.IntPtr, System.IntPtr, System.IntPtr, CopyProgressResult> callback)
{
CopyFileEx(oldFile, newFile, new CopyProgressRoutine(callback), IntPtr.Zero, ref pbCancel, CopyFileFlags.COPY_FILE_RESTARTABLE);
}
}
}
'@
Add-Type -TypeDefinition $signature
[Func[long, long, long, long, System.UInt32, Utilities.FileSystem+CopyProgressCallbackReason, System.IntPtr, System.IntPtr, System.IntPtr, Utilities.FileSystem+CopyProgressResult]]$copyProgressDelegate = {
param($total, $transfered, $streamSize, $streamByteTrans, $dwStreamNumber, $reason, $hSourceFile, $hDestinationFile, $lpData)
Write-Progress -Activity "Copying file" -Status "$Path ~> $Destination. $([Math]::Round(($transfered/1KB), 2))KB/$([Math]::Round(($total/1KB), 2))KB." -PercentComplete (($transfered / $total) * 100)
}
$fileName = [System.IO.Path]::GetFileName($Path)
$destFileName = [System.IO.Path]::GetFileName($Destination)
if ([string]::IsNullOrEmpty($destFileName) -or $destFileName -notlike '*.*') {
if ($Destination.EndsWith('\')) {
$destFullName = "$Destination$fileName"
}
else {
$destFullName = "$Destination\$fileName"
}
}
$wrapper = New-Object Utilities.FileSystem
$wrapper.CopyWithProgress($Path, $destFullName, $copyProgressDelegate)
}
Hope it helps.
Happy scripting!
Update:
Same thing, but using CopyFile2.
try {
Add-Type -TypeDefinition @'
namespace Utilities {
using System;
using System.Text;
using System.Runtime.InteropServices;
public delegate COPYFILE2_MESSAGE_ACTION CopyFile2ProgressRoutine(
[In] COPYFILE2_MESSAGE pMessage,
[In, Optional] IntPtr pvCallbackContext
);
[Flags]
public enum CopyFlags : uint {
COPY_FILE_FAIL_IF_EXISTS = 0x00000001,
COPY_FILE_RESTARTABLE = 0x00000002,
COPY_FILE_OPEN_SOURCE_FOR_WRITE = 0x00000004,
COPY_FILE_ALLOW_DECRYPTED_DESTINATION = 0x00000008,
COPY_FILE_COPY_SYMLINK = 0x00000800,
COPY_FILE_NO_BUFFERING = 0x00001000,
COPY_FILE_REQUEST_SECURITY_PRIVILEGES = 0x00002000,
COPY_FILE_RESUME_FROM_PAUSE = 0x00004000,
COPY_FILE_NO_OFFLOAD = 0x00040000,
COPY_FILE_REQUEST_COMPRESSED_TRAFFIC = 0x10000000
}
public enum COPYFILE2_MESSAGE_ACTION : uint {
COPYFILE2_PROGRESS_CONTINUE,
COPYFILE2_PROGRESS_CANCEL,
COPYFILE2_PROGRESS_STOP,
COPYFILE2_PROGRESS_QUIET,
COPYFILE2_PROGRESS_PAUSE
}
public enum COPYFILE2_MESSAGE_TYPE : uint {
COPYFILE2_CALLBACK_NONE,
COPYFILE2_CALLBACK_CHUNK_STARTED,
COPYFILE2_CALLBACK_CHUNK_FINISHED,
COPYFILE2_CALLBACK_STREAM_STARTED,
COPYFILE2_CALLBACK_STREAM_FINISHED,
COPYFILE2_CALLBACK_POLL_CONTINUE,
COPYFILE2_CALLBACK_ERROR,
COPYFILE2_CALLBACK_MAX
}
public enum COPYFILE2_COPY_PHASE : uint {
COPYFILE2_PHASE_NONE,
COPYFILE2_PHASE_PREPARE_SOURCE,
COPYFILE2_PHASE_PREPARE_DEST,
COPYFILE2_PHASE_READ_SOURCE,
COPYFILE2_PHASE_WRITE_DESTINATION,
COPYFILE2_PHASE_SERVER_COPY,
COPYFILE2_PHASE_NAMEGRAFT_COPY,
COPYFILE2_PHASE_MAX
}
[StructLayout(LayoutKind.Sequential)]
public struct ULARGE_INTEGER {
public Int64 QuadPart;
}
[StructLayout(LayoutKind.Sequential)]
public struct _ChunkStarted {
public uint dwStreamNumber;
public uint dwReserved;
public IntPtr hSourceFile;
public IntPtr hDestinationFile;
public ULARGE_INTEGER uliChunkNumber;
public ULARGE_INTEGER uliChunkSize;
public ULARGE_INTEGER uliStreamSize;
public ULARGE_INTEGER uliTotalFileSize;
}
[StructLayout(LayoutKind.Sequential)]
public struct _ChunkFinished {
public uint dwStreamNumber;
public uint dwFlags;
public IntPtr hSourceFile;
public IntPtr hDestinationFile;
public ULARGE_INTEGER uliChunkNumber;
public ULARGE_INTEGER uliChunkSize;
public ULARGE_INTEGER uliStreamSize;
public ULARGE_INTEGER uliStreamBytesTransferred;
public ULARGE_INTEGER uliTotalFileSize;
public ULARGE_INTEGER uliTotalBytesTransferred;
}
[StructLayout(LayoutKind.Sequential)]
public struct _StreamStarted {
public uint dwStreamNumber;
public uint dwReserved;
public IntPtr hSourceFile;
public IntPtr hDestinationFile;
public ULARGE_INTEGER uliStreamSize;
public ULARGE_INTEGER uliTotalFileSize;
}
[StructLayout(LayoutKind.Sequential)]
public struct _StreamFinished {
public uint dwStreamNumber;
public uint dwReserved;
public IntPtr hSourceFile;
public IntPtr hDestinationFile;
public ULARGE_INTEGER uliStreamSize;
public ULARGE_INTEGER uliStreamBytesTransferred;
public ULARGE_INTEGER uliTotalFileSize;
public ULARGE_INTEGER uliTotalBytesTransferred;
}
[StructLayout(LayoutKind.Sequential)]
public struct _PollContinue {
public uint dwReserved;
}
[StructLayout(LayoutKind.Sequential)]
public struct _Error {
COPYFILE2_COPY_PHASE CopyPhase;
uint dwStreamNumber;
IntPtr hrFailure;
uint dwReserved;
ULARGE_INTEGER uliChunkNumber;
ULARGE_INTEGER uliStreamSize;
ULARGE_INTEGER uliStreamBytesTransferred;
ULARGE_INTEGER uliTotalFileSize;
ULARGE_INTEGER uliTotalBytesTransferred;
}
[StructLayout(LayoutKind.Explicit)]
public struct COPYFILE2_MESSAGE {
[FieldOffset(0)]
public COPYFILE2_MESSAGE_TYPE Type;
[FieldOffset(1)]
public uint dwPadding;
[FieldOffset(2)]
public _ChunkStarted ChunkStarted;
[FieldOffset(2)]
public _ChunkFinished ChunkFinished;
[FieldOffset(2)]
public _StreamStarted StreamStarted;
[FieldOffset(2)]
public _StreamFinished StreamFinished;
[FieldOffset(2)]
public _PollContinue PollContinue;
[FieldOffset(2)]
public _Error Error;
}
[StructLayout(LayoutKind.Sequential)]
public struct COPYFILE2_EXTENDED_PARAMETERS {
public uint dwSize;
public CopyFlags dwCopyFlags;
public bool pfCancel;
public CopyFile2ProgressRoutine pProgressRoutine;
public IntPtr pvCallbackContext;
}
public class FileSystem {
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern uint CopyFile2(
string pwszExistingFileName,
string pwszNewFileName,
COPYFILE2_EXTENDED_PARAMETERS pExtendedParameters
);
public static void CopyFileEx(string filePath, string destination, Func<COPYFILE2_MESSAGE, IntPtr, COPYFILE2_MESSAGE_ACTION> callback) {
COPYFILE2_EXTENDED_PARAMETERS extParams = new();
extParams.dwSize = Convert.ToUInt32(Marshal.SizeOf(extParams));
extParams.dwCopyFlags = CopyFlags.COPY_FILE_NO_BUFFERING | CopyFlags.COPY_FILE_COPY_SYMLINK;
extParams.pProgressRoutine = new CopyFile2ProgressRoutine(callback);
extParams.pvCallbackContext = IntPtr.Zero;
uint result = CopyFile2(filePath, destination, extParams);
if (result != 0)
throw new SystemException(result.ToString());
}
}
}
'@
}
catch { }
[Func[
Utilities.COPYFILE2_MESSAGE,
IntPtr,
Utilities.COPYFILE2_MESSAGE_ACTION
]]$delegate = {
param([Utilities.COPYFILE2_MESSAGE]$message, $extArgs, $result)
if ($message.Type -eq [Utilities.COPYFILE2_MESSAGE_TYPE]::COPYFILE2_CALLBACK_CHUNK_FINISHED) {
Write-Progress -Activity 'Copying file.' -Status 'Copying...' -PercentComplete (($message.ChunkFinished.uliTotalFileSize.QuadPart / $message.ChunkFinished.uliStreamBytesTransferred.QuadPart) * 100)
}
}
if (Test-Path -Path C:\CopyFile2TestDestination -PathType Container) { [void](mkdir C:\CopyFile2TestDestination) }
[Utilities.FileSystem]::CopyFileEx('C:\superTest.dat', 'C:\CopyFile2TestDestination\superTestCopy.dat', $delegate)
Upvotes: 2
Reputation: 2057
Hate to be the one to bump an old subject, but I found this post extremely useful. After running performance tests on the snippets by stej and it's refinement by Graham Gold, plus the BITS suggestion by Nacht, I have decided that:
Faced with the decision between the two... I found that Start-BitsTransfer supported Asynchronous mode. So here is the result of my merging the two.
function Copy-File {
# ref: https://stackoverflow.com/a/55527732/3626361
param([string]$From, [string]$To)
try {
$job = Start-BitsTransfer -Source $From -Destination $To `
-Description "Moving: $From => $To" `
-DisplayName "Backup" -Asynchronous
# Start stopwatch
$sw = [System.Diagnostics.Stopwatch]::StartNew()
Write-Progress -Activity "Connecting..."
while ($job.JobState.ToString() -ne "Transferred") {
switch ($job.JobState.ToString()) {
"Connecting" {
break
}
"Transferring" {
$pctcomp = ($job.BytesTransferred / $job.BytesTotal) * 100
$elapsed = ($sw.elapsedmilliseconds.ToString()) / 1000
if ($elapsed -eq 0) {
$xferrate = 0.0
}
else {
$xferrate = (($job.BytesTransferred / $elapsed) / 1mb);
}
if ($job.BytesTransferred % 1mb -eq 0) {
if ($pctcomp -gt 0) {
$secsleft = ((($elapsed / $pctcomp) * 100) - $elapsed)
}
else {
$secsleft = 0
}
Write-Progress -Activity ("Copying file '" + ($From.Split("\") | Select-Object -last 1) + "' @ " + "{0:n2}" -f $xferrate + "MB/s") `
-PercentComplete $pctcomp `
-SecondsRemaining $secsleft
}
break
}
"Transferred" {
break
}
Default {
throw $job.JobState.ToString() + " unexpected BITS state."
}
}
}
$sw.Stop()
$sw.Reset()
}
finally {
Complete-BitsTransfer -BitsJob $job
Write-Progress -Activity "Completed" -Completed
}
}
Upvotes: 5
Reputation: 59
This recursive function copies files and directories recursively from source path to destination path If file already exists on destination path, it copies them only with newer files.
Function Copy-FilesBitsTransfer(
[Parameter(Mandatory=$true)][String]$sourcePath,
[Parameter(Mandatory=$true)][String]$destinationPath,
[Parameter(Mandatory=$false)][bool]$createRootDirectory = $true)
{
$item = Get-Item $sourcePath
$itemName = Split-Path $sourcePath -leaf
if (!$item.PSIsContainer){ #Item Is a file
$clientFileTime = Get-Item $sourcePath | select LastWriteTime -ExpandProperty LastWriteTime
if (!(Test-Path -Path $destinationPath\$itemName)){
Start-BitsTransfer -Source $sourcePath -Destination $destinationPath -Description "$sourcePath >> $destinationPath" -DisplayName "Copy Template file" -Confirm:$false
if (!$?){
return $false
}
}
else{
$serverFileTime = Get-Item $destinationPath\$itemName | select LastWriteTime -ExpandProperty LastWriteTime
if ($serverFileTime -lt $clientFileTime)
{
Start-BitsTransfer -Source $sourcePath -Destination $destinationPath -Description "$sourcePath >> $destinationPath" -DisplayName "Copy Template file" -Confirm:$false
if (!$?){
return $false
}
}
}
}
else{ #Item Is a directory
if ($createRootDirectory){
$destinationPath = "$destinationPath\$itemName"
if (!(Test-Path -Path $destinationPath -PathType Container)){
if (Test-Path -Path $destinationPath -PathType Leaf){ #In case item is a file, delete it.
Remove-Item -Path $destinationPath
}
New-Item -ItemType Directory $destinationPath | Out-Null
if (!$?){
return $false
}
}
}
Foreach ($fileOrDirectory in (Get-Item -Path "$sourcePath\*"))
{
$status = Copy-FilesBitsTransfer $fileOrDirectory $destinationPath $true
if (!$status){
return $false
}
}
}
return $true
}
Upvotes: 3
Reputation: 29479
I haven't heard about progress with Copy-Item
. If you don't want to use any external tool, you can experiment with streams. The size of buffer varies, you may try different values (from 2kb to 64kb).
function Copy-File {
param( [string]$from, [string]$to)
$ffile = [io.file]::OpenRead($from)
$tofile = [io.file]::OpenWrite($to)
Write-Progress -Activity "Copying file" -status "$from -> $to" -PercentComplete 0
try {
[byte[]]$buff = new-object byte[] 4096
[long]$total = [int]$count = 0
do {
$count = $ffile.Read($buff, 0, $buff.Length)
$tofile.Write($buff, 0, $count)
$total += $count
if ($total % 1mb -eq 0) {
Write-Progress -Activity "Copying file" -status "$from -> $to" `
-PercentComplete ([long]($total * 100 / $ffile.Length))
}
} while ($count -gt 0)
}
finally {
$ffile.Dispose()
$tofile.Dispose()
Write-Progress -Activity "Copying file" -Status "Ready" -Completed
}
}
Upvotes: 56
Reputation: 1503
i found none of the examples above met my needs, i wanted to copy a directory with sub directories, the problem is my source directory had too many files so i quickly hit the BITS file limit (i had > 1500 file) also the total directory size was quite large.
i found a function using robocopy that was a good starting point at https://keithga.wordpress.com/2014/06/23/copy-itemwithprogress/, however i found it wasn't quite robust enough, it didn't handle trailing slashes, spaces gracefully and did not stop the copy when the script was halted.
Here is my refined version:
function Copy-ItemWithProgress
{
<#
.SYNOPSIS
RoboCopy with PowerShell progress.
.DESCRIPTION
Performs file copy with RoboCopy. Output from RoboCopy is captured,
parsed, and returned as Powershell native status and progress.
.PARAMETER Source
Directory to copy files from, this should not contain trailing slashes
.PARAMETER Destination
DIrectory to copy files to, this should not contain trailing slahes
.PARAMETER FilesToCopy
A wildcard expresion of which files to copy, defaults to *.*
.PARAMETER RobocopyArgs
List of arguments passed directly to Robocopy.
Must not conflict with defaults: /ndl /TEE /Bytes /NC /nfl /Log
.PARAMETER ProgressID
When specified (>=0) will use this identifier for the progress bar
.PARAMETER ParentProgressID
When specified (>= 0) will use this identifier as the parent ID for progress bars
so that they appear nested which allows for usage in more complex scripts.
.OUTPUTS
Returns an object with the status of final copy.
REMINDER: Any error level below 8 can be considered a success by RoboCopy.
.EXAMPLE
C:\PS> .\Copy-ItemWithProgress c:\Src d:\Dest
Copy the contents of the c:\Src directory to a directory d:\Dest
Without the /e or /mir switch, only files from the root of c:\src are copied.
.EXAMPLE
C:\PS> .\Copy-ItemWithProgress '"c:\Src Files"' d:\Dest /mir /xf *.log -Verbose
Copy the contents of the 'c:\Name with Space' directory to a directory d:\Dest
/mir and /XF parameters are passed to robocopy, and script is run verbose
.LINK
https://keithga.wordpress.com/2014/06/23/copy-itemwithprogress
.NOTES
By Keith S. Garner (KeithGa@KeithGa.com) - 6/23/2014
With inspiration by Trevor Sullivan @pcgeek86
Tweaked by Justin Marshall - 02/20/2020
#>
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$Source,
[Parameter(Mandatory=$true)]
[string]$Destination,
[Parameter(Mandatory=$false)]
[string]$FilesToCopy="*.*",
[Parameter(Mandatory = $true,ValueFromRemainingArguments=$true)]
[string[]] $RobocopyArgs,
[int]$ParentProgressID=-1,
[int]$ProgressID=-1
)
#handle spaces and trailing slashes
$SourceDir = '"{0}"' -f ($Source -replace "\\+$","")
$TargetDir = '"{0}"' -f ($Destination -replace "\\+$","")
$ScanLog = [IO.Path]::GetTempFileName()
$RoboLog = [IO.Path]::GetTempFileName()
$ScanArgs = @($SourceDir,$TargetDir,$FilesToCopy) + $RobocopyArgs + "/ndl /TEE /bytes /Log:$ScanLog /nfl /L".Split(" ")
$RoboArgs = @($SourceDir,$TargetDir,$FilesToCopy) + $RobocopyArgs + "/ndl /TEE /bytes /Log:$RoboLog /NC".Split(" ")
# Launch Robocopy Processes
write-verbose ("Robocopy Scan:`n" + ($ScanArgs -join " "))
write-verbose ("Robocopy Full:`n" + ($RoboArgs -join " "))
$ScanRun = start-process robocopy -PassThru -WindowStyle Hidden -ArgumentList $ScanArgs
try
{
$RoboRun = start-process robocopy -PassThru -WindowStyle Hidden -ArgumentList $RoboArgs
try
{
# Parse Robocopy "Scan" pass
$ScanRun.WaitForExit()
$LogData = get-content $ScanLog
if ($ScanRun.ExitCode -ge 8)
{
$LogData|out-string|Write-Error
throw "Robocopy $($ScanRun.ExitCode)"
}
$FileSize = [regex]::Match($LogData[-4],".+:\s+(\d+)\s+(\d+)").Groups[2].Value
write-verbose ("Robocopy Bytes: $FileSize `n" +($LogData -join "`n"))
#determine progress parameters
$ProgressParms=@{}
if ($ParentProgressID -ge 0) {
$ProgressParms['ParentID']=$ParentProgressID
}
if ($ProgressID -ge 0) {
$ProgressParms['ID']=$ProgressID
} else {
$ProgressParms['ID']=$RoboRun.Id
}
# Monitor Full RoboCopy
while (!$RoboRun.HasExited)
{
$LogData = get-content $RoboLog
$Files = $LogData -match "^\s*(\d+)\s+(\S+)"
if ($null -ne $Files )
{
$copied = ($Files[0..($Files.Length-2)] | ForEach-Object {$_.Split("`t")[-2]} | Measure-Object -sum).Sum
if ($LogData[-1] -match "(100|\d?\d\.\d)\%")
{
write-progress Copy -ParentID $ProgressParms['ID'] -percentComplete $LogData[-1].Trim("% `t") $LogData[-1]
$Copied += $Files[-1].Split("`t")[-2] /100 * ($LogData[-1].Trim("% `t"))
}
else
{
write-progress Copy -ParentID $ProgressParms['ID'] -Complete
}
write-progress ROBOCOPY -PercentComplete ($Copied/$FileSize*100) $Files[-1].Split("`t")[-1] @ProgressParms
}
}
} finally {
if (!$RoboRun.HasExited) {Write-Warning "Terminating copy process with ID $($RoboRun.Id)..."; $RoboRun.Kill() ; }
$RoboRun.WaitForExit()
# Parse full RoboCopy pass results, and cleanup
(get-content $RoboLog)[-11..-2] | out-string | Write-Verbose
remove-item $RoboLog
write-output ([PSCustomObject]@{ ExitCode = $RoboRun.ExitCode })
}
} finally {
if (!$ScanRun.HasExited) {Write-Warning "Terminating scan process with ID $($ScanRun.Id)..."; $ScanRun.Kill() }
$ScanRun.WaitForExit()
remove-item $ScanLog
}
}
Upvotes: 4
Reputation: 2262
Trevor Sullivan has a write-up on how to add a command called Copy-ItemWithProgress to PowerShell on Robocopy.
Upvotes: 0
Reputation: 471
cmd /c copy /z src dest
not pure PowerShell, but executable in PowerShell and it displays progress in percents
Upvotes: 36
Reputation: 9521
Sean Kearney from the Hey, Scripting Guy! Blog has a solution I found works pretty nicely.
Function Copy-WithProgress
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$true,
Position=0)]
$Source,
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$true,
Position=0)]
$Destination
)
$Source=$Source.tolower()
$Filelist=Get-Childitem "$Source" –Recurse
$Total=$Filelist.count
$Position=0
foreach ($File in $Filelist)
{
$Filename=$File.Fullname.tolower().replace($Source,'')
$DestinationFile=($Destination+$Filename)
Write-Progress -Activity "Copying data from '$source' to '$Destination'" -Status "Copying File $Filename" -PercentComplete (($Position/$total)*100)
Copy-Item $File.FullName -Destination $DestinationFile
$Position++
}
}
Then to use it:
Copy-WithProgress -Source $src -Destination $dest
Upvotes: 2
Reputation: 3494
It seems like a much better solution to just use BitsTransfer, it seems to come OOTB on most Windows machines with PowerShell 2.0 or greater.
Import-Module BitsTransfer
Start-BitsTransfer -Source $Source -Destination $Destination -Description "Backup" -DisplayName "Backup"
Upvotes: 137
Reputation: 2485
I amended the code from stej (which was great, just what i needed!) to use larger buffer, [long] for larger files and used System.Diagnostics.Stopwatch class to track elapsed time and estimate time remaining.
Also added reporting of transfer rate during transfer and outputting overall elapsed time and overall transfer rate.
Using 4MB (4096*1024 bytes) buffer to get better than Win7 native throughput copying from NAS to USB stick on laptop over wifi.
On To-Do list:
Feel free to use/improve :-)
function Copy-File {
param( [string]$from, [string]$to)
$ffile = [io.file]::OpenRead($from)
$tofile = [io.file]::OpenWrite($to)
Write-Progress `
-Activity "Copying file" `
-status ($from.Split("\")|select -last 1) `
-PercentComplete 0
try {
$sw = [System.Diagnostics.Stopwatch]::StartNew();
[byte[]]$buff = new-object byte[] (4096*1024)
[long]$total = [long]$count = 0
do {
$count = $ffile.Read($buff, 0, $buff.Length)
$tofile.Write($buff, 0, $count)
$total += $count
[int]$pctcomp = ([int]($total/$ffile.Length* 100));
[int]$secselapsed = [int]($sw.elapsedmilliseconds.ToString())/1000;
if ( $secselapsed -ne 0 ) {
[single]$xferrate = (($total/$secselapsed)/1mb);
} else {
[single]$xferrate = 0.0
}
if ($total % 1mb -eq 0) {
if($pctcomp -gt 0)`
{[int]$secsleft = ((($secselapsed/$pctcomp)* 100)-$secselapsed);
} else {
[int]$secsleft = 0};
Write-Progress `
-Activity ($pctcomp.ToString() + "% Copying file @ " + "{0:n2}" -f $xferrate + " MB/s")`
-status ($from.Split("\")|select -last 1) `
-PercentComplete $pctcomp `
-SecondsRemaining $secsleft;
}
} while ($count -gt 0)
$sw.Stop();
$sw.Reset();
}
finally {
write-host (($from.Split("\")|select -last 1) + `
" copied in " + $secselapsed + " seconds at " + `
"{0:n2}" -f [int](($ffile.length/$secselapsed)/1mb) + " MB/s.");
$ffile.Close();
$tofile.Close();
}
}
Upvotes: 17
Reputation: 341
Alternativly this option uses the native windows progress bar...
$FOF_CREATEPROGRESSDLG = "&H0&"
$objShell = New-Object -ComObject "Shell.Application"
$objFolder = $objShell.NameSpace($DestLocation)
$objFolder.CopyHere($srcFile, $FOF_CREATEPROGRESSDLG)
Upvotes: 34
Reputation: 202062
Not that I'm aware of. I wouldn't recommend using copy-item for this anyway. I don't think it has been designed to be robust like robocopy.exe to support retry which you would want for extremely large file copies over the network.
Upvotes: 9