Je suis intéressé par un script PowerShell qui copie quotidiennement un grand nombre de fichiers d'un serveur et par la mise en oeuvre d'une barre de progression dans la console, telle
File copy status - XX% complete.
où XX%
est mis à jour sur la même ligne au lieu de nouvelle ligne après nouvelle ligne. J'ai décidé d'aller avec RoboCopy pour le moment. J'ai actuellement
ROBOCOPY 'C:\Users\JMondy\Desktop\Sample1' 'C:\Users\JMondy\Desktop\Sample2' . /E /IS /NFL /NJH
Quelle est la prochaine étape?
J'ai écrit une fonction PowerShell appelée Copy-WithProgress
qui vous permettra d'atteindre vos objectifs. Puisque vous avez spécifiquement indiqué que vous utilisiez robocopy, j'ai créé une fonction PowerShell qui encapsule la fonctionnalité robocopy (au moins une partie).
Permettez-moi de vous montrer comment cela fonctionne. J'ai également enregistré et posté une vidéo YouTube _ démontré comment la fonction est conçue pour fonctionner et invoquant une exécution test.
La fonction est divisée en régions:
Il y a plusieurs paramètres sur la fonction.
Au bas du script (après la définition de la fonction), vous trouverez un exemple complet sur la façon de l'appeler. Cela devrait fonctionner sur votre ordinateur, puisque tout est variable. Il y a cinq étapes:
Copy-WithProgress
Copy-WithProgress
et validez que seules les modifications sont répliquées.Voici une capture d'écran de la sortie de la fonction. Vous pouvez laisser le paramètre -Verbose
si vous ne voulez pas toutes les informations de débogage. Une PSCustomObject
est retournée, par la fonction, qui vous dit:
Voici une capture d'écran de la barre de progression PowerShell dans PowerShell ISE et de l'hôte de la console PowerShell.
Voici le code:
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;
Devez-vous absolument utiliser robocopy?
Sinon, vous pouvez appeler le code dans ce fil pour chaque fichier: Progression lors de la copie de fichiers volumineux (Copy-Item & Write-Progress?)
Vous pouvez également utiliser le commutateur/L de robocopy appelé par powershell pour obtenir la liste des fichiers que robocopy aurait copié et utiliser une boucle for-each pour exécuter chaque fichier via cette fonction de copie.
Vous pouvez même imbriquer des commandes de progression d'écriture afin de pouvoir signaler "fichier x de y - XX% terminé"
Quelque chose comme cela devrait fonctionner, nécessite un peu de travail pour les sous-répertoires (je suppose que plus que simplement ajouter -recurse à la commande gci) mais vous mettra dans la bonne direction.
NOTE: J'écris ceci sur un téléphone, le code n'a pas encore été testé ...
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)
};
Personnellement, j'utilise ce code pour les petites copies sur une clé USB, mais j'utilise robocopy dans un script PowerShell pour les sauvegardes sur PC.
Ces solutions sont excellentes, mais voici un moyen simple et rapide d’obtenir une progression flottante pour tous les fichiers:
robocopy <source> <destination> /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; }
Voici la version native de PowerShell GUI de RoboCopy. (AUCUN fichier EXE)
J'espère que ça aide quelqu'un.
https://gallery.technet.Microsoft.com/PowerShell-Robocopy-GUI-08c9cacb
FYI: Y a-t-il quelqu'un qui peut combiner l'outil d'interface graphique PowerCopy avec la barre Copy-WithProgress?
Les barres de progression sont jolies et presque toutes lors de la copie de centaines de fichiers, ce qui ralentit l’avancement des opérations, dans certains cas même. C'est l'une des raisons pour lesquelles l'aide de robocopy indique que l'indicateur/MT redirige la sortie vers le journal pour une meilleure performance.
J'ai fini par utiliser ceci basé sur la réponse suggérée par 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