77

PowerShellで(あるサーバーから別のサーバーに)非常に大きなファイルをコピーして、その進行状況を表示する方法はありますか?

Write-Progressをループと組み合わせて使用​​して多くのファイルをコピーし、進行状況を表示するソリューションがあります。しかし、1つのファイルの進行状況を示すものは何も見つからないようです。

何かご意見は?

4

12 に答える 12

118

BitsTransferを使用するだけの方がはるかに優れたソリューションのようですが、PowerShell2.0以降を搭載したほとんどのWindowsマシンではOOTBになるようです。

Import-Module BitsTransfer
Start-BitsTransfer -Source $Source -Destination $Destination -Description "Backup" -DisplayName "Backup"
于 2014-09-09T01:17:14.173 に答える
51

の進捗状況について聞いたことがありませんCopy-Item。外部ツールを使用したくない場合は、ストリームを試すことができます。バッファのサイズはさまざまです。さまざまな値(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
    }
}
于 2010-03-12T21:47:40.513 に答える
30

または、このオプションはネイティブウィンドウのプログレスバーを使用します...

$FOF_CREATEPROGRESSDLG = "&H0&"

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

$objFolder = $objShell.NameSpace($DestLocation) 

$objFolder.CopyHere($srcFile, $FOF_CREATEPROGRESSDLG)
于 2012-07-13T10:10:59.047 に答える
28
cmd /c copy /z src dest

純粋なPowerShellではありませんが、PowerShellで実行可能であり、進行状況をパーセントで表示します

于 2015-01-05T13:38:03.807 に答える
17

stejのコードを修正して(これは素晴らしく、必要なものだけです!)、より大きなバッファーを使用し、[long]をより大きなファイルに使用し、System.Diagnostics.Stopwatchクラスを使用して経過時間を追跡し、残り時間を推定しました。

また、転送中の転送速度のレポートを追加し、全体の経過時間と全体の転送速度を出力します。

4MB(4096 * 1024バイト)のバッファーを使用して、Wi-Fi経由でラップトップのNASからUSBスティックにWin7ネイティブスループットコピーよりも優れています。

To-Doリスト:

  • エラー処理の追加(キャッチ)
  • get-childitemファイルリストを入力として処理する
  • 複数のファイルをコピーする場合のネストされたプログレスバー(ファイルx of y、コピーされたデータの合計の場合は%など)
  • バッファサイズの入力パラメータ

自由に使用/改善してください:-)

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();
    }
}
于 2012-12-01T11:24:54.527 に答える
9

私が知っていることではありません。とにかく、これにcopy-itemを使用することはお勧めしません。ネットワーク上で非常に大きなファイルコピーが必要になる再試行をサポートするために、robocopy.exeのように堅牢になるように設計されているとは思いません。

于 2010-03-12T18:39:45.000 に答える
4

古い主題にぶつかるのは嫌いですが、この投稿は非常に役に立ちました。stejによるスニペットとGrahamGoldによる改良、およびNachtによるBITSの提案に対してパフォーマンステストを実行した後、私は次のことを明らかにしました。

  1. 時間の見積もりと速度の読み取りを備えたGrahamのコマンドが本当に気に入りました。
  2. また、転送方法としてBITSを使用することによる大幅な速度の向上も非常に気に入りました

2つの間の決定に直面しました...私はStart-BitsTransferが非同期モードをサポートしていることを発見しました。これが私の2つをマージした結果です。

function Copy-File {
    # ref: https://stackoverflow.com/a/55527732/3626361
    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 '" + ($From.Split("\") | Select-Object -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
    }
}
于 2019-04-05T03:17:49.720 に答える
4

上記の例のどれも私のニーズを満たしていないことがわかりました。サブディレクトリを含むディレクトリをコピーしたかったのですが、問題はソースディレクトリにファイルが多すぎるため、BITSファイルの制限(1500を超えるファイル)とディレクトリ全体にすぐに到達したことです。サイズはかなり大きかった。

https://keithga.wordpress.com/2014/06/23/copy-itemwithprogress/でrobocopyを使用した関数を見つけましたが、十分に堅牢ではなく、処理できませんでした。末尾のスラッシュ、スペースは適切に配置され、スクリプトが停止したときにコピーが停止しませんでした。

これが私の洗練されたバージョンです:

function Copy-ItemWithProgress
{
    <#
    .SYNOPSIS
    RoboCopy with PowerShell progress.

    .DESCRIPTION
    Performs file copy with RoboCopy. Output from RoboCopy is captured,
    parsed, and returned as Powershell native status and progress.

    .PARAMETER Source
    Directory to copy files from, this should not contain trailing slashes

    .PARAMETER Destination
    DIrectory to copy files to, this should not contain trailing slahes

    .PARAMETER FilesToCopy
    A wildcard expresion of which files to copy, defaults to *.*

    .PARAMETER RobocopyArgs
    List of arguments passed directly to Robocopy.
    Must not conflict with defaults: /ndl /TEE /Bytes /NC /nfl /Log

    .PARAMETER ProgressID
    When specified (>=0) will use this identifier for the progress bar

    .PARAMETER ParentProgressID
    When specified (>= 0) will use this identifier as the parent ID for progress bars
    so that they appear nested which allows for usage in more complex scripts.

    .OUTPUTS
    Returns an object with the status of final copy.
    REMINDER: Any error level below 8 can be considered a success by RoboCopy.

    .EXAMPLE
    C:\PS> .\Copy-ItemWithProgress c:\Src d:\Dest

    Copy the contents of the c:\Src directory to a directory d:\Dest
    Without the /e or /mir switch, only files from the root of c:\src are copied.

    .EXAMPLE
    C:\PS> .\Copy-ItemWithProgress '"c:\Src Files"' d:\Dest /mir /xf *.log -Verbose

    Copy the contents of the 'c:\Name with Space' directory to a directory d:\Dest
    /mir and /XF parameters are passed to robocopy, and script is run verbose

    .LINK
    https://keithga.wordpress.com/2014/06/23/copy-itemwithprogress

    .NOTES
    By Keith S. Garner (KeithGa@KeithGa.com) - 6/23/2014
    With inspiration by Trevor Sullivan @pcgeek86
    Tweaked by Justin Marshall - 02/20/2020

    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        [string]$Source,
        [Parameter(Mandatory=$true)]
        [string]$Destination,
        [Parameter(Mandatory=$false)]
        [string]$FilesToCopy="*.*",
        [Parameter(Mandatory = $true,ValueFromRemainingArguments=$true)] 
        [string[]] $RobocopyArgs,
        [int]$ParentProgressID=-1,
        [int]$ProgressID=-1
    )

    #handle spaces and trailing slashes
    $SourceDir = '"{0}"' -f ($Source -replace "\\+$","")
    $TargetDir = '"{0}"' -f ($Destination -replace "\\+$","")


    $ScanLog  = [IO.Path]::GetTempFileName()
    $RoboLog  = [IO.Path]::GetTempFileName()
    $ScanArgs = @($SourceDir,$TargetDir,$FilesToCopy) + $RobocopyArgs + "/ndl /TEE /bytes /Log:$ScanLog /nfl /L".Split(" ")
    $RoboArgs = @($SourceDir,$TargetDir,$FilesToCopy) + $RobocopyArgs + "/ndl /TEE /bytes /Log:$RoboLog /NC".Split(" ")

    # Launch Robocopy Processes
    write-verbose ("Robocopy Scan:`n" + ($ScanArgs -join " "))
    write-verbose ("Robocopy Full:`n" + ($RoboArgs -join " "))
    $ScanRun = start-process robocopy -PassThru -WindowStyle Hidden -ArgumentList $ScanArgs
    try
    {
        $RoboRun = start-process robocopy -PassThru -WindowStyle Hidden -ArgumentList $RoboArgs
        try
        {
            # Parse Robocopy "Scan" pass
            $ScanRun.WaitForExit()
            $LogData = get-content $ScanLog
            if ($ScanRun.ExitCode -ge 8)
            {
                $LogData|out-string|Write-Error
                throw "Robocopy $($ScanRun.ExitCode)"
            }
            $FileSize = [regex]::Match($LogData[-4],".+:\s+(\d+)\s+(\d+)").Groups[2].Value
            write-verbose ("Robocopy Bytes: $FileSize `n" +($LogData -join "`n"))
            #determine progress parameters
            $ProgressParms=@{}
            if ($ParentProgressID -ge 0) {
                $ProgressParms['ParentID']=$ParentProgressID
            }
            if ($ProgressID -ge 0) {
                $ProgressParms['ID']=$ProgressID
            } else {
                $ProgressParms['ID']=$RoboRun.Id
            }
            # Monitor Full RoboCopy
            while (!$RoboRun.HasExited)
            {
                $LogData = get-content $RoboLog
                $Files = $LogData -match "^\s*(\d+)\s+(\S+)"
                if ($null -ne $Files )
                {
                    $copied = ($Files[0..($Files.Length-2)] | ForEach-Object {$_.Split("`t")[-2]} | Measure-Object -sum).Sum
                    if ($LogData[-1] -match "(100|\d?\d\.\d)\%")
                    {
                        write-progress Copy -ParentID $ProgressParms['ID'] -percentComplete $LogData[-1].Trim("% `t") $LogData[-1]
                        $Copied += $Files[-1].Split("`t")[-2] /100 * ($LogData[-1].Trim("% `t"))
                    }
                    else
                    {
                        write-progress Copy -ParentID $ProgressParms['ID'] -Complete
                    }
                    write-progress ROBOCOPY  -PercentComplete ($Copied/$FileSize*100) $Files[-1].Split("`t")[-1] @ProgressParms
                }
            }
        } finally {
            if (!$RoboRun.HasExited) {Write-Warning "Terminating copy process with ID $($RoboRun.Id)..."; $RoboRun.Kill() ; }
            $RoboRun.WaitForExit()
            # Parse full RoboCopy pass results, and cleanup
            (get-content $RoboLog)[-11..-2] | out-string | Write-Verbose
            remove-item $RoboLog
            write-output ([PSCustomObject]@{ ExitCode = $RoboRun.ExitCode })

        }
    } finally {
        if (!$ScanRun.HasExited) {Write-Warning "Terminating scan process with ID $($ScanRun.Id)..."; $ScanRun.Kill() }
        $ScanRun.WaitForExit()

        remove-item $ScanLog
    }
}
于 2020-02-21T03:50:53.117 に答える
3

この再帰関数は、ファイルとディレクトリをソースパスから宛先パスに再帰的にコピーします

ファイルが宛先パスにすでに存在する場合は、新しいファイルでのみそれらをコピーします。

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
}
于 2017-08-21T15:43:59.587 に答える
1

Trevor Sullivanが、Copy-ItemWithProgressというコマンドをRobocopyのPowerShellに追加する方法についての記事を掲載しています。

于 2016-04-20T10:14:04.263 に答える
1

ねえ、スクリプトガイのショーンカーニー!ブログには、私が見つけた解決策があり、かなりうまく機能しています。

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

次にそれを使用するには:

Copy-WithProgress -Source $src -Destination $dest
于 2018-01-08T21:45:07.413 に答える
0

コピーされた単一の大きなファイルの更新された進行状況を取得するには

ファイルのコピー中にプログレスバーを取得する方法は、別のプロセスでコピー操作を開始し、子が完了するまで親プロセスから宛先ファイルのサイズをポーリングすることです。

function Copy-LargeFileWithProgress {
    param(
        [string] $SourcePath,
        [string] $DestPath
    )
    
    $source = Get-Item $SourcePath
    $name   = $source.Name
    $size   = $source.Size
    $argv   = @('-Command', "Copy-Item '$SourcePath' '$DestPath'")

    $proc   = Start-Process 'pwsh' $argv -PassThru 
 
    while (-not $proc.HasExited) {
        Start-Sleep -Seconds 2
        if (Test-Path $DestPath) {
            $destSize = (Get-Item $DestPath).Size
            $status   = '{0:N0}/{1:N0} bytes' -f $destSize, $size
            $complete = [Math]::Max($destSize / $size * 100, 100)
    
            Write-Progress -Activity        "Copying $name" `
                           -Status          $status `
                           -PercentComplete $complete
        }
    }
}

上記のコードはLinuxで動作しますが、Windowsでは試していません。Windowsでは、-WindowStyle Minimizedオプションが必要になる場合がありますStart-Process

上記の実装は、アプローチを簡潔に示すためにスパースであることを意図しています。1.51G/210.00Gステータスメッセージをより適切なフォーマットで(つまり、生のバイト数ではなく)表示するなど、誰かがそれを使用したい場合は、いくつかの洗練されたタッチを使用できます。また、最初に報告されたサイズは(Get-Item $DestPath).Size、数回の反復では正しくない可能性があります(したがって、max操作を使用します)。それはもっと優雅に説明することができます。

于 2022-02-07T02:49:54.033 に答える