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
You are a hero.
LikeLiked by 1 person
Good stuff. Might I add that replacing the x: from all the form element names is not required. Using some xpath trickery you can pull all the named variables out and define them. If $xamlMain is your xaml form it would look something like this:
# Read XAML
$reader=(New-Object System.Xml.XmlNodeReader $xamlMain)
$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 $xamlMain -Namespace $namespace -xpath $xpath_formobjects | Foreach {
$_.Node | Foreach {
Set-Variable -Name ($_.Name) -Value $window.FindName($_.Name)
}
}
LikeLike
I totally super suck at xpath, so I tried to do this for about an hour before I gave up and found the xpath used here in another blog. Thanks for posting this, I’m eager to try it out!
LikeLike
Ok Zloeber, tried your approach together with FoxDeploys goodies, worked just fine, thanks, both of you!
LikeLike
For me, the xpath fails:
select-xml : ‘//*[@*[contains(translate(name(.),βnβ,βNβ),βNameβ)]]’ has an invalid token.
LikeLike
Try to copy it from github instead.
LikeLike
Great posts, really enjoying it! Everything worked perfectly for me until I added the DisplayMemberBinding in the gridview tags. I changed the names to match the columns I’m returning. When running the Form, it now does nothing on button click.
LikeLiked by 1 person
Hi Beau! Make sure that your objects and your property name in the binding match, otherwise you’ll get blank columns! If you are having trouble, put your code on pasteBin and link it here!
LikeLike
Ah, silly me. The objects I’m returning in Powershell didn’t have spaces in the names like the columns in my ListView. Thanks for your help!
LikeLike
how do you hide buttons when a submenu has been selected?
example: menu has 4 categories [1 – FIX PC] [2 – TOOLS] [3 – OPTIONS] [4 – ADMIN LOGIN]
when you press [1 – FIX PC] , various buttons appear in subsection below.
when you press [2 – TOOLS] the respective buttons appear but the FIX PC buttons will become hidden.
is there a visibilty syntax or do you move them at an x,y coordinate out of the form range?
LikeLike
Pretty much everything has a .visible property. Set visible to false when you need to hide something
LikeLike
in XAML or PS?
LikeLike
Check out part III , I added examples of just this thing for you!
LikeLike
ignore my last reply…just read your GUI Blog pt.3 π
LikeLiked by 1 person
Stephen, you are a PS god. thanks! π
LikeLiked by 1 person
Aw, thanks for saying that π
I need some ideas for part 4 and beyond, so let me know if there is anything you’d like to see!
LikeLike
3 things:
1. When you launch a button, the midsection of your GUI will show a new set of buttons
and
2. when you press one of these new buttons in the midsection (example: ping workstation or show ADD/REMOVE programs in grid view, it will run a progress bar in the lower section (a rich text area perhaps?). When the PS script stops running the results are also displayed in the lower section showing the ping results or the grid view of the ADD/REMOVE programs.
see my ascii pic here:
—————————————————————-
| FoxDeploy Toolkit [x] |
|————————————————————–|
| [1 – PC TOOLS] [2 – INFO] | <- TOP SECTION
|————————————————————–|
| |
| [PING PC] [Show ADD/REMOVE] | <- MID SECTION
|————————————————————–|
| |
| |
| [======= ] 10% done | <- LOWER SECTION
| |
|_____________________________________|
3. Back in XAML, how to make the buttons rollover with a different colour or image.
Nuff stuff for you, mate? π
LikeLiked by 1 person
Check my site on Tuesday next week… Didn’t use all of these ideas but I used some of them… π
LikeLike
Did you manage to find some time to do the below ideas? π
LikeLike
I should have drawn a pic – probably confused you now, best to copy the above pic text and plonk it in Notepad! π
LikeLike
Why would I not be able to show the form a second time after running the snippet? It works fine the first time. I close out the form and make the change to the text to ‘Hello World’. When I try to open the form again by running $Form.ShowDialog() out-null I get an exception.
PS C:\windows\system32> $Form.ShowDialog() | Out-Null
Exception calling “ShowDialog” with “0” argument(s): “Cannot set Visibility or call Show, ShowDialog, or WindowInteropHelper.EnsureHandle after a
Window has closed.”
At line:1 char:1
+ $Form.ShowDialog() | Out-Null
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : InvalidOperationException
LikeLike
Ah, the variables persist but the form is consumed when it Is displayed then closed. You have to run the whole thing again to display the form again.
For this reason, I like to store my forms in functions, called something like Show-UserEntryForm or the like.
Does this make sense?
LikeLike
Not exactly. Could you expand please?
LikeLike
You need to rerun the xaml and everything prior to the $form.Show command whenever you want to display a form
LikeLike
Thanks for a hint, this works π
LikeLike
I fee dumb now not being able to figure out where to put this function and when it needs to be called. basically once i close my form (either through the button or forcing close) I need to re-run script and call it again with $Form.ShowDialog() | out-null, but then any “changes” made via PS are wiped, as the form goes back to square 1
LikeLike
Could you share your code? I’m having a hard time picturing it.
LikeLike
This behavior is the same even if I use the exact code from your example. Pasted into powershell and run it. Then call the form, then close the form, then run ps command to alter the text box then call the form again and I get that same error as mentioned above. You suggested using the function before calling the form.
LikeLike
Ignore my questions… clearly it was a long day and I was thinking inside the box!
LikeLike
I can see where a change is taking place but when I re-run the xaml it resets the changes (ie. ‘Hello World’) so the form doesn’t show them but instead shows ‘Textbox’.
LikeLike
This is all new to me, I’m getting stuck at the point where I paste my XAML in the here-string. I get the catch message “Unable to load Windows.Markup.XamlReader. Double-check syntax and ensure .net is installed.” Then when I try to run $form.ShowDialog() | Out-Null I get that message about not being able to call a method on a null valued expression.
Please advise
LikeLike
This is great! Is there a way to save/compile it so I could give what I build to other users?
LikeLike
This is an awesome and very usefull blogpost! I’ve always wanted to create GUI’s around my powershell scripts, but found the manual adding and finetuning of powershell code to do this to be tiresome. this makes it so much easier!
Thank-you!
LikeLike
Hi
I have learned a lot from your script.
I want to know if is a way to refer to the image in the current directory instead of the full path
Thank you.
LikeLike
Ron,
Try this in the script:
$WPFimage.Source = (Join-Path $(Split-Path -Parent $MyInvocation.MyCommand.Path) “my_logo.png”)
Add this underneath the “Actually make the objects work” section of the example, and of course, give it a valid image name instead of “my_logo.png”. This sets the image source to that file name in the directory where your script lives.
LikeLike
Hi,
i have an idea.
create a small windows with one button and 2 text fields.
1. username
2. password
3. button: join domain!
user will input these values and when he clicks join domain, the app will try to join the computer into AD domain π
LikeLike
Ow,
and when app launches, it should check if the PC is already member of a domain, and if it is, it should exit immediatly without any popups, but if its not, it should launch normally.
this could be good for client VPN connection, to run this script “OnConnect”, so that any remote PC will have simple means of joining the domain :d
LikeLike
Hey Stephen! Your post is super-detailed and got me intrigued big time. π
I created the form. However, when I put in the XAML in the script that you’ve given to extract the variables and stuff, it gave me errors at the line:
$xaml.SelectNodes(“//*[@Name]”) | %{Set-Variable -Name “WPF$($_.Name)” -Value $Form.FindName($_.Name)}.
Not sure if I can post the error here. Would look like a big load of spam. I can email you the XAML and the error⦠or something. Could you help, please?
LikeLike
Put your code on paste bin and send me the link, and I’ll try to help you!
LikeLiked by 1 person
Thank you! It’s here: http://pastebin.com/sUFxUT2Q
LikeLike
Just remove these ‘TextChanged=”textBox_TextChanged”‘ and your script will load. I’m not sure what you were trying to do with that, but let me know and I’ll show you how it works in PowerShell.
LikeLike
Wow… I have no idea what that is. Must have set some wrong property and then copied the controls.
Removed all the occurrences of ‘TextChanged=”textBox_TextChanged”
It works now. Thank you!
LikeLike
I had the same issue and resolution, Ram.
I added another replace method to block that out, since I figure it will probably happen again in the future, and there’s no guarantee at all that I’ll remember what I did to fix it! π
Thanks, Fox!
-Kyle
LikeLiked by 1 person
π haha, good thinking!
LikeLike
Hi, after opening the form with $Form.ShowDialog() the script is waiting until the form is closed. Is it possible to call the form and the close it automatically after a given time?
LikeLike
Hey Stephen,
Everything works fine, until I paste my XAML-Code into your script. For some reason he can’t compilate most of the textboxes and labels. I noticed some differences between a single label-declaration by your example-code and my code.
For example your code declared a label like this :
but my xaml-code from visual basic 2010 says:
Theres is a different syntax and theres no “x:” before the name.
Can you give me any advices or even better check my code and figure out, whats wrong
Kind Regards,
Tom
LikeLike
Ahh, this is a difference between 2010 and 2013. Look for the line that says something like -replace x:name etc etc and edit it accordingly!
LikeLike
Wow, I’m impressed!
This works great.
Thanks for sharing this with us!
Btw: I used Visual Studio 2015 Professional
LikeLike
I’m on the hunt for a lightweight substitute for visual Studio too! So far, I’ve tried xamlpad and kaxaml, both show promise but I think have their own learning curves.
I’ve also considered doing this in blender instead, but the different approach to code behind there makes it less desirable.
I’m happy you liked it! Feel free to send me your GUIs and I can cover them here!
LikeLike
So this worked fine until I added a Button, Checkbox or Textbox.
If I remove each, it still doesn’t work.
When I remove all.
It starts to work again
Found the following interactable elements from our form
Name Value
—- —–
WPFbtnSearchUser
WPFcbxUserDomain
WPFchkAdminYes
WPFimgLogo System.Windows.Controls.Image
WPFlblAdmin System.Windows.Controls.TextBlock
WPFlblMACAddress System.Windows.Controls.TextBlock
WPFlblManufacturer System.Windows.Controls.TextBlock
WPFlblModel System.Windows.Controls.TextBlock
WPFlblSerial System.Windows.Controls.TextBlock
WPFlblUser System.Windows.Controls.TextBlock
WPFlblUserConfirmed System.Windows.Controls.TextBlock
WPFlblUserDomain System.Windows.Controls.TextBlock
WPFtxtMACAddress System.Windows.Controls.TextBlock
WPFtxtManufacturer System.Windows.Controls.TextBlock
WPFtxtModel System.Windows.Controls.TextBlock
WPFtxtSerial System.Windows.Controls.TextBlock
WPFtxtUser
WPFtxtUserConfirmed System.Windows.Controls.TextBlock
PS C:\Users\.\Documents> $Form.ShowDialog() | out-null
Exception calling “ShowDialog” with “0” argument(s): “Cannot set Visibility or call Show, ShowDialog, or WindowInteropHelper.EnsureHandle a
fter a Window has closed.”
At line:1 char:1
+ $Form.ShowDialog() | out-null
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : InvalidOperationException
LikeLike
Post your whole code on GitHub or pastebin and then send us the link
LikeLike
http://pastebin.com/zgpzGWDn
It looks like it doesnt recognize the other objects one way or another.
Thx for looking up the problem!
LikeLike
So when I add just a textbox to the form, everything is ok.
If I add other components to the form it is not ok.
If I remove just the textbox and let the other components in the form,it is working again.
This is so weird.
———————————————————————-
———————————————————————-
LikeLike
http://pastebin.com/frJqAmWj
LikeLike
Sorry for my slow turnaround here, just had a new baby in the family :).
To fix this issue, remove the TextChanged=”…” from your text box. When you’re using PowerShell w/ XAML, you can’t use the TextChanged prop of a text box. Instead, we create an event like this,
$WPFtxtADUser.add_TextChanged({
#stuff to do when the textbox changes
})
LikeLike
Big thanks Mister Steven π
LikeLike
First: THANK YOU!!!
Second: Not sure if this has already been brought up and I missed it, but having any events defined in the XAML will cause the import to fail (these are defined for this purpose in the Powershell script below). Due to the script still trying to run the rest and the catch for that failure invoking a Write-Host only, it created a lot of on screen errors when trying to build the variables and form and didn’t actually show the error that was causing it to fail.
To catch these, I adjusted the Catch statement like so:
BEFORE:
$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.”}
AFTER:
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
try{$Form=[Windows.Markup.XamlReader]::Load( $reader )}
catch{return $Error[0]}
This allows the script to stop and display the error found in the XAML build.
Sample events that were created automatically in Visual Studio were buttonClick, listViewSelectedItem, etc.
LikeLike
Good catch! I get weekly requests from people to help with their guis and there is almost always an automatic click option causing the issue. I should strip those out when I get a chance…
Thanks for the kind words too π I’m working on post 5 right now
LikeLike
Awesome!! If you need any content, I’d love to lend what I’m currently building =]… Creating a tabbed GUI to manage Google Apps domain, specifically user deprovisioning, on the first one =]… Items to be included are comboboxes, textboxes, labels that alter depending on authenticated user, and validation functions to activate / deactivate buttons depending on whether logic is met or not
Either way, looking forward to part 5! π
LikeLike
Did you ever post this? Sounds fascinating!
LikeLike
Finally tried your code and I love it. I’ll edit the samples here to use it instead. It makes finding the actual error much, much easier.
LikeLike
Can you explain, while this didn’t work when you add an Combobox in the form?
LikeLike
What are you referring to?
LikeLike
Hi! I am testing this awesome thing and wanted to try our the following xaml code:
But when I want to use one of the buttons for validate the date with the get-date command it keeps showing me weird things and not the date in the format I want, it seems to be a problem with the click method somehow, these are the codes I tried for the Validation button:
$WPFValidate_Stg.Add_Click({
$WPFStage_Date.Clear()
$Day = $WPFSTG_DAY.Text
$Month = $WPFSTG_MONTH.Text
$Year = $WPFSTG_YEAR.Text
Get-Date -date $Month/$Day/$Year -DisplayHint date | % $WPFStage_Date.AddText($_)
})
or
$WPFValidate_Stg.Add_Click({
$WPFStage_Date.Clear()
$Day = $WPFSTG_DAY.Text
$Month = $WPFSTG_MONTH.Text
$Year = $WPFSTG_YEAR.Text
$DATESTG = Get-Date -date $Month/$Day/$Year -DisplayHint date
$WPFStage_Date.AddText($DATESTG)
})
This second one returns the date as I enter it instead of what I want which is something like this:
PS C:\Users\> Get-Date -date 01/01/2016 -DisplayHint date
Friday, January 01, 2016
Can you spot what is wrong with the button? Thank you!
LikeLike
When you run get-date, you get back not only the Date, but a full rich object with lots of properties. Powershell in the Shell itself knows to pick only certain properties, move even just one and to display it to you.
What you should do is run your get date command and pipe it into format-list and see all of the variations you’re getting, under the covers. Find the property that corresponds (probably datetime or simply date) and then run get-date Β¦select -expand Date, which will select just that property.
Now when you add it, it should render as expected.
LikeLike
Hi Stephen,
I would appreciate if you take up a combo box manipulation example, which would benefit me.
I was using autoIt to create a GUI and I was calling powershell script using it. This method of WPF is easier and the GUI is easy to build.
LikeLike
this is already there in the next post… π Thanks for reading my mind π
LikeLike
Hello Stephen,
Thank you for your post. I have been having similar issues when pasting different XAML code. I am not sure if I am missing something, but I keep getting: You cannot call a method on a null-valued expression.
At H:\PowerShell Scripts\stgmenu.ps1:45 char:62
+ … electNodes(“//*[@Name]”) | %{Set-Variable -Name “WPF$($_.Name)” -Valu …
+ ~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
I loaded the code on: http://pastebin.com/3P37x5Y5
Your help would greatly be appreciated.
LikeLike
Hi Jose! I missed this message from you two months ago! Don’t know if you moved on from this task but I found and fixed the error. It was in the Window Class name, your’s was set to stgWindow instead of Window for some reason. That caused the error. Neat concept for a tool I hope you finish it and share with the rest of us.
LikeLike
I’m kind of lost when you’re trying to import it to powershell. As I compare the xaml to the powershell script you posted, I can see from Windows X all the way to but the buttons and textbox is not there…
I need to know what you did to this portion?
You did mention that remove the x before the name, but on the powershell script below the sample you provide; doesn’t show those textbox or button property.
LikeLike
I too am getting this same error is this code compatible with Win 7 powershell 2?
LikeLike
Paste your code? It should work on v2, but I primarily design for v3 and up
LikeLike
Displays variables but never show form
LikeLike
$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'
$Form."ShowDialog()" | out-Null
LikeLike
There is no form? I don’t see anything under $inputXML. The UI you design needs to go here.
LikeLike
Getting the following error when trying to run $Form.ShowDialog() | out-null
You cannot call a method on a null-valued expression.
At line:1 char:1
+ $Form.ShowDialog() | out-null
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
LikeLike
I need to see your code to help you. Paste your whole code as a gist on github and send me the link.
LikeLike
Here is the code. https://gist.github.com/anonymous/2ea3eaa7ab505a17b8507612d9e2bde4
LikeLike
Ah, I see it. When you run this code, you see the following on the screen.
>To show the form, run the following
$Form.ShowDialog() | out-null
When I run $Form.ShowDialog() your code appears. :p
LikeLike
Still getting an error, below is sequence of what I am trying to run. Appreciate the help, im new to the whole XAML with powershell.
Found the following interactable elements from our form
Name Value
—- —–
WPFbutton System.Windows.Controls.Button: OK
WPFlabel System.Windows.Controls.Label: UserName
WPFtextBlock System.Windows.Controls.TextBlock
WPFtextBox System.Windows.Controls.TextBox: Enter UserName
To show the form, run the following
$Form.ShowDialog() | out-null
PS C:\WINDOWS\system32> $Form.ShowDialog()
You cannot call a method on a null-valued expression.
At line:1 char:1
+ $Form.ShowDialog()
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
LikeLike
When I run the code from the gist, it works. Are you doing this in the ISE or PowerShell console? Do you have skype/hangouts/Lync etc?
LikeLike
I am using powershell console and I use hangouts.
LikeLike
send me a message on hangouts, sred13@gmail.com
LikeLike
Code and everything works, created my own and it works, but the only problem I have is the PowerShell CLI and the PowerShell GUI both opens up. Any way to prevent the CLI opening up?
LikeLike
You want to run powershell with a hidden window. If you google that, you should find what you want. I think it’s powershell.exe – file your script.ps1 – showwindow hidden or something close to that!
LikeLike
Make a shortcut of your executable and add to the target this line: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -WindowStyle Hidden -file
LikeLike
Hello. Excellent posts and thank you for sharing. I am getting an error on the following line: (34 in your example above.)
$xaml.SelectNodes(“//*[@Name]”) | %{Set-Variable -Name “WPF$($_.Name)” -Value $Form.FindName($_.Name)}
I have posted my code on GitHub –
CreateGui.ps1
hosted with ❤ by GitHub
The error I get is as follows:
You cannot call a method on a null-valued expression.
At C:\Users\106226\Desktop\Scripts\Create GUI PowerShell.ps1:51 char:62
+ … electNodes(“//*[@Name]”) | %{Set-Variable -Name “WPF$($_.Name)” -Valu …
+ ~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
If you need to reference this display again, run Get-FormVariables
Found the following interactable elements from our form
To show the form, run the following
$Form.ShowDialog() | out-null
Any help would be greatly appreciated.
LikeLike
Here you go. GistRevision Link
Sometimes when you double click or drag an item in Visual Studio, it adds a property called ‘TextChanged’ or something similar to the XAML. PowerShell doesn’t know how to parse this and throws an error. Deleted that and removed an unneeded class reference from the window declaration and it’s good now.
LikeLike
Thank you very much. I will try this out soon. (Sorry again for the double post.)
LikeLike
This worked perfectly. Thank you very much.
LikeLike
First off, thank you for sharing this.
I am getting an error on line 32 in your example.
$xaml.SelectNodes(“//*[@Name]”) | %{Set-Variable -Name “WPF$($_.Name)” -Value $Form.FindName($_.Name)}
I have posted my code on GitHub –
CreateGui.ps1
hosted with ❤ by GitHub
I get the following error:
You cannot call a method on a null-valued expression.
At C:\Users\106226\Desktop\Scripts\Create GUI PowerShell.ps1:51 char:62
+ … electNodes(“//*[@Name]”) | %{Set-Variable -Name “WPF$($_.Name)” -Valu …
+ ~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
If you need to reference this display again, run Get-FormVariables
Found the following interactable elements from our form
To show the form, run the following
$Form.ShowDialog() | out-null
Any assistance you could offer would be great.
Apologies if this posts twice.
LikeLike
Hi Stephen,
First, thanks for this series. I found it right at a great time when I’m developing a small tool for my team to use to monitor processes to find out if they’ve died or malfunctioned. I walked through the first post with no problems. I love using VS for the graphical side! Very helpful.
However, when I started working with the XAML things went a little sideways on me. If I copy/paste the first code snippet you posted on this article, here’s what I get:
Cannot convert value “…*all of the XAML code here*…” to type “System.Xml.XmlDocument”. Error: “‘<', hexadecimal value 0x3C, is an invalid attribute character. Line 2, position 16."
This then returns the catch statement, and throws a bunch of "cannot call a method on a null-valued expression" errors, presumably because it can't load.
To complicate things a little further, I would swear the first example was working before I went to lunch! I could use Get-FormVariables successfully, but now it won't play ball…
Any insight you might be able to offer here?
LikeLike
Can you put your code in a github gist or on pastebin and send us the link? Be sure to sanitize it first (Remove your company names).
LikeLike
Happy to do it, but it’s literally a copy/paste from the first PowerShell code you posted on this segment.
http://pastebin.com/DdNF27pQ
LikeLike
Dunno how that happened, but the XAML namespace declarations at the top were in the form of HTML Links! This breaks the XAML. I fixed that bug. Here you go π http://pastebin.com/U67CwzPT
LikeLike
I’m not sure how that happened either…wonder if it was a copy/paste clipboard “helpful feature” issue. In any event, your fix did the trick, so thanks!
Also, man, for such an old post, your responsiveness and response times are freaking amazing! Thanks for all of this! I’ll be continuing on to the third part of this series today.
LikeLike
Part V for the series just dropped today too! If yo have ideas for future posts, drop me a line. Always happy to help.
π
LikeLike
Sweet! I’ll power through (…ahar har) all of them.
Also, to keep some of your follow-along PS newbies like me, you might add this to your $inputXML assignment:
-replace ” SelectionChanged=””listView_SelectionChanged”””, ”
That takes care of that pesky SelectionChanged thing I’ve seen you bring up a couple times. Thanks again!
LikeLike
…Why did I never think of this?! This is a great idea.
LikeLike
The last two ” is two single quotes with nothing between them. Looks weird in this font. π
LikeLike
One last thing…You can set the DisplayMemberBinding property in Visual Studio in the properties window. Click the little box beside “DisplayMemb…” (see here: http://imgur.com/fFt9DWR) and enter: “{Binding }” without the quotes. And you also don’t need to include quotes inside the binding even if the column heading includes spaces. This way, if you update the XAML and re-copy to your script, you don’t have to remember to manually add the DisplayMemberBinding attributes.
LikeLike
Oops…the post killed some of my formatting. You should enter “{Binding ..column header..}” without quotes
LikeLike
Urgh…no “.”‘s. If the column is named Alert Name just enter, {Binding Alert Name} for the Custom Expression.
LikeLike
can you make a gist on github and paste that, I’m not sure I’m following.
LikeLike
Going to reply in a fresh box….I had the step by step and it looks like it was eaten.. π¦
LikeLike
Here’s a way to add DisplayMemberBinding to your XAML in VS2015 so you don’t have to manually edit each time you update the form.
gistfile1.txt
hosted with ❤ by GitHub
LikeLike
Crap…sorry to bomb your blog, man! The image for #3 should be this: http://imgur.com/XxAhnKf Sorry, I’ve never used Gist before, so I’m not sure if I can edit as an anonymous post.
LikeLike
Oh for the love of…..last post, honestly. My other comment STILL has the wrong damn image. #3 should use THIS image: http://imgur.com/wOdnbl5
LikeLike
From the running GUI, how could I run an external powershell script. For example if I click on the OK button I would like to trigger another .ps1 file and pass it some parameters values comming from the fields in the GUI.
LikeLike
I’m wondering if it could be possible to call en external powershell script from the GUI. For example, if I click on a OK button, would it be possible to execute another powershell (.ps1) and pass it some parameter values comming from TextBox fields in my GUI ?
LikeLike
Sure, you definitely could do that. Just use the invocation character (Which is the ampersand, this thing: &)
Button.Add_Click({
& c:\Scripts\Script.ps1 -Param1 $WPFTextBox.Text
})
LikeLike
Im sure at least 100 of the page visits here are me refusing to let this page drop off my chrome “open tabs” list on reboots etc because ive been building a nicr script using the material within. Thanks!
LikeLiked by 1 person
Lol! Thanks for coming! Do you have anything cool to share with the class?
LikeLike
Soon… http://www.skype4badmin.com/skype4b-analog-device-manager-preview-and-cheat-snippet/
LikeLike
That looks great, shared it on Twitter!
LikeLike
How can I click the button by hitting ENTER ?
LikeLike
You want to add a key down listener. Check out what we do to close the form when we hit escape, ans you can use this same logic.
LikeLike
thanks for your quick reply. exactly, unfortunately I dont get it working. in the example above nothing happens when you hit escape and I can not find any code for it here. I tried some other codesnippets from google, but nothing works. any help would be appreciated.
LikeLike
You should be able to set the isDefault attribute of the button to true in the XAML. When you load the form, hitting Enter should activate the button.
LikeLike
Works! Thank you very much!
LikeLike
I found I needed to replace the line `[void][System.Reflection.Assembly]::LoadWithPartialName(‘presentationframework’)` with `Add-Type -AssemblyName presentationframework` for this to work on PoSH v5
LikeLike
I’ve used your page here for several tools at my previous employer, and always got them working simply following your examples and incredible instructions (brown nosing complete now to ask for the help π ). Now I’m somewhere new…trying to do something that should be very simple and cannot get it to work. If you are able to help determine what I’m doing wrong it would be appreciated.
Here’s the git link *I think* https://github.com/ArmedGeek/XAMLAHIS/blob/Powerhsell-XAML-Help/HelpPlease.ps1
LikeLike
I’m happy you like it! I send you a PR with my changes. π
LikeLike
Hi, I found the issue. The machine I was issued @ my new company had PS 2.0 only. I’ve upgraded and registered the new version in my ISE (PrimalScript) and the script works like it should. Thanks again for the blog and all the help that it’s provided the community!
Mike
LikeLike
I love how you’ve brought Data Binding into the discussion so early. Is there any chance you’ll cover Validation in a tutorial soon? All the examples I’ve found online are in C-something, and I just have never grasped that language family’s ways of handling objects, even after reading June Blender’s citation of Keith Hill’s blog.
LikeLike
I’m planning on a series of transitioning to c# from Powershell, but that will be something for early next year.
LikeLike
Hey Foxdeploy
What an awesome blog do you have!!! The way you write the posts it’s just so reader-friendly that makes it awesome!
Following your tutorial I have been trying to make my own GUI however I got stuck with an error. It’s like the xaml generated by my visual studio is somewhat inappropriate. For ex:
My textblock generated by visual studio:
Your textblock:
using my textblock generates an error and my windows doesn’t show up.
LikeLike
First off, thanks for taking the time to write me. I always wantes to learn the things I cover in this blog, but couldn’t understand the guides that other people wrote. Since I learn by explaining things, I decided to start this blog.
Can you put your full code on github as a gist or on pastebin and I’ll help you with this issue? Thanks!
LikeLike
Wow…what a fast repply! Back in Brazil it’s 2am… I was thinking that it would take until monday for you to you see this comment xD
For some reason the code that I posted didn’t appear.
but it was:
My textblock generated by visual studio:
**openTextBox tag** x:Name=”textBox” HorizontalAlignment=”Left” Height=”20″ Margin=”135,174,0,0″ TextWrapping=”Wrap” Text=”12313123123120938#01928301928.pdf” VerticalAlignment=”Top” Width=”358″ TextChanged=”textBox_TextChanged” **closeTextBox tag**
Your textblock:
**openTextBox tag** x:Name=”textBox” HorizontalAlignment=”Left” Height=”35″ Margin=”186,144,0,0″ TextWrapping=”Wrap” Text=”TextBox” VerticalAlignment=”Top” Width=”168″ FontSize=”16″ **closeTextBox tag**
Here is my full XAML: http://pastebin.com/FiqHi3TP
I could solve my problem by removing:
TextChanged=”textBox_TextChanged”
from my textbox element.
Again, congrats for the awesome blog (and the fast repply!). keep it up! \o/
LikeLike
Remove the textchanged values. Sometimes when you double click an item in visual studio, it adds a text changed property. This works fine for a c# GUI but doesn’t work with the method we use here in PowerShell. I’m working to strip that property with the next version of the template, but haven’t done /gotten it done yet.
LikeLike
Hi FoxDeploy
i hope you could help me with my problem. Take a look at my code on http://pastebin.com/SK41xhNy
Its just suppose to show localadmins on remote computers deppending on OU,Group or single computer. It works good in powershell but i needed to make a GUI for it just so other users could use it. The problem is that the column “Members” is showing as “System.Management.Automation.PSMemberInfoIntegratingCollection’1[System.management.automation.PsMemberInfo]”
When resolving $arr in powershell it looks good but im not getting it to work with the GUI.
LikeLike
Change line 167 to this
$WPFlistView.AddChild([pscustomobject]@{‘Computer’=$ComputerName;’Groupname’=$groupname;’Members’=$arr -join ‘;’})
This issue came up because $arr might contain more than one item. PowerShell knows how to handle this, but our dumb GUI doesn’t know what to do. In this case, we used the -join operator to join all the separate items into a semicolon separated list.
You may need to make similar changes elsewhere in your code.
LikeLike
I am trying to do something similar to this but have it pop up to an information box. I have everything working but the values are all in a one line string (just like they are in the above example). Is there a way to have each value on its own line so it looks like a list?
Thanks in advance!
LikeLike
If you put each on it’s own line, you should just break them out into their own entries with .AddChild() for each. Pseudo code-
$arr | ForEach {$listbox.AddChild($_)}
Otherwise your end user might be confused by a multi-line listbox. If you REALLY wanna go that way, replace -join ‘;’ with -join ‘`n’ to try adding a new line. Not sure if it will render properly with that control though.
LikeLike
Ok, i tried that and no luck. I also added $arr = $null just to see if that worked, if i could get any output but not even that worked. I tried the code without GUi and then i got 1 string just like one item, but the GUI is still showing nothing. Any more ideas or should i just give up π
Thanks for your help!
LikeLike
this will work.
forEach ($user in $arr){
$WPFlistView.AddChild([pscustomobject]@{‘Computer’=$ComputerName;’Groupname’=$groupname;’Members’=$user})
}
LikeLike
Thanks, that worked great!
LikeLike
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
Thanks for posting a sample! I’ll check it out and get back to you later today
LikeLike
Much appreciated!
I may have commented twice by accident… Apologies.
LikeLike