Barra di avanzamento RoboCopy personalizzata in PowerShell

Sono interessato a uno script PowerShell che copia giornalmente una grande quantità di file da un server e sono interessato a implementare una barra di avanzamento in-console come

File copy status - XX% complete.

dove XX% aggiorna sulla stessa riga invece che su newline dopo newline. Ho deciso di andare con RoboCopy per ora. Al momento ho

ROBOCOPY 'C:\Users\JMondy\Desktop\Sample1' 'C:\Users\JMondy\Desktop\Sample2' . /E /IS /NFL /NJH

Qual’è il prossimo passo?

Ho scritto una funzione di PowerShell chiamata Copy-WithProgress che realizzerà ciò che stai Copy-WithProgress . Dato che hai dichiarato espressamente che stavi usando Robocopy, ho creato una funzione di PowerShell che incapsula la funzionalità di robocopy (almeno parti di esso).

Permettimi di mostrarti come funziona. Ho anche registrato e pubblicato un video di YouTube che dimostra come la funzione è progettata per funzionare e invoca una corsa di prova.

La funzione è divisa in regioni:

  • Parametri comuni di robocopy
  • Staging (dove viene calcasting la dimensione del lavoro di robocopy)
  • Copia (dove viene avviato il lavoro di robocopy)
  • Barra di avanzamento (dove vengono monitorati i progressi della robocopia)
  • Output della funzione (dove vengono emesse alcune statistiche utili, da utilizzare nel resto dello script)

Ci sono diversi parametri sulla funzione.

  • Fonte : la directory di origine
  • Destinazione : la directory di destinazione
  • Gap : il “gap inter-pacchetto” in millisecondi supportato da robocopy, che rallenta artificialmente la copia, per il test)
  • ReportGap : l’intervallo (in millisecondi) per verificare i progressi di robocopy

Nella parte inferiore dello script (dopo la definizione della funzione), è un esempio completo di come chiamarlo. Dovrebbe funzionare sul tuo computer, dato che tutto è variabile. Ci sono cinque passaggi:

  1. Genera una directory di origine casuale
  2. Genera una directory di destinazione
  3. Chiama la funzione Copy-WithProgress
  4. Creare alcuni file sorgente aggiuntivi (per emulare le modifiche nel tempo)
  5. Richiama di nuovo la funzione Copy-WithProgress e convalida solo le modifiche vengono replicate

Ecco uno screenshot di come appare l’output della funzione. Puoi lasciare il parametro -Verbose , se non vuoi tutte le informazioni di debug. Un object PSCustomObject viene restituito dalla funzione che ti dice:

  1. Quanti byte sono stati copiati
  2. Quanti file sono stati copiati

Funzione PowerShell Copy-WithProgress

Ecco uno screenshot della barra di avanzamento di PowerShell in PowerShell ISE e l’host della console PowerShell.

Barra di avanzamento di PowerShell (ISE)

Barra di avanzamento di PowerShell (host di console)

Ecco il codice:

 function Copy-WithProgress { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Source , [Parameter(Mandatory = $true)] [string] $Destination , [int] $Gap = 200 , [int] $ReportGap = 2000 ) # Define regular expression that will gather number of bytes copied $RegexBytes = '(?<=\s+)\d+(?=\s+)'; #region Robocopy params # MIR = Mirror mode # NP = Don't show progress percentage in log # NC = Don't log file classes (existing, new file, etc.) # BYTES = Show file sizes in bytes # NJH = Do not display robocopy job header (JH) # NJS = Do not display robocopy job summary (JS) # TEE = Display log in stdout AND in target log file $CommonRobocopyParams = '/MIR /NP /NDL /NC /BYTES /NJH /NJS'; #endregion Robocopy params #region Robocopy Staging Write-Verbose -Message 'Analyzing robocopy job ...'; $StagingLogPath = '{0}\temp\{1} robocopy staging.log' -f $env:windir, (Get-Date -Format 'yyyy-MM-dd hh-mm-ss'); $StagingArgumentList = '"{0}" "{1}" /LOG:"{2}" /L {3}' -f $Source, $Destination, $StagingLogPath, $CommonRobocopyParams; Write-Verbose -Message ('Staging arguments: {0}' -f $StagingArgumentList); Start-Process -Wait -FilePath robocopy.exe -ArgumentList $StagingArgumentList -NoNewWindow; # Get the total number of files that will be copied $StagingContent = Get-Content -Path $StagingLogPath; $TotalFileCount = $StagingContent.Count - 1; # Get the total number of bytes to be copied [RegEx]::Matches(($StagingContent -join "`n"), $RegexBytes) | % { $BytesTotal = 0; } { $BytesTotal += $_.Value; }; Write-Verbose -Message ('Total bytes to be copied: {0}' -f $BytesTotal); #endregion Robocopy Staging #region Start Robocopy # Begin the robocopy process $RobocopyLogPath = '{0}\temp\{1} robocopy.log' -f $env:windir, (Get-Date -Format 'yyyy-MM-dd hh-mm-ss'); $ArgumentList = '"{0}" "{1}" /LOG:"{2}" /ipg:{3} {4}' -f $Source, $Destination, $RobocopyLogPath, $Gap, $CommonRobocopyParams; Write-Verbose -Message ('Beginning the robocopy process with arguments: {0}' -f $ArgumentList); $Robocopy = Start-Process -FilePath robocopy.exe -ArgumentList $ArgumentList -Verbose -PassThru -NoNewWindow; Start-Sleep -Milliseconds 100; #endregion Start Robocopy #region Progress bar loop while (!$Robocopy.HasExited) { Start-Sleep -Milliseconds $ReportGap; $BytesCopied = 0; $LogContent = Get-Content -Path $RobocopyLogPath; $BytesCopied = [Regex]::Matches($LogContent, $RegexBytes) | ForEach-Object -Process { $BytesCopied += $_.Value; } -End { $BytesCopied; }; $CopiedFileCount = $LogContent.Count - 1; Write-Verbose -Message ('Bytes copied: {0}' -f $BytesCopied); Write-Verbose -Message ('Files copied: {0}' -f $LogContent.Count); $Percentage = 0; if ($BytesCopied -gt 0) { $Percentage = (($BytesCopied/$BytesTotal)*100) } Write-Progress -Activity Robocopy -Status ("Copied {0} of {1} files; Copied {2} of {3} bytes" -f $CopiedFileCount, $TotalFileCount, $BytesCopied, $BytesTotal) -PercentComplete $Percentage } #endregion Progress loop #region Function output [PSCustomObject]@{ BytesCopied = $BytesCopied; FilesCopied = $CopiedFileCount; }; #endregion Function output } # 1. TESTING: Generate a random, unique source directory, with some test files in it $TestSource = '{0}\{1}' -f $env:temp, [Guid]::NewGuid().ToString(); $null = mkdir -Path $TestSource; # 1a. TESTING: Create some test source files 1..20 | % -Process { Set-Content -Path $TestSource\$_.txt -Value ('A'*(Get-Random -Minimum 10 -Maximum 2100)); }; # 2. TESTING: Create a random, unique target directory $TestTarget = '{0}\{1}' -f $env:temp, [Guid]::NewGuid().ToString(); $null = mkdir -Path $TestTarget; # 3. Call the Copy-WithProgress function Copy-WithProgress -Source $TestSource -Destination $TestTarget -Verbose; # 4. Add some new files to the source directory 21..40 | % -Process { Set-Content -Path $TestSource\$_.txt -Value ('A'*(Get-Random -Minimum 950 -Maximum 1400)); }; # 5. Call the Copy-WithProgress function (again) Copy-WithProgress -Source $TestSource -Destination $TestTarget -Verbose; 

Devi assolutamente usare Robocopy?

In caso contrario, è ansible chiamare il codice in questo thread per ciascun file: Avanzamento durante la copia di file di grandi dimensioni (Copia-Elemento e Scrittura-Avanzamento?)

In alternativa, utilizzare l’opzione / L di robocopy come chiamato da PowerShell per ottenere l’elenco dei file che Robocopy avrebbe copiato e utilizzare un ciclo for-each per eseguire ciascun file attraverso quella funzione di copia.

Puoi persino nidificare i comandi di avanzamento della scrittura in modo da poter segnalare “file x di y – XX% completo”

Qualcosa del genere dovrebbe funzionare, ha bisogno di un po ‘di lavoro per le sottodirectory (sospetto più che aggiungere solo -recurse al comando gci) ma ti metterà nella giusta direzione.

NOTA: Sto scrivendo questo su un telefono, il codice non è ancora testato …

 function Copy-File { param( [string]$from, [string]$to) $ffile = [io.file]::OpenRead($from) $tofile = [io.file]::OpenWrite($to) Write-Progress ` -Activity ("Copying file " + $filecount + " of " + $files.count) ` -status ($from.Split("\")|select -last 1) ` -PercentComplete 0 try { $sw = [System.Diagnostics.Stopwatch]::StartNew(); [byte[]]$buff = new-object byte[] 65536 [long]$total = [long]$count = 0 do { $count = $ffile.Read($buff, 0, $buff.Length) $tofile.Write($buff, 0, $count) $total += $count if ($total % 1mb -eq 0) { if([int]($total/$ffile.Length* 100) -gt 0)` {[int]$secsleft = ([int]$sw.Elapsed.Seconds/([int]($total/$ffile.Length* 100))*100) } else { [int]$secsleft = 0}; Write-Progress ` -Activity ([string]([int]($total/$ffile.Length* 100)) + "% Copying file")` -status ($from.Split("\")|select -last 1) ` -PercentComplete ([int]($total/$ffile.Length* 100))` -SecondsRemaining $secsleft; } } while ($count -gt 0) $sw.Stop(); $sw.Reset(); } finally { $ffile.Close() $tofile.Close() } } $srcdir = "C:\Source; $destdir = "C:\Dest"; [int]$filecount = 0; $files = (Get-ChildItem $SrcDir | where-object {-not ($_.PSIsContainer)}); $files|foreach($_){ $filecount++ if ([system.io.file]::Exists($destdir+$_.name)){ [system.io.file]::Delete($destdir+$_.name)} Copy-File -from $_.fullname -to ($destdir+$_.name) }; 

Personalmente uso questo codice per copie ridotte su una penna USB, ma io uso Robocopy in uno script PowerShell per i backup del PC.

Ecco la versione nativa di PowerShell GUI di RoboCopy. (NESSUN file EXE)

Spero che aiuti qualcuno.

inserisci la descrizione dell'immagine qui

https://gallery.technet.microsoft.com/PowerShell-Robocopy-GUI-08c9cacb

A proposito: c’è qualcuno che può combinare lo strumento PowerCopy GUI con la barra Copy-WithProgress?

Le barre di avanzamento sono belle e tutte tranne quando si copiano centinaia di file, mostrando che i progressi rallentano l’operazione, in alcuni casi un po ‘. È una delle ragioni per cui l’aiuto di robocopy dice che il flag / MT reindirizza l’output per registrare prestazioni migliori.

Ho finito per usare questo basato sulla risposta suggerita da Amrinder:

 robocopy.exe $Source $Destination $PatternArg $MirrorArg /NDL /NJH /NJS | ForEach-Object -Process { $data = $_.Split([char]9); if (($data.Count -gt 4) -and ("$($data[4])" -ne "")) { $file = "$($data[4])" Write-Progress "Percentage $($data[0])" -Activity "Robocopy" -CurrentOperation "$($file)" -ErrorAction SilentlyContinue; } else { Write-Progress "Percentage $($data[0])" -Activity "Robocopy" -CurrentOperation "$($file)" } } # Robocopy has a bitmask set of exit codes, so only complain about failures: [int] $exitCode = $global:LastExitCode; [int] $someCopyErrors = $exitCode -band 8; [int] $seriousError = $exitCode -band 16; if (($someCopyErrors -ne 0) -or ($seriousError -ne 0)) { Write-Error "ERROR: robocopy failed with a non-successful exit code: $exitCode" exit 1 } 

Fyi, Bill

Queste soluzioni sono grandi, ma un modo semplice e veloce per ottenere facilmente un progresso fluttuante per tutti i file è il seguente:

 robocopy   /MIR /NDL /NJH /NJS | %{$data = $_.Split([char]9); if("$($data[4])" -ne "") { $file = "$($data[4])"} ;Write-Progress "Percentage $($data[0])" -Activity "Robocopy" -CurrentOperation "$($file)" -ErrorAction SilentlyContinue; }