我对每天从服务器复制大量文件的 PowerShell 脚本感兴趣,并且我对实现控制台中的进度条感兴趣,例如
File copy status - XX% complete.
在XX%
换行符之后更新同一行而不是换行符。我现在决定使用 RoboCopy。我目前有
ROBOCOPY 'C:\Users\JMondy\Desktop\Sample1' 'C:\Users\JMondy\Desktop\Sample2' . /E /IS /NFL /NJH
你下一步怎么做?
我对每天从服务器复制大量文件的 PowerShell 脚本感兴趣,并且我对实现控制台中的进度条感兴趣,例如
File copy status - XX% complete.
在XX%
换行符之后更新同一行而不是换行符。我现在决定使用 RoboCopy。我目前有
ROBOCOPY 'C:\Users\JMondy\Desktop\Sample1' 'C:\Users\JMondy\Desktop\Sample2' . /E /IS /NFL /NJH
你下一步怎么做?
我编写了一个名为 PowerShell 的函数Copy-WithProgress
,它可以实现您的目标。由于您特别声明您使用的是 robocopy,因此我构建了一个 PowerShell 函数来封装 robocopy 功能(至少是其中的一部分)。
请允许我向您展示它是如何工作的。我还录制并发布了一段 YouTube 视频,演示了该功能的设计方式以及调用测试运行的方式。
功能分为区域:
该函数有几个参数。
在脚本的底部(函数定义之后),是如何调用它的完整示例。它应该可以在您的计算机上运行,因为一切都是可变的。有五个步骤:
Copy-WithProgress
函数Copy-WithProgress
函数,并验证仅复制更改这是函数输出的截图。-Verbose
如果您不想要所有调试信息,可以省略该参数。APSCustomObject
由函数返回,它告诉您:
这是PowerShell ISE 和 PowerShell 控制台主机中的PowerShell 进度条的屏幕截图。
这是代码:
function Copy-WithProgress {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string] $Source
, [Parameter(Mandatory = $true)]
[string] $Destination
, [int] $Gap = 200
, [int] $ReportGap = 2000
)
# Define regular expression that will gather number of bytes copied
$RegexBytes = '(?<=\s+)\d+(?=\s+)';
#region Robocopy params
# MIR = Mirror mode
# NP = Don't show progress percentage in log
# NC = Don't log file classes (existing, new file, etc.)
# BYTES = Show file sizes in bytes
# NJH = Do not display robocopy job header (JH)
# NJS = Do not display robocopy job summary (JS)
# TEE = Display log in stdout AND in target log file
$CommonRobocopyParams = '/MIR /NP /NDL /NC /BYTES /NJH /NJS';
#endregion Robocopy params
#region Robocopy Staging
Write-Verbose -Message 'Analyzing robocopy job ...';
$StagingLogPath = '{0}\temp\{1} robocopy staging.log' -f $env:windir, (Get-Date -Format 'yyyy-MM-dd HH-mm-ss');
$StagingArgumentList = '"{0}" "{1}" /LOG:"{2}" /L {3}' -f $Source, $Destination, $StagingLogPath, $CommonRobocopyParams;
Write-Verbose -Message ('Staging arguments: {0}' -f $StagingArgumentList);
Start-Process -Wait -FilePath robocopy.exe -ArgumentList $StagingArgumentList -NoNewWindow;
# Get the total number of files that will be copied
$StagingContent = Get-Content -Path $StagingLogPath;
$TotalFileCount = $StagingContent.Count - 1;
# Get the total number of bytes to be copied
[RegEx]::Matches(($StagingContent -join "`n"), $RegexBytes) | % { $BytesTotal = 0; } { $BytesTotal += $_.Value; };
Write-Verbose -Message ('Total bytes to be copied: {0}' -f $BytesTotal);
#endregion Robocopy Staging
#region Start Robocopy
# Begin the robocopy process
$RobocopyLogPath = '{0}\temp\{1} robocopy.log' -f $env:windir, (Get-Date -Format 'yyyy-MM-dd HH-mm-ss');
$ArgumentList = '"{0}" "{1}" /LOG:"{2}" /ipg:{3} {4}' -f $Source, $Destination, $RobocopyLogPath, $Gap, $CommonRobocopyParams;
Write-Verbose -Message ('Beginning the robocopy process with arguments: {0}' -f $ArgumentList);
$Robocopy = Start-Process -FilePath robocopy.exe -ArgumentList $ArgumentList -Verbose -PassThru -NoNewWindow;
Start-Sleep -Milliseconds 100;
#endregion Start Robocopy
#region Progress bar loop
while (!$Robocopy.HasExited) {
Start-Sleep -Milliseconds $ReportGap;
$BytesCopied = 0;
$LogContent = Get-Content -Path $RobocopyLogPath;
$BytesCopied = [Regex]::Matches($LogContent, $RegexBytes) | ForEach-Object -Process { $BytesCopied += $_.Value; } -End { $BytesCopied; };
$CopiedFileCount = $LogContent.Count - 1;
Write-Verbose -Message ('Bytes copied: {0}' -f $BytesCopied);
Write-Verbose -Message ('Files copied: {0}' -f $LogContent.Count);
$Percentage = 0;
if ($BytesCopied -gt 0) {
$Percentage = (($BytesCopied/$BytesTotal)*100)
}
Write-Progress -Activity Robocopy -Status ("Copied {0} of {1} files; Copied {2} of {3} bytes" -f $CopiedFileCount, $TotalFileCount, $BytesCopied, $BytesTotal) -PercentComplete $Percentage
}
#endregion Progress loop
#region Function output
[PSCustomObject]@{
BytesCopied = $BytesCopied;
FilesCopied = $CopiedFileCount;
};
#endregion Function output
}
# 1. TESTING: Generate a random, unique source directory, with some test files in it
$TestSource = '{0}\{1}' -f $env:temp, [Guid]::NewGuid().ToString();
$null = mkdir -Path $TestSource;
# 1a. TESTING: Create some test source files
1..20 | % -Process { Set-Content -Path $TestSource\$_.txt -Value ('A'*(Get-Random -Minimum 10 -Maximum 2100)); };
# 2. TESTING: Create a random, unique target directory
$TestTarget = '{0}\{1}' -f $env:temp, [Guid]::NewGuid().ToString();
$null = mkdir -Path $TestTarget;
# 3. Call the Copy-WithProgress function
Copy-WithProgress -Source $TestSource -Destination $TestTarget -Verbose;
# 4. Add some new files to the source directory
21..40 | % -Process { Set-Content -Path $TestSource\$_.txt -Value ('A'*(Get-Random -Minimum 950 -Maximum 1400)); };
# 5. Call the Copy-WithProgress function (again)
Copy-WithProgress -Source $TestSource -Destination $TestTarget -Verbose;
这些解决方案很棒,但是一种快速简便的方法可以轻松获得所有文件的浮动进度,如下所示:
robocopy <source> <destination> /MIR /NDL /NJH /NJS | %{$data = $_.Split([char]9); if("$($data[4])" -ne "") { $file = "$($data[4])"} ;Write-Progress "Percentage $($data[0])" -Activity "Robocopy" -CurrentOperation "$($file)" -ErrorAction SilentlyContinue; }
你绝对必须使用robocopy吗?
如果没有,您可以为每个文件调用此线程中的代码:大文件复制期间的进度(复制项目和写入进度?)
或者,使用从 powershell 调用的 robocopy 的 /L 开关来获取 robocopy 将复制的文件列表,并使用 for-each 循环通过该复制功能运行每个文件。
您甚至可以嵌套写入进度命令,以便报告“文件 x of y - XX% 完成”
像这样的东西应该可以工作,需要对子目录做一些工作(我怀疑不仅仅是将 -recurse 添加到 gci 命令),但会让你朝着正确的方向前进。
注意:我在手机上写这个,代码还未经测试......
function Copy-File {
param( [string]$from, [string]$to)
$ffile = [io.file]::OpenRead($from)
$tofile = [io.file]::OpenWrite($to)
Write-Progress `
-Activity ("Copying file " + $filecount + " of " + $files.count) `
-status ($from.Split("\")|select -last 1) `
-PercentComplete 0
try {
$sw = [System.Diagnostics.Stopwatch]::StartNew();
[byte[]]$buff = new-object byte[] 65536
[long]$total = [long]$count = 0
do {
$count = $ffile.Read($buff, 0, $buff.Length)
$tofile.Write($buff, 0, $count)
$total += $count
if ($total % 1mb -eq 0) {
if([int]($total/$ffile.Length* 100) -gt 0)`
{[int]$secsleft = ([int]$sw.Elapsed.Seconds/([int]($total/$ffile.Length* 100))*100)
} else {
[int]$secsleft = 0};
Write-Progress `
-Activity ([string]([int]($total/$ffile.Length* 100)) + "% Copying file")`
-status ($from.Split("\")|select -last 1) `
-PercentComplete ([int]($total/$ffile.Length* 100))`
-SecondsRemaining $secsleft;
}
} while ($count -gt 0)
$sw.Stop();
$sw.Reset();
}
finally {
$ffile.Close()
$tofile.Close()
}
}
$srcdir = "C:\Source;
$destdir = "C:\Dest";
[int]$filecount = 0;
$files = (Get-ChildItem $SrcDir | where-object {-not ($_.PSIsContainer)});
$files|foreach($_){
$filecount++
if ([system.io.file]::Exists($destdir+$_.name)){
[system.io.file]::Delete($destdir+$_.name)}
Copy-File -from $_.fullname -to ($destdir+$_.name)
};
就我个人而言,我将此代码用于 USB 记忆棒的小副本,但我在 powershell 脚本中使用 robocopy 进行 PC 备份。
这是我最终用于此类任务的代码片段:
$fileName = 'test.txt'
$fromDir = 'c:\'
$toDir = 'd:\'
$title = $null
&robocopy "$fromDir" "$toDir" "$fileName" /z /mt /move /w:3 /r:10 /xo | %{
$data = $_.Split("`t")
if ($title -and $data[0] -match '\d+(?=%)') {
Write-Progress $title -Status $data -PercentComplete $matches[0]
}
if($data[4]) {$title = $data[4]}
}
Write-Progress $title -complete
这是 RoboCopy 的本机 PowerShell GUI版本。(没有EXE文件)
我希望它对某人有所帮助。
https://gallery.technet.microsoft.com/PowerShell-Robocopy-GUI-08c9cacb
仅供参考:有没有人可以将 PowerCopy GUI 工具与 Copy-WithProgress 栏相结合?
进度条很好,但在复制数百个文件时,显示进度会减慢操作速度,在某些情况下会减慢速度。这是 robocopy 帮助说明 /MT 标志将输出重定向到日志以获得更好性能的原因之一。
我最终根据 Amrinder 的建议答案使用了这个:
robocopy.exe $Source $Destination $PatternArg $MirrorArg /NDL /NJH /NJS | ForEach-Object -Process {
$data = $_.Split([char]9);
if (($data.Count -gt 4) -and ("$($data[4])" -ne ""))
{
$file = "$($data[4])"
Write-Progress "Percentage $($data[0])" -Activity "Robocopy" -CurrentOperation "$($file)" -ErrorAction SilentlyContinue;
}
else
{
Write-Progress "Percentage $($data[0])" -Activity "Robocopy" -CurrentOperation "$($file)"
}
}
# Robocopy has a bitmask set of exit codes, so only complain about failures:
[int] $exitCode = $global:LastExitCode;
[int] $someCopyErrors = $exitCode -band 8;
[int] $seriousError = $exitCode -band 16;
if (($someCopyErrors -ne 0) -or ($seriousError -ne 0))
{
Write-Error "ERROR: robocopy failed with a non-successful exit code: $exitCode"
exit 1
}
仅供参考,比尔