82 votes

Progression pendant la copie d'un grand fichier (Copy-Item & Write-Progress ?)

Existe-t-il un moyen de copier un très gros fichier (d'un serveur à l'autre) dans PowerShell ET d'afficher sa progression ?

Il existe des solutions permettant d'utiliser Write-Progress en conjonction avec des boucles pour copier de nombreux fichiers et afficher la progression. Cependant, je ne trouve rien qui permette d'afficher la progression d'un seul fichier.

Des idées ?

124voto

Nacht Points 644

Il semble que la meilleure solution soit d'utiliser BitsTransfer, qui semble être livré en standard sur la plupart des machines Windows avec PowerShell 2.0 ou supérieur.

Import-Module BitsTransfer
Start-BitsTransfer -Source $Source -Destination $Destination -Description "Backup" -DisplayName "Backup"

0 votes

Super ! En effet, cela me donne également un indicateur de progression (powershell).

0 votes

Il ne serait probablement pas possible de tirer parti des capacités du BITS si vous ne tirez pas la source d'un emplacement distant, mais cela fonctionne sans problème.

4 votes

C'est exactement ce que je cherchais - il fonctionne parfaitement et donne une barre de progression !

51voto

stej Points 14257

Je n'ai pas entendu parler de progrès avec Copy-Item . Si vous ne voulez pas utiliser d'outil externe, vous pouvez expérimenter avec les flux. La taille du tampon varie, vous pouvez essayer différentes valeurs (de 2kb à 64kb).

function Copy-File {
    param( [string]$from, [string]$to)
    $ffile = [io.file]::OpenRead($from)
    $tofile = [io.file]::OpenWrite($to)
    Write-Progress -Activity "Copying file" -status "$from -> $to" -PercentComplete 0
    try {
        [byte[]]$buff = new-object byte[] 4096
        [long]$total = [int]$count = 0
        do {
            $count = $ffile.Read($buff, 0, $buff.Length)
            $tofile.Write($buff, 0, $count)
            $total += $count
            if ($total % 1mb -eq 0) {
                Write-Progress -Activity "Copying file" -status "$from -> $to" `
                   -PercentComplete ([long]($total * 100 / $ffile.Length))
            }
        } while ($count -gt 0)
    }
    finally {
        $ffile.Dispose()
        $tofile.Dispose()
        Write-Progress -Activity "Copying file" -Status "Ready" -Completed
    }
}

6 votes

Une solution intéressante. Lorsque je l'ai essayée, j'ai reçu une erreur - Cannot convert value "2147483648" to type "System.Int32". Erreur : "La valeur était soit trop grande, soit trop petite pour un Int32". Après avoir remplacé le [int] par un [long], cela a bien fonctionné. Merci

0 votes

Cela signifie que vous copiez des fichiers de plus de 2 Go ? Je pense que oui. Je suis content que ça marche :)

0 votes

+1 les solutions simples sont les meilleures ! Je suis en train de copier de gros fichiers (8GB+) d'un emplacement réseau à un autre ... réseau gigabit ... ( indication seulement ) ... l'utilisation de blocs de 1Mb signifie que l'adaptateur réseau fonctionne à environ 50% (je soupçonne un certain étranglement sur notre commutateur) ... les blocs plus petits n'étaient pas super.

30voto

Chris M Points 81

Alternativement, cette option utilise la barre de progression native de Windows...

$FOF_CREATEPROGRESSDLG = "&H0&"

$objShell = New-Object -ComObject "Shell.Application"

$objFolder = $objShell.NameSpace($DestLocation) 

$objFolder.CopyHere($srcFile, $FOF_CREATEPROGRESSDLG)

1 votes

C'est génial, mais comment spécifier le drapeau "ALWAYS OVERWRITE" pour cette méthode, est-ce possible ? Pour qu'il n'y ait pas de message lorsque des fichiers existent.

0 votes

@Rakha vous devez juste passer 16 comme paramètre sec à la fonction CopyHere comme ceci : $objFolder.CopyHere($srcFile, 16)

17voto

Graham Gold Points 910

J'ai modifié le code de stej (qui était génial, juste ce dont j'avais besoin !) pour utiliser un tampon plus grand, [long] pour les fichiers plus grands et j'ai utilisé la classe System.Diagnostics.Stopwatch pour suivre le temps écoulé et estimer le temps restant.

Nous avons également ajouté le signalement du taux de transfert pendant le transfert et la sortie du temps écoulé global et du taux de transfert global.

Utilisation d'une mémoire tampon de 4 Mo (4096*1024 octets) pour obtenir un débit supérieur au débit natif de Win7 en copiant du NAS vers une clé USB sur un ordinateur portable en wifi.

Sur la liste des choses à faire :

  • ajouter la gestion des erreurs (catch)
  • traiter la liste du fichier get-childitem comme entrée
  • barres de progression imbriquées lors de la copie de plusieurs fichiers (fichier x de y, % si données totales copiées, etc.)
  • paramètre d'entrée pour la taille du tampon

N'hésitez pas à utiliser/améliorer :-)

function Copy-File {
param( [string]$from, [string]$to)
$ffile = [io.file]::OpenRead($from)
$tofile = [io.file]::OpenWrite($to)
Write-Progress `
    -Activity "Copying file" `
    -status ($from.Split("\")|select -last 1) `
    -PercentComplete 0
try {
    $sw = [System.Diagnostics.Stopwatch]::StartNew();
    [byte[]]$buff = new-object byte[] (4096*1024)
    [long]$total = [long]$count = 0
    do {
        $count = $ffile.Read($buff, 0, $buff.Length)
        $tofile.Write($buff, 0, $count)
        $total += $count
        [int]$pctcomp = ([int]($total/$ffile.Length* 100));
        [int]$secselapsed = [int]($sw.elapsedmilliseconds.ToString())/1000;
        if ( $secselapsed -ne 0 ) {
            [single]$xferrate = (($total/$secselapsed)/1mb);
        } else {
            [single]$xferrate = 0.0
        }
        if ($total % 1mb -eq 0) {
            if($pctcomp -gt 0)`
                {[int]$secsleft = ((($secselapsed/$pctcomp)* 100)-$secselapsed);
                } else {
                [int]$secsleft = 0};
            Write-Progress `
                -Activity ($pctcomp.ToString() + "% Copying file @ " + "{0:n2}" -f $xferrate + " MB/s")`
                -status ($from.Split("\")|select -last 1) `
                -PercentComplete $pctcomp `
                -SecondsRemaining $secsleft;
        }
    } while ($count -gt 0)
$sw.Stop();
$sw.Reset();
}
finally {
    write-host (($from.Split("\")|select -last 1) + `
     " copied in " + $secselapsed + " seconds at " + `
     "{0:n2}" -f [int](($ffile.length/$secselapsed)/1mb) + " MB/s.");
     $ffile.Close();
     $tofile.Close();
    }
}

0 votes

Joli script, mais il donne une division par zéro. J'ai dû ajouter : if ( $secselapsed -ne 0 ) { [single]$xferrate = (($total/$secselapsed)/1mb) ; } else { [single]$xferrate = 0.0 }

0 votes

Ce n'est pas quelque chose que j'ai rencontré dans mon utilisation quotidienne de ce code. Quelle version de powershell utilisez-vous ? Cela fonctionne-t-il parfois pour vous ? Simple curiosité. Tout ce qui peut le rendre plus robuste me convient :-)

0 votes

Sous Powershell 2.0.1.1, cela fonctionnait par intermittence, mais la plupart du temps pas. Il semble que le premier bloc soit copié trop rapidement et que le $secelapsed soit ensuite arrondi à l'inférieur. J'ai mis la mise à jour, cela pourrait faire gagner du temps à quelqu'un. Merci encore, c'est un script utile.

9voto

Keith Hill Points 73162

Pas que je sache. Je ne recommanderais pas d'utiliser copy-item pour cela de toute façon. Je ne pense pas qu'il ait été conçu pour être robuste comme robocopy.exe pour supporter les tentatives, ce que vous voudriez pour les copies de fichiers extrêmement volumineux sur le réseau.

1 votes

Un point valable. Dans ce cas particulier, je ne m'inquiète pas trop de la robustesse. Il s'agit de copier un fichier de 15gig entre deux serveurs sur le même fond de panier. Cependant, dans d'autres situations, j'envisagerais certainement une solution plus robuste.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X