it-swarm.com.de

Fortschritt beim Kopieren großer Dateien (Copy-Item & Write-Progress?)

Gibt es eine Möglichkeit, eine wirklich große Datei (von einem Server auf einen anderen) in PowerShell zu kopieren UND den Fortschritt anzuzeigen?

Es gibt Lösungen, um Write-Progress in Verbindung mit Looping zu verwenden, um viele Dateien zu kopieren und den Fortschritt anzuzeigen. Ich kann jedoch anscheinend nichts finden, was den Fortschritt einer einzelnen Datei anzeigt.

Irgendwelche Gedanken?

48
Jason Jarrett

Ich habe nichts über den Fortschritt mit Copy-Item gehört. Wenn Sie kein externes Werkzeug verwenden möchten, können Sie mit Streams experimentieren. Die Größe des Puffers kann variieren, Sie können andere Werte ausprobieren (von 2 KB bis 64 KB).

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

Es scheint eine viel bessere Lösung zu sein, nur BitsTransfer zu verwenden, es scheint OOTB auf den meisten Windows-Computern mit PowerShell 2.0 oder höher zu sein.

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

Alternativ verwendet diese Option die native Windows-Fortschrittsleiste ... 

$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

nicht reine PowerShell, sondern in PowerShell ausführbar und zeigt den Fortschritt in Prozent an

13
Jirka Jr.

Ich habe den Code von stej geändert (was großartig war, genau das, was ich brauchte!), Um größere Puffer zu verwenden, [long] für größere Dateien und die Klasse System.Diagnostics.Stopwatch, um die verstrichene Zeit zu verfolgen und die verbleibende Zeit zu schätzen.

Zusätzliche Berichterstattung über die Übertragungsrate während der Übertragung und Ausgabe der insgesamt verstrichenen Zeit und der Gesamtübertragungsrate.

Verwenden Sie einen Puffer mit 4 MB (4096 * 1024 Byte), um besser als der native Durchsatz von Win7 zu werden, und kopieren Sie von NAS auf einen USB-Stick auf Ihrem Laptop über WLAN.

Auf der To-Do-Liste:

  • fehlerbehandlung hinzufügen (catch) 
  • get-Childitem-Dateiliste als Eingabe behandeln
  • geschachtelte Fortschrittsbalken beim Kopieren mehrerer Dateien (Datei x von y,% iftotal kopierte Daten usw.)
  • eingabeparameter für die Puffergröße

Fühlen Sie sich frei zu verwenden/verbessern :-)

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

Nicht dass ich wüsste. Ich würde sowieso nicht die Verwendung von Copy-Items empfehlen. Ich glaube nicht, dass er so robust ist wie robocopy.exe, um die Wiederholung zu unterstützen, die Sie für extrem große Dateikopien über das Netzwerk benötigen.

9
Keith Hill

Sean Kearney vom Hey, Scripting Guy! Blog hat eine Lösung, die ich finde, funktioniert ziemlich gut.

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++
    }
}

Dann verwenden Sie es:

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

Diese rekursive Funktion kopiert Dateien und Verzeichnisse rekursiv vom Quellpfad zum Zielpfad

Wenn die Datei bereits im Zielpfad vorhanden ist, werden diese nur mit neueren Dateien kopiert.

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 beschreibt, wie Sie einen Befehl namens Copy-ItemWithProgress zu PowerShell in Robocopy hinzufügen.

0
Wouter

Ich hasse es, derjenige zu sein, der ein altes Thema anspricht, aber ich fand diesen Beitrag äußerst nützlich. Nachdem ich Leistungstests für die Snippets von stej und deren Verfeinerung durch Graham Gold sowie den BITS - Vorschlag von Nacht durchgeführt habe, habe ich Folgendes entschieden:

  1. Ich wirklich mochte Grahams Befehl mit Zeitschätzungen und Geschwindigkeitsablesungen.
  2. Ich mochte auch wirklich die signifikante Geschwindigkeitssteigerung, wenn ich BITS als meine Übertragungsmethode verwendete.

Angesichts der Entscheidung zwischen den beiden ... fand ich, dass Start-BitsTransfer den asynchronen Modus unterstützt. Also hier ist das Ergebnis meiner Verschmelzung der beiden.

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