Using Windows 10 Toast Notifications with ConfigMgr Application Deployments

When deploying software with ConfigMgr, the ConfigMgr client can create a simple “New software is available” notification to inform the user that something new is available to install from the Software Center. But this notification is not overly descriptive. You might wish to provide a more detailed notification with a description of the software, why the user should install it, the installation deadline etc. For Windows 10, we can do that simply by disabling the inbuilt notifications on the deployment and creating our own custom toast notifications instead.

The Notification

Consider the examples below.

Here I have created a simple toast notification with the name of the software, what it does, what it is needed for, and a simple instruction to close Outlook before installing. The user can then choose to install it now – and clicking on that button will simply open the Software Center to that application via it’s sharing link. If they click Another time… the notification goes away for now, and if they dismiss it, it will move to the Action Center.

Title Only

In this version, I’ve added a logo instead of a title…

Image Only

…and in this version, I’ve added both.

Title and Image

If the deployment has a deadline, you can state the deadline in the notification as well as tell the user how long they have left before the deadline is reached.

Image with Deadline

Clicking Install now opens that app in the Software Center where the user can go ahead and install it…

Software Center

The big gotcha (for now) is that this only works with Application deployments, and you need to be running ConfigMgr 1706 or later. Please, Microsoft, make sharing links possible for other deployments (packages/programs, task sequences) too!

The client machines also need to be running Windows 10 Anniversary Update or later for the notification to work properly.

The Magic

So how does this work? Well, first we need to disable the inbuilt notifications on the application deployment, so set that to Display in Software Center, and only show notifications for computer restarts in the deployment type on the User Experience tab.

Next, we create a compliance item and compliance baseline which will display the notification. Target the compliance baseline at the same collection/s you are targetting your application.

The compliance item will have a PowerShell discovery script and remediation script. The discovery script will simply detect whether the software has been installed and report compliance if it is. The remediation script contains the code that displays the notification, and will only run if the discovery script does not report compliance, ie the software is not yet installed.

The Code

For the discovery script, create some code that will detect whether the software is installed. For my example, I used the code below which simply checks for the existence of a registry key.


## Discovery script for Veritas Enterprise Vault Outlook Add-in (x64) 12.2.1.1485

$RegKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{0DBA46D1-5D49-4888-BC50-D3DF38F85126}"
If (Test-Path $RegKey)
{
    "Compliant"
}
Else
{
    "Not compliant"
}

It’s important that the script outputs a value whether it’s compliant or not, so you don’t get issues with the instance not being found.

For the remediation script, I created the following code to display a toast notification:

Code Walkthrough

Let’s walk through the code to explain the variables and what it does.

Variables

Title is the notification title that displays more prominently, the name of the software for example.

SoftwareCenterShortcut is the sharing link from your ConfigMgr application. To get this, you simply deploy the application to a machine, go to the Software Center, open the application and in the top-right click the link and copy and paste the link as the variable value.

AudioSource is the sound that displays when the notification appears. There are various options here, see the reference in the script for more info.

SubtitleText and BodyText contain the main wording in the notification.

HeaderFormat is a choice of either:

  1. TitleOnly – this just displays a title in the notification header
  2. ImageOnly – this just displays an image in the notification header
  3. TitleAndImage – this displays both

Base64Image – if you wish to include an image or a logo, use this optional variable. You need to convert an image file to a base64 string first, and code is included in the script for how to do that. You can output the base64 string to a text file and copy and paste it back into the script in this variable.

The reason for encoding the image is simply to avoid any dependencies on files in network locations, setting directory access or requiring internet access. The script will convert the base64 string back to an image file and save it in the user’s temporary directory.

Deadline is an optional parameter. If your deployment has a deadline, you probably want to include that in the notification. Deadline should be a parseable datetime format.

What the Script Does

The script will register PowerShell in the HKCU registry as an application that can display notifications in the Action Center, if it isn’t registered already.

Next it defines the toast notification in XML format. I chose XML to avoid any dependencies on external modules, and it’s actually quite simple to create a notification that way. The schema for toast notification is all documented by Microsoft and you can find a reference in the script.

Next it manipulates the XML a bit depending on whether you chose to display an image or use a deadline etc.

Finally, the notification is displayed.

Duration

The notification uses the reminder scenario so that it stays visible on the screen until the user takes action with it. If this is undesirable, you can change it to a normal notification with either the standard or longer duration. In this case, you need to be sure that the text in the notification can be read in that time frame.

In the toast template XML definition, change the first line from:

<toast scenario=”reminder”>

to either (default duration 5 seconds)

<toast duration=”short”>

or (around 25 seconds)

<toast duration=”long”>

Creating the Compliance Item and Baseline

When creating the compliance item in SCCM, make sure of the following:

  • Supported platforms – should be Windows 10 only. Actually, I have used some features in toast notifications that are only available in the Anniversary Update and later, so don’t target versions less than.
  • User context – make sure the compliance item has the option Run scripts by using the logged on user credentials checked
  • Compliance rule value – the value returned by the script should equal “Compliant
  • Compliance rule remediation – make sure that Run the specified remediation script when this setting is noncompliant is checked

When creating the deployment for the compliance baseline in SCCM, make sure of the following:

  • Remediate noncompliant rules when supported is checked
  • Allow remediation outside the maintenance window is checked (if that is acceptable in your environment)

Conclusion

This is a handy way to create your own notifications for ConfigMgr application deployments in Windows 10 and is fully customizable per application, within the limits of the toast notification schema. If and when Microsoft make sharing links available for task sequences, or packages and programs too, this would become even more useful, for example, sending a custom notification when a Windows 10 version upgrade is available.

PowerShell DeepDive: WPF, Data Binding and INotifyPropertyChanged

PowerShell can be used to create some nice UI front-ends using the Windows Presentation Framework (WPF). You can create anything from a simple popup window to a full-blown, self-contained application. A major concept in WPF is that of data binding. Data binding allows you to create some separation between the design of the UI and data it operates with. Binding UI element attributes to a data source means that when you need to update data in the UI, you don’t need to edit the object property itself, rather you simply update the data source it is bound to. For that to work automatically, you need to bind to a collection type that implements the INotifyPropertyChanged interface. This interface provides a notification mechanism that can notify the bound element when a value in the datasource has changed. The UI can then automatically update to reflect the change.

Without using data binding you need to create some code to update the UI element and tell it what new data to display. In a single-threaded UI you can simply set the object property directly, and in a multi-threaded UI you will invoke the dispatcher on the object, so that the property can be safely updated without contention from another thread.

Maybe this all sound Greek (no offence if you speak Greek!), so lets do a couple of examples to illustrate.

Increment Me!

Here I created a simple function that will display a WPF UI window. Then I created an event handler so that when the button is clicked, the textbox will update. The textbox contains a number that will be incremented every time you click the button.


Add-Type -AssemblyName PresentationFramework

function Create-WPFWindow {
    Param($Hash)

    # Create a window object
    $Window = New-Object System.Windows.Window
    $Window.SizeToContent = [System.Windows.SizeToContent]::WidthAndHeight
    $Window.Title = "WPF Window"
    $window.WindowStartupLocation = [System.Windows.WindowStartupLocation]::CenterScreen
    $Window.ResizeMode = [System.Windows.ResizeMode]::NoResize

    # Create a textbox object
    $TextBox = New-Object System.Windows.Controls.TextBox
    $TextBox.Height = 85
    $TextBox.HorizontalContentAlignment = "Center"
    $TextBox.VerticalContentAlignment = "Center"
    $TextBox.FontSize = 30
    $Hash.TextBox = $TextBox

    # Create a button object
    $Button = New-Object System.Windows.Controls.Button
    $Button.Height = 85
    $Button.HorizontalContentAlignment = "Center"
    $Button.VerticalContentAlignment = "Center"
    $Button.FontSize = 30
    $Button.Content = "Increment Me!"
    $Hash.Button = $Button

    # Assemble the window
    $StackPanel = New-Object System.Windows.Controls.StackPanel
    $StackPanel.Margin = "5,5,5,5"
    $StackPanel.AddChild($TextBox)
    $StackPanel.AddChild($Button)
    $Window.AddChild($StackPanel)
    $Hash.Window = $Window
}

# Create a WPF window and add it to a Hash table
$Hash = @{}
Create-WPFWindow $Hash

# Set the value of the textbox
$Hash.TextBox.Text = [int]0

# Add an event for the button click
$Hash.Button.Add_Click{

    # Get the current value
    [int]$CurrentValue = $Hash.TextBox.Text

    # Increment the value
    $CurrentValue ++

    # Update the text property with the new value
    $Hash.TextBox.Text = $CurrentValue
} 

# Show the window
[void]$Hash.Window.Dispatcher.InvokeAsync{$Hash.Window.ShowDialog()}.Wait()

gif

Since the data type for the text property of the textbox is a string, I cannot increment the value directly, I must first cast it into an integer, then return it back to the textbox where it will again be stored as a string. Since the textbox text property is not bound to a data source, I must update the property directly. In this simple example that is not a problem, but it does mean that I cannot work independently of the data.

Creating a data binding

In the following example I have bound the textbox text property to a data source. Data binding in WPF can be done either in XAML or code, but here we are using code to illustrate how that can be done.

First, we will create a datasource object. This will be what we bind to. Since the datasource must notify the UI when the bound value has changed, we must use a collection that implements the INotifyPropertyChanged interface. You can actually create a custom class in C# to do that, as Ryan Ephgrave nicely describes on his blog. This is quite a flexible method, however it does mean you need to create all the properties used in your datasource up front in the class. A ‘native’ way to do that in .Net is to use an observable collection.

In this example, I am going to use an observable collection with a simple System.Object type, so I can add multiple kinds of object to the collection if I want.


# Create a WPF window and add it to a Hash table
$Hash = @{}
Create-WPFWindow $Hash

# Create a datacontext for the textbox and set it
$DataContext = New-Object System.Collections.ObjectModel.ObservableCollection[Object]
$Text = [int]0
$DataContext.Add($Text)
$hash.TextBox.DataContext = $DataContext

After creating the observable collection, I then add my initial value for the textbox – 0, and finally set the collection as the datacontext for the WPF window.

When using data binding, one can either declare the binding source directly, or set the datacontext for one of the parent elements. When you set the datacontext at the window level for example, this will be inherited by default by any child elements in the window, such as textboxes, panels etc. For more granular control, you can set the datacontext on the object itself rather than at the top-level. In either case, the bound object will use this datacontext.

Next, I create a binding object and pass to it which object in the datasource I want to bind to. In this case, I only have one item in the collection so I can simply reference it using it’s index number, which is [0]. If the collection contains multiple items, you need to keep a reference of which index your items are using.


# Create and set a binding on the textbox object
$Binding = New-Object System.Windows.Data.Binding -ArgumentList "[0]"

Another way to do that would be to set the path property of the binding object:


# Create and set a binding on the textbox object
$Binding = New-Object System.Windows.Data.Binding
$Binding.Path = "[0]"

If you prefer not to use a datacontext, you can also declare a binding source in the binding object instead:


# Create and set a binding on the textbox object
$Binding = New-Object System.Windows.Data.Binding
$Binding.Path = "[0]"
$Binding.Source = $DataContext

Now I need to set the binding mode, which is oneway in the case:


$Binding.Mode = [System.Windows.Data.BindingMode]::OneWay

And then finally I create the binding on the WPF element property. For the overload of the SetBinding() method, I need to provide the object I am binding to ($Hash.TextBox), then the property I am binding to (Text), then the binding object itself.


[void][System.Windows.Data.BindingOperations]::SetBinding($Hash.TextBox,[System.Windows.Controls.TextBox]::TextProperty, $Binding)

Now I can handle the button click event, but this time instead of updating the “text” property directly, I simply update the datasource it is bound to. In this instance, the datasource is an integer not a string, so I can increment it directly.


# Add an event for the button click
$Hash.Button.Add_Click{
    $DataContext[0] ++
} 

And finally now I run the window and the result is the same: the number is incremented with every click of the button. Only the datasource is being updated, but the text property of the textbox is bound to it and updates automatically when notified by the observable collection.

gif3

Here is the full code (using the same WPF Window function):


# Create a WPF window and add it to a Hash table
$Hash = @{}
Create-WPFWindow $Hash

# Create a datacontext for the textbox and set it
$DataContext = New-Object System.Collections.ObjectModel.ObservableCollection[Object]
$Text = [int]0
$DataContext.Add($Text)
$hash.TextBox.DataContext = $DataContext

# Create and set a binding on the textbox object
$Binding = New-Object System.Windows.Data.Binding # -ArgumentList "[0]"
$Binding.Path = "[0]"
$Binding.Mode = [System.Windows.Data.BindingMode]::OneWay
[void][System.Windows.Data.BindingOperations]::SetBinding($Hash.TextBox,[System.Windows.Controls.TextBox]::TextProperty, $Binding)

# Add an event for the button click
$Hash.Button.Add_Click{
    $DataContext[0] ++
} 

# Show the window
[void]$Hash.Window.Dispatcher.InvokeAsync{$Hash.Window.ShowDialog()}.Wait()

Multi-threading using the Dispatcher

Using data binding in this way can simplify the way you design your UI, and can also result in better UI performance, especially when using multiple threads because calling the dispatcher from another thread has a higher overhead. Using background threads is essential whenever you run any code that takes long enough that it will cause the UI to freeze and be unresponsive. Offloading that code to a background thread allows the UI to continue running happily in the main thread.

Does data binding work across threads? Yes it does, although when dealing with additional threads you need to consider the thread safety of the collections you are using as your datasource, as not all collections are inherently thread safe. There are some ways to achieve that though, but that’s for another post. Thread safety is important when you have multiple threads that may wish to access the same shared object at the same time.

For now, to illustrate data binding with a multi-threaded UI, lets first create the same WPF window but we will create and use a background thread to update the UI directly using the dispatcher. Each time the button is clicked, a new thread is created and the UI updated from that thread. The dispatcher allows us to queue work items for the window. This is a safe way to update the UI when using multiple threads as the dispatcher will manage each request in turn ensuring we do not suffer contention.

We are using the same WPF window function as previously. When the button is clicked, we read the current value of the texbox into a variable then increment it. We spin up a background thread and call the dispatcher on the window to update the UI. I’ve also included some code to cleanup completed threads using the Get-Runspace cmdlet (this is only available since PowerShell 5)


# Create a WPF window and add it to a Hash table
$Hash = @{}
Create-WPFWindow $Hash

# Set textbox value
$Hash.TextBox.Text = "0"

# Add an event for the button click
$Hash.Button.Add_Click{

    # Cleanup any completed runspaces
    $FinishedRS = (Get-Runspace | where {$_.RunspaceAvailability -ne "Busy"})
    If ($FinishedRS)
    {
        $FinishedRS.Dispose()
    }

    # Get current textbox value and increment
    [int]$CurrentValue = $Hash.TextBox.Text
    $CurrentValue ++

    # Create and invoke a background thread
    $ScriptBlock = {
        Param($Hash,$CurrentValue)
        $Hash.Window.Dispatcher.Invoke({
            $Hash.TextBox.Text = $CurrentValue
        })
    }
     $PowerShell = [PowerShell]::Create()
    [void]$PowerShell.AddScript($ScriptBlock)
    [void]$PowerShell.AddArgument($Hash)
    [void]$PowerShell.AddArgument($CurrentValue)
    $PowerShell.BeginInvoke()
} 

# Show the window
[void]$Hash.Window.Dispatcher.InvokeAsync{$Hash.Window.ShowDialog()}.Wait()

# Cleanup runspaces
(Get-Runspace | where {$_.RunspaceAvailability -ne "Busy"}).Dispose()

The result is that it works the same as before, but the response is somewhat slower. This is due to the additional overhead of creating a background thread and calling the dispatcher.

singlethreaddispatch

Multi-threading using data binding

Now lets do the same thing and use data binding. In this case, I only need to pass the datasource object to the background thread. When I update it in the background thread, the change is also reflected in the main UI thread.


# Create a WPF window and add it to a Hash table
$Hash = @{}
Create-WPFWindow $Hash

# Create a datacontext for the textbox and set it
$DataContext = New-Object System.Collections.ObjectModel.ObservableCollection[Object]
$Text = [int]0
$DataContext.Add($Text)
$hash.TextBox.DataContext = $DataContext

# Create and set a binding on the textbox object
$Binding = New-Object System.Windows.Data.Binding -ArgumentList "[0]"
$Binding.Mode = [System.Windows.Data.BindingMode]::OneWay
[void][System.Windows.Data.BindingOperations]::SetBinding($Hash.TextBox,[System.Windows.Controls.TextBox]::TextProperty, $Binding)

# Add an event for the button click
$Hash.Button.Add_Click{

    # Cleanup any completed runspaces
    $FinishedRS = (Get-Runspace | where {$_.RunspaceAvailability -ne "Busy"})
    If ($FinishedRS)
    {
        $FinishedRS.Dispose()
    }

    # Create and invoke a background thread
    $ScriptBlock = {
        Param($DataContext)
        $DataContext[0] ++
    }
     $PowerShell = [PowerShell]::Create()
    [void]$PowerShell.AddScript($ScriptBlock)
    [void]$PowerShell.AddArgument($DataContext)
    $PowerShell.BeginInvoke()
} 

# Show the window
[void]$Hash.Window.Dispatcher.InvokeAsync{$Hash.Window.ShowDialog()}.Wait()

# Cleanup runspaces
(Get-Runspace | where {$_.RunspaceAvailability -ne "Busy"}).Dispose()

The result this time is that even though there remains a small overhead in creating and managing a background thread, we get a much quicker response. Score!

singlethreaddb

Binding to the UI

Data binding can also be used to bind to items in the UI itself. In the following example, we have two textboxes. The one on the left is bound to a datasource that is incremented everytime the button is clicked. The one on the right is bound to the text property of the textbox on the left. The result is that both textboxes update incrementally, even though only one is bound to a datasource.

uibinding

This time, in the code, I have created a simple function to create a binding. When binding to a UI element, it is necessary to set both the source and path properties of the binding.


function Create-WPFWindow {
    Param($Hash)

    # Create a window object
    $Window = New-Object System.Windows.Window
    $Window.SizeToContent = [System.Windows.SizeToContent]::WidthAndHeight
    $Window.Title = "WPF Window"
    $window.WindowStartupLocation = [System.Windows.WindowStartupLocation]::CenterScreen
    $Window.ResizeMode = [System.Windows.ResizeMode]::NoResize

    # Create a button object
    $Button = New-Object System.Windows.Controls.Button
    $Button.Height = 85
    $Button.Width = [System.Double]::NaN # "auto" in XAML
    $Button.HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Center
    $Button.VerticalContentAlignment = [System.Windows.VerticalAlignment]::Center
    $Button.FontSize = 30
    $Button.Content = "Increment Me!"
    $Hash.Button = $Button

    # Create a textbox object
    $TextBox1 = New-Object System.Windows.Controls.TextBox
    $TextBox1.Name = "FirstTextBox"
    $TextBox1.Height = 85
    $TextBox1.HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Center
    $TextBox1.VerticalContentAlignment = [System.Windows.VerticalAlignment]::Center
    $TextBox1.FontSize = 30
    $TextBox1.BorderThickness = 0
    $Hash.TextBox1 = $TextBox1

    # Create a textbox object
    $TextBox2 = New-Object System.Windows.Controls.TextBox
    $TextBox2.Height = 85
    $TextBox2.HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Center
    $TextBox2.VerticalContentAlignment = [System.Windows.VerticalAlignment]::Center
    $TextBox2.FontSize = 30
    $TextBox2.BorderThickness = 0
    $Hash.TextBox2 = $TextBox2

    # Assemble the window
    $StackPanel = New-Object System.Windows.Controls.StackPanel
    $StackPanel.Orientation = [System.Windows.Controls.Orientation]::Horizontal
    $StackPanel.AddChild($TextBox1)
    $StackPanel.AddChild($TextBox2)

    $MainStackPanel = New-Object System.Windows.Controls.StackPanel
    $MainStackPanel.Margin = "5,5,5,5"
    $MainStackPanel.AddChild($StackPanel)
    $MainStackPanel.AddChild($Button)
    $Window.AddChild($MainStackPanel)
    $Hash.Window = $Window
}

Function Set-Binding {
    Param($Target,$Property,$Path,$Source)

    $Binding = New-Object System.Windows.Data.Binding
    $Binding.Path = $Path
    $Binding.Mode = [System.Windows.Data.BindingMode]::OneWay
    If ($Source)
    {
        $Binding.Source = $Source
    }
    [void][System.Windows.Data.BindingOperations]::SetBinding($Target,$Property,$Binding)

    # Another way to do it...
    #[void]$Target.SetBinding($Property,$Binding)
}

# Create a WPF window and add it to a Hash table
$Hash = @{}
Create-WPFWindow $Hash

# Create a datasource and set the initial value
$DataSource = New-Object System.Collections.ObjectModel.ObservableCollection[Object]
$DataSource.Add([int]0)
$DataSource.Add([int]0)
$Hash.Window.DataContext = $DataSource

# Bind the first text box to the data source
Set-Binding -Target $Hash.TextBox1 -Property $([System.Windows.Controls.TextBox]::TextProperty) -Path "[0]"

# Bind the second text box to the first textbox, text property
Set-Binding -Target $Hash.TextBox2 -Property $([System.Windows.Controls.TextBox]::TextProperty) -Source $Hash.TextBox1 -Path $([System.Windows.Controls.TextBox]::TextProperty)

# Event: Window Loaded
$Hash.Window.Add_Loaded{

    # Set the textbox widths to half the size of the button
    $Hash.TextBox1, $Hash.TextBox2 | foreach {
        $_.Width = $Hash.Button.ActualWidth / 2
    }

}

# Event: Button Clicked
$Hash.Button.Add_Click{

    # Increment the data source value
    $DataSource[0] ++

}

# Show the window
[void]$Hash.Window.Dispatcher.InvokeAsync{$Hash.Window.ShowDialog()}.Wait()

Learn your times tables!

Finally, my last example is a simple tool to help you learn your multiplication tables (you know you need reminding 😉 ). Here, the 3 text boxes with numbers are all bound to a datasource. When a button is clicked, the value is incremented or decremented in the datasource, and this is reflected in the UI by the binding. The result is then calculated based on the new values, and this is done by handling the TextChanged event on the first 2 text boxes. Again, the UI is never being updated directly, only the datasource is being changed, but the UI also updates because of the bindings.

multipier

The code for this is a bit long because I am creating the entire WPF window the ‘old-school’ way – in code, rather than in XAML. In practice, it is much better to create the UI definition in XAML, and then you will use a combination of XAML and PowerShell code to manage the UI.


function Create-WPFWindow {
    Param($Hash)

    # Create a window object
    $Window = New-Object System.Windows.Window
    $Window.SizeToContent = [System.Windows.SizeToContent]::WidthAndHeight
    $Window.Title = "Multiplication Tables"
    $window.WindowStartupLocation = [System.Windows.WindowStartupLocation]::CenterScreen
    $Window.ResizeMode = [System.Windows.ResizeMode]::NoResize

    # Create the first value textbox
    $TextBoxProperties = @{Height = 85; Width = 60; HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Center; VerticalContentAlignment = [System.Windows.VerticalAlignment]::Center; FontSize = 30; BorderThickness = 0; IsReadOnly = $True}
    $TextBox1 = New-Object System.Windows.Controls.TextBox
    $TextBoxProperties.GetEnumerator() | foreach {
        $TextBox1.$($_.Name) = $_.Value
    }
    $Hash.TextBox1 = $TextBox1

    # Create the "x" textbox
    $TextBoxProperties = @{Height = 85; Width = 40; HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Center; VerticalContentAlignment = [System.Windows.VerticalAlignment]::Center; FontSize = 30; BorderThickness = 0; Text = "x"; IsReadOnly = $True}
    $TextBox2 = New-Object System.Windows.Controls.TextBox
    $TextBoxProperties.GetEnumerator() | foreach {
        $TextBox2.$($_.Name) = $_.Value
    }
    $Hash.TextBox2 = $TextBox2

     # Create the second value textbox
    $TextBoxProperties = @{Height = 85; Width = 60; HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Center; VerticalContentAlignment = [System.Windows.VerticalAlignment]::Center; FontSize = 30; BorderThickness = 0; IsReadOnly = $True }
    $TextBox3 = New-Object System.Windows.Controls.TextBox
    $TextBoxProperties.GetEnumerator() | foreach {
        $TextBox3.$($_.Name) = $_.Value
    }
    $Hash.TextBox3 = $TextBox3

    # Create the "=" textbox
    $TextBoxProperties = @{Height = 85; Width = 40; HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Center; VerticalContentAlignment = [System.Windows.VerticalAlignment]::Center; FontSize = 30; BorderThickness = 0; Text = "="; IsReadOnly = $True }
    $TextBox4 = New-Object System.Windows.Controls.TextBox
    $TextBoxProperties.GetEnumerator() | foreach {
        $TextBox4.$($_.Name) = $_.Value
    }
    $Hash.TextBox4 = $TextBox4

    # Create the calculated value textbox
    $TextBoxProperties = @{Height = 85; Width = 80; HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Center; VerticalContentAlignment = [System.Windows.VerticalAlignment]::Center; FontSize = 30; BorderThickness = 0; IsReadOnly = $True}
    $TextBox5 = New-Object System.Windows.Controls.TextBox
    $TextBoxProperties.GetEnumerator() | foreach {
        $TextBox5.$($_.Name) = $_.Value
    }
    $Hash.TextBox5 = $TextBox5

    # Create the first "+" button
    $ButtonProperties = @{Height = 30; Width = 30; HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Center; VerticalContentAlignment = [System.Windows.VerticalAlignment]::Top; FontSize = 20; Content = "+"; Margin = "5,0,0,0"}
    $Button1 = New-Object System.Windows.Controls.Button
    $ButtonProperties.GetEnumerator() | foreach {
        $Button1.$($_.Name) = $_.Value
    }
    $Hash.Button1 = $Button1

    # Create the first "-" button
    $ButtonProperties = @{Height = 30; Width = 30; HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Center; VerticalContentAlignment = [System.Windows.VerticalAlignment]::Top; FontSize = 20; Content = "-"; Margin = "5,0,0,0"}
    $Button2 = New-Object System.Windows.Controls.Button
    $ButtonProperties.GetEnumerator() | foreach {
        $Button2.$($_.Name) = $_.Value
    }
    $Hash.Button2 = $Button2

    # Create the second "+" button
    $ButtonProperties = @{Height = 30; Width = 30; HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Center; VerticalContentAlignment = [System.Windows.VerticalAlignment]::Top; FontSize = 20; Content = "+"; Margin = "28,0,0,0"}
    $Button3 = New-Object System.Windows.Controls.Button
    $ButtonProperties.GetEnumerator() | foreach {
        $Button3.$($_.Name) = $_.Value
    }
    $Hash.Button3 = $Button3

    # Create the second "-" button
    $ButtonProperties = @{Height = 30; Width = 30; HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Center; VerticalContentAlignment = [System.Windows.VerticalAlignment]::Top; FontSize = 20; Content = "-"; Margin = "5,0,0,0"}
    $Button4 = New-Object System.Windows.Controls.Button
    $ButtonProperties.GetEnumerator() | foreach {
        $Button4.$($_.Name) = $_.Value
    }
    $Hash.Button4 = $Button4

     # Create the reset button
    $ButtonProperties = @{Height = 30; Width = 60; HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Center; VerticalContentAlignment = [System.Windows.VerticalAlignment]::Top; FontSize = 20; Content = "Reset"; Margin = "40,0,0,0"}
    $Button5 = New-Object System.Windows.Controls.Button
    $ButtonProperties.GetEnumerator() | foreach {
        $Button5.$($_.Name) = $_.Value
    }
    $Hash.Button5 = $Button5

    # Assemble the first stackpanel
    $StackPanel = New-Object System.Windows.Controls.StackPanel
    $StackPanel.Orientation = [System.Windows.Controls.Orientation]::Horizontal
    $TextBox1, $TextBox2, $TextBox3, $TextBox4, $TextBox5 | foreach {
        $StackPanel.AddChild($_)
    }

    # Assemble the second stackpanel
    $StackPanel2 = New-Object System.Windows.Controls.StackPanel
    $StackPanel2.Orientation = [System.Windows.Controls.Orientation]::Horizontal
    $Button1, $Button2, $Button3, $Button4, $Button5 | foreach {
        $StackPanel2.AddChild($_)
    }

    # Assemble the window
    $MainStackPanel = New-Object System.Windows.Controls.StackPanel
    $MainStackPanel.Margin = "5,5,5,5"
    $MainStackPanel.AddChild($StackPanel)
    $MainStackPanel.AddChild($StackPanel2)
    $Window.AddChild($MainStackPanel)
    $Hash.Window = $Window
}

Function Set-Binding {
    Param($Target,$Property,$Path,$Source)

    $Binding = New-Object System.Windows.Data.Binding
    $Binding.Path = $Path
    $Binding.Mode = [System.Windows.Data.BindingMode]::OneWay
    If ($Source)
    {
        $Binding.Source = $Source
    }
    [void][System.Windows.Data.BindingOperations]::SetBinding($Target,$Property,$Binding)

    # Another way to do it...
    #[void]$Target.SetBinding($Property,$Binding)
}

# Create a WPF window and add it to a Hash table
$Hash = @{}
Create-WPFWindow $Hash

# Create a datasource and set the initial values
$DataSource = New-Object System.Collections.ObjectModel.ObservableCollection[Object]
$DataSource.Add([int]1)
$DataSource.Add([int]1)
$DataSource.Add([int]1)
$Hash.Window.DataContext = $DataSource

# Bind the value text boxes to the data source
Set-Binding -Target $Hash.TextBox1 -Property $([System.Windows.Controls.TextBox]::TextProperty) -Path "[0]"
Set-Binding -Target $Hash.TextBox3 -Property $([System.Windows.Controls.TextBox]::TextProperty) -Path "[1]"
Set-Binding -Target $Hash.TextBox5 -Property $([System.Windows.Controls.TextBox]::TextProperty) -Path "[2]"

# Events: Button Clicks
$Hash.Button1.Add_Click{
    # Increment
    $DataSource[0] ++
}

$Hash.Button2.Add_Click{
    # Decrement
    $DataSource[0] --
}

$Hash.Button3.Add_Click{
    # Increment
    $DataSource[1] ++
}

$Hash.Button4.Add_Click{
    # Decrement
    $DataSource[1] --
}

$Hash.Button5.Add_Click{
    # Reset Values to 1
    $DataSource[0] = [int]1
    $DataSource[1] = [int]1
}

# Events: TextBox values changed
$Hash.TextBox1, $Hash.TextBox3, $hash.TextBox5 | foreach {
    $_.Add_TextChanged{
        # Calculate
        $DataSource[2] = $DataSource[0] * $DataSource[1]
    }
}

# Show the window
[void]$Hash.Window.Dispatcher.InvokeAsync{$Hash.Window.ShowDialog()}.Wait()

Conclusion

Data binding can get much more complex than this including two-way bindings, priority binding and multiple bindings, but hopefully these examples will whet your appetite to explore it further. When using data binding in your UI you are taking a step towards implementing the MVVM, a kind of best-practice concept for designing UI applications. Whether you really need to use data binding or not depends a lot on the type of UI you are creating and what it does, but certainly consider taking advantage of it to create a more efficient and logically designed PowerShell UI.

Have fun!

Explore WPF Controls with PowerShell

If you’ve ever tried creating a tool or an application with WPF, you know that the built-in controls contain many properties, methods and events, and finding the right one to use can be, well, fun!

As an aid to creating WPF applications, I created this simple tool which explores the various controls and exposes their properties, methods and events. It’s handy as a quick reference, or as a convenient way to get familiar with the various controls.

The tool is a PowerShell script, so simply download the script, right-click and ‘Run with PowerShell’.

More info here: https://smsagent.wordpress.com/tools/wpf-control-explorer/

wpf-controlexplorer

New Free App – ConfigMgr Deployment Reporter

Just released a new free application for ConfigMgr admins – ConfigMgr Deployment Reporter.  I developed this app for use in the organisation I currently work for, and it turned out quite well, so I decided to release a public version to the community!

capture

I developed this app as an alternative (and IMO easier) way to report on ConfigMgr deployments than using the ConfigMgr console. It uses a little different format than the console node allowing you to select which deployment you wish to view data for based on the “feature type” (ie application, package etc) and report on only that deployment.  It also introduces a separation of results between all applicable systems for a deployment, and only those systems which have currently reported status, which allows for a more accurate view of the success of a deployment as it progresses.

The app allows the creation of charts and HTML-format reports to give a nice graphical snapshot of a deployment.

I also added the capability to report per-device for Software Update and Task Sequence deployments.  For Software Updates, this allows you to see which updates from the deployment are applicable to the machine and the status of each update, and for Task Sequences it allows viewing the execution status of each step in the task sequence for the selected device.

As usual, I code purely in PowerShell using WPF for the UI.  This time I added metro styling using the excellent MahApps.Metro project 🙂

Download the app from here.

Detect an Active VPN Adapter During ConfigMgr Deployments

A common requirement with ConfigMgr deployments is to exclude clients that are connected to the corporate network via a VPN, when the total size of the content files for the deployment are too much to be throwing down a slow network link. There is more than one way to do this, but I have seen that not all are reliable and do not work in every case or for every VPN adapter out there.

For example, using PowerShell, you can run either of the following WMI queries to potentially detect an active VPN adapter (your VPN adapter description may be different):

Using Win32_NetworkAdapter


Get-WmiObject -Query "Select * from Win32_NetworkAdapter where Name like '%VPN%' and NetEnabled='True'"

Using Win32_NetworkAdapterConfiguration


Get-WmiObject -Query "Select * from Win32_NetworkAdapterConfiguration where Description like '%VPN%' and IPEnabled='True'"

Since Windows 8 / Server 2012 you can also use the Get-VPNConnection cmdlet:


(Get-VpnConnection -AllUserConnection).where{$_.Name -like "*VPN*" -and $_.ConnectionStatus -eq "Connected"}

Another method is simply:


ipconfig | Select-String 'PPP adapter'

But my preferred method is to check the IPv4 routing table. This is because VPN connections typically use their own subnet, so when connected they will add entries to the IP routing table for that subnet, and will remove them again when disconnected. If you know the subnets used by your VPN connections, you can query for them in WMI:


Get-WmiObject -Query "Select * from Win32_IP4RouteTable where Name like '10.0.99.%' or Name like '10.15.99.%' 

To use this with Application deployments in ConfigMgr, you can create a Global Condition with a script setting.  This condition could be used either to target or to exclude systems using VPN:

capture

Here is an example script that returns “VPN-Active” or “VPN-InActive” based on whether a VPN subnet is detected:


If (Get-WmiObject -Query "Select * from Win32_IP4RouteTable where Name like '10.0.99.%' or Name like '10.15.99.%'")
    {Write-host "VPN-Active"}
Else {Write-host "VPN-InActive"}

You can then add this as a requirement to an application:

capture

For task sequences, you can use a WMI query condition:

WMI Query


Select * from Win32_IP4RouteTable where Name like '10.0.99.%' or Name like '10.15.99.%'

 

capture

The only concession is if your VPN subnets ever change, you will need to update them in ConfigMgr.

New Tool: System Explorer for Windows

Today I am pleased to release a new tool for enterprises and home users alike: System Explorer for Windows.  This application can be used to view detailed system and hardware data for a local or remote computer by exposing WMI Win32 classes in an easy-to-use Graphical User Interface.  For enterprises that use System Center Configuration Manager, the application can connect to the SCCM database to allow viewing of hardware inventory data even if the target system is not currently online.

capture
System Explorer for Windows – Client WMI view

Check it out here: https://smsagent.wordpress.com/tools/system-explorer-for-windows/

 

Creating WPF GUI Applications with Pure PowerShell

When creating a GUI application in PowerShell, I usually use Visual Studio, or Blend for Visual Studio, to design a WPF application, then copy and run the XAML code in PowerShell.  Designing in VS is generally easier and quicker and creates less code, but it is also perfectly possible to create a WPF GUI using pure PowerShell, which is more akin to the Windows Forms method of GUI creation.  For more complex applications that’s not the ideal way because it takes longer and creates a lot more code, but for simple applications, or if you are used to designing something in Windows Forms, why not give it a go?  You simply need to create a window from the [System.Windows] .Net namespace, then add some controls from the [System.Windows.Controls] namespace, and you’re away!

Here’s a simple example application that displays the running processes on your machine, the list of services, and a little game “Click the Fruit” as a bonus 😉

capture


# Add required assembly
Add-Type -AssemblyName PresentationFramework

# Create a Window
$Window = New-Object Windows.Window
$Window.Height = "670"
$Window.Width = "700"
$Window.Title = "PowerShell WPF Window"
$window.WindowStartupLocation="CenterScreen"

# Create a grid container with 2 rows, one for the buttons, one for the datagrid
$Grid =  New-Object Windows.Controls.Grid
$Row1 = New-Object Windows.Controls.RowDefinition
$Row2 = New-Object Windows.Controls.RowDefinition
$Row1.Height = "70"
$Row2.Height = "100*"
$grid.RowDefinitions.Add($Row1)
$grid.RowDefinitions.Add($Row2)

# Create a button to get running Processes
$Button_Processes = New-Object Windows.Controls.Button
$Button_Processes.SetValue([Windows.Controls.Grid]::RowProperty,0)
$Button_Processes.Height = "50"
$Button_Processes.Width = "150"
$Button_Processes.Margin = "10,10,10,10"
$Button_Processes.HorizontalAlignment = "Left"
$Button_Processes.VerticalAlignment = "Top"
$Button_Processes.Content = "Get Processes"
$Button_Processes.Background = "Aquamarine"

# Create a button to get services
$Button_Services = New-Object Windows.Controls.Button
$Button_Services.SetValue([Windows.Controls.Grid]::RowProperty,0)
$Button_Services.Height = "50"
$Button_Services.Width = "150"
$Button_Services.Margin = "180,10,10,10"
$Button_Services.HorizontalAlignment = "Left"
$Button_Services.VerticalAlignment = "Top"
$Button_Services.Content = "Get Services"
$Button_Services.Background = "Aquamarine"

# Create a button to play Click the fruit
$Button_Cool = New-Object Windows.Controls.Button
$Button_Cool.SetValue([Windows.Controls.Grid]::RowProperty,0)
$Button_Cool.Height = "50"
$Button_Cool.Width = "150"
$Button_Cool.Margin = "350,10,10,10"
$Button_Cool.HorizontalAlignment = "Left"
$Button_Cool.VerticalAlignment = "Top"
$Button_Cool.Content = "Play 'Click the Fruit'"
$Button_Cool.Background = "Aquamarine"

# Create a datagrid
$DataGrid = New-Object Windows.Controls.DataGrid
$DataGrid.SetValue([Windows.Controls.Grid]::RowProperty,1)
$DataGrid.MinHeight = "100"
$DataGrid.MinWidth = "100"
$DataGrid.Margin = "10,0,10,10"
$DataGrid.HorizontalAlignment = "Stretch"
$DataGrid.VerticalAlignment = "Stretch"
$DataGrid.VerticalScrollBarVisibility = "Auto"
$DataGrid.GridLinesVisibility = "none"
$DataGrid.IsReadOnly = $true

# Add the elements to the relevant parent control
$Grid.AddChild($DataGrid)
$grid.AddChild($Button_Processes)
$grid.AddChild($Button_Services)
$grid.AddChild($Button_Cool)
$window.Content = $Grid

# Add an event on the Get Processes button
$Button_Processes.Add_Click({
    $Processes = Get-Process | Select ProcessName,HandleCount,NonpagedSystemMemorySize,PrivateMemorySize,WorkingSet,UserProcessorTime,Id
    $DataGrid.ItemsSource = $Processes
    })

# Add an event on the Get Services button
$Button_Services.Add_Click({
    $Services = Get-Service | Select Name,ServiceName,Status,StartType
    $DataGrid.ItemsSource = $Services
    })

# Add an event to play Click the fruit
$Button_Cool.Add_Click({

    $Fruit = @{
        Apples = "Green"
        Bananas = "Yellow"
        Oranges = "Orange"
        Plums = "Maroon"
    }

    $Fruit.GetEnumerator() | Foreach {
        # Create a transparent window at a random location on the screen
        $NewWindow = New-Object Windows.Window
        $NewWindow.SizeToContent = "WidthAndHeight"
        $NewWindow.AllowsTransparency = $true
        $NewWindow.WindowStyle = "none"
        $NewWindow.Background = "Transparent"
        $NewWindow.WindowStartupLocation = "Manual"
        $Height = Get-Random -Maximum (([System.Windows.SystemParameters]::PrimaryScreenHeight) - 100)
        $Width = Get-Random -Maximum (([System.Windows.SystemParameters]::PrimaryScreenWidth) - 100)
        $NewWindow.Left = $Width
        $NewWindow.Top = $Height

        # Add a label control for the fruit
        $NewLabel = New-Object Windows.Controls.Label
        $NewLabel.Height = "150"
        $NewLabel.Width = "400"
        $NewLabel.Content = $_.Name
        $NewLabel.FontSize = "100"
        $NewLabel.FontWeight = "Bold"
        $NewLabel.Foreground = $_.Value
        $NewLabel.Background = "Transparent"
        $NewLabel.Opacity = "100"

        # Add an event to close the window when clicked
        $NewWindow.Add_MouseDown({
            $This.Close()
        })

        # Add an event to change the cursor to a hand when the mouse goes over the window
        $NewWindow.Add_MouseEnter({
        $this.Cursor = "Hand"
        })

        # Display the window
        $NewWindow.Content = $NewLabel
        $NewWindow.ShowDialog()
    }
})

# Show the window
if (!$psISE)
{
    # Hide PS console window
    $windowcode = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'
    $asyncwindow = Add-Type -MemberDefinition $windowcode -name Win32ShowWindowAsync -namespace Win32Functions -PassThru
    $null = $asyncwindow::ShowWindowAsync((Get-Process -PID $pid).MainWindowHandle, 0) 

    # Run as an application in it's own context
    $app = [Windows.Application]::new()
    $app.Run($Window)
}
Else
{
    [void]$window.ShowDialog()
}