Feb 042011
 

My most popular article has thus far been the post over multithreading found here. As such I felt that my audience might like to see some more entries covering that topic. After all, it is a vast topic with a lot of very advanced things you can do. In this post I would like to talk about tracking the status of your various threads. In other words, how can you see a progress of what your child script is doing?

In exploring this myself, I worked with passing custom objects back to the parent which held various status messages that the parent could read in with the “receive-job” command. That actually worked, but it took a lot of code in both the parent and the child to generate and read the status from the child threads.

While working with this, though, I found that the Job object from “get-job”, a System.Management.Automation.Job, has a property called “Progress”. I then decided to look into how this actually gets filled out. And the answer eluded my research until I managed to do it, completely by accident. As it turns out, this very useful property is filled in automatically by a “write-progress” command run within the child thread. It is actually quite logical that the progress field is filled in by “write-progress”.

So, that is enough of a background on how I came about it. Let’s actually look at some code. First,I want to create a script block to run within my child job.

## Write some code for the child threads to execute
$ThreadCode = {
    $x = 0
    $time = 200
    While ($x -lt 100){
        $x++

        Write-Progress  -Activity "Testing"
            -Status "Testing"
            -PercentComplete $X
            -completed
        Start-Sleep -Milliseconds $time
    }
    Write-Progress  -Activity "Testing"
            -Status "Testing"
            -PercentComplete 100
            -Completed
    "Annnd we're done."
}

So here you’ll notice that I am basically creating a loop that will count 1 to 100 and then close. Along the way it will fill out its progress with the “write-progress” cmdlet. The one important note is that I am adding the “-completed” parameter to the “write-progress” cmdlet. That may look like a mistake in the code,but it isn’t. If you play around with this manner of getting progress from a child you will notice that when you run a “receive-job” your host thread will read all output from the child, including flashing all of the “write-progress” bars that were in the child. If you actually read the help files for “write-progress”, you’ll see that actual description of the functionality of “-complete” states that is simply hides the output bar. We need to do this so that when the parent thread runs a “receive-job” there is no odd behavior for the end user.

## Kill any current jobs in the sessions and get rid of them
Get-Job | Stop-Job
Get-Job | Remove-Job

## Start the child job and assign to a variable to save some code later
$Job = Start-Job -ScriptBlock $ThreadCode

So for this quick snippet we are just closing an remove any jobs our current session may have, and then starting our job. I assign it to a variable so we don’t have to track it down later, not that it’s hard.

While ($Job.State -eq "Running"){
    ## Get the Child job object
    $Child = $Job.ChildJobs[0]
   
    ## Get the newest Progress Object in the child job
    $Progress = $Child.Progress[$Child.Progress.Count - 1]

    ## If the progress object is not null
    If ($Progress.Activity -ne $Null){
        Write-Progress  -Activity $Job.Name
                        -Status $Progress.StatusDescription
                        -PercentComplete $Progress.PercentComplete
                        -ID $Job.ID
    }
   
    Start-Sleep -Milliseconds 200
}

So this is the bread and butter of the parent script. It simply loops around as long as our job is running. First we need to find the child job to our parent job. I am not sure why Microsoft chose to design it like this, but each job you start actually runs commands in a child job. I have a feeling it has something to do with functionality with Windows HPC, but that is neither here nor there. Once we have our child job, we want to find the latest progress object that has been passed. After that I just check to see if the object is empty, because the first check may not have given the child thread enough time to fill this out. If there is data, then we can fill out a “write-progress” bar on our host thread. From there we just repeat until the job is done.

Get-Job | Receive-Job

At the end we just want to read anything the child script may actually have sent. So that covers it. It is really easy to get custom progress bars for a child process because there is a good, built in method to do it.

Following is a full example of a script which will launch three child threads at a time until there have been 10 opened total. It will show you the output of each thread as it completes and the progress bars produced by each. I even included a random speed that each child will complete so you can see some bells and whistles going.

## Write some code for the child threads to execute
$ThreadCode = {
    $x = 0
    $time = Get-Random -Maximum 200 -SetSeed $(Get-Date).Millisecond
    While ($x -lt 100){
        $x++

        Write-Progress -Activity "Testing" -Status "Testing" -PercentComplete $X -completed
        Start-Sleep -Milliseconds $time
    }
    Write-Progress -Activity "Testing" -Status "Testing" -PercentComplete 100 -Completed
    "Annnd we're done."
}

## Kill any current jobs in the sessions and get rid of them
Get-Job | Stop-Job
Get-Job | Remove-Job

## Loop control
$x = 0

## While we are still launching threads or wait for them to close
While (($x -lt 10) -or ($(Get-Job -State "Running").count -gt 0)){
    ## While there are less than three jobs running and less than 10 have started
    While (($(Get-Job -State "Running").count -lt 3) -and ($x -lt 10)){
        Start-Job -ScriptBlock $ThreadCode | Out-Null
        $x++
    }
   
    ## Main portion - Read all jobs
    ForEach ($Job in Get-Job) {
        ## Read all children of all jobs
        ForEach ($Child in $Job.ChildJobs){
            ## Get the latest progress object of the job
            $Progress = $Child.Progress[$Child.Progress.Count - 1]
           
            ## If there is a progress object returned write progress
            If ($Progress.Activity -ne $Null){
                Write-Progress  -Activity $Job.Name
                                -Status $Progress.StatusDescription
                                -PercentComplete $Progress.PercentComplete
                                -ID $Job.ID
            }
           
            ## If this child is complete then stop writing progress
            If ($Progress.PercentComplete -eq 100){
                Write-Progress  -Activity $Job.Name
                                -Status $Progress.StatusDescription
                                -PercentComplete $Progress.PercentComplete
                                -ID $Job.ID
                                -Complete
                ## Clear all progress entries so we don't process it again
                $Child.Progress.Clear()
            }
        }
    }
   
    Get-Job -State "Completed" | Receive-Job
   
    ## Setting for loop processing speed
    Start-Sleep -Milliseconds 200
}

Get-Job | Wait-Job | Out-Null
Get-Job | Receive-Job

  3 Responses to “Getting the Progress of a Child Thread in PowerShell”

  1. http://msmvps.com/blogs/jtoner/default.aspx

    THIS IS COOL…. YOU SHOULD DO A THREAD ON THIS

  2. great article, very nice you found out about the progress property.

  3. Multithreading, plain and simple, the way it should be! Great job Ryan, thanks.

 Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">