Search code examples
powershellwinformsuser-interfacerobocopystart-job

Update winforms GUI from Start-Job (Powershell)


I am trying to create a custom Robocopy status GUI in powershell, here is my basic code.

$script:Robocopy = Start-Process -FilePath robocopy.exe -ArgumentList $ArgumentList -Verbose -PassThru -NoNewWindow;

$RCStatus = Start-Job -Name RCLOOP -ArgumentList $Robocopy, $ReportGap, $RobocopyLogPath, $RegexBytes, $RobocopyFileProgress, $RobocopyMBProgress, $RobocopyGBProgress, $RobocopyPcntProgress, $RobocopySpeed {
        param ($Robocopy, $ReportGap, $RobocopyLogPath, $RegexBytes, $RobocopyFileProgress, $RobocopyMBProgress, $RobocopyGBProgress, $RobocopyPcntProgress, $RobocopySpeed)
        $iterations = 0
        [array]$avgSpeedarray = @()
        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; };
            $global:MBCopied = ($BytesCopied / 1048576)
            $global:GBCopied = ($BytesCopied / 1073741824)
            $script:CopiedFileCount = $LogContent.Count - 1;
            $iterations++
        
            if ($iterations % 2 -eq 1) {
                $M = $MBCopied
            }
            else {
                $B = $MBCopied
                $script:TSpeed = ($B - $M) * 8
                $avgSpeedarray += $script:TSpeed
                if ($avgSpeedarray.count -gt 4) {
                    $script:avgSpeed = (($avgSpeedarray[-1..-10] | Measure-Object -sum).sum) / 10
                    if ($avgSpeedarray.count -gt 19) {
                        $keep = $avgSpeedarray[-1..-9]
                        $avgSpeedarray = @()
                        $avgSpeedarray += $keep
                    }
                }
            }
            if ($iterations % 20 -eq 0) {
                $keepAwake
            }
            Write-Verbose -Message ('MB copied: {0}' -f $MBCopied);
            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} | {2}GB of {3}GB | {4}MB of {5}MB | {6}% Complete (Average: {7}Mbps)" -f $CopiedFileCount, $TotalFileCount, [Math]::Round($GBCopied, 2), [Math]::Round($GBTotal, 2), [Math]::Round($MBCopied, 2), [Math]::Round($MBTotal), [Math]::Round($Percentage, 2), [Math]::Round($script:avgSpeed,2)) -PercentComplete $Percentage
            $RobocopyFileProgress.Text = ("Files: {0} of {1}" -f $CopiedFileCount, $TotalFileCount)
            $RobocopyMBProgress.Text = ("Data: {0}MB of {1}MB" -f [Math]::Round($MBCopied, 2), [Math]::Round($MBTotal, 2))
            $RobocopyGBProgress.Text = ("Data: {0}GB of {1}GB" -f [Math]::Round($GBCopied, 2), [Math]::Round($GBTotal, 2))
            $RobocopyPcntProgress.Text = ("Percentage: {0}%" -f [Math]::Round($Percentage, 2))
            $RobocopySpeed.text = ("Speed: {0}Mbps" -f [Math]::Round($script:avgSpeed, 2))
        }
    }

    $RobocopyProgressGUI.Show()

    #endregion Progress loop
    Register-ObjectEvent -InputObject $script:Robocopy -EventName OutputDataReceived -SourceIdentifier 'RobocopyStatus' -Action {
        $RobocopyFileProgress.Text = ("Files: {0} of {1}" -f $CopiedFileCount, $TotalFileCount)
        $RobocopyMBProgress.Text = ("Data: {0}MB of {1}MB" -f [Math]::Round($MBCopied, 2), [Math]::Round($MBTotal, 2))
        $RobocopyGBProgress.Text = ("Data: {0}GB of {1}GB" -f [Math]::Round($GBCopied, 2), [Math]::Round($GBTotal, 2))
        $RobocopyPcntProgress.Text = ("Percentage: {0}%" -f [Math]::Round($Percentage, 2))
        $RobocopySpeed.text = ("Speed: {0}Mbps" -f [Math]::Round($script:avgSpeed, 2))
    }

One of my issues is that my form just opens and closes without any script telling it to close, another is all the code above outputs is the information about the Job, not what the job is outputting.

I know my code is very verbose, it was part of the troubleshooting process, I know C# is a better option but I am trying to achieve it in powershell as I am not very good in C#. Any Help or suggestion would be amazing. If you need any other code from my program that I missed in this, please let me know and I will provide it


Solution

  • Here is an example of how to use robocopy with progress bars in powershell. I adapted an existing answer and then refined it further based on some issues i discovered while using it. Hopefully it helps.

    Progress during large file copy (Copy-Item & Write-Progress?)

    If you want to do a GUI i would also recommend using WPF/xaml instead of winforms. Winforms seems to be somewhat unstable in PowerShell. In addition you can create your xaml in visual studio and then copy/paste. This also allows you to separate visualization from functionality.