Multiple Sounds in WPF Application

So I’m making a small WPF game, which you can find here by the way 😛, and I come across a confusing and infuriating issue. WPF has two built-in audio players: SoundPlayer and MediaPlayer.

SoundPlayer: Can play audio files that are embedded resources in the application, however, this can only play one sound at once.

MediaPlayer: Cannot play audio files that are embedded resources in the application, however, this can play multiple sounds at once.

I think you can see the issue I was having. I wanted to play some background music in my game as well as some sound effects for certain actions in the game. This meant the easiest way to get this functionality was to use both of these options and save the background music to the local disk as a temporary file. Not as clean as I wanted, but it will do I guess 🤷‍♂️

First, I set up my SoundPlayer as this was the simplest one of the two. I created a new SoundPlayer object using the embedded resource:

private static readonly SoundPlayer _soundOne = new SoundPlayer(WPF.Properties.Resources.soundOne);

Then when I wanted this sound to play, I would simply use:

_soundOne.Play();

Simple enough for the SoundPlayer… Now we move onto the MediaPlayer.

First, I make sure that my audio file is set as an Embedded Resource under the Build Actions in the file’s properties in Visual Studio:

You can see from this, that I have a WAV file called “backgroundmusic.wav” which I need to save to disk, play continuously and then delete once the form is closed.

Now that we have done this, we can create the method for saving the embedded WAV file to the %temp% location on disk. This is how I did that:

public static void SaveMusicToDisk(){
    //This sets up a new temporary file in the %temp% location called "backgroundmusic.wav"
    using (FileStream fileStream = File.Create(Path.GetTempPath() + "backgroundmusic.wav")){
        
        //This them looks into the assembly and finds the embedded resource
        //inside the WPF project, under the assets folder
        //under the sounds folder called backgroundmusic.wav
        //PLEASE NOTE: this will be different to you
        Assembly.GetExecutingAssembly().GetManifestResourceStream("WPF.Assets.Sounds.backgroundmusic.wav").CopyTo(fileStream);
    }
}

Great we have now saved the embedded resource to disk, how can we play this now? 🤷‍♂️

We play this by creating a new MediaPlayer object and using the temp file location to play the audio:

//Create a new MediaPlayer object
private static readonly MediaPlayer _backgroundMusic = new MediaPlayer();

public static void StartBackgroundMusic(){
    //Open the temp WAV file saved in the temp location and called "backgroundmusic.wav"
    _backgroundMusic.Open(new Uri(Path.Combine(Path.GetTempPath(), "backgroundmusic.wav")));
    //Add an event handler for when the media has ended, this way
    //the music can be played on a loop
    _backgroundMusic.MediaEnded += new EventHandler(BackgroundMusic_Ended);
    //Start the music playing
    _backgroundMusic.Play();
}

My BackgroundMusic_Ended method looks like this and just makes sure that the music is always restarted once it has finished:

private static void BackgroundMusic_Ended(object sender, EventArgs e){
    //Set the music back to the beginning
    _backgroundMusic.Position = TimeSpan.Zro;
    //Play the music
    _backgroundMusic.Play();
}

Now that the music is playing continuously on a loop, how do we stop the music, dispose of the object and delete the temp WAV file from disk?

To stop and dispose of the MediaPlayer object is pretty easy, you can use:

public static void StopBackgroundMusic(){
    //Stops the music 
    _backgroundMusic.Stop();
    //Disposes of the MediaPlayer object
    _backgroundMusic.Close();
}

Now we go about removing the WAV file from the disk as we don’t want this to remain on the user’s computer once they have exited the application:

public static void DeleteMusicFromDisk(){
    File.Delete(Path.Combine(Path.GetTempPath(), "backgroundmusic.wav"));
}

DONE! ✔️

With all the above code and a little tinkering to fit your specific setup, you should be able to use multiple sounds at once without an issue. Enjoy!

Saving Embedded WPF Resources as Files on Disk

In this post, I am going to show you how you can save an embedded resource as a file on disk. This is useful in certain scenarios. For example, I used this to save some background music onto the computer as the native MediaPlayer couldn’t use the application paths.

First things first, you want to make sure that you have set the Build Action to Embedded Resource in the properties for each of the items:

Once you have done that, we can go about creating a method to get the resource and save it to file. For me, that is as simple as:

public static void SaveMusicToDisk(){
    //This creates a temp file in the %temp% directory called backgroundMusic.wav
    using (FileStream fileStream = File.Create(Path.GetTempPath() + "backgroundMusic.wav")){
        
        //This looks into the assembly and gets the resource by name
        //For this to work, you need to use the full application path to the resource
        //You get this by using your project name following by the folder tree to your item
        Assembly.GetExecutingAssembly().GetManifestResourceStream("WPF.Assets.Sounds.backgroundmusic.wav").CopyTo(fileStream);
    }
}

So that saves the file, what if I want to delete the file once the user wants to close the application?

For that I would use:

public static void DeleteMusicFromDisk(){
    //This looks into the %temp% folder and deletes the file called "backgroundMusic.wav"
    File.Delete(Path.Combine(Path.GetTempPath(), "backgroundMusic.wav"));
}

Peeeeerfect! 🎉 Does everything I need 😊

So, what if I wanted to save all the resources?

For that I could put all the resources into a string array and loop through them like this:

public static void SaveAllResources(){
    
    //Gets all the resources associated with the assembly and puts them into an array
    string[] resources = Assembly.GetExecutingAssembly().GetManifestResourceNames();

    foreach (string resource in resources)
    {
        //Create a new file in the %temp% for each resource
        using (FileStream fileStream = File.Create(Path.GetTempPath() + resource))
        {
            //Get the resource and save it to the current file
            Assembly.GetExecutingAssembly().GetManifestResourceStream(resource).CopyTo(fileStream);
        }
    }
}

I hope you learn something or found this helpful. Enjoy!

Caliburn.Micro Notify Change for Static Property

Normally in Caliburn.Micro and MVVM in general, you would have non-static properties that would be updated and notified like this:

private string _name;

public string Name
{
    get { return _name; }
    set
    {
        _name = value;
        NotifyOfPropertyChange(() => Name);
    }
}

However, if I had a static property, for example, if I wanted a property that could be accessed by another namespace, I would need to implement my own event handler as the default NotifyOfPropertyChange doesn’t operate on static properties.

This is the new EventHandler that I implemented:

public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged;
private static void NotifyStaticPropertyChanged(string propertyName)
{
    StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName));
}

Finally, in order to use it. I would use this instead of the first example for non-static properties:

private string _name;

public string Name
{
    get { return _name; }
    set
    {
        _name = value;
        NotifyStaticPropertyChanged(() => Name);
    }
}

I hope this helps someone. Enjoy!

Caliburn.Micro MVVM Boolean To Visibility Converter

Say I wanted to toggle the visibility of a WPF object in an MVVM way, what would I need to do?

Here is what I currently have:

  • ViewModels
    • ShellViewModel
    • LoadingViewModel
  • Views
    • ShellView
    • LoadingView

When the application is loaded, the ShellViewModel is used to display the LoadingView in a ContentControl object. I have a button on there that I want to become visible after the LoadingView has been activated for 5 seconds.

What I need to do is created a custom class that has two methods: a way of converting a boolean to a visibility; and a way of converting a visibility to a visibility.

So I created a new folder in my tree called “Converters“. In here, I create a new class called “BooleanToVisiblityConverter“.  Here is what my tree now looks like:

  • ViewModels
    • ShellViewModel
    • LoadingViewModel
  • Views
    • ShellView
    • LoadingView
  • Converters
    • BooleanToVisibilityConverter

In my BooleanToVisibilityConverter class, I inherited from IValueConverter and added the necessary two methods. You can see the entire class below:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;

namespace Project.Converters
{
    /// <summary>
    /// Contains the converter and convertback methods for the boolean to visibility conversions
    /// </summary>
    public sealed class BooleanToVisibilityConverter : IValueConverter
    {
        /// <summary>
        /// Used to convert a boolean to a visibility
        /// </summary>
        /// <param name="value">This is the boolean input</param>
        /// <param name="targetType"></param>
        /// <param name="parameter"></param>
        /// <param name="culture"></param>
        /// <returns>Returns a visibility</returns>
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (!(value is bool))
            {
                //If there is an issue with the input, return collapsed
                return Visibility.Collapsed;
            }
            return (bool)value ? Visibility.Visible : Visibility.Collapsed;
        }

        /// <summary>
        /// Used to take a visibility and returns a visibility
        /// </summary>
        /// <param name="value">This is the boolean input</param>
        /// <param name="targetType"></param>
        /// <param name="parameter"></param>
        /// <param name="culture"></param>
        /// <returns>Returns a visibility</returns>
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (!(value is Visibility))
            {
                //If there is an issue wtih the input, return collapsed
                return Visibility.Collapsed;
            }
            return (Visibility)value == Visibility.Visible;
        }
    }
}

Next, in my LoadingView, I added “ xmlns:sp=”clr-namespace:Project.Converters” so that the view could use the converter namespace. This is what my view dependancies look like:

<UserControl x:Class="Project.Views.MainView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:cal="http://www.caliburnproject.org"
             xmlns:sp="clr-namespace:Project.Converters"
             xmlns:local="clr-namespace:Project.Views"
             mc:Ignorable="d" FontSize="14" FontFamily="/Project;component/Assets/Fonts/#Roboto"
             d:DesignHeight="550" d:DesignWidth="400">

I then added the binding to the visibility property of my button. You can see this below:

<Button Name="LoadingButton" Content="Press Me" Visibility="{Binding ButtonIsVisible, Converter={StaticResource BooleanConverter}, FallBackValue=Collapsed}"

This means that it will get its visibility value from the LoadingViewModel and if this fails, it will fall back to being collapsed.

We’re almost done. In the LoadingViewModel create a full property which will hold and change the value for the visibility. This needs to be named the same as the binding given in the LoadingView (i.e ButtonVisibility). You can see this below:

private bool _buttonIsVisible;

public bool ButtonIsVisible
{
    get {return _buttonIsVisible;
    set
    {
        if (value != _buttonIsVisible)
        {
            _buttonIsVisible = value;
            NotifyOfPropertyChange(() => ButtonIsVisible);
        }
    }
}

So now if you want the button to be visible, you can just update the ButtonIsVisible property in the LoadingViewModel. Here is an example below (Don’t actually do this):

public LoadingViewModel(){
    ButtonIsVisible = false;
    Task.Delay(5000);
    ButtonIsVisible = true;
}

I hope this helped you. Enjoy!

Setting Up Caliburn.Micro MVVM

This is a fairly lengthy post that shows how to set up an initial MVVM WPF form using Caliburn.Micro.

Let’s get started, we’ll open up Visual Studio and chose to create a new WPF:

Visual Studio -> New Project -> WPF App (.Net Framework)

I usually set the project name to something like WPFUI and my solution name to be something like MVVMProject or the actual product name. For example, Microsoft might use Microsoft Outlook. Probably not, but you get what I’m saying…

 

1)

First I will add Caliburn.Micro to my project. To do this go to Solution Explorer -> References -> Add NuGet Packages and search for Caliburn.Micro:

 

2)

Now we can delete the MainWindow.xaml

 

3)

Create a Views and ViewModels folder. This is where your viewmodels and views will call home.

 

4)

Create a new class in the ViewModels folder and call it ShellViewModel and make it public. You’ll also want to inherit from screen and add the using statement for Caliburn.Micro:

5)

Create a new window in the Views folder and call it ShellView

6)

Create a new class in the root directory called Bootstrapper and inherit from BootstrapperBase and add the using statement for Caliburn.Micro

7)

In App.xaml remove the StartupUri element and add the bootstrapper class as a resource by adding:

<ResourceDictionary>
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary>
            <local:Bootstrapper x:Key="Bootstrapper" />
        </ResourceDictionary>
    </ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

Your app.xaml should look like this:

 

8)

Now go back into your Bootstrapper class and add the following:

public Bootstrapper()
{
    Initialize();
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
    DisplayRootViewFor<ShellViewModel>();
}

You’ll also need to add a using statement for System.Windows and ProjectName.ViewModels.

The program should now launch and show an empty white screen with “ShellView” as the window’s title. This is the first entry into a hopefully long series into the world of Caliburn.Micro.

Enjoy! 😊

WPF Spinning Image – Loading Wheel

This is just a quick post for something that caused me quite a bit of hassle. How to spin an image in WPF. This is useful for creating loading icons such as a spinning icon.

This is what my implementation looks like:

To create this effect, I first added a resource to my user control. This is what that looks like:

    <UserControl.Resources>
        <Storyboard x:Key="imageRotationStoryboard" Storyboard.TargetName="loadingImage" Storyboard.TargetProperty="RenderTransform.(RotateTransform.Angle)">
            <DoubleAnimation From="0" To="360" BeginTime="0:0:0" Duration="0:0:1.5" RepeatBehavior="Forever"/>
        </Storyboard>
    </UserControl.Resources>

What this does is create a story animation that rotates the image from 0 degrees to 360 degrees (a full circle) in 5 seconds. It also does this forever, hence the spinning effect 😊

I then created an image object and set my source. I also changed the RenderTransformOrigin to be 0.5,0.5, this means that the rotation happens in the centre of the image instead of the default top left corner. I then created an event on the image at loaded to start the storyboard animation and set the rotate to 0 on load. Heres what that looks like:

<Image Name="loadingImage" Margin="200"  RenderTransformOrigin="0.5,0.5" Source="/SlickRHI;component/Assets/Images/Loading.png">
	<Image.Triggers>
		<EventTrigger RoutedEvent="FrameworkElement.Loaded">
			<EventTrigger.Actions>
				<BeginStoryboard Storyboard="{StaticResource imageRotationStoryboard}" />
			</EventTrigger.Actions>
		</EventTrigger>
	</Image.Triggers>
	<Image.RenderTransform>
		<RotateTransform Angle="0"/>
	</Image.RenderTransform>
</Image>

Hope this helps someone, I know I will need this at least once again in the future. Enjoy 😊

Performing Asynchronous Remote WMI Calls C#

This was a problem that I was stuck on for a long time. Originally, I made a program in PowerShell that myself and a couple of other people in the company used to query remote computers to retrieve certain information (SN etc). I wanted to make the move to using WPF with C# simply because of its scale, simplicity and flexibility. The things that I saw being done in WPF with C# was beyond anything I could ever dream with PowerShell.

After making the initial leap, I realised that I would how much code I would have to re-write/re-imagine into C# code. Go figure… One thing that I thought would be simple is performing asynchronous operations but this proved to be something I should use with caution. See, I could simply use Task.Run() and perform the actions, but this is like using a sledgehammer to drive in a nail. After a lot of research, I moved from using this approve to using an observer class to watch the operation and using the QueryInstancesAsync method instead of QueryInstances()

This is the code that I ended up with:

//Used to define what is returned in the async results
public static CimAsyncMultipleResults<CimInstance> GetValues(CimSession _session)
{
    return _session.QueryInstancesAsync(@"root\cimv2", "WQL", "SELECT Username FROM Win32_ComputerSystem");
}

//This watches the async progress
class CimInstanceWatcher : IObserver<CimInstance>
{
    public void OnCompleted()
    {
        Console.WriteLine("Done");
    }

    public void OnError(Exception e)
    {
        Console.WriteLine("Error: " + e.Message);
    }

    public void OnNext (CimInstance value)
    {
        Console.WriteLine("Value: " + value);
    }
}

private static void Main()
{
    //Leaving cimsession creation as sync because is happens "instantly"
    CimSession Session = CimSession.Create("PC-NAME");
    //Creating a new watcher object
    var instanceObject = new CimInstanceWatcher();
    //Subscribing the watcher object to the async call
    GetValues(Session).Subscribe(instanceObject);
    Console.ReadLine();
}

This information was provided from Microsofts forums

Enjoy!

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!

LAPS 3

Okay, this is probably my final update to the whole LAPS thing. I have created two iterations in the past but neither were really groundbreaking or my own design. Not that this update is groundbreaking either though. This is a further update to the below post:

LAPS WinForm 2

I wanted to completely redo my LAPS form (again) to make it my own design, responsive and ultimately better. This is what the final form looks like. It is completely responsive and resizeable:

I will include the source code here but the best place to download this would be from my TechNet gallery.

There are a couple of things you need to change in the form to make it work:

  • Adding your domain controller and domain root to the variables at the top of the script
  • Add your BASE64 data into the BASE64 variable to use your own logo

Heres the code:

#Enter your domain and domain controller below :)
$script:domainController = "DOMAIN CONTROLLER HERE" #E.G domaincontroller.domain.lan
$script:domainRoot = "DOMAIN ROOT HERE" #E.G domain.lan

#LOADING ASSEMBLIES
Add-Type -AssemblyName PresentationFramework, System.Drawing, System.Windows.Forms, WindowsFormsIntegration

#ICON FOR FORM
[string]$base64=@'
BASE64 DATA HERE
'@

#CREATING THE IMAGE FROM BASE64 DATA
$bitmap = New-Object System.Windows.Media.Imaging.BitMapImage
$bitmap.BeginInit()
$bitmap.StreamSource = [System.IO.MemoryStream][System.Convert]::FromBase64String($base64)
$bitmap.EndInit()
$bitmap.Freeze()

#LAPS WINDOW XML
[xml]$LAPSXaml = @"
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    Title="LAPS UI" Height="400" Width="400" MinHeight="400" MinWidth="400" WindowStartupLocation="CenterScreen">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="2"/>
            <ColumnDefinition/>
            <ColumnDefinition Width="Auto" MinWidth="75"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto" MinHeight="7"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Label Content="ComputerName:" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Column="1" FontSize="14"/>
        <TextBox Name="Computer_Textbox" VerticalContentAlignment="Center" HorizontalAlignment="Stretch" Grid.Row="1" TextWrapping="Wrap" VerticalAlignment="Stretch" Margin="3" Grid.Column="1" FontSize="14"/>
        <Button Name="Search_Button" Content="Search" Grid.Column="2" HorizontalAlignment="Stretch" Grid.Row="1" VerticalAlignment="Stretch" Margin="0,3,5,3"/>
        <Label Content="Password" Grid.Column="1" HorizontalAlignment="Stretch" Grid.Row="2" VerticalAlignment="Stretch" FontSize="14"/>
        <TextBox Name="Password_Textbox" Grid.Column="1" HorizontalAlignment="Stretch" Grid.Row="3" TextWrapping="Wrap" Margin="3" VerticalAlignment="Stretch" IsReadOnly="True" FontSize="14"/>
        <Button Name="Copy_Button" Content="Copy" Grid.Column="2" HorizontalAlignment="Stretch" Grid.Row="3" Margin="0,3,5,3" VerticalAlignment="Stretch"/>
        <Label Content="Password Expires" Grid.Column="1" HorizontalAlignment="Stretch" Grid.Row="4" VerticalAlignment="Stretch" FontSize="14"/>
        <TextBox Name="Password_Ex_Textbox" Grid.Column="1" IsReadOnly="True" HorizontalAlignment="Stretch" Grid.Row="5" TextWrapping="Wrap" VerticalAlignment="Stretch" Margin="3" FontSize="14"/>
        <Label Content="New Expiration" Grid.Column="1" HorizontalAlignment="Stretch" Grid.Row="6" VerticalAlignment="Stretch" FontSize="14"/>
        <DatePicker Name="Date_Picker" Grid.Column="1" HorizontalAlignment="Stretch" Grid.Row="7" VerticalAlignment="Stretch" Margin="3" FontSize="14"/>
        <Button Name="Set_Button" Content="Set" Grid.Column="2" HorizontalAlignment="Stretch" Grid.Row="7" VerticalAlignment="Stretch" Margin="0,5,5,5"/>
        <GridSplitter IsEnabled="False" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="8" Grid.Column="1" Margin="5,2,5,2" Grid.ColumnSpan="2"/>
        <TextBox Name="Output_Textbox" VerticalScrollBarVisibility="Auto" IsReadOnly="True" HorizontalAlignment="Stretch" Grid.Row="9" TextWrapping="Wrap" Margin="1,5,1,1" VerticalAlignment="Stretch" Grid.ColumnSpan="3" FontSize="12"/>
    </Grid>
</Window>
"@

#LOADING XAML
$LAPSReader=(New-Object System.Xml.XmlNodeReader $LAPSXaml)
$LAPSWindow=[Windows.Markup.XamlReader]::Load($LAPSReader)
$LAPSWindow.Icon = $bitmap

#ASSIGNING CONTROLS
$Computer_Textbox = $LAPSWindow.FindName("Computer_Textbox")
$Search_Button = $LAPSWindow.FindName("Search_Button")
$Password_Textbox = $LAPSWindow.FindName("Password_Textbox")
$Copy_Button = $LAPSWindow.FindName("Copy_Button")
$Password_Ex_Textbox = $LAPSWindow.FindName("Password_Ex_Textbox")
$Date_Picker = $LAPSWindow.FindName("Date_Picker")
$Set_Button = $LAPSWindow.FindName("Set_Button")
$Output_Textbox = $LAPSWindow.FindName("Output_Textbox")

#FUNCTION TO SET OUTPUT TEXTBOX
function set-output-textbox{
    param(
        [string]$value,
        [bool]$date
    )
    if ($date){
        $Output_Textbox.Text = ("[$(Get-Date)] - $value `r`n")
    }else{
        $Output_Textbox.Text = $value
    }
}

#FUNCTION TO UPDATE OUTPUT TEXTBOX
function update-output-textbox{
    param(
        [string]$value,
        [bool]$date
    )
    if ($date){
        $Output_Textbox.AppendText("[$(Get-Date)] - $value `r`n")
    }else{
        $Output_Textbox.AppendText("     $value `r`n")
    }
    $Output_Textbox.ScrollToEnd()
}

#FUNCTION TO UPDATE FORM
function update-form{
    [System.Windows.Forms.Application]::DoEvents()
}

#FUNCTION TO UPDATE PASSWORD TEXTBOX
function update-password-textbox($value){
    $Password_Textbox.Text = $value
}

#FUNCTION TO UPDATE PASSWORD EX TEXTBOX
function update-passwordex-texbox($value){
    $Password_Ex_Textbox.Text = $value
}

#FUNCTION TO SET CONTROLS
function set-controls{
    param(
        [bool]$switcher,
        [bool]$setswitcher
    )
    $Search_Button.IsEnabled = $switcher
    $Set_Button.IsEnabled = $setswitcher
    $Date_Picker.IsEnabled = $setswitcher
}

#DECIDE IF COPY BUTTON SHOULD BE ENABLED
$Copy_Button.IsEnabled = $false
$Password_Textbox.Add_TextChanged({
    if ($Password_Textbox.Text.Length -gt 0){
        $Copy_Button.IsEnabled = $true
    }else{
        $Copy_Button.IsEnabled = $false
    }
})

#MAKING COMPUTER NAME UPPERCASE ON FOCUS LOST
$Computer_Textbox.Add_LostFocus({
    $Computer_Textbox.Text = $Computer_Textbox.Text.ToUpper()
})

#COPY BUTTON LOGIC
$Copy_Button.Add_Click({
    Set-Clipboard -Value $Password_Textbox.Text
})

#COMPUTER TEXTBOX KEYDOWN LOGIC
$Computer_Textbox.Add_KeyDown({
    if ($args.Key -eq 'Enter'){
        $Search_Button.RaiseEvent((New-Object -TypeName System.Windows.RoutedEventArgs $([System.Windows.Controls.Button]::ClickEvent)))
    }
})

#DISABLING CONTROLS ON FORM LOAD
set-controls -switcher $true -setswitcher $false

#WELCOME MESSAGE ON FORM LOAD
$Output_Textbox.HorizontalContentAlignment="Center"
$Output_Textbox.VerticalContentAlignment="Center"
set-output-textbox -date $false -value "Welcome to version 3 of this form! It is now responsive and a lot cleaner in the background. Nothing you ever had to worry about though :)"

#SEARCH BUTTON LOGIC
$Search_Button.Add_Click({

    #DISABLING CONTROLS ON BUTTON PRESS
    $Output_Textbox.HorizontalContentAlignment="Left"
    $Output_Textbox.VerticalContentAlignment="Top"
    set-controls -switcher $false -setswitcher $false
    update-password-textbox -value $null
    update-passwordex-texbox -value $null
    $Date_Picker.Text = $null

    if ($Computer_Textbox.Text.Length -le 0){
        #OUTPUT IF EMPTY SEARCH AND ENABLING CONTROLS
        set-output-textbox -date $true -value "Input cannot be empty"
        set-controls -switcher $true -setswitcher $false    
    }else{
        set-output-textbox -date $true -value "Please Wait"
        
        #PUTTING INPUT INTO VARIABLE
        $script:computerName = $Computer_Textbox.Text

        #CREATING A SYNCHRONISED HASHTABLE
        $script:syncHash = [hashtable]::Synchronized(@{})

        #CREATING SEARCH RUNSPACE
        $searchRunspace = [runspacefactory]::CreateRunspace()
        $searchRunspace.ApartmentState = "STA"
        $searchRunspace.ThreadOptions = "ReuseThread"
        $searchRunspace.Open()
        $searchRunspace.SessionStateProxy.SetVariable("syncHash",$syncHash)
        $searchRunspace.SessionStateProxy.SetVariable("computerName",$computerName)
        $searchRunspace.SessionStateProxy.SetVariable("domainController",$domainController)

        #POWERSHELL TO BE RAN IN RUNSPACE
        $searchPowerShell = [powershell]::Create().AddScript({
            $syncHash.searchADComputer = Get-ADComputer -Identity $computerName
            $syncHash.searchInvoke = Invoke-Command -ComputerName $domainController -ScriptBlock { Get-AdmPwdPassword -ComputerName $args[0] } -ArgumentList $computerName | Select-Object Password, ExpirationTimeStamp
        })

        #ASSIGNING RUNSPACE TO POWERSHELL
        $searchPowerShell.Runspace = $searchRunspace
        #STARTING THE RUNSPACE AND POWERSHELL
        $searchObject = $searchPowerShell.BeginInvoke()

        #REFRESHING UNTIL POWERSHELL IS COMPLETE
        do{
            Start-Sleep -Milliseconds 100
            update-form
        }while (!$searchObject.IsCompleted)

        #ENDING POWERSHELL INVOKE AND DISPOSING OF RUNSPACE
        $searchPowerShell.EndInvoke($searchObject)
        $searchPowerShell.Dispose()
    
        if ($syncHash.searchADComputer){
            #COMPUTER IS FOUND ON DOMAIN
            if ($syncHash.searchInvoke){
                #INVOKE SUCCESSFUL
                $admpwdPassword = $syncHash.searchInvoke.password
                $admpwdPasswordExpiration = $syncHash.searchInvoke.ExpirationTimeStamp
                $admpwdPasswordExpirationFormatted = $admpwdPasswordExpiration.ToString("dd/MM/yyyy hh:mm:ss")

                #UPDATING FIELDS
                update-output-textbox -date $true -value "Information retrieved"
                update-password-textbox -value $admpwdPassword
                update-passwordex-texbox -value $admpwdPasswordExpirationFormatted
                set-controls -switcher $true -setswitcher $true
            }else{
                #INVOKE FAILED
                update-output-textbox -date $true -value "Failded to retrieve password information"
                update-password-textbox -value $null
                update-passwordex-texbox -value $null
                set-controls -switcher $true -setswitcher $false
            }
        }else{
            #COMPUTER NOT FOUND ON DOMAIN
            update-output-textbox -date $true -value "Host not found on domain"
            update-password-textbox -value $null
            update-passwordex-texbox -value $null
            set-controls -switcher $true -setswitcher $false
        }
    }
})

#SET EXPIRATION BUTTON LOGIC
$Set_Button.Add_Click({
    
    #DISABLING CONTROLS ON BUTTON PRESS
    set-controls -switcher $false -setswitcher $false

    if ($Date_Picker.Text.Length -le 0){
        #OUTPUT IF EMPTY DATE AND ENABLING CONTROLS
        update-output-textbox -date $true -value "No date selected"
        set-controls -switcher $true -setswitcher $true
    }else{
        #GETTING NEW DATES FOR EXPIRATION
        $newExpirationString = $Date_Picker.SelectedDate.ToString("MM/dd/yyyy")
        $script:newExpirationDate = [datetime]::ParseExact($newExpirationString, 'MM/dd/yyyy', $null)
        
        #OUTPUTTING FRIENDLY EXPIRATION TO OUTPUT TEXTBOX
        update-output-textbox -date $true -value "Setting expiration to $newExpirationString..."

        #CREATING SEARCH RUNSPACE
        $setRunspace = [runspacefactory]::CreateRunspace()
        $setRunspace.ApartmentState = "STA"
        $setRunspace.ThreadOptions = "ReuseThread"
        $setRunspace.Open()
        $setRunspace.SessionStateProxy.SetVariable("syncHash",$syncHash)
        $setRunspace.SessionStateProxy.SetVariable("computerName",$computerName)
        $setRunspace.SessionStateProxy.SetVariable("domainController",$domainController)
        $setRunspace.SessionStateProxy.SetVariable("newExpirationDate",$newExpirationDate)

        #POWERSHELL TO BE RAN IN RUNSPACE
        $setPowerShell = [powershell]::Create().AddScript({
            try{
                $syncHash.setInvoke = Invoke-Command -ComputerName $domainController -ScriptBlock {Reset-AdmPwdPassword -ComputerName $args[0] -WhenEffective $args[1] } -ArgumentList $computerName, $newExpirationDate -ErrorAction Stop
                try{
                    Invoke-GPUpdate -Computer $computerName -ErrorAction Stop
                    $syncHash.setGPUpdate = $true
                }catch{
                    #GP UPDATE FAILED
                    $syncHash.setGPUpdate = $null
                }
            }catch{
                #CHANGING EXPIRATION FAILED
                $syncHash.setInvoke = $null
            }
        })

        #ASSIGNING RUNSPACE TO POWERSHELL
        $setPowerShell.Runspace = $setRunspace
        #STARTING THE RUNSPACE AND POWERSHELL
        $setObject = $setPowerShell.BeginInvoke()

        #REFRESHING UNTIL POWERSHELL IS COMPLETE
        do{
            Start-Sleep -Milliseconds 100
            update-form
        }while (!$setObject.IsCompleted)

        #ENDING POWERSHELL INVOKE AND DISPOSING OF RUNSPACE
        $setPowerShell.EndInvoke($setObject)
        $setPowerShell.Dispose()

        #CHECKING PASSWORD EXPIRATION SUCCESS
        if ($syncHash.setInvoke){
            update-output-textbox -date $true -value "Successfully reset password expiration date"
            #CHECKING GP UPDATE SUCCESS
            if ($syncHash.setGPUpdate){
                update-output-textbox -date $true -value "Succesfully ran GP update"
            }else{
                update-output-textbox -date $true -value "Failed to run GP update, this is probably due to permissions"
            }
        }else{
            update-output-textbox -date $true -value "Failed to reset password expiration date"
        }

        #RESETTING CONTROLS
        set-controls -switcher $true -setswitcher $true
    }
})

#CHECK FOR AD MODULE AND TEST IF ON LOCAL DOMAIN/NETWORK
if ( Test-Connection $domainRoot -Count 1 -Quiet){
    #DOMAIN IS ACCESSIBLE
    if (Get-Module -List ActiveDirectory ){
        #AD MODULE INSTALLED
        #FORM WILL BE DISPLAYED WITHOUT ANY MODIFICATIONS
    }else{
        #AD MODULE NOT INSTALLED
        set-output-textbox -date $false -value "Install the AD module and restart"
        set-controls -switcher $false -setswitcher $false
        $Computer_Textbox.IsEnabled = $false
    }
}else{
    #DOMAIN ISN'T ACCESSIBLE
    set-output-textbox -date $false -value "$domainRoot is not accessible"
    set-controls -switcher $false -setswitcher $false
    $Computer_Textbox.IsEnabled = $false
}   

#REMOVING PROCESS ON FORM CLOSE
$LAPSWindow.Add_Closing({
    try{
        $syncHash.Clear() | Out-Null
    }catch{}
    
    Stop-Process -Name "LAPS" -ErrorAction SilentlyContinue
})

#DISPLAY FORM WHILST TESTING
$app = [Windows.Application]::new()
$app.run($LAPSWindow)

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!