web-dev-qa-db-fra.com

Barre de progression personnalisée RoboCopy dans PowerShell

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.

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?

24
codo-sapien

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:

  • Paramètres communs robocopy
  • Staging (où la taille du travail robocopy est calculée)
  • Copier (où le travail de robocopy est lancé)
  • Barre de progression (où la progression de la copie est contrôlée)
  • Sortie de fonction (où des statistiques utiles sont sorties, à utiliser dans le reste de votre script)

Il y a plusieurs paramètres sur la fonction.

  • Source: le répertoire source
  • Destination: le répertoire de destination
  • Gap: "intervalle entre paquets" en millisecondes pris en charge par robocopy, ce qui ralentit artificiellement la copie, pour le test)
  • ReportGap: intervalle (en millisecondes) à vérifier pour vérifier l'état d'avancement du processus de copie

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:

  1. Générer un répertoire source aléatoire
  2. Générer un répertoire de destination
  3. Appelez la fonction Copy-WithProgress
  4. Créer des fichiers sources supplémentaires (pour émuler les modifications au fil du temps)
  5. Appelez à nouveau la fonction 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:

  1. Combien d'octets ont été copiés
  2. Combien de fichiers ont été copiés

Copy-WithProgress PowerShell Function

Voici une capture d'écran de la barre de progression PowerShell dans PowerShell ISE et de l'hôte de la console PowerShell.

PowerShell Progress Bar (ISE)

PowerShell Progress Bar (Console Host)

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;
71
Trevor Sullivan

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.

2
Graham Gold

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; }
1
Amrinder

Voici la version native de PowerShell GUI de RoboCopy. (AUCUN fichier EXE)

J'espère que ça aide quelqu'un.

enter image description here

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?

1
Deniz Porsuk

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.

1
Nelis

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

0
Bill Tutt