有没有办法在PowerShell中复制一个非常大的文件(从一台服务器到另一台服务器)并显示其进度?
有一些解决方案可以将Write-Progress与循环结合使用来复制许多文件并显示进度。但是,我似乎无法找到任何可以显示单个文件进度的内容。
有什么想法吗?
答案 0 :(得分:76)
使用BitsTransfer似乎是一个更好的解决方案,在大多数使用PowerShell 2.0或更高版本的Windows机器上似乎都会出现OOTB。
Import-Module BitsTransfer
Start-BitsTransfer -Source $Source -Destination $Destination -Description "Backup" -DisplayName "Backup"
答案 1 :(得分:44)
我没有听说过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
[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
}
}
答案 2 :(得分:21)
另外,此选项使用本机Windows进度条...
$FOF_CREATEPROGRESSDLG = "&H0&"
$objShell = New-Object -ComObject "Shell.Application"
$objFolder = $objShell.NameSpace($DestLocation)
$objFolder.CopyHere($srcFile, $FOF_CREATEPROGRESSDLG)
答案 3 :(得分:14)
我修改了stej中的代码(这很好,正是我需要的!)使用更大的缓冲区,[long]用于更大的文件,并使用System.Diagnostics.Stopwatch类来跟踪已用时间和估计剩余时间。
还添加了传输过程中传输速率的报告,并输出了总体经过时间和总体传输速率。
使用4MB(4096 * 1024字节)缓冲区,比通过wifi从笔记本电脑上的NAS到USB记忆棒的Win7本机吞吐量更好。
待办事项列表:
随意使用/改进: - )
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();
}
}
答案 4 :(得分:14)
cmd /c copy /z src dest
不是纯PowerShell,而是PowerShell中的可执行文件,它以百分比显示进度
答案 5 :(得分:9)
不是我知道的。无论如何,我不建议使用copy-item。我不认为它设计得像robocopy.exe一样强大,以支持你想要通过网络进行超大文件拷贝的重试。
答案 6 :(得分:3)
我发现上面的示例都不能满足我的需求,我想复制一个包含子目录的目录,问题是我的源目录中的文件太多,因此我也很快达到了BITS文件的限制(我的文件> 1500)总目录大小很大。
我发现使用robocopy的函数是在https://keithga.wordpress.com/2014/06/23/copy-itemwithprogress/的一个很好的起点,但是我发现它还不够健壮,它不能正确处理尾部斜杠,空格,并且在出现以下情况时不会停止复制该脚本已暂停。
这是我的改进版本:
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
}
}
答案 7 :(得分:1)
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
}
答案 8 :(得分:0)
Trevor Sullivan撰写了关于如何在Robocopy上向PowerShell添加名为Copy-ItemWithProgress的命令的文章。
答案 9 :(得分:0)
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
答案 10 :(得分:0)
讨厌成为颠覆旧主题的人,但是我发现这篇文章非常有用。在对stej的代码片段进行性能测试并由Graham Gold对其进行完善之后,再加上Nacht的BITS建议,我断言:
面对两者之间的决定,我发现Start-BitsTransfer支持异步模式。这是我将两者合并的结果。
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
}
}