This post is part of the Learning GUI Toolmaking Series, here on FoxDeploy. Click the banner to return to the series jump page!
I got a lot of feedback last time, everyone wants the rest of the series, and you guys want it now! So I’m skipping my normal 1,000 word limit for this post and making this bad-boy LONG! There will still be a part three where I’ll show you how to use some of the trickier form elements. Some of them are absolute hacks to make them work in PowerShell, so if you’re the god of XAML and WPF, please have mercy on us mere-mortals and add some comments to let us know how I ought to be doing it.
Let’s jump back in and take our finished XAMl from last time and put it into PowerShell.
Whoa whoa, what’s XAML
I don’t know if you noticed this window. This whole time we’ve been adding elements, dropping boxes and text and things like that, it’s been updating in real time!
The language here is XAML, (Extensible Application Markup Language) which is a Microsoft language built on XML to make very small but effective applications easily. We’ll copy this code, but first, we need to make some changes to it before it will ‘just work’.
If you want to use my example as the base for your GUI, you can copy this right into Visual Studio:
<Window x:Class="FoxDeploy.Window1" | |
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:Azure" | |
mc:Ignorable="d" | |
Title="FoxDeploy Awesome GUI" Height="524.256" Width="332.076"> | |
<Grid Margin="0,0,45,0"> | |
<Image x:Name="image" HorizontalAlignment="Left" Height="100" Margin="24,28,0,0" VerticalAlignment="Top" Width="100" Source="C:\Users\Stephen\Dropbox\Docs\blog\foxdeploy favicon.png"/> | |
<TextBlock x:Name="textBlock" HorizontalAlignment="Left" Height="100" Margin="174,28,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="282" FontSize="16"><Run Text="Use this tool to find out all sorts of useful disk information, and also to get rich input from your scripts and tools"/><InlineUIContainer> | |
<TextBlock x:Name="textBlock1" TextWrapping="Wrap" Text="TextBlock"/> | |
</InlineUIContainer></TextBlock> | |
<Button x:Name="button" Content="OK" HorizontalAlignment="Left" Height="55" Margin="370,235,0,0" VerticalAlignment="Top" Width="102" FontSize="18.667"/> | |
<TextBox x:Name="textBox" HorizontalAlignment="Left" Height="35" Margin="221,166,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="168" FontSize="16"/> | |
<Label x:Name="label" Content="UserName" HorizontalAlignment="Left" Height="46" Margin="56,162,0,0" VerticalAlignment="Top" Width="138" FontSize="16"/> | |
</Grid> | |
</Window> |
Basically, we need to remove some properties from the window declaration, and also remove the x: before each name. But don’t do all of that by hand, just copy and paste it into this little blob I’ve made for you 🙂
Importing this into PowerShell
#Your XAML goes here 🙂 | |
$inputXML = @" | |
<Window x:Class="Azure.Window1" | |
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:Azure" | |
mc:Ignorable="d" | |
Title="FoxDeploy Awesome GUI" Height="524.256" Width="332.076"> | |
<Grid Margin="0,0,174,0"> | |
</Grid> | |
</Window> | |
"@ | |
$inputXML = $inputXML -replace 'mc:Ignorable="d"','' -replace "x:N",'N' -replace '^<Win.*', '<Window' | |
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework') | |
[xml]$XAML = $inputXML | |
#Read XAML | |
$reader=(New-Object System.Xml.XmlNodeReader $xaml) | |
try{ | |
$Form=[Windows.Markup.XamlReader]::Load( $reader ) | |
} | |
catch{ | |
Write-Warning "Unable to parse XML, with error: $($Error[0])`n Ensure that there are NO SelectionChanged or TextChanged properties in your textboxes (PowerShell cannot process them)" | |
throw | |
} | |
#=========================================================================== | |
# Load XAML Objects In PowerShell | |
#=========================================================================== | |
$xaml.SelectNodes("//*[@Name]") | %{"trying item $($_.Name)"; | |
try {Set-Variable –Name "WPF$($_.Name)" –Value $Form.FindName($_.Name) –ErrorAction Stop} | |
catch{throw} | |
} | |
Function Get-FormVariables{ | |
if ($global:ReadmeDisplay -ne $true){Write-host "If you need to reference this display again, run Get-FormVariables" –ForegroundColor Yellow;$global:ReadmeDisplay=$true} | |
write-host "Found the following interactable elements from our form" –ForegroundColor Cyan | |
get-variable WPF* | |
} | |
Get-FormVariables | |
#=========================================================================== | |
# Use this space to add code to the various form elements in your GUI | |
#=========================================================================== | |
#Reference | |
#Adding items to a dropdown/combo box | |
#$vmpicklistView.items.Add([pscustomobject]@{'VMName'=($_).Name;Status=$_.Status;Other="Yes"}) | |
#Setting the text of a text box to the current PC name | |
#$WPFtextBox.Text = $env:COMPUTERNAME | |
#Adding code to a button, so that when clicked, it pings a system | |
# $WPFbutton.Add_Click({ Test-connection -count 1 -ComputerName $WPFtextBox.Text | |
# }) | |
#=========================================================================== | |
# Shows the form | |
#=========================================================================== | |
write-host "To show the form, run the following" –ForegroundColor Cyan | |
'$Form.ShowDialog() | out-null' | |
Updated January 2018 with better error catching!
If you’ve taken a look at the blog article on the MS site which I linked last week, some of the above might look very familiar. In fact, I’ve added a bit of extra output to help us in troubleshooting (by dumping a list of all of the variables relating to objects on our form) and made this into a snippet, which you can download and embed in your own ISE.
That’s a lot of code, but you only need to copy and paste your XAML from VisualStudio to PowerShell in between the here-string.
What’s a here-string?
If you’ve never heard of a here-string, it’s a programming term for a multi-line variable that maintains spacing and allows for variable expansion. Long-story short, it’s the @” “@ above.
When you’ve copied and pasted your XAML into the here-string above and hit F5 in the PowerShell ISE (or wherever you edit scripts), you’ll see the following:
Our tool has scanned through the GUI and created hooks associated with every interactable element on the screen. We can now make changes to these things with code, just by changing their objects. This little display here is actually a function embedded in the code. You can run it again later if you forget the variable names by running Get-FormVariables.
As you see above, if we want to run the GUI, we just type in
>$Form.ShowDialog() | Out-Null

Changing values on the GUI is easy!
Now, what if we wanted to do something a bit more complex, like change the value of the Text where it says ‘TextBox’.
We need to hook into the properties that the tool displayed to us earlier. In this case, the name is $WPFTextBox. This is an object which this refers to the object on our form. That means we can change the text just by looking for a .text property on this object.
$WPFtextBox.Text >TextBox
If we change this with a simple equals statement…
$WPFtextbox.Text = 'Hello World'
This is the basic flow we’ll take to interact with all of our GUIs hence-forth. Draw something cool in Visual Studio, copy and paste it into the snippet, then run Get-FormVariables and see what the name is for our new cool GUI features (they’re called ‘Form controls’ if we want to be specific). Then look at the new object and see what it’s properties and methods are.
But it doesn’t work…
One last thing before we fully upgrade this into an awesome tool, let’s try clicking the OK Button.
Nothing happens! Here’s why: by default, we need to give our button an action to run when we click it. You do this using the Add_Click() method, which lets us put a {script-block} into the () overload, which will be executed when the user clicks a button. This makes our buttons a great place to setup hooks if we want to grab the values from a box.
For instance, if we want our OK button to just close our form, we run this little number
$WPFbutton.Add_Click({$form.Close()})
After the form has closed, the value of all of these objects in the form still persist, so if the user made typed something like ‘YoDawg’ into our textbox, we can get it once the form is gone by running:
$WPFtextBox.Text
YoDawg
Alright, let’s make this into a WMI info gathering tool.
Building a better WMI tool
I’ve got a tool that I walk people through making in my Learning PowerShell bootcamp course (if you want me to come deliver one at your company, send me a message!), in which we learn how to query WMI/CIM and then expand from there and make it do cool fancy stuffs. The output of the tool at the end of the day looks like this.
We can make a GUI version of this by adding a ListView, which is pretty much embedding something like an Excel datasheet into this GUI. To do this, click ListView in the ToolBox and drag it to your form and size appropriately. You’ll notice that I also made a few tweaks to the layout to better fit what we want this tool to do.
You can just barely make it out, but there is a grid there now, waiting to receive our beautiful rows and columns. Let’s add some stuff, just a warning, this can be a bit tricky at first.
In Visual Studio, in the XAML, click the GridView Tag.
In properties on the right, click ‘Columns’
This will bring up the Column collection editor
Now, in this area, you’ll want to click the ‘Add’ button and change the width to about 100 for each, and specify the column name in the Header Box. I’ll add one each for each of the fields my tool returns:
• Drive Letter
• Drive Label
• Size(MB)
• FreeSpace%
As before, you can change the font by clicking on the area with text, then go to Properties>Text on the right side of the screen. When finished you should have something like this:
If we want to make our buttons and form actually work though, we’ll need to hook into the form again, as we did previously.
Making all of the new stuff work
If you want to catch up with where we are now in the walkthrough, get this stuff:
$inputXML = @" | |
<Window x:Class="WpfApplication2.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:WpfApplication2" mc:Ignorable="d" Title="FoxDeploy Awesome Tool" Height="416.794" Width="598.474" Topmost="True"> | |
<Grid Margin="0,0,45,0"> | |
<Image x:Name="image" HorizontalAlignment="Left" Height="100" Margin="24,28,0,0" VerticalAlignment="Top" Width="100" Source="C:\Users\Stephen\Dropbox\Docs\blog\foxdeploy favicon.png"/> | |
<TextBlock x:Name="textBlock" HorizontalAlignment="Left" Height="100" Margin="174,28,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="282" FontSize="16"><Run Text="Use this tool to find out all sorts of useful disk information, and also to get rich input from your scripts and tools"/><InlineUIContainer> | |
<TextBlock x:Name="textBlock1" TextWrapping="Wrap" Text="TextBlock"/> | |
</InlineUIContainer></TextBlock> | |
<Button x:Name="button" Content="get-DiskInfo" HorizontalAlignment="Left" Height="35" Margin="393,144,0,0" VerticalAlignment="Top" Width="121" FontSize="18.667"/> | |
<TextBox x:Name="textBox" HorizontalAlignment="Left" Height="35" Margin="186,144,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="168" FontSize="16"/> | |
<Label x:Name="label" Content="ComputerName" HorizontalAlignment="Left" Height="46" Margin="24,144,0,0" VerticalAlignment="Top" Width="138" FontSize="16"/> | |
<ListView x:Name="listView" HorizontalAlignment="Left" Height="156" Margin="24,195,0,0" VerticalAlignment="Top" Width="511" FontSize="16"> | |
<ListView.View> | |
<GridView> | |
<GridViewColumn Header="Drive Letter" Width="120"/> | |
<GridViewColumn Header="Drive Label" Width="120"/> | |
<GridViewColumn Header="Size(MB)" Width="120"/> | |
<GridViewColumn Header="FreeSpace%" Width="120"/> | |
</GridView> | |
</ListView.View> | |
</ListView> | |
</Grid> | |
</Window> | |
"@ | |
$inputXML = $inputXML -replace 'mc:Ignorable="d"','' -replace "x:N",'N' -replace '^<Win.*', '<Window' | |
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework') | |
[xml]$XAML = $inputXML | |
#Read XAML | |
$reader=(New-Object System.Xml.XmlNodeReader $xaml) | |
try{ | |
$Form=[Windows.Markup.XamlReader]::Load( $reader ) | |
} | |
catch{ | |
Write-Warning "Unable to parse XML, with error: $($Error[0])`n Ensure that there are NO SelectionChanged or TextChanged properties (PowerShell cannot process them)" | |
throw | |
} | |
#=========================================================================== | |
# Load XAML Objects In PowerShell | |
#=========================================================================== | |
$xaml.SelectNodes("//*[@Name]") | %{"trying item $($_.Name)"; | |
try {Set-Variable –Name "WPF$($_.Name)" –Value $Form.FindName($_.Name) –ErrorAction Stop} | |
catch{throw} | |
} | |
Function Get-FormVariables{ | |
if ($global:ReadmeDisplay -ne $true){Write-host "If you need to reference this display again, run Get-FormVariables" –ForegroundColor Yellow;$global:ReadmeDisplay=$true} | |
write-host "Found the following interactable elements from our form" –ForegroundColor Cyan | |
get-variable WPF* | |
} | |
Get-FormVariables | |
#=========================================================================== | |
# Use this space to add code to the various form elements in your GUI | |
#=========================================================================== | |
#Reference | |
#Adding items to a dropdown/combo box | |
#$vmpicklistView.items.Add([pscustomobject]@{'VMName'=($_).Name;Status=$_.Status;Other="Yes"}) | |
#Setting the text of a text box to the current PC name | |
#$WPFtextBox.Text = $env:COMPUTERNAME | |
#Adding code to a button, so that when clicked, it pings a system | |
# $WPFbutton.Add_Click({ Test-connection -count 1 -ComputerName $WPFtextBox.Text | |
# }) | |
#=========================================================================== | |
# Shows the form | |
#=========================================================================== | |
write-host "To show the form, run the following" –ForegroundColor Cyan | |
'$Form.ShowDialog() | out-null' |
If you scroll to the bottom, just below Get-FormVariables, you’ll see an example of how to add data to a field. This part of our script is where the XAML has been parsed, and objects have been created to hook into them. This is where we’ll need to put our magic sauce to make the buttons and fields work and do cool things.
So, scroll down to the ‘Make the Objects Actually Work’ area.
First things first, take this snippet of code which accepts a computer name and returns the disk information:
Function Get-DiskInfo { param($computername =$env:COMPUTERNAME) Get-WMIObject Win32_logicaldisk -ComputerName $computername | Select-Object @{Name='ComputerName';Ex={$computername}},` @{Name=‘Drive Letter‘;Expression={$_.DeviceID}},` @{Name=‘Drive Label’;Expression={$_.VolumeName}},` @{Name=‘Size(MB)’;Expression={[int]($_.Size / 1MB)}},` @{Name=‘FreeSpace%’;Expression={[math]::Round($_.FreeSpace / $_.Size,2)*100}} }
Here is what to do next.
- I want my textbox to default to displaying my computer name
- I want to add a trigger that when I click the Get-DiskInfo button, it should run the Get-DiskInfo function, using the computer name specified in the textbox
- Finally, I want to take the objects I get from that and for each of them, add a new row to my ListView area
Change your code to the following, beginning on line 61 (or just below Get-FormVariables)
Function Get-DiskInfo { param($computername =$env:COMPUTERNAME) Get-WMIObject Win32_logicaldisk -ComputerName $computername | Select-Object @{Name='ComputerName';Ex={$computername}},` @{Name=‘Drive Letter‘;Expression={$_.DeviceID}},` @{Name=‘Drive Label’;Expression={$_.VolumeName}},` @{Name=‘Size(MB)’;Expression={[int]($_.Size / 1MB)}},` @{Name=‘FreeSpace%’;Expression={[math]::Round($_.FreeSpace / $_.Size,2)*100}} } $WPFtextBox.Text = $env:COMPUTERNAME $WPFbutton.Add_Click({ Get-DiskInfo -computername $WPFtextBox.Text | % {$WPFlistView.AddChild($_)} })
Let’s run it and see what happen’s when you click the button.
If it breaks in a new way, I call that progress
Well, crap. Adding new rows worked, but now every column has the output for every property. This is happening because, very similar to when you work with the pipeline in PowerShell or make a function, you have to tell PowerShell how to bind to values.
To fix this, go up to your XAML for your GridView Columns and add a DisplayMemberBinding Property like this. Make sure if you’re deviating from the walkthrough and doing your own thing to pick names that make sense. If your name has a space in it, use single quotes around it.
<GridView> | |
<GridViewColumn Header="Drive Letter" DisplayMemberBinding ="{Binding 'Drive Letter'}" Width="120"/> | |
<GridViewColumn Header="Drive Label" DisplayMemberBinding ="{Binding 'Drive Label'}" Width="120"/> | |
<GridViewColumn Header="Size(MB)" DisplayMemberBinding ="{Binding Size(MB)}" Width="120"/> | |
<GridViewColumn Header="FreeSpace%" DisplayMemberBinding ="{Binding FreeSpace%}" Width="120"/> | |
</GridView> |
And the finished product:
Whats up next?
Alright guys, I want to thank you for sticking with me to the end of this VERY long blog post. I hope you enjoy it and will use this technique to make some awesome GUIs of your own.
Join me for my post next time on this topic, part III in the GUI series, in which we dig into how to add some of the cooler and more difficult features to our GUI, like a tabbed interface (to get other WMI values) and how to use checkboxes and radio buttons, dropdown boxes and more!
Part III – Using Advanced GUI Elements in PowerShell
Final XAML Code here
<Window x:Class="WpfApplication2.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:WpfApplication2" | |
mc:Ignorable="d" | |
Title="FoxDeploy Awesome Tool" Height="416.794" Width="598.474" Topmost="True"> | |
<Grid Margin="0,0,45,0"> | |
<Image x:Name="image" HorizontalAlignment="Left" Height="100" Margin="24,28,0,0" VerticalAlignment="Top" Width="100" Source="C:\Users\Stephen\Dropbox\Docs\blog\foxdeploy favicon.png"/> | |
<TextBlock x:Name="textBlock" HorizontalAlignment="Left" Height="100" Margin="174,28,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="282" FontSize="16"><Run Text="Use this tool to find out all sorts of useful disk information, and also to get rich input from your scripts and tools"/><InlineUIContainer> | |
<TextBlock x:Name="textBlock1" TextWrapping="Wrap" Text="TextBlock"/> | |
</InlineUIContainer></TextBlock> | |
<Button x:Name="button" Content="get-DiskInfo" HorizontalAlignment="Left" Height="35" Margin="393,144,0,0" VerticalAlignment="Top" Width="121" FontSize="18.667"/> | |
<TextBox x:Name="textBox" HorizontalAlignment="Left" Height="35" Margin="186,144,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="168" FontSize="16"/> | |
<Label x:Name="label" Content="ComputerName" HorizontalAlignment="Left" Height="46" Margin="24,144,0,0" VerticalAlignment="Top" Width="138" FontSize="16"/> | |
<ListView x:Name="listView" HorizontalAlignment="Left" Height="156" Margin="24,195,0,0" VerticalAlignment="Top" Width="511" FontSize="16"> | |
<ListView.View> | |
<GridView> | |
<GridViewColumn Header="Drive Letter" DisplayMemberBinding ="{Binding 'Drive Letter'}" Width="120"/> | |
<GridViewColumn Header="Drive Label" DisplayMemberBinding ="{Binding 'Drive Label'}" Width="120"/> | |
<GridViewColumn Header="Size(MB)" DisplayMemberBinding ="{Binding Size(MB)}" Width="120"/> | |
<GridViewColumn Header="FreeSpace%" DisplayMemberBinding ="{Binding FreeSpace%}" Width="120"/> | |
</GridView> | |
</ListView.View> | |
</ListView> | |
</Grid> | |
</Window> |
Full PowerShell Code here
$inputXML = @" COPY Xaml from above 🙂 "@ $inputXML = $inputXML -replace 'mc:Ignorable="d"','' -replace "x:N",'N' -replace '^<Win.*', '<Window' [void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework') [xml]$XAML = $inputXML #Read XAML $reader=(New-Object System.Xml.XmlNodeReader $xaml) try{$Form=[Windows.Markup.XamlReader]::Load( $reader )} catch{Write-Warning "Unable to parse XML, with error: $($Error[0])`n Ensure that there are NO SelectionChanged properties (PowerShell cannot process them)" throw} #=========================================================================== # Load XAML Objects In PowerShell #=========================================================================== $xaml.SelectNodes("//*[@Name]") | %{"trying item $($_.Name)"; try {Set-Variable -Name "WPF$($_.Name)" -Value $Form.FindName($_.Name) -ErrorAction Stop} catch{throw} } Function Get-FormVariables{ if ($global:ReadmeDisplay -ne $true){Write-host "If you need to reference this display again, run Get-FormVariables" -ForegroundColor Yellow;$global:ReadmeDisplay=$true} write-host "Found the following interactable elements from our form" -ForegroundColor Cyan get-variable WPF* } Get-FormVariables #=========================================================================== # Actually make the objects work #=========================================================================== Function Get-DiskInfo { param($computername =$env:COMPUTERNAME) Get-WMIObject Win32_logicaldisk -ComputerName $computername | Select-Object @{Name='ComputerName';Ex={$computername}},` @{Name=‘Drive Letter‘;Expression={$_.DeviceID}},` @{Name=‘Drive Label’;Expression={$_.VolumeName}},` @{Name=‘Size(MB)’;Expression={[int]($_.Size / 1MB)}},` @{Name=‘FreeSpace%’;Expression={[math]::Round($_.FreeSpace / $_.Size,2)*100}} } $WPFtextBox.Text = $env:COMPUTERNAME $WPFbutton.Add_Click({ $WPFlistView.Items.Clear() start-sleep -Milliseconds 840 Get-DiskInfo -computername $WPFtextBox.Text | % {$WPFlistView.AddChild($_)} }) #Sample entry of how to add data to a field #$vmpicklistView.items.Add([pscustomobject]@{'VMName'=($_).Name;Status=$_.Status;Other="Yes"}) #=========================================================================== # Shows the form #=========================================================================== write-host "To show the form, run the following" -ForegroundColor Cyan $Form.ShowDialog() | out-null
Absolutely amazing guide. I am new to adding GUI’s to Powershell so i’m not so good at the moment!🙂
I have completed the script however the Get-Variable WPF* is not finding anything, and when i run the script without the ‘ ‘ between the show dialog at the bottom, I just get a blank iVision Azure Accelerator pop-up.
My code can be found here – http://pastebin.com/m4PauNm0
Many Thanks!
LikeLike
LOL, your first line says $inportXML, instead of $inputXML. 🙂 Once I changed that, I got some .net errors. These happen because PowerShell doesn’t support both the Click and TextChanged properties within XAML. You can do those (and I cover how to do them in later posts) but just not within XAML, so I removed them all. And it loads.
Looks pretty good to, very nice tool. Show this to your boss and demand a raise. The IT Best Practices forum gave a price of something like $60 per user interaction. Think of how much time your help desk will save with your new tool! Nicely done.
LikeLike
LOL What even…. oops! It was a long night!
Many thanks, I will try this once I get home and let you know the results!
LikeLike
Hey Mate, it’s me again.
I’ve taken the advise and got it working again, but then i decided to try and jazz up the UI, and it doesn’t work again! Nice aye! I get –
“Exception calling ‘showdialog’ with ‘0’ argument(s): ‘ cannot set visibility or call show, showdialog, or windowinteropholper.ensurehandle after a window has close.”
I’ve pasted my code again here http://pastebin.com/57jd9Vfx
Appreciate the response.
Rainer
LikeLike
Though, for trying to make it look nicer, it might be worth me making a CSS page that then runs powershell’s when buttons are pressed.
Might be my best option.
LikeLike
the UI is consumed and deleted from memory after being displayed once. This means you have to rerun the whole script everytime you want to display the UI.
LikeLike
Nice script Rainer-Michael. Just had a look 🙂
Added a 1 to change the name of the button on line 143. Getting some inspiration from it, hope you don’t mind.
Sven
LikeLike
This would be much simpler.
Function Get-DiskInfo {
param ($computername)
Get-WMIObject Win32_logicaldisk -ComputerName $computername | ?{ $_.freespace }|% {
[pscustomobject]@{
computername = $computername
‘Drive Letter’ = $_.DeviceID
‘Drive Label’ = $_.VolumeName
‘Size(MB)’ = [int]($_.Size / 1MB)
‘FreeSpace%’ = [math]::Round($_.FreeSpace / $_.Size, 2) * 100
}
}
}
$form1_Load = {
$textbox1.Text = $env:COMPUTERNAME
}
$button1_Click={
$diskinfo = Get-DiskInfo $textbox1.text
$datagridview1.DataSource=[collections.arraylist]$diskinfo
}
LikeLike
Effective, but not a GUI…
LikeLike
Where is part 3? I’m definitely interested in learning about having tabs in the gui
LikeLike
If you click the banner up top, it will take you to the home page for GUI posts, the link for post three.
LikeLike
ok I see it now. You should add a link to it at the bottom of the article! Also… I’m so happy I found this. I’m working on an app now. I’m still learning posh but i’m challenging myself and going to get it right. I want to know… is it possible to make it so a tab is only available when the actions of the second tab are complete? OR better yet… is there a way to make it so I can have a next button that bring the user to a new “slide” for lack of a better term?
LikeLike
That’s a good idea, I’ll add links!
Also, you can do what you’re asking about I’ll whip up an example and send it over in a few hours
LikeLike
Ok I see it now! You should add a link to it at the bottom of the article! Also… I’m so happy I found this. I’m working on an app now. I’m still learning posh but I’m challenging myself and going to get it right. I want to know… is it possible to make it so a tab is only available when the actions of the first tab are completed? OR better yet… is there a way to make it so I can have a next button that bring the user to a new “slide”/ “window” (for lack of a better term?) without having it open a separate window, rather, it just loads a new view on the same window?
(PS… not spam… won’t let me delete other comment, realized it didn’t make sense lol)
LikeLike
Here you go!
Show-Form.ps1
hosted with ❤ by GitHub
LikeLike
Hello guys, how can I bring the window into the front, I tried different things like $Form.Activate() or $Form.IsActive=$true but nothing works.
I guess the solution is super easy!?
Please give me a tip.
thanks a lot.
LikeLike
It’s showdialog()
LikeLike
$MainForm.StartPosition = “CenterScreen” where $MainForm is, of course the name of the main form.
LikeLike
Property ‘CenterScreen’ cannot be found on this object.
is this order right?
$Form.StartPosition=”CenterScreen”
$Form.ShowDialog() | out-null
LikeLike
I have it located in the beginning of my properties of the form:
$MainForm = New-Object System.Windows.Forms.Form
$MainForm.AutoScroll = $True
$MainForm.AutoSize = $True
$MainForm.AutoSizeMode = “GrowAndShrink”
$MainForm.StartPosition = “CenterScreen”
LikeLike
thanks, but in my case its another thing, I just inserted Topmost=”True” in the xaml code. now it comes into the front BUT if I open a Out-GridView from the window, this Out-gridview will be displayed behind the window. is there a workaround?
LikeLike
got it. 1st call when a button is clicked $Form.Topmost=$false.
thank you guys. sometimes I need just to talk about with others and the solution comes by itself.
LikeLike
I’m not sure if my code below made it to my last comment so I’m reposting. Please let me know why it keeps erroring out.
$inputXML = @”
“@
$inputXML = $inputXML -replace ‘mc:Ignorable=”d”‘,” -replace “x:N”,’N’ -replace ‘^<Win.*', '<Window'
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
[xml]$XAML = $inputXML
#Read XAML
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
try{$Form=[Windows.Markup.XamlReader]::Load( $reader )}
catch{Write-Host "Unable to load Windows.Markup.XamlReader. Double-check syntax and ensure .net is installed."}
#===========================================================================
# Load XAML Objects In PowerShell
#===========================================================================
$xaml.SelectNodes("//*[@Name]") | %{Set-Variable -Name "WPF$($_.Name)" -Value $Form.FindName($_.Name)}
Function Get-FormVariables{
if ($global:ReadmeDisplay -ne $true){Write-host "If you need to reference this display again, run Get-FormVariables" -ForegroundColor Yellow;$global:ReadmeDisplay=$true}
write-host "Found the following interactable elements from our form" -ForegroundColor Cyan
get-variable WPF*
}
Get-FormVariables
#===========================================================================
# Actually make the objects work
#===========================================================================
#Sample entry of how to add data to a field
#$vmpicklistView.items.Add([pscustomobject]@{'VMName'=($_).Name;Status=$_.Status;Other="Yes"})
#===========================================================================
# Shows the form
#===========================================================================
write-host "To show the form, run the following" -ForegroundColor Cyan
'$Form.ShowDialog() | out-null'
LikeLike
Hi Nick, can you post the full code into PasteBin or a Gist and send me the link? Should be able to help you out 🙂
LikeLike
Hello, I am really enjoying this tutorial that you have. Just what I have been looking for. I have become stuck with an error and I was wondering if you could assist me. Pretty much all my code is the same to your with the exception that I need to run my powershell in single threaded apartment or -sta mode since I am working on a Windows 7 64 bit with powershell 2.0. The code runs properly in powershell up to the point where I click the button. In which case my powershell throws out:
Method invocation failed because [System.Windows.Controls.ListView] doesn’t contain a method named ‘AddChild’.
At line:2 char:71
+ Get-DiskInfo -computername $WPFtextBox.Text | % {$WPFlistView.AddChild <<<< ($_)}
+ CategoryInfo : InvalidOperation: (AddChild:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
Is there something I am missing here? I have looked for a solution but so far have had no luck.
LikeLike
Hey man happy you like it. Would you mind posting your full code (remember to remove your company name or anything identifying) on paste bin or as a GitHub gist? Then post the link here. Don’t worry if it doesn’t appear in your post, I’ll see it and approve it
LikeLike
I have not gotten far enough to apply to my existing script. haha. Here is the pastebin: http://pastebin.com/W95T7A0Y
Thanks for the help.
LikeLike
I’ve never seen someone call PowerShell -sta within a script before. I think instead you should run the script like this
%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe -sta -file “c:\pathto\yourscript.ps1”
And see if that fixes the problem.
When I run the code on its own (without calling powershell -sta) it runs just fine.
LikeLike
I Have been able to get my GUI to “Kind of” work.
I have it execute a script block but i was wondering how i can have the output get displayed in the GIU.
I wrote a script to remotely decrypt BitLocker encrypted hard drives using Invoke-Command In powershell it works flawlessly, but i want to create a nicer looking tool for my end-users to use.
When i enter the computer name It runs the powershell script and freezes there until the drive is decrypted and closes (which is fine).
How can i make the GUI display the output of the powershell console?
LikeLike
Stay tuned for a future blog post to cover this topic.
LikeLike
Awesome! By the way, this tutorial is awesome. Thank you for taking your time to do this. Is there somewhere I could make a donation?
LikeLike
Could use a little help myself. I’m new to Visual Studio but have some experience with PowerShell. What I’ve written is a form to allow a user to enter a username then click a search button to see if the user exists in Active Directory. This is for our HR department. It works on the initial search the way I intend it to but how do I refresh it so I can enter a new name to search for without having to rerun the script every time? Thanks.
LikeLike
I’d have to see your code to understand why it doesn’t work after the first search. Put it in Pastebin or Gist and give me the link and I’ll have a look.
LikeLike
For some reason it is not allowing me to reply to my older post. I did the change you suggested in my batch script but still ran into some issues. So I just gave in and updated my power shell to v.5.
Afterwards the script ran correctly within powershell, but when I attempted to run it through a batch script I ran into this error:
â?~Drive : The term ‘â?~Drive’ 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.
After some research, the cause for that error turned out to be the single quotation marks located here:
Get-WMIObject Win32_logicaldisk -ComputerName $computername | Select-Object @{Name=’ComputerName’;Ex={$computername}},`
@{Name=’Drive Letter’;Expression={$_.DeviceID}},`
@{Name=’Drive Label’;Expression={$_.VolumeName}},`
@{Name=’Size(MB)’;Expression={[int]($_.Size / 1MB)}},`
@{Name=’FreeSpace%’;Expression={[math]::Round($_.FreeSpace / $_.Size,2)*100}}
At drive letter, drive label, size(mb) and free space. For some reason it did not get those correctly so I just deleted and re-entered them. This part I copied from your script, so perhaps the browser changed the quotation marks to a different symbol.
LikeLike
Just found this and looks amazing! So tried right away but unfortunately I’m getting an error in Powershell ISE when loading my created example. Pastebin is https://pastebin.com/ryxqm8W6. I used Visual Studio Community 2017 to create the XAML. The error is due to line 55 with the xaml reader. Error message is “Exception calling “Load” with “1” argument(s): “Failed to create a ‘TextChanged’ from the text ‘textBox_TextChanged’.” Can you help please?
LikeLike
Hey man! One downside is that we can’t parse XAML event properties, like TextChanged, RadioChanged or ButtonClick, but have to add our own event handlers as I cover later in the blog.
When I removed these events from you XAML, it worked! Here’s the UI aftrer my edits.
https://pastebin.com/iNadHkny
LikeLike
Wow! You’re quick with replying! Thanks man. It works indeed! I’m gonna run through this complete tutorial series before asking something again, I promise 😉 Thanks again for the quick help.
LikeLike
Has anyone else found a fix to the “Unable to load Windows.Markup.XamlReader. Double-check syntax and ensure .net is installed.”?
I have tried running -sta, and somewhere else I heard about removing some lines of the XAML:
xmlns:d=”http://schemas.microsoft.com/expression/blend/2008″
xmlns:mc=”http://schemas.openxmlformats.org/markup-compatibility/2006″
xmlns:local=”clr-namespace:MetLifePreflightChecker”
mc:Ignorable=”d”
I have tried both, but no luck.
I even went as far as to install .net 4.7 as that was the version I had on my dev box.
Apart from that, on the working development bow I have a great working program that took much less time to create than the Windows.System.Forms method.
Now if only it would work on a production machine..
LikeLike
Post your quote code as a gist
LikeLike
I get the same error ““Unable to load Windows.Markup.XamlReader. Double-check syntax and ensure .net is installed.” Apologies but I do not understand what is meant by “Post your quote code as a gist”
LikeLike
Go to gist github.com and make a ‘public Gist’, then send me the link. It will let me see your code, ans make edits to it very easily.
LikeLike
Nevermind, worked it out, elements needed to be named. Thanks.
LikeLike
Great post. I learned a lot from this. I am going to blog a little about this on my blog as well. This is a quick an easy way to write a GUI.
LikeLiked by 1 person
Cool! Post back here with the URL! I’m doing a 1 MM hit retrospective and I plan to call attention to cool things people have shared
LikeLiked by 1 person
That is really cool. My blog is a lot like yours. Maybe we can gain ideas from one another.
https://zacheryolinske.wordpress.com/
LikeLike
Is there a way to sort the data when you click on a column header?
LikeLike
This was great, thanks a lot for the information, nicely done, it has pretty good details to follow step by step
LikeLike
Hey Foxy McFoxerton,
would you say that there is an advantage using the Visual Studio IDE over the SAPIEN PrimalForms IDE? I have so far only used the Community Edition (which was free until a while ago) and I’m a programming n00b – yet I keep trying. But I would like to hear your opinion re. the environment to put the script together in.
Thanks!
LikeLike
Lol! My favorite nickname ever!
Sapien makes awesome tools but they’re not free. I try to teach to a completely free tool chain.
Their tools are worth the money though.
LikeLike
Hi FoxDeploy!
I just wanted to contribute to this wonderful guide, as I have followed your steps and instead of copying your example I have created one of my own with Buttons and a TextBox using VS 2015.
I couldn’t start the GUI because it always threw the following exception:
Exception calling “Load” with “1” argument(s): “Must compile XAML file that specifies events. Line ‘0’ Position ‘0’.”
At line:1 char:41
+ $Form=[Windows.Markup.XamlReader]::Load <<<< ( $reader )
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
Only after removing the attributes "Click" and "TextChanged" from the Buttons and TextBox respectively in the inputXml it did work.
I added those changes as replaces in the conversion command for what I guess are pretty common for a copy & paste of the UI elements.
$inputXML = $inputXML -replace 'mc:Ignorable="d"','' -replace "x:N",'N' -replace '^<Win.*', '<Window' -replace 'Click="button\d*_Click"', '' -replace 'TextChanged="textBox\d*_TextChanged"',''
Thanks a lot for your awesome guide !!
LikeLike
Hi Fer, can you send me a gist / pastebin link of your code? I’ll help you find the issue.
LikeLike
Thanks FoxDeploy,
it is not necessary. I needed to remove the actions created by the buttons automatically inside the XAML and that fixed the issue.
Thanks a lot for your awesome guides ! Extremely helpful.
LikeLike
Thanks for the awesome guide FoxDeploy, I’m learning alot! Thank you FER for figuring out the additional replacements, that saved me a lot of time xD
LikeLike
Hello Foxdeploy,
probably a bit late to comment on this but i am in a bind. i get to the point where you paste the XAML data into the here string, but when i run the script it doesnt show the form variables. my XAML is from the 2017 edition of the community visual studio so i dont know if that whats causing the issue. heres my code via pastebin https://pastebin.com/Pqii5txQ if you are still able to help that would be awesome.
LikeLike
Hi! I look a look today and you ran into an issue I didn’t mention before. The method we use in this post depends on every item having a distinct name. Simply assign a name property for each element within the grid (your image, button, textblocks and label) by adding Name = ‘SomeName’ to each, and the code will work.
I added names to each, and updated the paste here.
LikeLike
Awesome! thank you! i am having a couple other issues on another part of the guide. ill reply there with the info on that.
LikeLike
Hello Fox, I have troubles with my code.
Here is a pastebin of it : https://pastebin.com/gJ6DhjKP
The output says :
Unable to load Windows.Markup.XamlReader. Double-check syntax and ensure .net is installed.
You cannot call a method on a null-valued expression.
At C:\Users\maxime.cholon\Documents\GitLab\PowerShell_Tools\GUI_Test.ps1:69 char:62
+ … electNodes(“//*[@Name]”) | %{Set-Variable -Name “WPF$($_.Name)” -Valu …
+ ~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
I copy pasted your Xaml Reader and added my XAML but it doesn’t work for some reason.
.NET Framework 4.7 is installed and i tried Powershell -STA too.
Can you help me with this issue ?
LikeLike
Found the errors, you have a ‘SelectionChanged’ property specified in one of your items, which PowerShell can’t handle. I added better error checking to catch this in the future though. Sorry for my late reply! Here’s the modified version. https://pastebin.com/YXEFUYNK
LikeLiked by 1 person
Hi Stephen,
Ever looked at integrating MahApps into this?
I’m trying but miserably failing!
Rich
LikeLike
I’ve actually never heard of that before. Can you send me a link to what you’re talking about?
LikeLike
I apologies in advance for your weekend being consumed by shinyness.. http://mahapps.com
LikeLike
I figured it in the end! Will share once i’ve got it a bit more refined.
LikeLike
Hi Fox,
I followed your instructions but my powershell does not find any interactable elements from the form
localadmin.ps1
hosted with ❤ by GitHub
I tried to put my own XAML in to it and even tried yours but neither works
Can you help me?
LikeLike
Your elements did not include a Name: property. The PowerShell scippet looks for Name properties to bind the items and surface as a variable. I made a few tweaks here, and it works now. https://gist.github.com/1RedOne/d2c834aaf21220bb4c9d2d173b33d86a
LikeLike
A million thanks for this awesome guide! This really saved my butt with a time-sensitive project (are there any other kind? lol). I’ve got one small issue, and I’m sure it’s just something simple I’m missing.
What I’m doing is searching a given directory for all EXE and DLL files and returning a table with the file name, description, and version number. It also exports the list to a TXT file in the c:\temp\ directory.
The issue I’m having is when I run it, the view in the GUI doesn’t seem to return anything, but the text file shows the info. What’s even stranger is in the table view in the GUI, a scroll bar appears and it looks like there are a bunch of blank entries in the table, almost like it’s returning it in invisible text.
The code is available here: https://gist.github.com/anonymous/8078b779cdc84b09c05e66cfefff9550
Any help you can provide would be most appreciated. Thanks again for all your hard work in putting this together!
Cheers,
~Will~
LikeLike
I like it! I made two tweaks, namely adding a ‘\’ to the directory name, then I modified how you build a custom object by directly dereferencing .FileVersion, because a lot of .dll and .exe files won’t have that populated. Finally, I removed the `| format-table` command, because that was really the source of the trouble here. The Format- commands actually strip all file info and instead prepare a payload of console instructions (like ‘tab tab tab Name space space space File Description), and that’s why your bindings in the GridView wasn’t doing anything.
I’ve tested this on a few directories, and it works! https://gist.github.com/1RedOne/44ce904e89b3da7d3a4eaed65be1cced
LikeLike
OMG! Thank you SO MUCH! This works perfectly. I really appreciate your help. Dinner’s on me if you’re ever in Vegas. Cheers!
Will
LikeLiked by 1 person
Hi, please post a new thread on reddit/r/foxdeploy. Describe what you want to do, what’s going wrong, and paste in your code, and I’ll help you out ☺
LikeLike
Love this, but i canney see your XAML examples. I’m guessing and googling as i go along. Am i missing a browser plug-in to see the examples ?
LikeLike
Make sure you have JavaScript enabled, there should be little plus buttons to click to show the code ☺
LikeLike
Make sure you have JavaScript enabled. I use the WordPress Syntax Highlighter plug-in to collapse the code into an expandable JavaScript container between paragraphs.
LikeLike
Hey Man, ,there was a bug in WordPress’ syntax highlighter plugin, it’s being worked on now!
LikeLike
Hi,
Firstly thank you for this awesome tutorial which allowed me to easily add GUI to my PowerShell scripts !
I was just wondering where was your “XAML parser script” come from and under wich license it could be used ?
LikeLike
Hi!
The original core code came from an article by Chris Conte on the ‘Hey Scripting Guy’ blog. The code is provided under an MIT License, enjoy.
LikeLike
Hey, loving the tutorial so far. I keep getting this error when running my code from ISE: WARNING: Unable to parse XML, with error: Exception calling “Load” with “1” argument(s): “Cannot create unknown type ‘{http://schemas.microsoft.com/winfx/2006/xaml/presentation}Null’.”
Full Paste is here: https://pastebin.com/pQTR9C2R
Thanks in advance!
LikeLike
Thanks for posting the paste link already! I’ll take a look tomorrow Moroni!
LikeLike
Here you go! It looks like something funky happened with the properties of the Grid, so I cleaned it up a bit. Working now! https://pastebin.com/HQv7AWcW
LikeLike
Thanks a bunch! Working properly now!
LikeLike
Also, just to let you know. None of your XAML code shows in any browser. Latest version of Chrome, Firefox and IE. This is across two PCs. Chrome has no add-ins installed (I don’t use it, only have it as a test/alternate browser). IE never gets opened unless I’m desparate (Which I am now). Not being able to see the XAML examples is severely hindering progress :(. Please help!
LikeLike
Just this one post? OK I’ll check it out! Thanks
LikeLike
Its any of the tutorials. The lines are there (If I highlight, I can see how long each line of code should be). Its also being stripped from the boxes with both PoSH and XAML. PoSh is visible, XAML isn’t.
LikeLike
It’s a bug in WordPress, they’re on it, should be fixed soon!
LikeLike
It’s fixed now, sorry, it was a bug in the syntax highlighter add-in.
LikeLike