The Series


Hey everyone!

Just wanted to say thank you for all the wonderful feedback I have received on the previous four articles by putting them into a module that hopefully makes the code easier to use.

Check out the full details

The Series


Alright, so we have the details lined out that we want to see. We have decent performance so we know our progress bar is not going to crash anyones system or slow our awesome sauce PowerShell scripts down. Now what?

It looks decent, how about let’s make it look good and add a few color options? Seems like a complicated task right? Enter… XAML templates.

XAML Templates

So, one of the reason I love hacking stuff together in HTML is that you can Google for 5 seconds and find a million awesome looking css frameworks that you can <LINK href=> into your page and it makes you look like you are good with colors or something like that.

The same thing exists (although not quite as prevalent) in XAML.You can spec out designs for a XAML textbox, save that as a separate xaml, reference it at the beginning of your XAML and it automagically makes copies in the styling that you defined previously (MSDN Walkthrough).

Does this mean I made my own XAML templates? Heck no. There are free ones out there that are way better than anything I could ever come up with.

If you have not heard of Material Design yet, it is a design spec published by Google you can read about here1. Basically Google sharing with us lower peeps what good looks like. Let’s take a look and see what it takes to apply ButchersBoy’s Material Design in XAML Toolkit2 to our progressbar.

Adding in the magic

Step 1 – Import DLLs

You can download the compiled DLLs for his project from the github source page here (1.68 MB). The latest build he has posted there requires .NET 4.5. It does some cool stuff but we don’t need super duper fancy for our progress bars, so I went with backwards compatibility.

Unzip the DLLs into the folder of your choice and then import them using the Import-Module cmdlet as follows (Make sure to unblock them first):

 
Import-Module .\src\MaterialDesignColors.dll -ErrorAction Stop
Import-Module .\src\MaterialDesignThemes.Wpf.dll -ErrorAction Stop
 

Step 2 - Referencing the Resource Dictionaries

Now, per ButchersBoy’s getting started guide3, we need to add the proper resource dictionaries to our prior XAML code. Resource dictionaries, in case you haven’t read the MSDN article above (and you should), are similar to css libraries in my mind. They hold the magic styling sauce for your XAML GUI.

Since my top level element is Window, I want to add the resource dictionaries into the following XML block:

                    <Window.Resources>
                        <ResourceDictionary>
                            <ResourceDictionary.MergedDictionaries>
                            
                            #####
                            [Insert dictionary references here]
                            #####
                            
                            </ResourceDictionary.MergedDictionaries>            
                        </ResourceDictionary>
                    </Window.Resources>

He has a lot of dictionaries for us to choose from. We can choose either a dark or light theme by using the following line. I picked the Dark theme:

<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Dark.xaml" />

Then we add the default xaml file in:

<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />

Then we pick our primary and accent colors. I picked blue for my primary and lightblue for my accent. You can see the full list of colors here.

<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Primary/MaterialDesignColor.Blue.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Accent/MaterialDesignColor.LightBlue.xaml" />

Lastly we add in some extra properties to the Window element as follows:

<Window [...]
        TextElement.Foreground="{DynamicResource MaterialDesignBody}"
        Background="{DynamicResource MaterialDesignPaper}"
        TextElement.FontWeight="Medium"
        TextElement.FontSize="14"
        FontFamily="pack://application:,,,/MaterialDesignThemes.Wpf;component/Resources/Roboto/#Roboto"
        [...] >

That’s it! Now re-run the script from last week and it will look like this:

Behold Material Design in PowerShell

Sweet! Now… what about one of those fancy circle progress bars? Wouldn’t that be cool? Well… turns out… now that we have our framework in place all we have to change is our progress bar XAML line to look like this:

<ProgressBar Style="{StaticResource MaterialDesignCircularProgressBar}" Height="560" Width="560" Name="ProgressBar" />

Adjust the width on your window to be 630 to fit the width of the circular progressbar and voila!

Behold Material Design Circlular Progress Bars in PowerShell

That wasn’t so bad right? Now, it’s still way too much work to edit and discover all that’s out there so with anything let’s build it into our function.

Step 3 - Building dynamic XAML with PowerShell herestrings

There are three different methods we could use for building out the necessary XAML to cover all possible variations of the xaml we have just looked at:

  1. We could statically type out all of the possibilities, save them as individual XAML and then import them using something like the following:
 
$xaml = Get-Content ".\src\$Theme_$Color_$ProgressBarStyle_$SizeVariation.xaml"
 
  1. We could use the build the string as you go method with something like the following:
 
$xaml = @"
<Window 
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
            Name="Window" Title="Progress..." WindowStartupLocation = "CenterScreen" 
            Width = "630" Height="130" SizeToContent="Height" ShowInTaskbar = "True"
            TextElement.Foreground="{DynamicResource MaterialDesignBody}"
            Background="{DynamicResource MaterialDesignPaper}"
            TextElement.FontWeight="Medium"
            TextElement.FontSize="14"
            FontFamily="pack://application:,,,/MaterialDesignThemes.Wpf;component/Resources/Roboto/#Roboto"> 
            
            <Window.Resources>
                <ResourceDictionary>
                    <ResourceDictionary.MergedDictionaries>
"@

if($Theme -eq "Dark")
{
    $xaml += ''
}
 
  1. We could use dynamically created here-strings.

How do we dynamically create a “here-string”? There are two types of here-strings (Read up on them here).

  1. Those that start with @” and end with “@
  2. Those that start with @’ and end with ‘@

The first type will expand any variables inside the here-string (More on Variable Expansion in Strings). The second will not. This means if we put an expression inside the here-string it will execute the expression and return the results directly into the string you are creating. So if I wanted to only add special properties to the window XAML element if someone uses a -MaterialDesign switch I could type a here-string like the following:

 
$syncHash.XAML = @" 
        <Window 
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
            Name="Window" Title="Progress..." WindowStartupLocation = "CenterScreen" 
            Width = "560"
            $(
                if($MaterialDesign)
                {
                    @'
                        TextElement.Foreground="{DynamicResource MaterialDesignBody}"
                        Background="{DynamicResource MaterialDesignPaper}"
                        TextElement.FontWeight="Medium"
                        TextElement.FontSize="14"
                        FontFamily="pack://application:,,,/MaterialDesignThemes.Wpf;component/Resources/Roboto/#Roboto"
    '@
                }
            )
            >
"@
 

The middle string only gets added if the switch is present. This does make the code a bit more difficult to read but, for me at least, speeds up the development process and is a little bit of a personal preference.

I was able to code out a full function New-ProgressBar in a pretty short time that gives all the possible options and I think it is decently maintainable as long as you know how here-strings work.

Check it out!

Behold Material Design Circlular Progress Bars in PowerShell

The full code is getting pretty long to post right here so I threw it up on GitHub to download.

Demo

Don’t forget to change the file path of the Import-Module function in the New-ProgressBar function to match the location of your downloaded DLLs (Reference Step 1 of this blog posting) otherwise it will default to using the basic styling.

 
$Files = dir $env:USERPROFILE -Recurse


$ProgressBars = @()

$ProgressBars += New-ProgressBar -MaterialDesign -IsIndeterminate $true -Type Circle -PrimaryColor Green -AccentColor DeepPurple -Size Large -Theme Dark
$ProgressBars += New-ProgressBar -MaterialDesign -Type Horizontal -PrimaryColor Red -AccentColor LightBlue -Size Medium -Theme Light
$ProgressBars += New-ProgressBar -MaterialDesign -Type Vertical -PrimaryColor Amber -AccentColor DeepOrange -Size Large -Theme Dark
$ProgressBars += New-ProgressBar -MaterialDesign -Type Circle -PrimaryColor Blue -AccentColor Cyan -Size Small -Theme Light

Start-Sleep -Seconds 2

$i = 0
foreach ($File in $Files) { 
                    $i++
                    Start-Sleep -Milliseconds 2
                    foreach ($ProgressBar in $ProgressBars) {
    
    
                        Write-ProgressBar `
                                -ProgressBar $ProgressBar `
                                -Activity "Viewing Files" `
                                -PercentComplete (($i/$Files.count) * 100) `
                                -CurrentOperation $File.FullName `
                                -Status $File.Name `
                                -SecondsRemaining ($Files.Count - $i)

                    }

                }
 
  1. https://www.google.com/design/spec/material-design/introduction.html 

  2. http://materialdesigninxaml.net/ 

  3. https://github.com/ButchersBoy/MaterialDesignInXamlToolkit/wiki/Getting-Started 

The Series


Okay, so we have a progress bar that shows percentage. Big whoop, who cares!? I need all the details! I need to show all the things! Well…. the time has come. Behold, details being added…

Easy Peasy

At this point, in order to add in a bit of details we need to do the following tasks:

  1. Add a textblock to our XAML underneath the plain old progress bar
  2. Add an update call to the update block triggered by the timer in our previous posting.
  3. Add if statements to our Write-ProgressBar cmdlet

The typical Write-Progress cmdlet takes the following three parameters and mushes them together, in order, underneath your progressbar:

  1. Status
  2. Seconds Remaining
  3. CurrentOperation

On the cmdlet side it helps to have these split out to make calling the cmdlet easier. On the UI side though it just complicates things unnecessarily, so I simply added a single variable to my New-ProgressBar cmdlet called AdditionalInfo on my synchash:

 
$syncHash.AdditionalInfo = ''
 

We add the following textblock code to our XAML to create the label underneath the progress bar:

 

 

Making all three variables squished into one makes the addition to my update block a single line of code:

 
$SyncHash.AdditionalInfoTextBlock.Text = $SyncHash.AdditionalInfo
 

Now I add in the addtional parameters to the param section of my Write-ProgressBar cmdlet as follows:

 
        [String]$Status = $Null,
        [int]$SecondsRemaining = $Null,
        [String]$CurrentOperation = $Null
 

My if statements on whether or not to add these parameters and combining them with spacing into the AdditionalInfo paramater looks like this:

 
if($SecondsRemaining)
   {

       [String]$SecondsRemaining = "$SecondsRemaining Seconds Remaining"

   }
   else
   {

       [String]$SecondsRemaining = $Null

   }

   Write-Verbose -Message "Setting AdditionalInfo to $Status       $SecondsRemaining$(if($SecondsRemaining){ " seconds remaining..." }else {''})       $CurrentOperation"
   $ProgressBar.AdditionalInfo = "$Status       $SecondsRemaining       $CurrentOperation"

 

Now if I run the demo provided at the end of this article we get the following:

Give me alllll the deets!

Nice eh!?

Full Code

 
Function New-ProgressBar {
 
    [void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework') 
    $syncHash = [hashtable]::Synchronized(@{})
    $newRunspace =[runspacefactory]::CreateRunspace()
    $syncHash.Runspace = $newRunspace
    $syncHash.AdditionalInfo = ''
    $newRunspace.ApartmentState = "STA" 
    $newRunspace.ThreadOptions = "ReuseThread"           
    $data = $newRunspace.Open() | Out-Null
    $newRunspace.SessionStateProxy.SetVariable("syncHash",$syncHash)           
    $PowerShellCommand = [PowerShell]::Create().AddScript({    
        [string]$xaml = @" 
        <Window 
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
            Name="Window" Title="Progress..." WindowStartupLocation = "CenterScreen" 
            Width = "560" Height="130" SizeToContent="Height" ShowInTaskbar = "True"> 
            <StackPanel Margin="20">
               <ProgressBar Width="560" Name="ProgressBar" />
               <TextBlock Text="{Binding ElementName=ProgressBar, Path=Value, StringFormat={}{0:0}%}" HorizontalAlignment="Center" VerticalAlignment="Center" />
               <TextBlock Name="AdditionalInfoTextBlock" Text="" HorizontalAlignment="Center" VerticalAlignment="Center" />
            </StackPanel> 
        </Window>
"@ 
   
        $syncHash.Window=[Windows.Markup.XamlReader]::parse( $xaml ) 
        #===========================================================================
        # Store Form Objects In PowerShell
        #===========================================================================
        ([xml]$xaml).SelectNodes("//*[@Name]") | %{ $SyncHash."$($_.Name)" = $SyncHash.Window.FindName($_.Name)}

        $updateBlock = {            
            
            $SyncHash.Window.Title = $SyncHash.Activity
            $SyncHash.ProgressBar.Value = $SyncHash.PercentComplete
            $SyncHash.AdditionalInfoTextBlock.Text = $SyncHash.AdditionalInfo
            #$SyncHash.Window.MinWidth = $SyncHash.Window.ActualWidth
                       
        }

        ############### New Blog ##############
        $syncHash.Window.Add_SourceInitialized( {            
            ## Before the window's even displayed ...            
            ## We'll create a timer            
            $timer = new-object System.Windows.Threading.DispatcherTimer            
            ## Which will fire 4 times every second            
            $timer.Interval = [TimeSpan]"0:0:0.01"            
            ## And will invoke the $updateBlock            
            $timer.Add_Tick( $updateBlock )            
            ## Now start the timer running            
            $timer.Start()            
            if( $timer.IsEnabled ) {            
               Write-Host "Clock is running. Don't forget: RIGHT-CLICK to close it."            
            } else {            
               $clock.Close()            
               Write-Error "Timer didn't start"            
            }            
        } )

        $syncHash.Window.ShowDialog() | Out-Null 
        $syncHash.Error = $Error 

    }) 
    $PowerShellCommand.Runspace = $newRunspace 
    $data = $PowerShellCommand.BeginInvoke() 
   
    
    Register-ObjectEvent -InputObject $SyncHash.Runspace `
            -EventName 'AvailabilityChanged' `
            -Action { 
                
                    if($Sender.RunspaceAvailability -eq "Available")
                    {
                        $Sender.Closeasync()
                        $Sender.Dispose()
                    } 
                
                } | Out-Null

    return $syncHash

}

function Write-ProgressBar
{

    Param (
        [Parameter(Mandatory=$true)]
        $ProgressBar,
        [Parameter(Mandatory=$true)]
        [String]$Activity,
        [int]$PercentComplete,
        [String]$Status = $Null,
        [int]$SecondsRemaining = $Null,
        [String]$CurrentOperation = $Null
    ) 
   
   Write-Verbose -Message "Setting activity to $Activity"
   $ProgressBar.Activity = $Activity

   if($PercentComplete)
   {
       
       Write-Verbose -Message "Setting PercentComplete to $PercentComplete"
       $ProgressBar.PercentComplete = $PercentComplete

   }
   
   if($SecondsRemaining)
   {

       [String]$SecondsRemaining = "$SecondsRemaining Seconds Remaining"

   }
   else
   {

       [String]$SecondsRemaining = $Null

   }

   Write-Verbose -Message "Setting AdditionalInfo to $Status       $SecondsRemaining$(if($SecondsRemaining){ " seconds remaining..." }else {''})       $CurrentOperation"
   $ProgressBar.AdditionalInfo = "$Status       $SecondsRemaining       $CurrentOperation"

}

function Close-ProgressBar
{

    Param (
        [Parameter(Mandatory=$true)]
        [System.Object[]]$ProgressBar
    )

    $ProgressBar.Window.Dispatcher.Invoke([action]{ 
      
      $ProgressBar.Window.close()

    }, "Normal")
 
}
 

Demo

 

$ProgressBar = New-ProgressBar

Measure-Command -Expression {
    $Files = dir $env:USERPROFILE -Recurse
    $i = 0
    $Files | foreach {
    
                        $i++
                        Start-Sleep -Milliseconds 10
    
                        Write-ProgressBar `
                                -ProgressBar $ProgressBar `
                                -Activity "Viewing Files" `
                                -PercentComplete (($i/$Files.count) * 100) `
                                -CurrentOperation $_.FullName `
                                -Status $_.Name `
                                -SecondsRemaining (100 - $_.count)
                     }
}

 

The Series


Welcome back for more fun with PowerShell and XAML ProgressBars! Today we are going to tackle performance tuning all on it’s own.

The Problem

If you attempted to run the demo at the end of PowerShell ProgressBar – Part 1, and you were sneaky enough to remove my Start-Sleep cmdlet you may have noticed that the performance is AWFUL!

Here are the stats just running the following progress bar demo code (Start-Sleep removed):

 
1..100 | foreach {Write-ProgressBar -ProgressBar $ProgressBar -Activity "Counting $_ out of 100" -PercentComplete $_ }
 
50 seconds to count to 100!

50 seconds just to count to 100 is a lot of overhead to a script just to add a progress bar. In most cases end users want speed over shininess so we are going to need to fix this.

##The Solution Doing a quick Google search turned up the fact that other people had experienced the same issue when using dispatcher.invoke1 it turns out this is because dispatcher.invoke is thread blocking (i.e. your script has to wait for the invoke function, which has quite a bit of overhead, to complete before it will continue).

Turns out the there are a few other non-threadblocking methods for updating your GUI. I chose to use something called the dispatchertimer because of this dandy article2 and a sweet simple demo from Richard Siddaway3.

What the dispatchertimer allows us to do is tell our GUI to run some code on a set interval. So, I want my GUI to update every ten milliseconds to reflect the properties on my $Synchash and this should dramatically increase our performance.

So, to walk it through. We added a property for the activity and a property to for the percentcomplete to our $Synchash

 
$syncHash.Activity = ''
$syncHash.PercentComplete = 0
 

We then created a scriptblock to that we wanted to run at the set interval which will update the GUI.

 
$updateBlock = {            
            
    $SyncHash.Window.Title = $SyncHash.Activity
    $SyncHash.ProgressBar.Value = $SyncHash.PercentComplete
                       
 }
 

Then we will create the dispatchtimer which will call the code and set the interval to be 10 milliseconds.

 

$syncHash.Window.Add_SourceInitialized( {            
            ## Before the window's even displayed ...            
            ## We'll create a timer            
            $timer = new-object System.Windows.Threading.DispatcherTimer            
            ## Which will fire 4 times every second            
            $timer.Interval = [TimeSpan]"0:0:0.01"            
            ## And will invoke the $updateBlock            
            $timer.Add_Tick( $updateBlock )            
            ## Now start the timer running            
            $timer.Start()            
            if( $timer.IsEnabled ) {            
               Write-Host "Clock is running. Don't forget: RIGHT-CLICK to close it."            
            } else {            
               $clock.Close()            
               Write-Error "Timer didn't start"            
            }            
  } )

 

This makes my Write-ProgressBar cmdlet as simple as changing a property on the variable.

 

function Write-ProgressBar
{

    Param (
        [Parameter(Mandatory=$true)]
        $ProgressBar,
        [Parameter(Mandatory=$true)]
        [String]$Activity,
        [int]$PercentComplete
    ) 
   
   $ProgressBar.Activity = $Activity

   if($PercentComplete)
   {
      
       $ProgressBar.PercentComplete = $PercentComplete

   }

}

 

The end results in performance are… Drumroll please!!!

68 milliseconds to complete the same code!

##Full Code

 

Function New-ProgressBar {
 
    [void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework') 
    $syncHash = [hashtable]::Synchronized(@{})
    $newRunspace =[runspacefactory]::CreateRunspace()
    $syncHash.Runspace = $newRunspace
    $syncHash.Activity = ''
    $syncHash.PercentComplete = 0
    $newRunspace.ApartmentState = "STA" 
    $newRunspace.ThreadOptions = "ReuseThread"           
    $data = $newRunspace.Open() | Out-Null
    $newRunspace.SessionStateProxy.SetVariable("syncHash",$syncHash)           
    $PowerShellCommand = [PowerShell]::Create().AddScript({    
        [xml]$xaml = @" 
         
            
               
               
             
         
"@ 
  
        $reader=(New-Object System.Xml.XmlNodeReader $xaml) 
        $syncHash.Window=[Windows.Markup.XamlReader]::Load( $reader ) 
        #===========================================================================
        # Store Form Objects In PowerShell
        #===========================================================================
        $xaml.SelectNodes("//*[@Name]") | %{ $SyncHash."$($_.Name)" = $SyncHash.Window.FindName($_.Name)}

        $updateBlock = {            
            
            $SyncHash.Window.Title = $SyncHash.Activity
            $SyncHash.ProgressBar.Value = $SyncHash.PercentComplete
                       
        }

        ############### New Blog ##############
        $syncHash.Window.Add_SourceInitialized( {            
            ## Before the window's even displayed ...            
            ## We'll create a timer            
            $timer = new-object System.Windows.Threading.DispatcherTimer            
            ## Which will fire 4 times every second            
            $timer.Interval = [TimeSpan]"0:0:0.01"            
            ## And will invoke the $updateBlock            
            $timer.Add_Tick( $updateBlock )            
            ## Now start the timer running            
            $timer.Start()            
            if( $timer.IsEnabled ) {            
               Write-Host "Clock is running. Don't forget: RIGHT-CLICK to close it."            
            } else {            
               $clock.Close()            
               Write-Error "Timer didn't start"            
            }            
        } )

        $syncHash.Window.ShowDialog() | Out-Null 
        $syncHash.Error = $Error 

    }) 
    $PowerShellCommand.Runspace = $newRunspace 
    $data = $PowerShellCommand.BeginInvoke() 
   
    
    Register-ObjectEvent -InputObject $SyncHash.Runspace `
            -EventName 'AvailabilityChanged' `
            -Action { 
                
                    if($Sender.RunspaceAvailability -eq "Available")
                    {
                        $Sender.Closeasync()
                        $Sender.Dispose()
                    } 
                
                } | Out-Null

    return $syncHash

}
 

function Write-ProgressBar
{

    Param (
        [Parameter(Mandatory=$true)]
        $ProgressBar,
        [Parameter(Mandatory=$true)]
        [String]$Activity,
        [int]$PercentComplete
    ) 
   
   $ProgressBar.Activity = $Activity

   if($PercentComplete)
   {
      
       $ProgressBar.PercentComplete = $PercentComplete

   }

}

function Close-ProgressBar
{

    Param (
        [Parameter(Mandatory=$true)]
        [System.Object[]]$ProgressBar
    )

    $ProgressBar.Window.Dispatcher.Invoke([action]{ 
      
      $ProgressBar.Window.close()

    }, "Normal")
 
}

 

Demo

 
 #Put a Start-Sleep back in if you actually want to see the progress bar up.
$ProgressBar = New-ProgressBar
Measure-Command -Expression {
    1..100 | foreach {Write-ProgressBar -ProgressBar $ProgressBar -Activity "Counting $_ out of 100" -PercentComplete $_}
}
Close-ProgressBar $ProgressBar

 

The Series


Making PowerShell progress bars has always been a handy feature of PowerShell. End users and admins alike love to see that bar progressing just so they know the script is going somewhere. Write-Progress is a great tool built into Windows but sometimes you want to hide that black scary screen and surface a beautiful shiny bar of progress that will make your end user oooo and ahhhhh and your PowerShell prowess.

What I am going to do over the next few posts is create a decent PowerShell progress bar that will have the following feautures:

  1. Update asynchronous from the script (credit goes to Boe Prox1 and Rhys W Edwards 2)
  2. Awesome styles (Material Design and MahApps)

The Basics

This module will be made up of three cmdlets:

  1. New-ProgressBar - Used to create a ProgressBar variable attached to progressbar in separaterunspace. Select styling.
  2. Write-ProgressBar - Used to send progress events to the ProgressBar. Attempts to modify the variable directly will fail due to runspace security
  3. Close-ProgressBar - Used to close out the ProgressBar safely and cleanly to prevent memory issues.

New-ProgressBar

Let’s start with implementing the New-ProgressBar cmdlet and see what all that entails. I strongly recommend reading the referenced article from Boe Prox on managing runspaces before continuing (It’s short).

In order to have the progress bar run without interrupting the current process we need to create it in a separate runspace.


    $SyncHash = [hashtable]::Synchronized(@{})
    $newRunspace =[runspacefactory]::CreateRunspace()
    $SyncHash.Runspace = $newRunspace
    $newRunspace.ApartmentState = "STA" 
    $newRunspace.ThreadOptions = "ReuseThread"           
    $newRunspace.Open() 
    $newRunspace.SessionStateProxy.SetVariable("syncHash",$syncHash)

The $SyncHash variable is going to be used to manage the progress bar from the current thread as we’ll see later in the Write-Progress cmdlet.

The next step is to create the command that will run in this alternate runspace which creates a very basic xaml progress bar and adds the elements of the progressbar to the $SyncHash for later modificiation.


    $PowerShellCommand = [PowerShell]::Create().AddScript({    
        [xml]$xaml = @" 
         
            
               
               
             
         
"@ 
  
        $reader=(New-Object System.Xml.XmlNodeReader $xaml) 
        $syncHash.Window=[Windows.Markup.XamlReader]::Load( $reader ) 
        #===========================================================================
        # Store Form Objects In PowerShell
        #===========================================================================
        $xaml.SelectNodes("//*[@Name]") | %{ $SyncHash."$($_.Name)" = $SyncHash.Window.FindName($_.Name)}


        $syncHash.Window.ShowDialog() | Out-Null 
        $syncHash.Error = $Error 

    }) 
    $PowerShellCommand.Runspace = $newRunspace 
    $data = $PowerShellCommand.BeginInvoke()

At this point we should have a progress bar running asynchronously in a separate runspace. Now all we have to do is wrap this in a function and return the $SyncHash as the result for future modification.

I did, however, just in case someone sent it to the pipeline without storing it in a variable a sort of safeguard to close the runspace if it became orphaned or the progress bar was closed.


Register-ObjectEvent -InputObject $SyncHash.Runspace `
            -EventName 'AvailabilityChanged' `
            -Action { 
                
                    if($Sender.RunspaceAvailability -eq "Available")
                    {
                        $Sender.Closeasync()
                        $Sender.Dispose()
                    } 
                
                }

This will basically listen for when the availability (typically busy while the progress bar is running) to change and if it is Available go ahead and close out the runspace and dispose it.

The full function appears here:


Function New-ProgressBar {
 
    [void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework') 
    $syncHash = [hashtable]::Synchronized(@{})
    $newRunspace =[runspacefactory]::CreateRunspace()
    $syncHash.Runspace = $newRunspace
    $newRunspace.ApartmentState = "STA" 
    $newRunspace.ThreadOptions = "ReuseThread"           
    $newRunspace.Open() 
    $newRunspace.SessionStateProxy.SetVariable("syncHash",$syncHash)           
    $PowerShellCommand = [PowerShell]::Create().AddScript({    
        [xml]$xaml = @" 
         
            
               
               
             
         
"@ 
  
        $reader=(New-Object System.Xml.XmlNodeReader $xaml) 
        $syncHash.Window=[Windows.Markup.XamlReader]::Load( $reader ) 
        #===========================================================================
        # Store Form Objects In PowerShell
        #===========================================================================
        $xaml.SelectNodes("//*[@Name]") | %{ $SyncHash."$($_.Name)" = $SyncHash.Window.FindName($_.Name)}


        $syncHash.Window.ShowDialog() | Out-Null 
        $syncHash.Error = $Error 

    }) 
    $PowerShellCommand.Runspace = $newRunspace 
    $data = $PowerShellCommand.BeginInvoke() 
   
    
    Register-ObjectEvent -InputObject $SyncHash.Runspace `
            -EventName 'AvailabilityChanged' `
            -Action { 
                
                    if($Sender.RunspaceAvailability -eq "Available")
                    {
                        $Sender.Closeasync()
                        $Sender.Dispose()
                    } 
                
                } 

    return $SyncHash

}

Write-ProgressBar

Now to build our starting Write-ProgressBar function. To start out we aren’t going to want to mess with re-creating every functionality of Write-Progress, so we are just going to add the ability to pass in an updated Activity which will update the title of the progress bar window and PercentComplete.

If you didn’t read Boe’s article you may have already attempted to update the progressbar using the $SyncHash Variable. This will sadly not work. Something about security or something. So what we are going to do is modify the properties in the second runspace by using the dispatcher which is exposed in our $SyncHash variable.


function Write-ProgressBar
{

    Param (
        [Parameter(Mandatory=$true)]
        [System.Object[]]$ProgressBar,
        [Parameter(Mandatory=$true)]
        [String]$Activity,
        [int]$PercentComplete
    ) 
   
   # This updates the control based on the parameters passed to the function 
   $ProgressBar.Window.Dispatcher.Invoke([action]{ 
      
      $ProgressBar.Window.Title = $Activity

   }, "Normal")

   if($PercentComplete)
   {

       $ProgressBar.Window.Dispatcher.Invoke([action]{ 
      
          $ProgressBar.ProgressBar.Value = $PercentComplete

       }, "Normal")

   }

}

Close-ProgressBar

The Close-ProgressBar function is going to be very similar.


function Close-ProgressBar
{

    Param (
        [Parameter(Mandatory=$true)]
        [System.Object[]]$ProgressBar
    )

    $ProgressBar.Window.Dispatcher.Invoke([action]{ 
      
      $ProgressBar.Window.Close()

    }, "Normal")
 
}

Hope you enjoy! The next posting we will get into replicating the exact functionality of the write-progress function as well as dealing with some of the performance issues you will see when running the below demo. The third we will get into styling our progress bars.

Below is the full code so far and a demo:

Full Code



# Function to facilitate updates to controls within the window 
Function New-ProgressBar {
 
    [void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework') 
    $syncHash = [hashtable]::Synchronized(@{})
    $newRunspace =[runspacefactory]::CreateRunspace()
    $syncHash.Runspace = $newRunspace
    $newRunspace.ApartmentState = "STA" 
    $newRunspace.ThreadOptions = "ReuseThread"           
    $newRunspace.Open() 
    $newRunspace.SessionStateProxy.SetVariable("syncHash",$syncHash)           
    $PowerShellCommand = [PowerShell]::Create().AddScript({    
        [xml]$xaml = @" 
         
            
               
               
             
         
"@ 
  
        $reader=(New-Object System.Xml.XmlNodeReader $xaml) 
        $syncHash.Window=[Windows.Markup.XamlReader]::Load( $reader ) 
        #===========================================================================
        # Store Form Objects In PowerShell
        #===========================================================================
        $xaml.SelectNodes("//*[@Name]") | %{ $SyncHash."$($_.Name)" = $SyncHash.Window.FindName($_.Name)}


        $syncHash.Window.ShowDialog() | Out-Null 
        $syncHash.Error = $Error 

    }) 
    $PowerShellCommand.Runspace = $newRunspace 
    $data = $PowerShellCommand.BeginInvoke() 
   
    
    Register-ObjectEvent -InputObject $SyncHash.Runspace `
            -EventName 'AvailabilityChanged' `
            -Action { 
                
                    if($Sender.RunspaceAvailability -eq "Available")
                    {
                        $Sender.Closeasync()
                        $Sender.Dispose()
                    } 
                
                } 

    return [System.Collections.Hashtable]$SyncHash

}
 

function Write-ProgressBar
{

    Param (
        [Parameter(Mandatory=$true)]
        [System.Object[]]$ProgressBar,
        [Parameter(Mandatory=$true)]
        [String]$Activity,
        [String]$Status,
        [int]$Id,
        [int]$PercentComplete,
        [int]$SecondsRemaining,
        [String]$CurrentOperation,
        [int]$ParentId,
        [Switch]$Completed,
        [int]$SourceID
    ) 
   
   # This updates the control based on the parameters passed to the function 
   $ProgressBar.Window.Dispatcher.Invoke([action]{ 
      
      $ProgressBar.Window.Title = $Activity

   }, "Normal")

   if($PercentComplete)
   {

       $ProgressBar.Window.Dispatcher.Invoke([action]{ 
      
          $ProgressBar.ProgressBar.Value = $PercentComplete

       }, "Normal")

   }

}


function Close-ProgressBar
{

    Param (
        [Parameter(Mandatory=$true)]
        [System.Object[]]$ProgressBar
    )

    $ProgressBar.Window.Dispatcher.Invoke([action]{ 
      
      $ProgressBar.Window.Close()

    }, "Normal")
 
}

Demo

Run this after creating the above functions


$ProgressBar = New-ProgressBar

1..100 | foreach {Write-ProgressBar -ProgressBar $ProgressBar -Activity "Counting $_ out of 100" -PercentComplete $_; Start-Sleep -Milliseconds 250}

Close-ProgressBar $ProgressBar