PowerShell is first and foremost a Shell – a command line environment. The latest Insider builds of Windows 10 now have it as the default Shell. Those of us who have been along for the ride for the last 10 years had maybe hoped this would happen sooner, but it’s definitely coming. PowerShell was conceived as an answer to the over-use of GUIs in system management.
Having said that, there are many situations where GUIs are useful. PowerShell caters to those situations through its deep relationship with the .NET Framework. As usual with PowerShell there’s more than one way to do it.
Many scripting languages like Perl and Python offer access to GUI elements. You build a Form object, assign various controls, all in code. It can be laborious to do anything substantial. PowerShell offers that same approach using the Windows Forms class library and if you’re familiar with GTK+ and other similar technologies this could be the way for you.
There is another way. The Windows Presentation Foundation class library (henceforth WPF) is also available in the .NET Framework. WPF uses XAML (Extensible Application Mark up Language) to describe graphical elements. You can use visual design tools to create your GUI and then import it into your PowerShell solution. Microsoft Visual Studio products (including commercial, Community and Express versions, but not VS Code) include visual XAML editors that allow drag and drop design.
For the purposes of this series I will step through the process of designing a GUI for a PowerShell script that gathers some system information. Disclaimer: this script will concentrate on the GUI elements at the expense of functionality and error checking.
The PowerShell script:
$System = Get-WMIObject -Class Win32_ComputerSystem
Obviously we can output any of the $System object’s properties to the console, but for the purposes of the article we’ll build a WPF GUI to display them (we could also use Out-GridView).
I’m going to use Visual Studio Community 2015 to do the visual design (other design tools are available, including KAXAML http://kaxaml.com/).
Once I’ve created a new WPF project I’m presented with a blank WPF form to work with. I have a graphical view in the top half of the window and a textual view of the underlying XAML below that. Changes made in either window are reflected in the other. I drag a TextBox control onto the form and use the graphical window to size and position it, then set a fixed-width font. Once I’m happy with the layout I copy the XAML code onto the clipboard and switch to my PowerShell script. I create a Here String and paste the XAML into it like so:
[xm]$Xaml=@" <Window x:Class="SimpleWindow.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:SimpleWindow" mc:Ignorable="d" Title="MainWindow" Height="150" Width="350"> <Grid> <TextBox x:Name="TextBox" FontFamily="Consolas" /> </Grid> </Window> "@
To make everything work correctly I need to remove the text
x:Class="SimpleWindow.MainWindow" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:SimpleWindow" mc:Ignorable="d"
from the XAML, along with the
from the Name attribute of the TextBox.
At the top of my script I add a reference to the WPF class library:
Add-Type -AssemblyName PresentationFramework
After the Here String I add code to load the XAML:
$reader = New-Object System.Xml.XmlNodeReader($Xaml) $Window = [Windows.Markup.XamlReader]::Load($reader)
Now I have a XAML GUI nearly ready for use. In order to put text into the text box I need a reference to the TextBox object. I can get this using the FindName method of our $Window object:
$TextBox = $Window.FindName("TextBox")
Now I can take whatever properties I want and send them to the text box, then show the $Window object on the screen.
$TextBox.Text = $System | Select-Object Name, Model, Manufacturer | Out-String $Window.ShowDialog()
That’s all there is to it!
In part 2 I’ll look at some of the obvious shortcomings of this simple example.