Building a PowerShell GUI (Part 2)

If you would like to read the other parts in this article series please go to:

In my previous article, I explained that it is possible to build PowerShell scripts that have a graphical user interface, but that in order to do so you will have to delve into the world of Visual Studio. In this article, it’s time to get our collective hands dirty and get to work building a graphical interface.

For the purposes of this article, I am assuming that you already have a PowerShell script that you want to use. For the sake of demonstration, I will start with a simple script, but everything that I will be showing you can be applied to more complex scripts.

So to get started, go ahead and launch Visual Studio. All of my instructions in this article will be based around the use of Visual Studio 2015 Professional with Update 1. When Visual Studio starts, click on the New Project link.

At this point, you will see a really cluttered screen asking you what type of project you want to create. Type WPF into the Search box (I am assuming that Visual Studio is configured for general use) and then choose the WPF Application option (I will be using the template for Visual C#).

If you think back to the previous article, you will recall that I mentioned that we will be using PowerShell and XAML, but I never said anything about WPF. WPF is the Windows Presentation Foundation. It is a part of the .NET Framework and is a rendering engine for graphical applications. As we work with WPF, we will be creating the required XAML file.

So with that said, go ahead and give your project a custom name and click OK. As you can see in Figure A, I am calling my project PowerShellGUISample.

Figure A: Choose the WPF Application template and assign a name to your project.
Figure A: Choose the WPF Application template and assign a name to your project.

Now you will be taken to the screen that you will be using to develop your project. If you look closely at Figure B, you will notice that there is some XAML code shown at the bottom of the screen. Although this code does not do anything, you will eventually be able to copy and paste code directly from this window into an XAML file.

Figure B: This is the screen that is used for developing the project.
Figure B: This is the screen that is used for developing the project.

With that said, I want to get started by showing you how to build a really simple GUI. Once this GUI is functional then my plan is to show you how to create something more useful.

Begin the process by creating two folders on your hard disk. One of these folders should be called Forms and the other should be called Scripts. The Forms folder will store the XAML files that you create, while the Scripts folder will store PowerShell scripts. Technically, you don’t have to use two different folders, and you can use any folder names that you want, but it is generally advisable to store scripts in a separate location from XAML files.

Now, let’s create the first PowerShell script. This script should reside in the C:\Scripts folder and should be called LoadDialog.ps1. This is the script that does all of the heavy lifting. The script brings together the Windows Presentation Foundation and various .NET assemblies in order to render the graphical interface. The good news is that you do not have to create this form from scratch. Microsoft has already built the script for you. In fact, the simple GUI interface that I am going to show you how to create borrows heavily from this article. Once I’ve covered the basics then I will of course give you some code that is more original. With that said, here is the code that you will need to add to the LoadDialog.ps1 file:

[code]
[CmdletBinding()]

Param(
[Parameter(Mandatory=$True,Position=1)]
[string]$XamlPath
)

[xml]$Global:xmlWPF = Get-Content -Path $XamlPath

#Add WPF and Windows Forms assemblies

try{
Add-Type -AssemblyName PresentationCore,PresentationFramework,WindowsBase,system.windows.forms
} catch {
Throw "Failed to load Windows Presentation Framework assemblies."
}

#Create the XAML reader using a new XML node reader

$Global:xamGUI = [Windows.Markup.XamlReader]::Load((new-object System.Xml.XmlNodeReader $xmlWPF))

#Create hooks to each named object in the XAML

$xmlWPF.SelectNodes("//*[@Name]") | %{
Set-Variable -Name ($_.Name) -Value $xamGUI.FindName($_.Name) -Scope Global
}
[/code]

The next thing that we have to do is to design the GUI interface. As I said earlier, I am going to start out by partially following the Microsoft example. To create the GUI, switch back over to Visual Studio and then open the Toolbox by clicking on the Toolbox link on the left side of the screen (you can also use the View menu). When you open the toolbox, you will see a series of items that you can add to the GUI. Simply drag these items from the toolbox to the GUI. For the sake of demonstration, I have added a label and a button, as shown in Figure C.

Image
Figure C: I have added a label and a button to the GUI.

As you add these elements to the GUI, the source code at the bottom of the screen is updated automatically. Go ahead and copy the source code into a text file and then save the file into the C:\Forms folder as MyForm.XAML. Here is the code that was generated by Visual Studio:

[code]
<Window x:Class="PowerShellGUISample.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:PowerShellGUISample" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525">

<Grid>

<Button x:Name="button" Content="Button" HorizontalAlignment="Left" Margin="249,132,0,0" VerticalAlignment="Top" Width="75"/>

<Label x:Name="label" Content="Label" HorizontalAlignment="Left" Margin="88,73,0,0" VerticalAlignment="Top"/>

</Grid>

</Window>
[/code]

As it stands right now, this code is almost usable, but not quite. We have to make a few minor modifications.

As you look at the code above, you will notice that there is a line that starts with <Button and a line that starts with <Label. Both of these lines include x:Name=. In order to make this code work correctly, you will need to get rid of X:. It is also very important to understand that whatever name you choose to assign through the Name attribute will become the name of a PowerShell variable later on. For the sake of demonstration, I will use the names MyButton and MyLabel.

We also need to make is to delete these lines:

[code]
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

xmlns:local="clr-namespace:PowerShellGUISample"

mc:Ignorable="d"
[/code]

Finally, we have to get rid of the X:Class section in the first line. Here is what the modified code looks like:

[code]
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525">
<Grid Background="#FFB7C9C4">
<Button Name="MyButton" Content="Button" HorizontalAlignment="Left" Margin="249,132,0,0" VerticalAlignment="Top" Width="75" Background="#FF057DF5" Foreground="#FFFDF9F9" BorderBrush="#FF171717"/>
<Label Name="MyLabel" Content="Label" HorizontalAlignment="Left" Margin="88,73,0,0" VerticalAlignment="Top"/>

</Grid>
</Window>

[/code]

The last thing that we need to do is to create our main PowerShell script. I am going to call this script HelloWorld.ps1. Since I am trying to keep this simple, the Hello World script is very short and straightforward. Here is the script that I am using:

[code]
#Required to load the XAML form and create the PowerShell Variables
./LoadDialog.ps1 -XamlPath ‘C:\Forms\MyForm.xaml’

#EVENT Handler
$MyButton.add_Click({
$MyLabel.Content = “Hello World”
})

#Launch the window
$xamGUI.ShowDialog() | out-null
[/code]

The first section loads the LoadDialog.ps1 file and the MyForm.xaml file. The second section is an event handler. This section tells PowerShell to replace the contents of the label with the phrase Hello World when the button is clicked. The final section is what causes the GUI to actually be displayed. I realize that I have given you a very brief description of what this script does, but I will be covering the script in greater depth when we adapt it to a more useful purpose later on.

For right now, I want to show you what happens when we run the HelloWorld.ps1 script. Before executing the script, be sure to set PowerShell’s execution policy to allow the script to run. With that said, running the script will produce the screen shown in Figure D. Notice that the text says Label.

Image
Figure D: This is what is displayed when the script is run.

When we click the button, Label is replaced by Hello World, as shown in Figure E.

Image
Figure E: Clicking the button causes Hello World to be displayed.

Conclusion

In this article, I have shown you how to create a very simple PowerShell GUI. You can download the working script files in a zip package from here.

In my next article, I want to begin showing you how to make the GUI do something more useful.

28 thoughts on “Building a PowerShell GUI (Part 2)”

  1. Great article! I am working my way through your tutorial. I did want to make you aware of an error received from LoadDialog. The smlWPF variable cannot be a string and is easily fixed using this line instead:
    [xml]$Global:xmlWPF = Get-Content -Path $XamlPath

  2. Hey,

    Getting stuck on :

    ./LD.ps1 -XamlPath ‘C:\Dev\infobox\interface.xaml’

    I get :

    ./LD.ps1 : The term ‘./LD.ps1’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check
    the spelling of the name, or if a path was included, verify that the path is correct and try again.

    Both scripts are in the same directory.

    Could you advise anything at this point?

  3. That error message means that PowerShell is having trouble locating the script. I would recommend making sure that you are in the script’s folder when you run the command. If that doesn’t work, then check the script’s filename to make sure that it is LD.ps1. It’s a good idea to make sure that your computer is configured to show filename extensions. I have occasionally encountered situations in which a hidden filename extension kept a PowerShell script from running. Good luck!

  4. So, pretty much did a copy/paste of the examples, and got:
    At H:\Notes\scripts\LoadDialog.ps1:20 char:21
    + $xmlWPF.SelectNodes( “//*[@Name]” ) | %{
    + ~
    Missing ‘)’ in method call.
    At H:\Notes\scripts\LoadDialog.ps1:20 char:22
    + $xmlWPF.SelectNodes( “//*[@Name]” ) | %{
    + ~~~~~~~~~~~~~~~~
    Unexpected token ‘“//*[@Name]”’ in expression or statement.
    At H:\Notes\scripts\LoadDialog.ps1:20 char:39
    + $xmlWPF.SelectNodes( “//*[@Name]” ) | %{
    + ~
    Unexpected token ‘)’ in expression or statement.
    At H:\Notes\scripts\LoadDialog.ps1:20 char:41
    + $xmlWPF.SelectNodes( “//*[@Name]” ) | %{
    + ~
    An empty pipe element is not allowed.
    + CategoryInfo : ParserError: (:) [], ParseException
    + FullyQualifiedErrorId : MissingEndParenthesisInMethodCall

    Any thoughts on what’s going on? I did check my source against the LoadDialog script, and they are identical…

  5. ***Update. Never mind. the fance quotation marks used in the script above were the problem… Once I converted
    “ blah ” to “Blah” everything worked. lol

  6. Hi Brien,

    I appreciate you providing all the detail here. It’s a great tutorial. Unfortunately, I’m stumped here. I’m on Part 2 of your tutorial and I keep getting an error when I run the application (see below). I ran it on my local Windows 7 PC that has Powershell v2.0 and a Windows 2012 R2 server that has Powershell 5.0 installed. I’m using Visual Studio Community 2017.

    ———————————————————————————————–

    New-Object : Cannot find an overload for “XmlNodeReader” and the argument count: “13”.
    At C:\Scripts\LoadDialog.ps1:17 char:63
    + $Global:xamGUI = [Windows.Markup.XamlReader]::Load((new-object <<<< System.Xml.XmlNodeReader $xmlWPF))
    + CategoryInfo : InvalidOperation: (:) [New-Object], MethodException
    + FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand

    Method invocation failed because [System.Object[]] doesn't contain a method named 'SelectNodes'.
    At C:\Scripts\LoadDialog.ps1:20 char:20
    + $xmlWPF.SelectNodes <<<< ("//*[@Name]") | %{
    + CategoryInfo : InvalidOperation: (SelectNodes:String) [], RuntimeException
    + FullyQualifiedErrorId : MethodNotFound

    ———————————————————————————————–
    Any feedback you could provide would be greatly appreciated.

    Thanks,

    Stu

  7. I haven’t ever seen that particular error before, and I’m not sure what is going on. My advice would be to move forward with the rest of the series. In one of the later articles (I can’t remember which one), I show a completely different method for building a GUI. That method has fewer dependencies and is easier to use.

  8. Brien,

    Thanks for the quick response. I pushed on, like you suggested, and have been able to complete my script. I appreciate your help!

    Stu

  9. Hello Brien, I am trying to follow your guide but I cannot access the LoadDialog.ps1 from the hyperlink you gave us, it redirect to the year 2014 of the Scripting Guy Blog and there is not direct link/information to get the script.

    Thanks

  10. Hi Valentin,
    I would recommend moving on to the next article in the series. A few articles in, I decided that it would be better to use a simpler method for creating a GUI. I think that you will find that the alternate method works a lot better for you.

  11. It really bothers me that so many people are getting this error. I want to try to figure out what is going on. Can you tell me the exact message that PowerShell is giving you, along with any information that it provides as to where in the script the error is occurring?
    Is there any chance that the error is occurring because of a security setting? When I wrote the script, I ran it with full administrative credentials.

  12. Hello Brien,
    I get this same error. I am using PowerShell ISE to test with and ran it as admin. I also made sure to set my Execution Policy to “Unrestricted” but I get the same error:

    ______________________________________________________________________

    new-object : Cannot find an overload for “XmlNodeReader” and the argument count: “10”.
    At U:\test_GUI_scripts\Scripts\loadDialog.ps1:17 char:53
    + … rkup.XamlReader]::Load((new-object System.Xml.XmlNodeReader $xmlWPF))
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [New-Object], MethodException
    + FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand

    Method invocation failed because [System.String] does not contain a method named ‘SelectNodes’.
    At U:\test_GUI_scripts\Scripts\loadDialog.ps1:20 char:1
    + $xmlWPF.SelectNodes(“//*[@Name]”) | %{
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : MethodNotFound

    You cannot call a method on a null-valued expression.
    At U:\test_GUI_scripts\Scripts\helloWorld.ps1:5 char:1
    + $myButt.add_Click({
    + ~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

    You cannot call a method on a null-valued expression.
    At U:\test_GUI_scripts\Scripts\helloWorld.ps1:10 char:1
    + $xamGUI.ShowDialog() | out-null
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

    ____________________________________________________________________

    I will follow your advice above and continue on with the lessons.

    Thanks!

    1. You have to type cast xmlWPF to System.Xml.XmlDocument as follows:

      $Global:xmlWPF = [System.Xml.XmlDocument](Get-Content -Path $XamlPath)

  13. I have found my original code, and verified that it does work. It’s possible that maybe the Website is filtering or incorrectly displaying characters. I am going to do a copy and paste of my code in the comments section. I have also asked the editors of the site to let me add some screen captures of the code.

  14. LoadDialog.ps1

    [CmdletBinding()]
    Param(
    [Parameter(Mandatory=$True,Position=1)]
    [string]$XamlPath
    )

    [xml]$Global:xmlWPF = Get-Content -Path $XamlPath

    #Add WPF and Windows Forms assemblies
    try{
    Add-Type -AssemblyName PresentationCore,PresentationFramework,WindowsBase,system.windows.forms
    } catch {
    Throw “Failed to load Windows Presentation Framework assemblies.”
    }

    #Create the XAML reader using a new XML node reader
    $Global:xamGUI = [Windows.Markup.XamlReader]::Load((new-object System.Xml.XmlNodeReader $xmlWPF))

    #Create hooks to each named object in the XAML
    $xmlWPF.SelectNodes(“//*[@Name]”) | %{
    Set-Variable -Name ($_.Name) -Value $xamGUI.FindName($_.Name) -Scope Global
    }

  15. MyForm.xaml

    Please note that this file is being called from a specific folder (in my case it is C:\Forms). Also notice that the first line of code is missing a > sign. This is intentional, and adding the missing > will break the code.

  16. Hello World.ps1

    #Required to load the XAML form and create the PowerShell Variables
    ./LoadDialog.ps1 -XamlPath ‘C:\Forms\MyForm.xaml’

    #EVENT Handler
    $MyButton.add_Click({
    $MyLabel.Content = “Hello World”
    })

    #Launch the window
    $xamGUI.ShowDialog() | out-null

  17. Hi everyone,

    The article has been reworked to display the script parts in special code blocks. Hopefully this solves the quotation mark rendering issue and things will work after a copy-paste. Just to be sure though, there is now a download link at the end of the article pointing to a zip with tested code files in it.

  18. Hi,

    Originally I had the same errors coming up eventhough I made sure the path is pointing to my own folder and Execution policy is set but later on I noticed ‘MyForm.XAML’ file has 0 bytes. I then copied your one and overwritten mine and that worked. So my advice will be to look into the xaml file.
    Hope this helps.

    Paulos

Leave a Comment

Your email address will not be published.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Scroll to Top