Copy-ItemWithProgress.ps1
<#PSScriptInfo .VERSION 1.0 .GUID c000f0c4-f6d4-407c-81e5-7e824d339f4a .AUTHOR sosbu .COMPANYNAME .COPYRIGHT .TAGS .LICENSEURI .PROJECTURI .ICONURI .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES .PRIVATEDATA #> <# .DESCRIPTION Copy Files with RoboCopy, showing progress with Standard PowerShell Write-Progress #> Param() Function Copy-ItemWithProgress{ <# .SYNOPSIS RoboCopy with PowerShell progress. .DESCRIPTION Copies Files with RoboCopy while processing the RoboCopy output to display Powershell native status and progress. .PARAMETER Path 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 Filter (Optional) A wildcard expresion of which files to copy, defaults to *.* .PARAMETER Recurse (Optional) Recursive Copy through the Path sub-directories .PARAMETER ProgressParentID (Optional) [Int] Use this as the ParentID for the write-progress bar. This allows nesting in complex scripts .PARAMETER ProgressID (Optional) [Int] Use this as the ID for the write-progress bar .PARAMETER LogFile (Optional) Path to copy the Robocopy Log to .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 -Recurse, only files from the root of c:\src are copied. .EXAMPLE C:\PS> .\Copy-ItemWithProgress "c:\Source Files" d:\Dest -Recurse -Verbose Copy the contents of the 'c:\Source Files' directory and sub-directories to d:\Dest, and script is run verbose .LINK .NOTES By sosburne@gmail.com By Keith S. Garner (KeithGa@KeithGa.com) - 6/23/2014 With inspiration by Keith S. Garner (KeithGa@KeithGa.com) - 6/23/2014, Trevor Sullivan @pcgeek86, Justin Marshall - 02/20/2020 #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string]$Path, [Parameter(Mandatory=$true)] [string]$Destination, [Parameter(Mandatory=$false)] [string]$Filter="*.*", [Parameter(Mandatory=$false)] [bool]$Recurse=$False, [Parameter(Mandatory=$false)] [int]$ParentProgressID=0, [Parameter(Mandatory=$false)] [int]$ProgressID=-1, [Parameter(Mandatory=$false)] [string[]] $Log=$false ) #handle spaces and trailing slashes $SourceDir = '"{0}"' -f ($Path -replace "\\+$","") $TargetDir = '"{0}"' -f ($Destination -replace "\\+$","") $ScanLog = [IO.Path]::GetTempFileName() $RoboLog = [IO.Path]::GetTempFileName() $ScanArgs = "$SourceDir $TargetDir $FilesToCopy /E /ZB /MT /R:5 /W:3 /ndl /bytes /NP /Log:$ScanLog /nfl /L" $RoboArgs = "$SourceDir $TargetDir $FilesToCopy /E /ZB /COPY:DATO /DCOPY:DAT /MT /R:5 /W:3 /J /ndl /bytes /Log:$RoboLog /NC" $ScanRun = start-process robocopy -PassThru -WindowStyle Hidden -ArgumentList $ScanArgs try { $RoboRun = start-process robocopy -PassThru -WindowStyle Hidden -ArgumentList $RoboArgs #determine progress parameters <# $ProgressParms=@{} if ($ParentProgressID -ge 0) { $ProgressParms['ParentID']=$ParentProgressID } if ($ProgressID -gt 0) { $ProgressParms['ID']=$ProgressID } else { $ProgressParms['ID']=$($RoboRun.Id) } /#> if ($ProgressID -lt 0) { $ProgressID = $($RoboRun.Id) } Write-Verbose -Message "ParentProgressID $ParentProgressID" Write-Verbose -Message "ProgressID $ProgressID" try { $CurrentFile = "Skipped" $CurrentFilePercent = $null If ($VerbosePreference -eq "Continue") { Write-Verbose "Waiting on ScanRun PID $($ScanRun.Id)" While (-not $ScanRun.HasExited) { Start-Sleep -Seconds 1 $Seconds++ If ($PsISE) { Write-Host "." -ForegroundColor Cyan -NoNewline } Else { Write-Host "`rScanRun PID $($ScanRun.Id) - (Running for $Seconds seconds)" -NoNewline -ForegroundColor Cyan } } Write-Host `r`n -NoNewline Write-Verbose "ScanRun PID $($ScanRun.Id) Completed" } $ScanRun.WaitForExit() # Parse Robocopy "Scan" pass $LogData = get-content $ScanLog if ($ScanRun.ExitCode -ge 8) { $LogData|out-string|Write-Error #throw "Robocopy $($ScanRun.ExitCode)" Write-Warning "ScanRun ExitCode: $($ScanRun.ExitCode)" } $FilesLengthSum = [regex]::Match($LogData[-4],".+:\s+(\d+)\s+(\d+)").Groups[2].Value #write-verbose ($LogData -join "`n") # Monitor Full RoboCopy #Write-Verbose "RoboRun has exited = $($RoboRun.HasExited)" write-progress -Activity "ROBOCOPY $Source to $Destination" -PercentComplete 0 -Id $ProgressID -ParentId $ParentProgressID while (-not $($RoboRun.HasExited)) { #Write-Verbose "RoboRun PID $($RoboRun.Id) running" Start-Sleep -Milliseconds 100 $LogData = get-content $RoboLog $Files = $LogData -match "^\s*(\d+)\s+(\S+)" #$Files[-1] if (!([string]::IsNullOrEmpty($Files))) { $CopiedLength = ($Files[0..($Files.Length-2)] | ForEach-Object {$_.Split("`t")[-2]} | Measure-Object -sum).Sum $File = $Files[-1].Split("`t")[-1] $FileLength = $Files[-1].Split("`t")[-2] write-progress -Activity "ROBOCOPY $Source to $Destination" -Status "$([math]::Round($($CopiedLength / 1MB),2)) MB of $([math]::Round($($FilesLengthSum / 1MB), 2)) MB" -PercentComplete $($CopiedLength / $FilesLengthSum * 100) -Id $ProgressID -ParentId $ParentProgressID #Write-Verbose "$($CopiedLength / 1MB) of $($FilesLengthSum / 1MB)" if ($LogData[-1] -match "(100|\d?\d)\%") { $FilePercent = $LogData[-1].Trim("% `t") If (($File -eq $CurrentFile) -and ($FilePercent -eq $CurrentFilePercent)) { Continue } $CurrentFile = $File $CurrentFilePercent = $FilePercent write-progress -Activity "$file" -Status "$([math]::Round($($FileLength / 100 * $FilePercent / 1MB),2)) MB of $([math]::Round($($FileLength / 1MB), 2)) MB" -PercentComplete $FilePercent -ParentID $ProgressID -Id $($ProgressID + 100) } else { If ($File -eq $CurrentFile) { Continue } write-progress -Activity "$file" -Status "$([math]::Round($($FileLength / 1MB),2)) MB of $([math]::Round($($FileLength / 1MB), 2)) MB" -PercentComplete "100" -ParentID $ProgressID -Id $($ProgressID + 100) } } } } finally { #if (!$RoboRun.HasExited) {Write-Warning "Terminating copy process with ID $($RoboRun.Id)..."; $RoboRun.Kill() ; } $RoboRun.WaitForExit() write-progress -Activity "$CurrentFile" -ParentID $ProgressID -Id $($ProgressID + 100) -Completed Write-Progress -Activity "ROBOCOPY $Source to Destination" -Id $ProgressID -ParentId $ParentProgressID -Completed # Parse full RoboCopy pass results, and cleanup (get-content $RoboLog)[-11..-2] | out-string | Write-Verbose If ($Log) {Copy-Item -Path $RoboLog -Destination "$Log"} remove-item $RoboLog } } finally { if (!$ScanRun.HasExited) {Write-Warning "Terminating scan process with ID $($ScanRun.Id)..."; $ScanRun.Kill() } $ScanRun.WaitForExit() remove-item $ScanLog } } |