PowerShell中的自定义RoboCopy进度条

时间:2012-12-14 17:20:52

标签: powershell

我对PowerShell脚本感兴趣,该脚本每天从服务器复制大量文件,我有兴趣实现一个控制台进度条,如

File copy status - XX% complete.

其中XX%在换行后更新同一行而不是换行符。我决定暂时使用RoboCopy。我现在有了

ROBOCOPY 'C:\Users\JMondy\Desktop\Sample1' 'C:\Users\JMondy\Desktop\Sample2' . /E /IS /NFL /NJH

下一步是什么?

7 个答案:

答案 0 :(得分:73)

我编写了一个名为Copy-WithProgress的PowerShell函数,它将实现您的目标。由于您明确声明您使用的是robocopy,因此我构建了一个PowerShell函数,该函数封装了robocopy功能(至少部分内容)。

请允许我告诉你它是如何运作的。我还recorded and posted a YouTube video演示了函数的设计方式,并调用了测试运行。

该功能分为几个区域:

  • 常见的robocopy参数
  • 分段(计算robocopy作业大小的位置)
  • 复制(启动robocopy作业的地方)
  • 进度条(监控robocopy进度)
  • 函数输出(输出一些有用的统计信息,用于脚本的其余部分)

该功能有几个参数。

  • 来源:源目录
  • 目的地:目标目录
  • 差距:robocopy支持的“数据包间间隔”(以毫秒为单位),人为地减慢了副本的速度,以便进行测试)
  • ReportGap :检查robocopy进度的间隔(以毫秒为单位)

在脚本的底部(在函数定义之后),是一个如何调用它的完整示例。它应该可以在您的计算机上运行,​​因为所有内容都是可变的。有五个步骤:

  1. 生成随机源目录
  2. 生成目标目录
  3. 调用Copy-WithProgress函数
  4. 创建一些其他源文件(以模拟一段时间内的更改)
  5. 再次调用Copy-WithProgress函数,并仅验证是否复制了更改
  6. 这是函数输出的截图。如果您不想要所有调试信息,可以不使用-Verbose参数。函数返回PSCustomObject,告诉您:

    1. 复制了多少字节
    2. 复制了多少个文件
    3. Copy-WithProgress PowerShell Function

      以下是PowerShell ISE和PowerShell控制台主机中 PowerShell进度条的屏幕截图。

      PowerShell Progress Bar (ISE)

      PowerShell Progress Bar (Console Host)

      以下是代码:

      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;
      

答案 1 :(得分:2)

你绝对必须使用robocopy吗?

如果没有,你可以在这个帖子中为每个文件调用代码:Progress during large file copy (Copy-Item & Write-Progress?)

或者使用从powershell调用的robocopy的/ L开关来获取robocopy将复制的文件列表,并使用for-each循环通过该复制函数运行每个文件。

您甚至可以嵌套写入进度命令,这样您就可以报告“文件x的y - XX%完成”

像这样的东西应该工作,需要为子目录做一些工作(我怀疑不仅仅是在gci命令中添加-recurse),而且会让你朝着正确的方向前进。

注意:我正在手机上写这个,代码尚未经过测试......

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备份。

答案 2 :(得分:1)

这些解决方案非常棒,但可以轻松快捷地获取所有文件的浮动进度,如下所示:

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

答案 3 :(得分:1)

以下是RoboCopy的原生PowerShell GUI 版本。 (没有EXE文件)

我希望它有所帮助。

enter image description here

https://gallery.technet.microsoft.com/PowerShell-Robocopy-GUI-08c9cacb

仅供参考:有没有人可以将PowerCopy GUI工具与Copy-WithProgress栏结合使用?

答案 4 :(得分:1)

进度条很好,除了复制数百个文件之外,显示进度会减慢操作速度,在某些情况下会相当多。这是robocopy帮助说明/ MT标志将输出重定向到日志以获得更好性能的一个原因。

答案 5 :(得分:1)

这是我最终用于此类任务的代码段:

$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

答案 6 :(得分:0)

我最终根据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
}

据透露, 比尔