【问题标题】:powershell Gui progress bar with batch file带有批处理文件的powershell Gui进度条
【发布时间】:2015-06-21 20:28:40
【问题描述】:

我在另一个网站上找到了这个 gui 进度条。它解释的方式应该解决我的问题,即当作业运行时不要让 gui 冻结。

但是,由于我正在处理批处理文件(安装应用程序),我需要做一个 foreach 应用程序并一个一个地安装它们,而不是让 gui 冻结。

这里是网站的链接link

EDIT: UPDATE the SCRIPT. so far this works but all batch files install at the same time which is causing them to fail. I have more applications but for testing I just added 3. 

我只是假设 $job 没有将其状态传递给“updatescript”和“completedscript”

$Appname = @("Adobe_FlashPlayer", "Acrobat_Reader, "Microsoft_RDP_8.1")

$formJobProgress_Load={
#TODO: Initialize Form Controls here
$timer1.Interval = 1000
$timer1.Tag = 0
$timer1.Start()
[System.Windows.Forms.Application]::DoEvents()
}

$formMain_FormClosed=[System.Windows.Forms.FormClosedEventHandler]{
#Event Argument: $_ = [System.Windows.Forms.FormClosedEventArgs]
#Stop any pending jobs
Stop-JobTracker
}

$timerJobTracker_Tick={
Update-JobTracker   
}
#region Job Tracker
$JobTrackerList = New-Object System.Collections.ArrayList
function Add-JobTracker
{

Param (
    #[ValidateNotNull()]
    #[Parameter(Mandatory = $true)]
    [string]$Name,
    #[ValidateNotNull()]
    #[Parameter(Mandatory = $true)]
    [ScriptBlock]$CompletedScript,
    [ScriptBlock]$UpdateScript,
    [ScriptBlock]$JobScript,
    $ArgumentList = $null)

    $job = Start-Job -ScriptBlock $JobScript -ArgumentList $ArgumentList





if($job -ne $null)
{
    #Create a Custom Object to keep track of the Job & Script Blocks
    $psObject = New-Object System.Management.Automation.PSObject

    Add-Member -InputObject $psObject -MemberType 'NoteProperty' -Name Job      -Value $job
    Add-Member -InputObject $psObject -MemberType 'NoteProperty' -Name CompleteScript  -Value $CompletedScript
    Add-Member -InputObject $psObject -MemberType 'NoteProperty' -Name UpdateScript  -Value $UpdateScript

    [void]$JobTrackerList.Add($psObject)    

    #Start the Timer
    if(-not $timerJobTracker.Enabled)
    {
        $timerJobTracker.Start()
    }
}
elseif($CompletedScript -ne $null)
{
    #Failed
    Invoke-Command -ScriptBlock $CompletedScript -ArgumentList $null
}

}
function Update-JobTracker
{
<#
    .SYNOPSIS
        Checks the status of each job on the list.
#>

#Poll the jobs for status updates
$timerJobTracker.Stop() #Freeze the Timer

for($index =0; $index -lt $JobTrackerList.Count; $index++)
{
    $psObject = $JobTrackerList[$index]

    if($psObject -ne $null) 
    {
        if($psObject.Job -ne $null)
        {
            if($psObject.Job.State -ne "Running")
            {               
                #Call the Complete Script Block
                if($psObject.CompleteScript -ne $null)
                {
                    #$results = Receive-Job -Job $psObject.Job
                    Invoke-Command -ScriptBlock $psObject.CompleteScript -   ArgumentList $psObject.Job
                }

                $JobTrackerList.RemoveAt($index)
                Remove-Job -Job $psObject.Job
                $index-- #Step back so we don't skip a job
            }
            elseif($psObject.UpdateScript -ne $null)
            {
                #Call the Update Script Block
                Invoke-Command -ScriptBlock $psObject.UpdateScript -ArgumentList $psObject.Job
            }
        }
    }
    else
    {
        $JobTrackerList.RemoveAt($index)
        $index-- #Step back so we don't skip a job
    }
}

if($JobTrackerList.Count -gt 0)
{
    $timerJobTracker.Start()#Resume the timer   
}   
}
function Stop-JobTracker
{
<#
    .SYNOPSIS
        Stops and removes all Jobs from the list.
#>
#Stop the timer
$timerJobTracker.Stop()

#Remove all the jobs
while($JobTrackerList.Count -gt 0)
{
    $job = $JobTrackerList[0].Job
    $JobTrackerList.RemoveAt(0)
    Stop-Job $job
    Remove-Job $job
}
}#endregion


$buttonStartJob_Click= {
$progressbaroverlay1.Value = 0
$progressbaroverlay1.Step = 1
$progressbaroverlay1.Maximum = $Appname.Count
$this.Enabled = $false
$buttonStartJob.Enabled = $false

#Create a New Job using the Job Tracker
foreach ($app in $Appname)
{
    $install = "C:\pstools\Update\cmd\$app\install.cmd"
    $run = "C:\Windows\System32\cmd.exe"
    Add-JobTracker -Name "test"`
                   -JobScript {
        param (
            [string]$batchFilePath
        )
        Write-Verbose "Launching: [$batchFilePath]" -Verbose
        Set-Location $env:windir
        & ($run, $batchFilePath)

    }` -ArgumentList $install -CompletedScript {

        Param ($Job)
        #$progressbar1.Value = 100
        #Enable the Button
        $ProgressBarOverlay1.PerformStep()
        $buttonStartJob.ImageIndex = -1
        $buttonStartJob.Enabled = $true
}`
    -UpdateScript {
        Param ($Job)
        $results = Receive-Job -Job $Job | Select-Object -Last 1

        if ($results -is [int])
        {
            $progressbaroverlay1.Value = $results
        }

    }
}
}
$timer1_Tick={

    #TODO: Place custom script here

    #if ([timespan]::FromSeconds($timerUpdate.Tag) -ge     [timespan]::Fromminutes(1))
    IF($progressbaroverlay1.Value -eq 100)
    {

        $timerUpdate.stop()
        $formSampleTimer.Close()
    }
    else
    {
        [System.Windows.Forms.Application]::DoEvents()
        $label1.Text = [timespan]::FromSeconds($timer1.Tag++)
    }

}

【问题讨论】:

  • 您已经发布了多次试图获得与此相关的帮助,虽然我们已经为您提供了功能性答案,但您似乎并没有在代码中投入太多精力。上面的代码与您 5 天前提出的问题 link 的公认答案相差甚远。你太沉迷于Sapien script。我最好建议它使用内置的进度条,或者在 Sapien 论坛上寻求建议。
  • 嗨,并不是我没有尝试。相信我,我一直在尝试编写我的代码。但是我被困在这个过程中,所以这就是为什么我一直在以不同的方式询问。
  • 我实际上想出了如何调用它。现在我的问题是一切都在背靠背运行,而不是等待第一个完成。
  • 用你的半功能代码更新你的问题,我敢打赌我们可以让它正常工作。我有一种感觉,你正在使用作业,只需要一个位置合适的 Wait-Job
  • @TheMadTechnician 刚刚更新。我把整个剧本。我尝试添加 While(get-job -state 'running'){wait-job} 但这完全冻结了 GUI。感谢您的帮助。

标签: user-interface powershell batch-file


【解决方案1】:

我假设您要么从某个地方复制它并将其修改为适合您的工作,要么您使用 PowerGUI 或 Sapien 等工具为您生成脚本,因为在我看来这很难阅读代码。

如果我理解正确,您可以将 $JobTrackerList 创建为 ArrayList 对象。然后,对于您要安装的每个应用程序,您添加一个 PSCustomObject 到具有 3 个属性的 ArrayList,一个是在后台运行的 Job,一个是完成后运行的脚本,一个是用于更新进度条的脚本。所以每个对象都有一个正在运行的后台作业,这是目前的根本问题。但是等等,我们还没有完成,你有一个正在运行的计时器(实际上并没有在你给我们的代码中定义)运行,每 1000 个滴答声它就会停止,检查每个作业,如果它完成它运行“完成”您为该对象定义的脚本块,然后从ArrayList 中删除该对象。如果仍未完成,它将使用“更新”脚本块更新进度条。然后它再次启动计时器。

这是我认为需要发生的事情才能像你想要的那样工作......你需要继续制作你的ArrayList,并为你想要运行的每个应用程序添加对象,但不要创建他们所有人的工作!一旦创建了所有对象,就为第一个应用程序启动一个作业(并像以前一样将其添加为对象的成员)。然后,当您的脚本检查已完成的作业时,在它删除它正在查看的对象之前,它应该为下一个对象启动作业。

因此,这将需要对您的代码进行一些重新编写。我不确定从哪里真正开始,因为您拥有的代码与我编写它的方式非常不同,所以我给了您我的建议,现在我会让您从这里。如果您在编写代码时需要帮助(至少先尝试),请告诉我,我会看看我能做什么。可能不是今天,我累死了,也没有参加我的 A 游戏,但如果你最终需要帮助,我相信我明天可以想出办法。

编辑:好的,我想我的代码应该可以满足您的需求,并且可以使用您已有的代码:

$formJobProgress_Load={
    #TODO: Initialize Form Controls here
    $timer1.Interval = 1000
    $timer1.Tag = 0
    $timer1.Start()
    [System.Windows.Forms.Application]::DoEvents()
}

$timerJobTracker_Tick={
    $timerJobTracker.Stop() #Freeze the Timer
    for($index =0; $index -lt $JobTrackerList.Count; $index++){
        $psObject = $JobTrackerList[$index]
        if($psObject -ne $null -AND $psObject.Job -ne $null -AND $psObject.Job.State -ne "Running"){
            #Perform old 'Complete' scriptblock to update progressbar
            $ProgressBarOverlay1.PerformStep()

            #Check if this is the only job, and if not start the next one
            If($JobTrackerList.count -gt 1){
                $NextJob = $JobTrackerList[($index+1)]
                $NextJob.Job = Start-Job -ScriptBlock $NextJob.JobScript
            }
            $JobTrackerList.RemoveAt($index)
            Remove-Job -Job $psObject.Job
            $index-- #Step back so we don't skip a job
        }Else{
            $results = Receive-Job -Job $Job | Select-Object -Last 1
            if ($results -is [int]){
                $progressbaroverlay1.Value = $results
            }
        }
    }

    if($JobTrackerList.Count -gt 0){
        $timerJobTracker.Start()#Resume the timer   
    }
}

$formMain_FormClosed=[System.Windows.Forms.FormClosedEventHandler]{
    #Stop the timer
    $timerJobTracker.Stop()

    #Remove all the jobs
    while($JobTrackerList.Count -gt 0){
        $job = $JobTrackerList[0].Job
        $JobTrackerList.RemoveAt(0)
        Stop-Job $job
        Remove-Job $job
    }
}


$Appname = @("Adobe_FlashPlayer", "Acrobat_Reader", "Microsoft_RDP_8.1")

$JobTrackerList = New-Object System.Collections.ArrayList

$buttonStartJob_Click= {
    $progressbaroverlay1.Value = 0
    $progressbaroverlay1.Step = 1
    $progressbaroverlay1.Maximum = $Appname.Count
    $this.Enabled = $false
    $buttonStartJob.Enabled = $false

    ForEach($App in $Appname){

        $install = "C:\pstools\Update\cmd\$app\install.cmd"
        $run = "C:\Windows\System32\cmd.exe"
        $JobScript = {
            Write-Verbose "Launching: [$install]" -Verbose
            Set-Location $env:windir
            & $install
        }

        [void]$JobTrackerList.Add((New-Object PSObject -Property @{
            'Application' = $Name
            'JobScript' = $JobScript
            'Job' = $null
        }))
    }

    $JobTrackerList[0].Job = Start-Job -ScriptBlock $JobTrackerList[0].JobScript
}

$timer1_Tick={

    #TODO: Place custom script here

    #if ([timespan]::FromSeconds($timerUpdate.Tag) -ge     [timespan]::Fromminutes(1))
    IF($progressbaroverlay1.Value -eq 100)
    {

        $timerUpdate.stop()
        $formSampleTimer.Close()
    }
    else
    {
        [System.Windows.Forms.Application]::DoEvents()
        $label1.Text = [timespan]::FromSeconds($timer1.Tag++)
    }

}

我希望这对你有用。

【讨论】:

  • 非常感谢您的帮助。我添加了这个,但它没有启动任何应用程序。我假设脚本块不适用于索引。因为如果我终止该进程,它会要求一个“id”来停止工作。就其自身的代码而言,是的,我从另一个站点(Sapien)复制了它,并尝试为我正在尝试做的事情实现它。我分享了原帖中的链接。再次感谢您的帮助。
  • 经过所有的测试和使用你的代码。我想这又归结为我的主要问题。为什么脚本块不运行 cmd 文件?这是我想要做的第一个问题。我想我需要阅读更多关于这个的信息。无法将 install.cmd 传递给脚本块。
  • 您使用的是 Sapien,还是他们的一些代码?您的代码是否比您上面的问题中显示的更多?
  • 我正在使用 sapien 是的。但没有比上面更多的代码。
  • 我并不精通 Sapien,但它是否在某些时候在运行脚本时声明 Launching: [C:\pstools\Update\cmd\Adobe_FlashPlayer\install.cmd]?您在此处的代码看起来像是一个较大脚本的一部分,该脚本具有 GUI、按钮和进度条(除其他外)。当您在 Sapien 中运行脚本时,您看到所有这些了吗?
猜你喜欢
  • 1970-01-01
  • 2012-09-12
  • 1970-01-01
  • 2016-04-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-08
  • 1970-01-01
相关资源
最近更新 更多