Skip to content

Instantly share code, notes, and snippets.

@NegativeZero000
Created September 28, 2018 13:06
Show Gist options
  • Save NegativeZero000/76dde93966055bed68793c10ea54dda7 to your computer and use it in GitHub Desktop.
Save NegativeZero000/76dde93966055bed68793c10ea54dda7 to your computer and use it in GitHub Desktop.
Copy-WithRoboCopyProgress
function Copy-WithRobocopyProgress{
# Copy-WithRobocopyProgress -Source C:\Temp\test\from -Destination C:\Temp\test\to -Arguments "/e"
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string] $Source,
[Parameter(Mandatory = $true)]
[string] $Destination,
[Parameter(Mandatory = $true)]
[string[]]$Arguments
)
<# Query arguments. These are used in addition to those passed via parameters to determine the cost of the
robocopy operation.
/l - Specifies that files are to be listed only (and not copied, deleted, or time stamped).
/bytes - Prints sizes, as bytes.
/ndl - Specifies that directory names are not to be logged.
/njh - Specifies that there is no job header.
/njs - Specifies that there is no job summary.
/nfl - Specifies that file names are not to be logged.
#>
$executionArguments = "/ndl","/bytes","/njh"
$costArguments = $executionArguments + "/l", "/nfl"
function Parse-RobocopySummary{
# This function will parse the robocopy footer as custom object
param(
[string[]]$Footer
)
$Footer | ForEach-Object {
if ($_ -like "*Dirs*"){
$lineAsArray = (($_.Split(':')[1]).trim()) -split '\s+'
$Dirs = [pscustomobject][ordered]@{
Total = $lineAsArray[0]
Copied = $lineAsArray[1]
Skipped = $lineAsArray[2]
Mismatch = $lineAsArray[3]
FAILED = $lineAsArray[4]
Extras = $lineAsArray[5]
}
}
if ($_ -like "*Files*"){
$lineAsArray = ($_.Split(':')[1]).trim() -split '\s+'
$Files = [pscustomobject][ordered]@{
Total = $lineAsArray[0]
Copied = $lineAsArray[1]
Skipped = $lineAsArray[2]
Mismatch = $lineAsArray[3]
FAILED = $lineAsArray[4]
Extras = $lineAsArray[5]
}
}
if ($_ -like "*Bytes*"){
$lineAsArray = ($_.Split(':')[1]).trim() -split '\s+'
$Bytes = [pscustomobject][ordered]@{
Total = $lineAsArray[0]
Copied = $lineAsArray[1]
Skipped = $lineAsArray[2]
Mismatch = $lineAsArray[3]
FAILED = $lineAsArray[4]
Extras = $lineAsArray[5]
}
}
if ($_ -like "*Times*"){
$lineAsArray = ($_.Split(':',2)[1]).trim() -split '\s+'
$Times = [pscustomobject][ordered]@{
Total = $lineAsArray[0]
Copied = $lineAsArray[1]
FAILED = $lineAsArray[2]
Extras = $lineAsArray[3]
}
}
if ($_ -like "*Error*"){
# Something wrong has happened.
Write-Error ($Footer -join "`n")
}
}
[PSCustomObject]@{
'Dirs' = $Dirs
'Files' = $Files
'Bytes' = $Bytes
'Times' = $Times
}
}
# Check if this is a folder or single file request. Arguments need to change if that is the case.
if(-not (Get-Item $Source).PSisContainer){
Write-Verbose "Source is a file."
Write-Verbose "Supplied source: $Source"
Write-Verbose "Supplied arguments: $Arguments"
$fileName = Split-Path $Source -Leaf
# Change the source so that the file name is not present.
$Source = Split-Path $Source
# Remove the '/e' if present in the arguments and add the file name
$Arguments = @($fileName) + ($Arguments | Where-Object{$_ -ne "/e"})
Write-Verbose "Updated source: $Source"
Write-Verbose "Updated arguments: $Arguments"
}
# Use robocopy to get a list only view of the proposed copy operation.
# Determine overall cost from the job summary
Write-Verbose "robocopy ""$Source"" ""$Destination"" $($Arguments + $costArguments)"
$reportSummary = Parse-RobocopySummary -Footer (robocopy "$Source" "$Destination" ($Arguments + $costArguments))
Write-Verbose ($reportSummary | Format-List * | Out-String).TrimEnd()
# Baseline statistics
$totalFilesCount = $reportSummary.Files.Copied
$totalByteCost = $reportSummary.Bytes.Copied
$bytesTotalDenomination = if($totalByteCost -gt 2GB){"1GB"}else{"1MB"}
# Start the copy as a job and monitor progress. Ensure bytes switch is used. No issues if duplicated
$robocopyJob = Start-Job -Name "RoboCopy" -ScriptBlock {robocopy.exe $args} -ArgumentList @("$Source","$Destination" + $Arguments + $executionArguments)
# Progress variable initialization
$bytesCopied = New-Object 'System.Collections.Generic.List[Int64]'
$totalFilesCopied = 0
while ($robocopyjob.State -eq 'running'){
# Get jobs current data output
$currentProgress = Receive-Job -Job $robocopyJob -ErrorAction SilentlyContinue
# Assuming we have output to process
if($currentProgress -and $reportSummary.Files.Copied -gt 0){
# Are we in the middle of copying a file right now?
# If the last line in the output contains a percentage value record or assume 0
$currentFilePercentage = if($currentProgress[-1] -match "^\s+([\d|\.]+)%"){$Matches[1]}else{0}
# Determine number of files copied so far.
# Find the number of lines that contain New File or Newer designations as they need to be counted.
$robocopyNewFileData = ($currentProgress | Select-String -Pattern "^\s+New")
# Check if new files are being copied that were not previously
if($robocopyNewFileData){
# Update the files counts
$totalFilesCopied += $robocopyNewFileData.Count
# Update the byte counts
# Get the first number in the line which would be after the newer/new file text
$robocopyNewFileData -replace "^.*?(\d+).*$",'$1' | ForEach-Object{
$bytesCopied.Add($_)
}
# Update progress indicators.
$currentFile = ($robocopyNewFileData[-1] -replace "^.*?(\d+)").Trim()
$bytesFileDenomination = if($bytesCopied[-1] -gt 1GB){"1GB"}elseif($bytesCopied[-1] -gt 1MB){"1MB"}else{"1KB"}
}
# Byte count of files that have already been copied.
[int64]$bytesCopiedInProgressTotal = ($bytesCopied | Select-Object -SkipLast 1 | Measure-Object -Sum).Sum
$parentProgressArgs = @{
ID = 1
Activity = "Copying files from '$Source' to '$Destination', $($totalFilesCopied - 1) of $totalFilesCount files copied"
Status = "{0:N2} of {1:N2} {2} copied" -f ($bytesCopiedInProgressTotal / $bytesTotalDenomination),($totalByteCost / $bytesTotalDenomination),$bytesTotalDenomination.TrimStart("1")
Percent = $bytesCopiedInProgressTotal / $totalByteCost * 100
}
$childProgressArgs = @{
ID = 2
# To prevent nulls here make activity at least an empty string.
Activity = "" + $currentFile
Status = "{0:N2} / {1:N2} {2}" -f (($bytesCopied[-1] * ($currentFilePercentage / 100)) / $bytesFileDenomination),($bytesCopied[-1] / $bytesFileDenomination),$bytesFileDenomination.TrimStart("1")
Percent = $currentFilePercentage
ParentID = 1
}
Write-Progress @parentProgressArgs
Write-Progress @childProgressArgs
}
# Limit update frequency
sleep -Milliseconds 500
}
# Mark the progress as completed assuming it was started.
if($parentProgressArgs){
$parentProgressArgs.Percent = 100
$parentProgressArgs.Status = "{0:N2} of {0:N2} {1} copied" -f ($totalByteCost / $bytesTotalDenomination),$bytesTotalDenomination.TrimStart("1")
$parentProgressArgs.Activity = "Done"
$parentProgressArgs.Completed = $true
Write-Progress @parentProgressArgs
}
# Remove the completed job
$robocopyJob | Remove-Job
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment