Office Click-To-Run and XML Files

So, it used to be that we would install Office using a batch script that would invoke a setup.exe, assign a specific /configure flag and manually assign a specific XML file that contained the product that we wanted to install. This was bulky. It got too bulky when we needed to install 32-bit and 64-bit versions.

TIME FOR A CHANGE!

This is when I started thinking: “wow I really hate batch. I’m really glad I’m not the one that had to write this old script. Lets PowerShell this shit!”

First I needed a template XML file to modify, So this is what that looks like:

<Configuration>
  <Add OfficeClientEdition="64" Channel="Current">
    <Product ID="O365BusinessRetail">
      <Language ID="MatchOS" />
    </Product>
  </Add>
</Configuration>

This is the file that we will edit to say which product we want installing also if we want 64-bit or 32-bit.

Next, I needed to create a PowerShell script that would take a user’s input, edit the XML file accordingly and start the setup.exe with this flag. I also needed the bit-version that they wanted.

I started by defining the variables I would need for the script:

#Variables used for the installation
$bitVersion = ""
$officeProduct = ""
$pathToOffice = "\\path\to\office\folder"
$xmlFile = "OfficeXML.xml"
$pathToXMLFile = Join-Path -Path $pathToOffice -ChildPath $xmlFile

Then I created a function I would use to update the XML file. I needed two parameters, the product that they wanted installing and the bit version they wanted:

#Updates the XML file based on the input
function Update-XMLFile([string]$product, [string]$bit){

    try{
        #Loading the XML document
        [xml]$xmlDoc = Get-Content -Path $pathToXMLFile

        #Edit the document
        $xmlDoc.Configuration.Add.OfficeClientEdition = $bit
        $xmlDoc.Configuration.Add.Product.ID = $product

        #Save the document
        $xmlDoc.Save($pathToXMLFile)
    }catch{
        $errorMessage = $_.Exception.Message
        Write-Host $errorMessage -ForegroundColor Red
        Read-Host "The script encountered the above error - will now exit"
    }
}

I then created another function to start the installation. This also required two parameters, the bit version and the XML file name

#Function to start the installation
function Start-Installation([string]$bit, [string]$xmlName){
    try{
        .\setup.exe /configure $bit\$xmlName
    }catch{
        $errorMessage = $_.Exception.Message
        Write-Host $errorMessage
        Read-Host "The script encountered the above error - will now exit"
    }
}

My final function was a verification test. Since we want to only use 64-bit for future installations, I had to make sure that whoever was using the script knew this and would be competent enough to do a little bit of math:

#Function to check the user wants 32 bit
function Get-Verification(){
    $output = $false

    Write-Host "Are you sure you want to install 32-bit?" -ForegroundColor Red
    Write-Host "All new installs should use 64-bit instead"
    Write-Host "If you want to install 32-bit, complete the test below, otherwise enter the wrong answer"

    $firstNumber = Get-Random -Minimum 1 -Maximum 11
    $secondNumber = Get-Random -Minimum 1 -Maximum 11

    $sumToCheck = $firstNumber + $secondNumber

    $verificationInput = Read-Host "$($firstNumber) + $($secondNumber) = ?"

    if ($verificationInput -eq $sumToCheck){
        Write-Host "Fine! 32-bit will be installed..."
        $output = $true
    }else{
        Write-Host "Finally! 64-bit will be installed"
        $output = $false
    }
    return $output
}

Now that all my functions were defined, I could start with the actual meat of the script. This included cleaning the screen, asking the user some questions, launching the 32-bit verification is needed, updating the XML file using a switch statement and finally kicking off the installation. Heres what that looked like:

#Clear the screen
Clear-Host

#region Checking if the user wants 64 bit or 32 bit

do{

    Write-Host "Do you want" -NoNewline
    Write-Host " 64-bit " -NoNewline -ForegroundColor Yellow
    Write-Host "or" -NoNewline
    Write-Host " 32-bit " -NoNewline -ForegroundColor Green
    Write-Host "? (64 or 32): " -NoNewline
    $bitVersionInput = (Read-Host).ToUpper()
}while((64 ,32) -notcontains $bitVersionInput)

#endregion

#Check the user definitely wants 32 bit
if ($bitVersionInput -eq "32"){
    if (Get-Verification){
        $bitVersion = $bitVersionInput
    }else{
        $bitVersionInput = "64"
    }
}

#Update the bitVersion variable
$bitVersion = $bitVersionInput

#region Asking what product to install

#Ask the user what product they want to install
Write-Host @"

Please select one product from the below list

"@

Write-Host @"
1) Business Retail
2) ProPlus Retail

"@ -ForegroundColor Cyan

Write-Host @"
3) Visio Std Volume
4) Visio Pro Volume
5) Visio Pro Retail

"@ -ForegroundColor Green

Write-Host @"
6) Project Std Volume
7) Project Pro Volume
8) Project Pro Retail

"@ -ForegroundColor Gray

Write-Host @"
C) Cancel

"@ -ForegroundColor Red

do{
    $officeProductInput = (Read-Host "Enter a number").ToUpper()
}while((1,2,3,4,5,6,7,8, "C") -notcontains $officeProductInput)

#endregion

#Update the product variable
$officeProduct = $officeProductInput

#region Switch the input to see what it is and perform the required operation

switch($officeProduct){
    
    #Business Retail
    1 { Update-XMLFile -product "O365BusinessRetail" -bit $bitVersion}
    #ProPlus
    2 { Update-XMLFile -product "O365ProPlusRetail" -bit $bitVersion}
    #Visio Std Volume
    3 { Update-XMLFile -product "VisioStd2019Volume" -bit $bitVersion}
    #Visio Pro Volume
    4 { Update-XMLFile -product "VisioPro2019Volume" -bit $bitVersion}
    #Visio Pro Retail
    5 { Update-XMLFile -product "VisioPro2019Retail" -bit $bitVersion}
    #Project Std Volume
    6 { Update-XMLFile -product "ProjectStd2019Volume" -bit $bitVersion}
    #Project Pro Volume
    7 { Update-XMLFile -product "ProjectPro2019Volume" -bit $bitVersion}
    #Project Pro Retail
    8 { Update-XMLFile -product "ProjectPro2019Retail" -bit $bitVersion}
    #Cancel
    "C" {Exit}
    default {Exit}
}

#endregion

#Start the installation
Write-Host "Installing..." -ForegroundColor Green
Start-Installation -bit $bitVersion -xmlName $xmlFile
Write-Host "This window can be closed"
Read-Host

Done!

If you’re wondering what the script looks like as a whole, wonder no longer:

#Variables used for the installation
$bitVersion = ""
$officeProduct = ""
$pathToOffice = "\\sandpdc\software\Office"
$xmlFile = "OfficeXML.xml"
$pathToXMLFile = Join-Path -Path $pathToOffice -ChildPath $xmlFile

#Updates the XML file based on the input
function Update-XMLFile([string]$product, [string]$bit){

    try{
        #Loading the XML document
        [xml]$xmlDoc = Get-Content -Path $pathToXMLFile

        #Edit the document
        $xmlDoc.Configuration.Add.OfficeClientEdition = $bit
        $xmlDoc.Configuration.Add.Product.ID = $product

        #Save the document
        $xmlDoc.Save($pathToXMLFile)
    }catch{
        $errorMessage = $_.Exception.Message
        Write-Host $errorMessage -ForegroundColor Red
        Read-Host "The script encountered the above error - will now exit"
    }
}

#Function to start the installation
function Start-Installation([string]$bit, [string]$xmlName){
    try{
        .\setup.exe /configure $bit\$xmlName
    }catch{
        $errorMessage = $_.Exception.Message
        Write-Host $errorMessage
        Read-Host "The script encountered the above error - will now exit"
    }
}

#Function to check the user wants 32 bit
function Get-Verification(){
    $output = $false

    Write-Host "Are you sure you want to install 32-bit?" -ForegroundColor Red
    Write-Host "All new installs should use 64-bit instead"
    Write-Host "If you want to install 32-bit, complete the test below, otherwise enter the wrong answer"

    $firstNumber = Get-Random -Minimum 1 -Maximum 11
    $secondNumber = Get-Random -Minimum 1 -Maximum 11

    $sumToCheck = $firstNumber + $secondNumber

    $verificationInput = Read-Host "$($firstNumber) + $($secondNumber) = ?"

    if ($verificationInput -eq $sumToCheck){
        Write-Host "Fine! 32-bit will be installed..."
        $output = $true
    }else{
        Write-Host "Finally! 64-bit will be installed"
        $output = $false
    }
    return $output
}

#Clear the screen
Clear-Host

#region Checking if the user wants 64 bit or 32 bit

do{

    Write-Host "Do you want" -NoNewline
    Write-Host " 64-bit " -NoNewline -ForegroundColor Yellow
    Write-Host "or" -NoNewline
    Write-Host " 32-bit " -NoNewline -ForegroundColor Green
    Write-Host "? (64 or 32): " -NoNewline
    $bitVersionInput = (Read-Host).ToUpper()
}while((64 ,32) -notcontains $bitVersionInput)

#endregion

#Check the user definitely wants 32 bit
if ($bitVersionInput -eq "32"){
    if (Get-Verification){
        $bitVersion = $bitVersionInput
    }else{
        $bitVersionInput = "64"
    }
}

#Update the bitVersion variable
$bitVersion = $bitVersionInput

#region Asking what product to install

#Ask the user what product they want to install
Write-Host @"

Please select one product from the below list

"@

Write-Host @"
1) Business Retail
2) ProPlus Retail

"@ -ForegroundColor Cyan

Write-Host @"
3) Visio Std Volume
4) Visio Pro Volume
5) Visio Pro Retail

"@ -ForegroundColor Green

Write-Host @"
6) Project Std Volume
7) Project Pro Volume
8) Project Pro Retail

"@ -ForegroundColor Gray

Write-Host @"
C) Cancel

"@ -ForegroundColor Red

do{
    $officeProductInput = (Read-Host "Enter a number").ToUpper()
}while((1,2,3,4,5,6,7,8, "C") -notcontains $officeProductInput)

#endregion

#Update the product variable
$officeProduct = $officeProductInput

#region Switch the input to see what it is and perform the required operation

switch($officeProduct){
    
    #Business Retail
    1 { Update-XMLFile -product "O365BusinessRetail" -bit $bitVersion}
    #ProPlus
    2 { Update-XMLFile -product "O365ProPlusRetail" -bit $bitVersion}
    #Visio Std Volume
    3 { Update-XMLFile -product "VisioStd2019Volume" -bit $bitVersion}
    #Visio Pro Volume
    4 { Update-XMLFile -product "VisioPro2019Volume" -bit $bitVersion}
    #Visio Pro Retail
    5 { Update-XMLFile -product "VisioPro2019Retail" -bit $bitVersion}
    #Project Std Volume
    6 { Update-XMLFile -product "ProjectStd2019Volume" -bit $bitVersion}
    #Project Pro Volume
    7 { Update-XMLFile -product "ProjectPro2019Volume" -bit $bitVersion}
    #Project Pro Retail
    8 { Update-XMLFile -product "ProjectPro2019Retail" -bit $bitVersion}
    #Cancel
    "C" {Exit}
    default {Exit}
}

#endregion

#Start the installation
Write-Host "Installing..." -ForegroundColor Green
Start-Installation -bit $bitVersion -xmlName $xmlFile
Write-Host "This window can be closed"
Read-Host

 

Button Masher Game in WPF and C#

I’ve been trying to learn WPF with C# as the backend instead of PowerShell. Since there’s a lot more information on using C# over PowerShell, I decided I would take the plunge and learn as much as I could to create some apps.

This is the first app that I completed. In all honesty, it only took me around 2 hours to get the functionality completed. I did this whilst feeling ill and waiting for the wife to get back from work. I was really surprised by how easy it was for me to understand and write in C# and I was quite proud of the final outcome.

The app will probably look really different on your screen but it looks really nice on my computer. This is what the app looks like once it’s loaded:

The goal of the game is for one person to mash the A button as fast as possible and for another person to mash the B button as fast as possible. The first person to mash their respective buttons 100 times is the winner. It then displays a winning screen and allows the users to play again:

Here is a little GIF of what the game looks and feels like:

And here is the download for the game’s exe file:

ButtonMasher

Now for the nitty gritty stuff. I have included the XML and C# code used for the project below and will also include them as downloads:

XML

<Window x:Class="ButtonMasher.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ButtonMasher"
        mc:Ignorable="d"
        Title="Button Masher" Width="450" MinHeight="145" MinWidth="300" KeyDown="WindowKeyDown" SizeToContent="Height" Icon="Icon.ico">
    <Window.Background>
        <LinearGradientBrush StartPoint="-1,-1" EndPoint="1.5,1.5" >
            <GradientStop Color="#FF018A8F" Offset="0.41" />
            <GradientStop Color="#FF00FFD6" Offset="1" />
        </LinearGradientBrush>
    </Window.Background>
    <Grid Name="Grid">
        <Grid.Background>
            <ImageBrush ImageSource="Winner_Background.jpg" Stretch="UniformToFill" Opacity="0" />
        </Grid.Background>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="1*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Label Visibility="Visible" Name="PlayerOneLabel" Content="0" FontSize="46" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="White" FontFamily="Arial"  />
        <Label Visibility="Visible" Name="PlayerTwoLabel" Content="0" FontSize="46" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="1" Foreground="White" FontFamily="Arial"/>
        <ProgressBar Visibility="Visible" Name="ProgressBar1" Margin="10,5" Background="Transparent" BorderBrush="White" BorderThickness="2" Minimum="0" Maximum="100" Value="0" HorizontalAlignment="Stretch" Height="20"  Grid.Row="1" VerticalAlignment="Center" >
            <ProgressBar.Foreground>
                <LinearGradientBrush EndPoint="0,0.5" StartPoint="1,0.5">
                    <GradientStop Color="#83EAF1" Offset="0"/>
                    <GradientStop Color="#63A4FF" Offset="1"/>
                </LinearGradientBrush>
            </ProgressBar.Foreground>
        </ProgressBar>
        <ProgressBar Visibility="Visible" Name="ProgressBar2" Margin="10,5" Background="Transparent" BorderBrush="White" BorderThickness="2" Minimum="0" Maximum="100" Value="0" HorizontalAlignment="Stretch" Height="20" Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" RenderTransformOrigin="0.5,0.5"  >
            <ProgressBar.Foreground>
                <LinearGradientBrush EndPoint="0,0.5" StartPoint="1,0.5">
                    <GradientStop Color="#83EAF1" Offset="0"/>
                    <GradientStop Color="#63A4FF" Offset="1"/>
                </LinearGradientBrush>
            </ProgressBar.Foreground>
            <ProgressBar.RenderTransform>
                <TransformGroup>
                    <ScaleTransform/>
                    <SkewTransform/>
                    <RotateTransform Angle="180"/>
                    <TranslateTransform/>
                </TransformGroup>
            </ProgressBar.RenderTransform>
        </ProgressBar>
        <TextBlock Name="RulesTextBlock" MouseUp="ToggleRules" Visibility="Visible" Grid.Row="2" VerticalAlignment="Bottom" HorizontalAlignment="Left" FontFamily="Arial" Foreground="White" FontSize="10" Cursor="Hand" >
            See Rules
        </TextBlock>
        <TextBlock Name="WonLabel" Text="Winner" Padding="0,40,0,0" Visibility="Collapsed" Grid.ColumnSpan="2" FontSize="46" HorizontalAlignment="Center" VerticalAlignment="Bottom" FontWeight="Bold" TextDecorations="{x:Null}" Foreground="#FFFECE26" FontFamily="Arial">
            <TextBlock.Effect>
                <DropShadowEffect BlurRadius="1" Direction="320" ShadowDepth="2"/>
            </TextBlock.Effect>
        </TextBlock>
        <Border Name="PlayAgainBorder" Visibility="Collapsed" Grid.Row="1" Cursor="Hand" Grid.ColumnSpan="2" CornerRadius="10" Margin="0,0,0,10" HorizontalAlignment="Center" VerticalAlignment="Center" MouseUp="PlayAgain">
            <Border.Style>
                <Style>
                    <Setter Property="Border.Background" Value="#FF494949" />
                    <Setter Property="Border.BorderThickness" Value="3"/>
                    <Style.Triggers>
                        <Trigger Property="Border.IsMouseOver" Value="True" >
                            <Setter Property="Border.Background" Value="#2ecc71"/>
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </Border.Style>
            <Label Content="Play Again" VerticalAlignment="Center" Foreground="White" HorizontalAlignment="Center" VerticalContentAlignment="Center" FontSize="16" FontFamily="Arial"/>
        </Border>
        <TextBlock Name="RulesSection" Foreground="White" Grid.Row="3" Grid.ColumnSpan="2" TextWrapping="Wrap" TextAlignment="Center" Padding="20" Visibility="Collapsed" FontFamily="Arial" FontSize="14">
            The rules are simple, player one (left side) has to mash the 'A' button as fast as possible while player two (right side) has to mash the 'B' button as fast as possible. The first one to reach 100 button "mashes" wins the game!
        </TextBlock>
    </Grid>
</Window>

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace ButtonMasher
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        //player one score
        int player1Score = 0;
        int player2Score = 0;

        //LOAD FORM
        public MainWindow()
        {
            InitializeComponent();
        }

        //UPDATE PLAYER ONE SCORE
        void UpdatePlayerOneScore()
        {
            player1Score++;
            PlayerOneLabel.Content = player1Score;
        }

        //UPDATE PLAYER TWO SCORE
        void UpdatePlayerTwoScore()
        {
            player2Score++;
            PlayerTwoLabel.Content = player2Score;
        }

        //SET WON SCREEN VISIBILITY
        void WonScreenVisibility(bool visible)
        {
            if (visible)
            {
                //show won screen
                PlayerOneLabel.Visibility = Visibility.Collapsed;
                PlayerTwoLabel.Visibility = Visibility.Collapsed;
                ProgressBar1.Visibility = Visibility.Collapsed;
                ProgressBar2.Visibility = Visibility.Collapsed;
                RulesTextBlock.Visibility = Visibility.Collapsed;
                RulesSection.Visibility = Visibility.Collapsed;
                WonLabel.Visibility = Visibility.Visible;
                PlayAgainBorder.Visibility = Visibility.Visible;
                Grid.Background.Opacity = 100;
                MinWidth = 350;
                MinHeight = 170;
            }
            else
            {
                //hide won screen
                PlayerOneLabel.Visibility = Visibility.Visible;
                PlayerTwoLabel.Visibility = Visibility.Visible;
                ProgressBar1.Visibility = Visibility.Visible;
                ProgressBar2.Visibility = Visibility.Visible;
                RulesTextBlock.Visibility = Visibility.Visible;
                RulesSection.Visibility = Visibility.Collapsed;
                WonLabel.Visibility = Visibility.Collapsed;
                PlayAgainBorder.Visibility = Visibility.Collapsed;
                Grid.Background.Opacity = 0;
                MinWidth = 300;
                MinHeight = 145;
            }
            //switch "show" or "hide" in rules textblock
            if (RulesSection.Visibility == Visibility.Visible)
            {
                RulesTextBlock.Text = "Hide Rules";
            }
            else
            {
                RulesTextBlock.Text = "Show Rules";
            }
        }

        //WON SCREEN
        void WonScreen(string player)
        {
            if(player == "1")
            {
                WonScreenVisibility(true);
                WonLabel.Text = "Player 1 won!";
            }else if(player == "2")
            {
                WonScreenVisibility(true);
                WonLabel.Text = "Player 2 won!";
            }
        }

        //RESET SCORES AND BARS
        void Reset()
        {
            player1Score = 0;
            player2Score = 0;
            PlayerOneLabel.Content = player1Score;
            PlayerTwoLabel.Content = player2Score;
            ProgressBar1.Value = player1Score;
            ProgressBar2.Value = player2Score;
        }

        //USER PRESSES BUTTON FORM
        private void WindowKeyDown(object sender, KeyEventArgs e)
        {
            //checking which key was pressed
            if (e.Key == Key.A)
            {
                UpdatePlayerOneScore(); 
            }else if(e.Key == Key.L)
            {
                UpdatePlayerTwoScore();
            }

            //calculating progress bar value
            ProgressBar1.Value = player1Score;
            ProgressBar2.Value = player2Score;

            //checking if anyone won
            if (ProgressBar1.Value > 99)
            {
                //player one has won
                WonScreen("1");

            }
            else if (ProgressBar2.Value > 99)
            {
                //player two has won
                WonScreen("2");
            }            
        }        

        //PLAY AGAIN
        private void PlayAgain(object sender, RoutedEventArgs e)
        {
            WonScreenVisibility(false);
            Reset();
        }

        //TOGGLE RULES
        void ToggleRules(object sender, RoutedEventArgs e)
        {
            if (RulesSection.Visibility == Visibility.Collapsed)
            {
                //show rules
                RulesSection.Visibility = Visibility.Visible;
                RulesTextBlock.Text = "Hide Rules";
                SizeToContent = SizeToContent.Height;
                MinHeight = 265;
            }
            else
            {
                //hide rules
                RulesSection.Visibility = Visibility.Collapsed;
                RulesTextBlock.Text = "Show Rules";
                SizeToContent = SizeToContent.Height;
                MinHeight = 145;
            }
        }
    }
}

I really hope you enjoy this little game that I created. Please feel free to use it, change it and distribute your own version at will 🙂

Enjoy!

Building Cleaner, Responsive WPF Forms

In my first two posts on this subject, I was just getting started with learning responsive WPF form building. I’m here today to show you a better way to build a responsive WPF using runspaces that will do the exact same thing as my previous uploads showed. Just better.

This time, I won’t be putting the form into its own runspace. As I learnt you didn’t need to from JRV over on the TechNet forums. This has some benefits that I will very helpfully, briefly and probably incorrectly list below:

  • Don’t have to use syncHash when updating the form
  • One less runspace for the form
  • Having a form in its own runspace creates additional overhead and possible errors

So to display the form I would use something like the below:

[xml]$xml = @"
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    Title="Counter" Height="119" Width="351.5" ResizeMode="CanMinimize" WindowStartupLocation="CenterScreen">
    <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
        <Label Name="Label" Content="0" HorizontalAlignment="Left" Margin="16.666,9.333,0,0" VerticalAlignment="Top" FontSize="18"/>
        <Button Name="Button" Content="Start" HorizontalAlignment="Center" VerticalAlignment="Top" Width="75" Margin="123.25,63,123.25,0"/>
    </Grid>
</Window>
"@

$Reader=(New-Object System.Xml.XmlNodeReader $xml)
$Window=[Windows.Markup.XamlReader]::Load($Reader)

$Label = $Window.FindName("Label")
$Button = $Window.FindName("Button")

$Window.ShowDialog() | Out-Null

Which will give us the below form:

But what if I want the button press to make the label number increase? I would use something like this on the button press:

[xml]$xml = @"
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    Title="Counter" Height="119" Width="351.5" ResizeMode="CanMinimize" WindowStartupLocation="CenterScreen">
    <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
        <Label Name="Label" Content="0" HorizontalAlignment="Left" Margin="16.666,9.333,0,0" VerticalAlignment="Top" FontSize="18"/>
        <Button Name="Button" Content="Start" HorizontalAlignment="Center" VerticalAlignment="Top" Width="75" Margin="123.25,63,123.25,0"/>
    </Grid>
</Window>
"@

$Reader=(New-Object System.Xml.XmlNodeReader $xml)
$Window=[Windows.Markup.XamlReader]::Load($Reader)

$Label = $Window.FindName("Label")
$Button = $Window.FindName("Button")

$Button.Add_Click({
    $counter = 1 

    do{
        Start-Sleep -Milliseconds 5
        $label.content = $counter
        [System.Windows.Forms.Application]::DoEvents()
        $counter += 1
    }while ($counter -le 5000)

})

$Window.ShowDialog() | Out-Null

This produces a form which increases the label up to 5000 when the button is pressed. You can see this below:

But what if I want to actually run something in a runspace. For example, test the connection to google.com? Then I would use the below code:

[xml]$xml = @"
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    Title="Counter" Height="119" Width="351.5" ResizeMode="CanMinimize" WindowStartupLocation="CenterScreen">
    <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
        <Label Name="Label" Content="0" HorizontalAlignment="Left" Margin="16.666,9.333,0,0" VerticalAlignment="Top" FontSize="18"/>
        <Button Name="Button" Content="Start" HorizontalAlignment="Center" VerticalAlignment="Top" Width="75" Margin="123.25,63,123.25,0"/>
    </Grid>
</Window>
"@

$Reader=(New-Object System.Xml.XmlNodeReader $xml)
$Window=[Windows.Markup.XamlReader]::Load($Reader)

$Label = $Window.FindName("Label")
$Button = $Window.FindName("Button")

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

    $powershell = [powershell]::Create().AddScript({
        $connection = Test-Connection -ComputerName google.com -Count 5
        $syncHash.output = [math]::Round(($connection.ResponseTime | Measure-Object -Average).Average)
    })

    $powershell.Runspace = $Runspace

    $Object = $powershell.BeginInvoke()

    do {
        Start-Sleep -Milliseconds 50
        [System.Windows.Forms.Application]::DoEvents()
    }while(!$Object.IsCompleted)

    $powershell.EndInvoke($Object)
    $powershell.Dispose()

    $label.Content = $syncHash.output
})

$Window.ShowDialog() | Out-Null

All the above examples will stay responsive whilst the action is performed. There are a couple of different methods to do this as you can see above, for anything that takes some time to complete, a runspace is needed. But when you are updating the form quickly, like the counter, then no runspace is needed. 

Enjoy!