web-dev-qa-db-fra.com

Progression lors de la copie de fichiers volumineux (Copy-Item & Write-Progress?)

Est-il possible de copier un fichier très volumineux (d'un serveur à un autre) dans PowerShell ET d'en afficher la progression?

Il existe des solutions pour utiliser Write-Progress avec la création de boucles pour copier de nombreux fichiers et afficher la progression. Cependant, je n'arrive pas à trouver quoi que ce soit qui montrerait les progrès d'un seul fichier.

Des pensées?

48
Jason Jarrett

Je n'ai pas entendu parler de progrès avec Copy-Item. Si vous ne souhaitez utiliser aucun outil externe, vous pouvez expérimenter avec des flux. La taille de la mémoire tampon varie, vous pouvez essayer différentes valeurs (de 2 Ko à 64 Ko).

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
        [int]$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 ([int]($total/$ffile.Length* 100))
            }
        } while ($count -gt 0)
    }
    finally {
        $ffile.Dispose()
        $tofile.Dispose()
        Write-Progress -Activity "Copying file" -Status "Ready" -Completed
    }
}
41
stej

Cela semble être une bien meilleure solution que d’utiliser BitsTransfer, cela semble venir OOTB 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"
70
Nacht

Alternativement, cette option utilise la barre de progression des fenêtres natives ... 

$FOF_CREATEPROGRESSDLG = "&H0&"

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

$objFolder = $objShell.NameSpace($DestLocation) 

$objFolder.CopyHere($srcFile, $FOF_CREATEPROGRESSDLG)
20
Chris M
cmd /c copy /z src dest

pas purement PowerShell, mais exécutable dans PowerShell et affiche les progrès en pourcentage

13
Jirka Jr.

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

Ajout de la création de rapports sur le taux de transfert pendant le transfert et l'affichage du temps écoulé et du taux de transfert globaux.

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

Sur la liste des tâches:

  • ajouter la gestion des erreurs (catch) 
  • gérer la liste de fichiers get-childitem en entrée
  • barres de progression imbriquées lors de la copie de plusieurs fichiers (fichier x de y,% if données totales copiées, etc.)
  • paramètre d'entrée pour la taille de la mémoire 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();
    }
}
12
Graham Gold

Pas que je sache. Je ne recommanderais de toute façon pas d'utiliser copy-item pour ça. Je ne pense pas qu'il a été conçu pour être robuste, comme robocopy.exe, afin de prendre en charge les nouvelles tentatives que vous souhaiteriez pour des copies de fichiers extrêmement volumineuses sur le réseau.

9
Keith Hill

Sean Kearney du Salut, le scénariste! Blog a une solution que j'ai trouvée qui fonctionne assez bien.

Function Copy-WithProgress
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true,
            ValueFromPipelineByPropertyName=$true,
            Position=0)]
        $Source,
        [Parameter(Mandatory=$true,
            ValueFromPipelineByPropertyName=$true,
            Position=0)]
        $Destination
    )

    $Source=$Source.tolower()
    $Filelist=Get-Childitem "$Source" –Recurse
    $Total=$Filelist.count
    $Position=0

    foreach ($File in $Filelist)
    {
        $Filename=$File.Fullname.tolower().replace($Source,'')
        $DestinationFile=($Destination+$Filename)
        Write-Progress -Activity "Copying data from '$source' to '$Destination'" -Status "Copying File $Filename" -PercentComplete (($Position/$total)*100)
        Copy-Item $File.FullName -Destination $DestinationFile
        $Position++
    }
}

Puis l'utiliser:

Copy-WithProgress -Source $src -Destination $dest
1
E-rich

Cette fonction récursive copie les fichiers et les répertoires de manière récursive du chemin source au chemin de destination.

Si le fichier existe déjà dans le chemin de destination, il ne les copie qu'avec les fichiers les plus récents.

Function Copy-FilesBitsTransfer(
        [Parameter(Mandatory=$true)][String]$sourcePath, 
        [Parameter(Mandatory=$true)][String]$destinationPath, 
        [Parameter(Mandatory=$false)][bool]$createRootDirectory = $true)
{
    $item = Get-Item $sourcePath
    $itemName = Split-Path $sourcePath -leaf
    if (!$item.PSIsContainer){ #Item Is a file

        $clientFileTime = Get-Item $sourcePath | select LastWriteTime -ExpandProperty LastWriteTime

        if (!(Test-Path -Path $destinationPath\$itemName)){
            Start-BitsTransfer -Source $sourcePath -Destination $destinationPath -Description "$sourcePath >> $destinationPath" -DisplayName "Copy Template file" -Confirm:$false
            if (!$?){
                return $false
            }
        }
        else{
            $serverFileTime = Get-Item $destinationPath\$itemName | select LastWriteTime -ExpandProperty LastWriteTime

            if ($serverFileTime -lt $clientFileTime)
            {
                Start-BitsTransfer -Source $sourcePath -Destination $destinationPath -Description "$sourcePath >> $destinationPath" -DisplayName "Copy Template file" -Confirm:$false
                if (!$?){
                    return $false
                }
            }
        }
    }
    else{ #Item Is a directory
        if ($createRootDirectory){
            $destinationPath = "$destinationPath\$itemName"
            if (!(Test-Path -Path $destinationPath -PathType Container)){
                if (Test-Path -Path $destinationPath -PathType Leaf){ #In case item is a file, delete it.
                    Remove-Item -Path $destinationPath
                }

                New-Item -ItemType Directory $destinationPath | Out-Null
                if (!$?){
                    return $false
                }

            }
        }
        Foreach ($fileOrDirectory in (Get-Item -Path "$sourcePath\*"))
        {
            $status = Copy-FilesBitsTransfer $fileOrDirectory $destinationPath $true
            if (!$status){
                return $false
            }
        }
    }

    return $true
}
1
Dudi72

Trevor Sullivan a décrit comment ajouter une commande appelée Copy-ItemWithProgress à PowerShell sur Robocopy.

0
Wouter

Je déteste être celui qui a heurté un vieux sujet, mais j'ai trouvé ce post extrêmement utile. Après avoir exécuté des tests de performance sur les extraits par stej et son raffinement par Graham Gold, ainsi que la suggestion de BITS par Nacht, j'ai décidé que:

  1. J'ai vraiment j'aimais la commande de Graham avec des estimations de temps et des lectures de vitesse.
  2. J'ai aussi vraiment aimé l'augmentation significative de la vitesse d'utilisation de BITS comme méthode de transfert.

Face à la décision entre les deux ... J'ai constaté que Start-BitsTransfer supportait le mode asynchrone. Donc, voici le résultat de ma fusion des deux.

function Copy-File {
    param([string]$from, [string]$to)

    try {
        $job = Start-BitsTransfer -Source $from -Destination $to `
                   -Description "Moving: $from => $to" `
                   -DisplayName "Backup" -Asynchronous

        # Start stopwatch
        $sw = [System.Diagnostics.Stopwatch]::StartNew()
        Write-Progress -Activity "Connecting..."

        while ($job.JobState.ToString() -ne "Transferred") {
            switch ($job.JobState.ToString()) {
                "Connecting" {
                    break
                }
                "Transferring" {
                    $pctcomp = ($job.BytesTransferred / $job.BytesTotal) * 100
                    $elapsed = ($sw.elapsedmilliseconds.ToString()) / 1000

                    if ($elapsed -eq 0) {
                        $xferrate = 0.0
                    } else {
                        $xferrate = (($job.BytesTransferred / $elapsed) / 1mb);
                    }

                    if ($job.BytesTransferred % 1mb -eq 0) {
                        if ($pctcomp -gt 0) {
                            $secsleft = ((($elapsed / $pctcomp) * 100) - $elapsed)
                        } else {
                            $secsleft = 0
                        }

                        Write-Progress -Activity ("Copying file '" + ($PathName.Split("\") | Select -last 1) + "' @ " + "{0:n2}" -f $xferrate + "MB/s") `
                                       -PercentComplete $pctcomp `
                                       -SecondsRemaining $secsleft
                    }
                    break
                }
                "Transferred" {
                    break
                }
                Default {
                    throw $job.JobState.ToString() + " unexpected BITS state."
                }
            }
        }

        $sw.Stop()
        $sw.Reset()
    } finally {
        Complete-BitsTransfer -BitsJob $job
        Write-Progress -Activity "Completed" -Completed
    }
}
0
Shane