2016-05-19 8 views
2

Цель:

Я хочу асинхронно запускать индикатор выполнения (который показывает прошедшее время/расчетное время) в ожидании молчащих установок. Например,Как запустить индикатор выполнения асинхронно при выполнении автоматической установки?

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) 

Проблему

Хотя выше реализация работает как задумано, когда полезная нагрузка аргумент RunWithProgressBar является установить событие, которое обновляет индикатор выполнения перестает запускаться.

Что я ищу:

Как изменить свою текущую реализацию для обновления индикатор выполнения каждую секунду, даже во время выполнения установки?

+0

По какой мере вы хотите обновить индикатор выполнения? Система не может заглянуть в будущее, чтобы увидеть, когда будет завершена установка, и не узнает, какие элементы будет обрабатываться установщиком. –

+0

Вы принимаете решение как человек, основанный на том, как долго вы думаете, что это займет. Ex. Notepadd ++ должен установить в течение 5 минут. Этот скрипт будет запущен на нескольких машинах того же типа; разумные оценки легко найти с первого запуска. –

ответ

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) 
} 
2

Я чувствую, что вы пытаетесь изобретать колесо в вашей RunWithProgressBar функции:

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

Теперь Intead из 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 
    ) 
.... 
} 

Пожалуйста, обратите внимание также изменить остальную часть скрипта для читаемости.

Чтобы вызвать функцию с ProgressBar, вы должны сначала загрузить 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) 

Ваш скрипт должен работать сейчас, даже есть еще много для рефакторинга e. г. Имена функций, прицелы

Ответ на ваш комментарий:

1.) Вы можете использовать Receive-Job внутри цикла, чтобы получить выход:

$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 

2.) После казнил $initializationScriptBlock используя:

. $initializationScriptBlock 

Вы можете использовать любые функции:

& $functions.BottleOfBeer 
+0

Не совсем то, что я искал (но полезно). 2 проблемы: 1) Как можно изменить приведенное выше, чтобы показать выход задания во время работы? Возможно ли это? 2) Есть ли способ использовать функции вне (или обернуть их) свойство initializationscriptblock functions? –

+0

Да, я отредактировал свой ответ. –