如何在执行静默安装时异步运行进度条?

时间:2016-05-19 13:35:34

标签: powershell asynchronous progress

目标:

我希望在等待静默安装时异步运行进度条(显示已用时间/估计时间)。例如,

socket.on("getPhoto") { data,ack in
    if let buffer = data[0] as? NSData {
        let img = UIImage(data: buffer)
    }
}

最近我来了:

RunWithProgressBar "cmd /c """" /Wait ""Setup.exe""" $(New-Timespan -Minutes 5)

问题

虽然上述实现按预期运行,但只要## Functions function global:BottleOfBeer { $beer = $args[0] if ($beer -eq 1) { echo "$beer beer on the wall."; } elseif ($beer -eq 0) { echo "No beer left on the wall!"; } else { echo "$beer beers left on the wall."; } sleep 1 } function global:BeersOnTheWall { $NumBeers = $args[0] for ($i=$NumBeers; $i -ge 0; $i--) { BottleOfBeer $i } } function global:Install { cmd /c @" "AutoHotkey112306_Install.exe" /S /D="%cd%" "@ } function global:Uninstall { cmd /c start "" /wait "Installer.ahk" /Uninstall } ####START Progress Bar Stuff function global:DisplayProgress { $display = $args[0] Write-Progress -Activity "Running..." -Status "$display" } function global:FormatDisplay { $StartTime = $args[0] $RunningTime = ($args[1]).Elapsed $EstimatedTime = $args[2] $RunningTimeDisplay = $([string]::Format("{0:d2}:{1:d2}:{2:d2}", $RunningTime.hours, $RunningTime.minutes, $RunningTime.seconds)) $EstimatedEnd = $StartTime + $EstimatedTime return $([string]::Format("(Start: {0}) (Elapsed/Estimated: {1}/{2}) (EstimatedEnd: {3})", $StartTime.ToShortTimeString(), $RunningTimeDisplay, $EstimatedTime, $EstimatedEnd.ToShortTimeString())) } function global:TearDownProgressBar { $job = $args[0] $event = $args[1] $job,$event | Stop-Job -PassThru | Remove-Job #stop the job and event listener Write-Progress -Activity "Working..." -Completed -Status "All done." } function RunWithProgressBar { $Payload = $args[0] $EstimatedTime = $args[1] $global:StartTime = Get-Date $global:RunningTime = [System.Diagnostics.Stopwatch]::StartNew() $global:EstimatedTime = $EstimatedTime $progressTask = { while($true) { Register-EngineEvent -SourceIdentifier MyNewMessage -Forward $null = New-Event -SourceIdentifier MyNewMessage -MessageData "Pingback from job." Start-Sleep -Seconds 1 } } $job = Start-Job -ScriptBlock $progressTask $event = Register-EngineEvent -SourceIdentifier MyNewMessage -Action { DisplayProgress $(FormatDisplay $global:StartTime $global:RunningTime $global:EstimatedTime) } try { sleep 1 Invoke-Expression $Payload } finally { TearDownProgressBar $job $event } } ####END Progress Bar Stuff ## MAIN RunWithProgressBar "BeersOnTheWall 2" $(New-Timespan -Seconds 3) RunWithProgressBar "Install" $(New-Timespan -Seconds 30) RunWithProgressBar "Uninstall" $(New-Timespan -Seconds 5) RunWithProgressBar "BeersOnTheWall 2" $(New-Timespan -Seconds 3) 的有效负载参数是安装,更新进度条的事件就会停止触发。

我正在寻找:

如何修改我当前的实现以每秒更新进度条,即使在执行安装时也是如此?

2 个答案:

答案 0 :(得分:2)

我觉得你正试图在 $progressTask = { while($true) { Register-EngineEvent -SourceIdentifier MyNewMessage -Forward $null = New-Event -SourceIdentifier MyNewMessage -MessageData "Pingback from job." Start-Sleep -Seconds 1 } } $job = Start-Job -ScriptBlock $progressTask $event = Register-EngineEvent -SourceIdentifier MyNewMessage -Action { DisplayProgress $(FormatDisplay $global:StartTime $global:RunningTime $global:EstimatedTime) } 函数中重新发明方向

$timer = new-object timers.timer     
$action = {DisplayProgress $(FormatDisplay $global:StartTime $global:RunningTime $global:EstimatedTime)} 
$timer.Interval = 1000 
Register-ObjectEvent -InputObject $timer -EventName elapsed –SourceIdentifier thetimer -Action $action | out-null
$timer.Start()

这基本上是计时器,可以重构:

scriptblock

但是,要解决您的问题,您可以在作业中执行任务并等到他们完成。

不要将这些功能公开,而是考虑 $initializationScriptBlock = { $functions = @{ Install = { cmd /c '"AutoHotkey112306_Install.exe" /S /D="%cd%"' } BottleOfBeer = { $beer = $args[0] if ($beer -eq 1) { echo "$beer beer on the wall."; } elseif ($beer -eq 0) { echo "No beer left on the wall!"; } else { echo "$beer beers left on the wall."; } sleep 1 } BeersOnTheWall = { $NumBeers = $args[0] for ($i=$NumBeers; $i -ge 0; $i--) { BottleOfBeer $i } } Uninstall = { cmd /c start "" /wait "Installer.ahk" /Uninstall } } } 中定义它们:

Invoke-Expression $Payload

现在,通过initializationScriptBlock和实际函数$functionToExecute作为scriptblock来执行$executingJob = start-Job -InitializationScript $initializationScriptBlock -ScriptBlock $functionToExecute while ((Get-job $executingJob.Id).State -eq 'Running') { sleep 1; } ,您RunWithProgressBar开始一项新工作。

PowerShell

您的function RunWithProgressBar { Param( [Parameter(Mandatory=$true, Position=0)] [scriptblock]$functionToExecute, [Parameter(Mandatory=$true, Position=1)] [timespan]$EstimatedTime ) .... } 函数现在正在使用initializationScriptBlock - 就像函数定义一样:

. $initializationScriptBlock

请考虑更改可读性的其余脚本。

要使用进度条调用函数,首先必须加载RunWithProgressBar $functions.BottleOfBeer (New-Timespan -Seconds 3) RunWithProgressBar $functions.Install (New-Timespan -Seconds 30) 到当前的运行空间:

## Functions
$initializationScriptBlock = {
    $functions = @{

        Install = {
        Sleep 5
        <#cmd /c @"
        "AutoHotkey112306_Install.exe" /S /D="%cd%"
"@#>
        }

        BottleOfBeer = {
          $beer = $args[0]
          if ($beer -eq 1) {
            echo "$beer beer on the wall.";
          } elseif ($beer -eq 0) {
            echo "No beer left on the wall!";
          } else {
            echo "$beer beers left on the wall.";
          }
          sleep 1
        }

        BeersOnTheWall = {
            $NumBeers = $args[0]
            for ($i=$NumBeers; $i -ge 0; $i--) 
            {
                BottleOfBeer $i
            }
        }

        Uninstall =  {
            cmd /c start "" /wait "Installer.ahk" /Uninstall
        }

    }

}

####START Progress Bar Stuff
function global:DisplayProgress {
  $display = $args[0]
  Write-Progress    -Activity "Running..." -Status "$display"
}

function global:FormatDisplay {
  $StartTime = $args[0]
  $RunningTime = ($args[1]).Elapsed
  $EstimatedTime = $args[2]
  $RunningTimeDisplay = $([string]::Format("{0:d2}:{1:d2}:{2:d2}",
    $RunningTime.hours, 
    $RunningTime.minutes, 
    $RunningTime.seconds))
  $EstimatedEnd = $StartTime + $EstimatedTime
  return $([string]::Format("(Start: {0}) (Elapsed/Estimated: {1}/{2}) (EstimatedEnd: {3})",
    $StartTime.ToShortTimeString(), 
    $RunningTimeDisplay, 
    $EstimatedTime,
    $EstimatedEnd.ToShortTimeString()))
}


function RunWithProgressBar 
{
    Param(
        [Parameter(Mandatory=$true, Position=0)]
        [scriptblock]$functionToExecute,

        [Parameter(Mandatory=$true, Position=1)]
        [timespan]$EstimatedTime
    )

  $global:StartTime = Get-Date
  $global:RunningTime = [System.Diagnostics.Stopwatch]::StartNew()
  $global:EstimatedTime = $EstimatedTime


  $timer = new-object timers.timer 

    $action = {DisplayProgress $(FormatDisplay $global:StartTime $global:RunningTime $global:EstimatedTime)} 
    $timer.Interval = 1000  

    Register-ObjectEvent -InputObject $timer -EventName elapsed –SourceIdentifier thetimer -Action $action | out-null

    $timer.Start()

  try {
    $executingJob =  start-Job -InitializationScript $initializationScriptBlock -ScriptBlock $functionToExecute
    while ((Get-job $executingJob.Id).State -eq 'Running')
    {
        sleep 1;
    }

  } finally {
    $timer.stop() 

    Unregister-Event thetimer
  }
}
####END Progress Bar Stuff

## MAIN
. $initializationScriptBlock

RunWithProgressBar $functions.BottleOfBeer (New-Timespan -Seconds 3)
RunWithProgressBar $functions.Install (New-Timespan -Seconds 30)
RunWithProgressBar $functions.Uninstall (New-Timespan -Seconds 5)
RunWithProgressBar $functions.BeersOnTheWall (New-Timespan -Seconds 3)

现在您可以调用这样的函数:

$executingJob =  start-Job -InitializationScript $initializationScriptBlock -ScriptBlock $functionToExecute
while ((Get-job $executingJob.Id).State -eq 'Running')
{
    Receive-Job $executingJob.Id
    sleep 1;
}
Receive-Job $executingJob.Id

您的整个脚本现在看起来像这样:

$initializationScriptBlock

你的脚本现在应该可以工作,即使还有很多重构 e。 G。函数名称,范围

回答您的评论:

1。)您可以在循环中使用Receive-Job来检索输出:

. $initializationScriptBlock

2)。 使用:

执行& $functions.BottleOfBeer
ng-repeat

您可以使用任何功能:

<uib-accordion>

答案 1 :(得分:1)

虽然所提供的解决方案赢得了赏金,但这不是我最终做的事情。这是RunWithProgressBar()的最终版本:

param ( 
    [alias("IM")]
    [bool]$IgnoreMain = $false
)

function RunAsBAT([string]$commands) {
    # Write commands to bat file
    $tempFile = $global:scriptDir + '\TemporaryBatFile.bat'
    $commands = "@echo off `n" + $commands
    Out-File -InputObject $commands -FilePath $tempFile -Encoding ascii
    # Wait for bat file to run
    & $tempFile
    # Delete bat file
    Remove-Item -Path $tempFile
}

function DisplayProgress([string]$display) {
    Write-Progress  -Activity "Running..." -Status "$display"
}

function FormatDisplay([System.DateTime]$StartTime, [System.TimeSpan]$RunningTime, [System.TimeSpan]$EstimatedTime) {
    $RunningTimeDisplay = $([string]::Format("{0:d2}:{1:d2}:{2:d2}",
        $RunningTime.hours, 
        $RunningTime.minutes, 
        $RunningTime.seconds))
    $EstimatedEnd = $StartTime + $EstimatedTime
    return $([string]::Format("(Start: {0}) (Elapsed/Estimated: {1}/{2}) (EstimatedEnd: {3})",
        $StartTime.ToShortTimeString(), 
        $RunningTimeDisplay, 
        $EstimatedTime,
        $EstimatedEnd.ToShortTimeString()))
}

function RunWithProgressBar([scriptblock]$payload, [System.TimeSpan]$EstimatedTime) {
    $global:StartTime = Get-Date
    $global:RunningTime = [System.Diagnostics.Stopwatch]::StartNew()
    $global:EstimatedTime = $EstimatedTime

    try {
        $logFile = $global:scriptDir + '\TemporaryLogFile.txt'
        $StartInfo = New-Object System.Diagnostics.ProcessStartInfo -Property @{
                        FileName = 'Powershell'
                        # load this script but don't run MAIN (to expose functions/variables); 
                        # run the payload (and also log to file);
                        # if error, pause (so the window stays open to display the error)
                        Arguments = ". $global:scriptPath -IM 1; & $payload | Tee-Object -file $logFile;" + ' if ( $LastExitCode -ne 0 ) { cmd /c pause }'
                        UseShellExecute = $true
                    }
        $Process = New-Object System.Diagnostics.Process
        $Process.StartInfo = $StartInfo
        [void]$Process.Start()

        do
        {
            DisplayProgress $(FormatDisplay $global:StartTime ($global:RunningTime).Elapsed $global:EstimatedTime)
            Start-Sleep -Seconds 1
        }
        while (!$Process.HasExited)

    }
    finally {
        if (Test-Path $logFile) {
            Get-Content -Path $logFile
            Remove-Item -Path $logFile
        } else {
            Write-Host "No output was logged..."
        }
        Write-Progress  -Activity "Working..." -Completed -Status "All done."
    }
}

function TestBlockingCall {
    RunAsBAT(@"
        timeout 5
"@)
}

## MAIN

if (-Not $IgnoreMain) {
    RunWithProgressBar { TestBlockingCall } $(New-Timespan -Seconds 7)
}