I've Got a PowerShell Secret: Adding a GUI to Scripts

Summary: Microsoft PFE, Chris Conte, talks about adding a GUI for Windows PowerShell scripts.

Microsoft Scripting Guy, Ed Wilson, is here. Today Chris Conte is our guest blogger.

Chris Conte is a premier field engineer at Microsoft who grew up with a passion for computers and the creative arts. At age 12, he started programming by copying assembly game code on his dad's Commodore 64. Eventually, he parlayed that experience into learning about Visual Basic for Applications (VBA), ASP.NET, C#, and a variety of client scripting languages and frameworks. You can learn more about Chris via LinkedIn or Twitter @CrashConte.

Now, Chris…

This is not my original idea, but I’ve been able to document the methodology presented in this post. Before we start, I’d like the opportunity to thank my mentor, Donald Scott, and my colleague Michael Melone. Mike is the guru who figured this out, and Don and I created the helper cmdlet.

Windows Server admins have a lot of choices for automating routine tasks, but for those looking to stay up-to-date, the lingua franca is certainly Windows PowerShell. Windows PowerShell allows you to deal with objects instead of only strings and it has a syntax that C# and JavaScript programmers can easily relate to. In addition, with Microsoft heavily investing to propagate this awesome language throughout the ecosystem, you can be sure it's not going away soon.

So it’s settled, right? Learn Windows PowerShell and thrive. But honestly, I didn't get into Windows to work from the command line. I'm what you call a "GUI Guy." I love Windows, and as a developer, I have spent more than my share of time designing beautiful interfaces for the web. I believe that good design can have a huge influence on the success or failure of the underlying code. Don't get me wrong, I love the power and speed of scripted tasks. But that's why this "secret" is so special. You don't lose anything that Windows PowerShell has to offer.

What do you get when you mix XML, XPath, XAML, WPF, Windows PowerShell and a little creativity? Well, I say it's nothing short of magic. Even after you understand how it works, I hope you still get that little twinkle in your eyes that keeps the inner geek excited.

Image of chart

Tooling up

To follow along, you'll need to get a few tools. You've probably got these lying around. Here's what you'll need:

The Windows PowerShell ISE is an optional feature, and it requires .NET Framework 3.51 to be installed. To install the Windows PowerShell ISE on Windows Server 2008 R2, use the Add Features Wizard or run the following Windows PowerShell command:

Import-Module ServerManager; Add-WindowsFeature PowerShell-ISE

Visual Studio Express for Windows Desktop lets you take full advantage of Windows with XAML designers, a productive IDE, and a variety of programming languages.

Getting started

I'm going to presume a lot here. I'm going to assume you've got modern Windows Server admin skills and are a proficient Windows PowerShell scripter. I'm also going to assume you've heard of XML and XPath, event-driven programming, and some basics about object-oriented programming. If not, please get started with the following resources.

     Note  The mention of these resources is not an endorsement.  

WPF and XAML are important concepts to grasp for this methodology. To begin, let's review a bit about WPF and XAML (from the documentation). Here are some significant pieces of the documentation that should help you understand a bit more about why we are using them.

WPF is a next-generation presentation system for building Windows client applications. It is a resolution-independent and vector-based rendering engine. WPF extends the core with a comprehensive set of application development features that include Extensible Application Markup Language (XAML). WPF is included in the Microsoft .NET Framework, so you can build applications that incorporate other elements of the .NET Framework class library.

WPF exists as a subset of .NET Framework types that are for the most part located in the System.Windows namespace. You instantiate classes, set properties, call methods, and handle events by using your favorite .NET Framework programming language.

WPF offers the ability to develop an application by using markup and code-behind, which is an experience that ASP.NET developers should be familiar with. You generally use XAML markup to implement the appearance of an application while using managed programming languages (code-behind) to implement its behavior.

XAML is an XML-based markup language that is used to implement an application's appearance declaratively. It is typically used to create windows, dialog boxes, pages, and user controls, and to fill them with controls, shapes, and graphics. Because XAML is XML-based, the UI that you compose with it is assembled in a hierarchy of nested elements known as an element tree.

Building the interface

To create the interface, fire up Visual Studio and create a new WPF project. The path is: File > New > Project.

 Image of menu

When the project loads, you will see a prebuilt Window. Simply drag-and-drop controls onto the canvas from common WPF controls. In the following screenshot, I've added a text box and a button. Notice the handles that surround the control. You are welcome to use these handles to resize the control.

Image of controls

Notice the XAML in the window under the design canvas. You can see a Label and Button control added to the Grid element. Use the XAML resource documentation if you want to get fancy, but you now have all the XAML you need to continue. Simply copy and XAML and paste it into a Notepad window. Remove the x:Class attribute from the Window element. Leave the XML namespace elements (xmlns and xmlns:x), Title, Height, and Width. Save the file with the .xaml extension (for example, MyForm.xaml). Here is the code:




  Title="MainWindow" Height="350" Width="525">


  <Label Content="Label" HorizontalAlignment="Left" Margin="68,38,0,0" VerticalAlignment="Top" Width="197"/>

  <Button Content="Button" HorizontalAlignment="Left" Margin="307,41,0,0" VerticalAlignment="Top" Width="75"/>



Loading the dialog box

To make this easier (but admittedly more complex to understand), you'll need the following helper cmdlet to load and interpret the XAML. Simply save the following text as loadDialog.ps1:







$Global:xmlWPF = Get-Content -Path $XamlPath


#Add WPF and Windows Forms assemblies


 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


LoadDialog walkthrough

This loadDialog helper cmdlet is extremely important, so let's break it down. This cmdlet accepts one parameter: the FileName and Path of the XAML file. The contents are loaded into a global variable (typed as XML) by using the following line:

$Global:xmlWPF = Get-Content -Path $XamlPath

Next, WPF and Windows Forms assemblies are loaded from the .NET Framework. These allow us to make sense of the XAML to display the dialog.

Add-Type -AssemblyName PresentationCore,PresentationFramework,WindowsBase,system.windows.forms

With the power of our loaded assemblies, we load the XML variable, xmlWPF, as a parameter into a XmlNodeReader method, and subsequently, XamlReader. The result is loaded into a global XAML-typed variable. Thanks to our loaded assemblies, our code now "understands" how to interpret this XAML (more about this later).

Last, we wire the XAML notes in the tree to Windows PowerShell variables. By using the power of XPath, we scan through all the XAML tree nodes to extract the Name attribute. This name is passed down the pipe and turned into a global variable.

$xmlWPF.SelectNodes("//*[@Name]") | %{

 Set-Variable -Name ($_.Name) -Value $xamGUI.FindName($_.Name) -Scope Global


Those of you who are following intently have noticed that there isn't a Name attribute by default using Visual Studio. You'll need to add this manually. Simple enough. Simply edit the XAML file and add a Name attribute in the elements that you want to control via Windows PowerShell. Continuing with our example, I have added name "Label1" to our Label element and "Button1" to the Button element in the XAML.

  <Label Name="Label1" Content="Label" HorizontalAlignment="Left" Margin="68,38,0,0" VerticalAlignment="Top" Width="197"/>

 <Button Name="Button1" Content="Button" HorizontalAlignment="Left" Margin="307,41,0,0" VerticalAlignment="Top" Width="75"/>

Finally, notice that nothing is actually displaying our dialog box…yet! We're getting there.

Putting it all together

So, we've finally arrived. You're ready to build your HelloWorld.ps1. Of course, by now you've realized that to make this work, there are actually three files involved. This may seem like a lot, but as you build multiple scripts, this will make sense. You have your primary script named "HelloWorld.ps1", the loadDialog.ps1 helper cmdlet, and your XAML form (MyForm.xaml).

Image of flow chart

For clean separation, I recommend that you store your .ps1 files in a Scripts folder and the XAML forms in a Forms folder.

To review what's going on, your original Windows PowerShell script calls the loadDialog.ps1 helper cmdlet, which in turn, loads the XAML form into a variable and creates matching Windows PowerShell variables for all the elements in the tree, based on the Name attribute. Near the top of your HelloWorld.ps1 script, you should see a line like this:

#Required to load the XAML form and create the PowerShell Variables

.\loadDialog.ps1 -XamlPath '..\Forms\MyForm.xaml'

After this line successfully executes, we have access to the form via Windows PowerShell variables.



The properties to set and get are a matter of research on websites like MSDN or exploring the properties window inside of Visual Studio. For example, here is an example of the Button class.

Adding events

Remember when I mentioned event-driven programming? At this point, you'll want to wire-up an event to the button. Here's how:

#EVENT Handler


 $Label1.Content = "Hello World"


When the button is clicked, the code is setting the Content property of our Label1 Windows PowerShell variable with "Hello World." Because this is wired directly to the WPF form, we actually get to see the results on our screen.

Launching the window

With everything set, we are finally able to load the window by using the ShowDialog() method of our XAML object:

#Launch the window

$xamGUI.ShowDialog() | out-null

This must be the last statement in your script. From that point forward, everything is controlled via the events you have configured.

Execution policy

We have it all put together and we're ready to launch. If you haven't already, remember to set the execution policy so that you can run the script:

Set-ExecutionPolicy -ExecutionPolicy Unrestricted

Also, remember to run Windows PowerShell as an administrator. Otherwise you'll hit this error message:

Set-ExecutionPolicy : Access to the registry key

'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell' is denied. To change the execution policy for the default (LocalMachine) scope, start Windows PowerShell with the "Run as administrator" option. To change the execution policy for the current user, run "Set-ExecutionPolicy -Scope CurrentUser".

Kick off your HelloWorld script: .\HelloWorld.ps1

When the window loads, simply click the button to see the results. They should look like this:

Image of window

Methods like this don’t come along by chance. This mountain top of Windows PowerShell GUI is viewed on the shoulders of giant minds. My heartfelt thanks go to the engineering rock stars who made this possible: Microsoft senior premier field engineers, Mike Melone and Don Scott.

The methodology to create a Windows PowerShell GUI is somewhat advanced, but not beyond the reach of those who spend a great deal of time scripting Windows PowerShell solutions for others to use. I have personally used this method to create input validation and to control the choices of input from my users.

This is a very basic example, but WPF forms can be very sophisticated. The fun part is the creativity you bring that will help empower and guide others to be successful without getting stuck at the command line.

The files demonstrated in this blog post are available for download from the Scripting Guys Script Repository: PowerShell GUI.


Thanks for this innovative post, Chris.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

Comments (29)

  1. Great guide and thanks for the links to more information on the various elements. For anyone like me who’s entirely a sysadmin and before now hasn’t touched Visual Studio I’d recommend first reading

    http://foxdeploy.com/2015/04/10/part-i-creating-powershell-guis-in-minutes-using-visual-studio-a-new-hope/ (from where I actually found this post). It doesn’t go into as much detail or as deeply as this, but it is more idiot proof with the basics to get
    you started.

  2. Anonymous says:

    Hi, this is just what I needed. have got this working well, I have a form that allows input of a user name and retrieves 5 AD-User items, and displays them in a . The only issue I seem to be having is clearing the Listbox after each query. I have tried
    listbox1.items.removeListbox also items.clear. I can’t seem to find anything that helps, any ideas?

  3. Thristan says:

    You can create the GUI in the free community version of PrimalForms then copy paste the code created into ISE. Easier for me since no conversion is required.

  4. jkavanagh58 says:

    Visual Studio Express is intriguing buy my license expired… I paid for an old version of Primal Forms that I think I will keep using.

  5. jshaw says:

    From the perspective of performance, how does this compare to building .NET forms using powershell?

    As demonstrated here,

  6. I actually used a similar technique that you did in creating what was my first forray into xaml/wpf rendering in powershell (http://gallery.technet.microsoft.com/Exchange-Log-Level-GUI-f9e8cb21).
    I used visual studio express and followed most of the steps that you did but I also defined the name property in the gui before copying out the xaml. I remove the x:class element but due to that class being inherited (or whatever) in the form all the name
    variables were defined as x:Name. So to hook into each of the named elements I had to do things slightly different than you. The code is a bit more convoluted but this way you don’t have to manually name your form elements I suppose.

    [xml]$xaml = @’

    #Read XAML
    $reader=(New-Object System.Xml.XmlNodeReader $xaml)
    $window=[Windows.Markup.XamlReader]::Load( $reader )

    $namespace = @{ x = ‘http://schemas.microsoft.com/winfx/2006/xaml‘ }
    $xpath_formobjects = "//*[@*[contains(translate(name(.),’n’,’N’),’Name’)]]"

    # Create a variable for every named xaml element
    Select-Xml $xaml -Namespace $namespace -xpath $xpath_formobjects | Foreach {
    $_.Node | Foreach {
    Set-Variable -Name ($_.Name) -Value $window.FindName($_.Name)

    In looking at it now I probably don’t need to define the namespace at all and could use your code but with a slightly modified xpath query:

    $xmlWPF.SelectNodes("//*[@*[contains(translate(name(.),’n’,’N’),’Name’)]]") | %{
    Set-Variable -Name ($_.Name) -Value $xamGUI.FindName($_.Name) -Scope Global

    That should grab x:Name, name, Name, or x:name and create variables for ’em I think.

    Great article btw, thanks for sharing with the community!


  7. Kiran says:

    Thanks Chris… Gui’s without multithreading are in my opinion functional but annoying and frustrating..boe prox has done some grt work on this but for me powershell + runspaces = headache….so it would be great if u did another article with multithreading
    and it’s use in loops…

  8. Matu says:

    @Thristan -PrimalForms is not longer available.. could you share installation file?

  9. Dr. Rhodes says:

    PowerShell Studio is much better suited for this task, though it is expensive. Also, it doesn’t require any advanced knowledge of xpath or XAML.

  10. Krushna says:

    Thanks !!!!!!!!!!!!!!!!!!!

  11. Krushna says:

    There is an Event named "Click" but how could you use an event "add_Click" which i couldn’t find using get member in the script.
    Could you please explain !!!!!!

  12. Ricky says:

    As much as I appreciate you putting this together, it is difficult to follow the "putting it together" part. More specifically, if you read this part "So, we’ve finally arrived. You’re ready to build your HelloWorld.ps1. Of course, by now you’ve realized
    that to make this work, there are actually three files involved. This may seem like a lot, but as you build multiple scripts, this will make sense. You have your primary script named "HelloWorld.ps1", the loadDialog.ps1 helper cmdlet, and your XAML form (MyForm.xaml)".
    You said you are ready to build your script, then say you already have it. There is also no example of how .loadDialog.ps1 -XamlPath ‘..FormsMyForm.xaml’
    is implemented. Maybe I am just overlooking some key components here, but I find this part very difficult to follow.

  13. Ricky says:

    Nevermind, found it… im retarded

  14. Topher says:

    Admin Script Editor (ASE) 4.0 is free and IMO, has the best script-form designer available.


    It’s a simple drag and drop of controls on the form, and code behind is PowerShell. No XML, XAML, or WPF required. And to top it off, once your GUI script is completed, you can compile it to an exe right in the IDE.

  15. Dave says:

    Is there anyway to pull this off from Powershell Web Access? It works on my local machine and the Powershell Web Access server but not when I run it from the web console.

  16. Kyle says:

    Does anyone know how to stop processing of the script but keep the form open? Basically have the form stay open so that the user can change input and then run the script again. I’ve tried break, and exit but both give me an unhandled exception even if
    I call the commands within a catch block.

  17. Kyle says:

    Hey everyone, so Return will actually work. It will return control back to the user. and will allow you to run the script from the start again. Cheers,

  18. Manoj says:

    Hi Below is the Code whcih i created to play with Linux Command but i need to add a Expand and collapse Button to the Label .kindly help us to fix it

    [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
    [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

    [System.Windows.Forms.MessageBox]::Show("Welcome to GUI." , "Welcome")
    #Adding Label
    $label = New-Object Windows.Forms.Label
    $label.Location = New-Object Drawing.Point 87,05
    $label.Size = New-Object Drawing.Point 200,15
    $label.text = "NAVIGATION"

    Start-Sleep -s 0.5
    [System.Windows.Forms.SendKeys]::SendWait("cd [directory name]")
    Start-Sleep -s 0.5

    $objForm = New-Object System.Windows.Forms.Form
    $objForm.Text = "GUI"
    $objForm.Size = New-Object System.Drawing.Size(260,660)
    $objForm.StartPosition = "CenterScreen"
    $Button1 = New-Object System.Windows.Forms.Button
    $Button1.Location = New-Object System.Drawing.Size(05,30)
    $Button1.Size = New-Object System.Drawing.Size(75,23)
    $Button1.Text = "Move DIR"

    $ToolTip = New-Object System.Windows.Forms.ToolTip
    $ToolTip.BackColor = [System.Drawing.Color]::LightGoldenrodYellow
    $ToolTip.IsBalloon = $true
    $ToolTip.InitialDelay = 300
    $ToolTip.ReshowDelay = 500
    $ToolTip.SetToolTip($Button1, "Move to another directory")

    [void] $objForm.ShowDialog()

  19. mike says:

    I followed this tutorial using Visual Studio 2015 CTP.

    When I rename and save MyForm.xaml file it creates a second file with the extension xaml.cs which contains the following code:

    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 WpfApplication1
    /// Interaction logic for MainWindow.xaml
    public partial class MainWindow : Window
    public MainWindow()

    When I try to build solution I get the following error:

    "The name InitializeComponent’ does not exist in the current context."

    Please assist.

  20. pete says:

    Interesting article, although the question is why not just create a WPF or Windows Forms Application If you want to create a Desktop Application. I think it’s great that Powershell can consume XAML though. 🙂

  21. carc1n0gen says:

    I stopped reading when I saw you re-throwing an exception in a catch. Why would anyone catch an exception if they were just going to throw it?

  22. Peter says:

    Great article. I learned a lot implementing these 3 files together 🙂
    Anyway to make these 3 files into a standalone executable, that i could give to others to run??


  23. Ronan says:


    I currently have an issue with this GUI code that when I want to read in variable $SplunkIPSearch since inside the command Search-Splunk -Search ‘index="dns" $SplunkIPSearch’ | Format-List -Property host, containing single and double quotes it is for some reason
    not being recognized correctly.

    The GUI calls in an IP from a TextBox $SplunkIPSearch = New-Object System.Windows.Forms.TextBox which I the attempt to place inside my Splunk API command to gain DNS records for this IP.

    #EVENT Handler
    $SplunkIPSearch = $SplunkSearch.Text
    $TextBox.Text = Search-Splunk -Search ‘index="dns" $SplunkIPSearch’ | Format-List -Property host, source, raw | Out-String

    Would anyone know a way for me to call this variable so it is recognized as a variable, since the single quotes and double quote’s are needed for the command to work.

    Any help would be greatly appreciated.

  24. Mitch says:

    What about scenarios that you would like to perform data-validation, such as a text box field (make sure the user cannot place an integer value within something you only want alphabetical characters)? It seems that the WPF GUI seems to work great in scenarios
    for clicking a button and grabbing already entered text fields.

  25. Mark says:


    I have been playing this this all day but I’m having some issue with using a button1 to select a file and pass the file path $filepath to button2 to import the file and edit it.

    can so one have a look and see what I have done wrong

    .loadDialog.ps1 -XamlPath ‘.MainWindow.xaml’
    function Read-OpenFileDialog([string]$WindowTitle, [string]$InitialDirectory, [string]$Filter = "All files (*.*)|*.*", [switch]$AllowMultiSelect)
    Add-Type -AssemblyName System.Windows.Forms
    $openFileDialog = New-Object System.Windows.Forms.OpenFileDialog
    $openFileDialog.Title = $WindowTitle
    if (![string]::IsNullOrWhiteSpace($InitialDirectory)) { $openFileDialog.InitialDirectory = $InitialDirectory }
    $openFileDialog.Filter = $Filter
    if ($AllowMultiSelect) { $openFileDialog.MultiSelect = $true }
    $openFileDialog.ShowHelp = $true # Without this line the ShowDialog() function may hang depending on system configuration and running from console vs. ISE.
    $openFileDialog.ShowDialog() > $null
    if ($AllowMultiSelect) { return $openFileDialog.Filenames } else { return $openFileDialog.Filename }


    $filePath = Read-OpenFileDialog -WindowTitle "Select Text File Example" -InitialDirectory ‘C:’ -Filter "CSV File (*.Csv)|*.Csv"
    if (![string]::IsNullOrEmpty($filePath)) { Write-Host "You selected the file: $filePath" }
    else { "You did not select a file." }



    $a = get-content $filePath
    $a[0..($a.length)] | % {$_ -replace ‘"’,"" -replace "DisplayName","" -replace "MailAddress","

    " } | out-file -FilePath $filePath -Force -Encoding ascii

    $xamGUI.ShowDialog() | out-null

    the output I get is;

    PS C:UsersasbridgemDocumentsVisual Studio 2015ProjectsWpfApplication1WpfApplication1> C:UsersasbridgemDesktopUntitled2.ps1
    You selected the file: C:UsersasbridgemDesktopkyocera address book raw – Copy.csv
    Get-Content : Cannot bind argument to parameter ‘Path’ because it is null.
    At C:UsersasbridgemDesktopUntitled2.ps1:27 char:18
    + $a = get-content $filePath
    + ~~~~~~~~~
    + CategoryInfo : InvalidData: (:) [Get-Content], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.GetContentCommand

    Cannot index into a null array.
    At C:UsersasbridgemDesktopUntitled2.ps1:28 char:1
    + $a[0..($a.length)] | % {$_ -replace ‘"’,"" -replace "DisplayName","+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : NullArray

  26. Jhylander says:

    I am having an issue with this script. It works fine in the Powershell_ISE, but when I try to run it in the powershell console I get the following error: " Exception calling "Load" with "1" arguments(s): "The invocation of the constructor on type ‘System.Windows.Window’
    that matches the specified binding constraints threw an exception." when the [Windows.Markup.XamlReader]::Load method is called. Any help would be appreciated.

  27. JHylander says:

    I found the answer to my own problem. Powershell Console needs to run in Single Threaded Apartment mode in order for all the form controls in my GUI to load. Simply needed to launch the console with the command line "powershell -STA"

Skip to main content