Part IV – PowerShell GUIs – how to handle events and create a Tabbed Interface

series_PowerShellGUI

This post is part of the Learning GUI Toolmaking Series, here on FoxDeploy. Click the banner to return to the series jump page!


Where we left off

Previously in Part III of our GUI series, we left off with a review of some advanced GUI options, things like radio buttons, text replacement and things like that. In this section, we’ll grow our tool from Part I, our lowly Ping tool, and expand it to become a fully-fledged remote PC management tool. We’ll do this to learn our way through some new GUI concepts we haven’t run into before. I’ll take you through making most of this tool, and leave you at a place where you should be able to take it the rest of the way.

What we’ll cover

If you walk through this guide, I guarantee that we’ll cover each of these items below. If there is something you’d like to see covered in a future post, send me an e-mail! Each of the items covered in this post came from requests from my readers.

Seriously.

I am gathering input for post five right now, so let me know what’d you’d like to do with PowerShell, and we can make it happen.

Making a Tabbed Interface

We’re going to talk about the right way to use this element, to make it obvious to users (something called ‘Discoverability’) that more tools and options exist, if they’d only click this tab and explore around a bit! In tab one, we’ll drop in the whole of our pinging tool from Part I of this walkthrough, while we’ll hide extra functionality in tabs two, three and four. For extra niftiness, we’ll have the others tabs be disabled (unclickable) until the user first verifies that a PC is online in tab one.

Handling UI Events

This is the part that has me the most excited, mostly because I just learned about how to do it, and it solves so many issues. Want to have your GUI run a chunk of code when the user edits a textBox. Or handle the event which is triggered if the user moves the mouse over a button? We can even wait till the user is finished entering text, and then run a short chunk of code to validate an entry. These are all real world scenarios for a User Interface Developer, and we can easily deal with them in PowerShell.

Let’s get started.

Tabs, not just for playing ‘The house of the rising sun’

Now, I said we’ll be bringing back our pinger…and we kind of will. But we’re not ready to open up that code, just yet.

See, the thing is that while it IS possible to add tabs to an existing project or UI, it’s much easier to start off with a new project, get the tabs, status bar, dock panels, etc setup the way you want them to look, and then copy and paste in other GUI elements from Visual Studio, or some XAML code.

Let’s start the right way with a tabbed UI. We’ll be making a new project for this walkthrough, and at this point, it should feel familiar. Launch Visual Studio, click New, New Project, WPF.

Pick a good starting size for your form (I’ll do 345×450), then add a Tab control from the toolbox.

I like to draw a somewhat wide tab, like this.

00_Fill all

Then, right-click the top of the tab…uh, holder, right next to your last tab and choose Layout-Fill All. I do this because it’s actually surprisingly hard to drag your tab to fill the window, so this is the easy shortcut.

01_Fill all
This will expand the tabs to fill the whole UI.

Now that we have our tabs taking up the whole window, we can add more by right-clicking the background and choosing ‘Add Tab Item’. We can also rename the tabs by clicking one and using their properties, or going down to the XAML and changing the name property there.

For our purposes, we’ll name the Tabs ‘Computer Name’, ‘System Info’, ‘Services’ and ‘Processes’. Now, for the process of adding content to tabs.

This is a bit tricky

When it comes to adding guts to our tabs, I don’t like to build the new guts for each tab within the tab window itself.

No, instead, I like to make a new window (right click the project in Solution Explorer, Choose Add, then New Window, and pick Window WPF) for each of my tabs. Then I can decorate them there, get them sized up, and then copy-paste the XAML or the UI elements into the Tab where they’ll reside when I’m done.

This way, I make a good ComputerConnection.XAML window once, then I can copy it and paste it as many times as needed, and reuse it later on down the line. If the code’s already embedded into a tabcontrol, pulling it out to reuse later can be tricky.

Making a new window for each tab is much easier for development

Now with a new window created, I pick the tools I need, build it here, and then when it’s finished, I copy and paste it into the tabbed UI. I found it quite difficult to do this by manually dragging and dropping tools from the toolbox into the tab itself. If you’ve got a gamer mouse or much better dexterity, or are just plain cooler than me (you probably are, I write a blog about a scripting language, that doesn’t scream ‘Taking the cheerleader to prom’ to me) then go ahead and build your whole tool, bypassing the method I provide here.

This is what a blank window looks like. Time to put some clothes on this naked baby!
This is what a blank window looks like. Time to put some clothes on this naked baby!

So I’ve got my new window which I’ve named and titled ComputerName_Tab. I’m going to drop in an image, a textblock, a button, and a textbox, and change the background color to something dark and moody looking.

Important tip

This is a time to make sure we’re using good naming conventions for our UI elements. When we paste our XAML over to PowerShell snippet, we’ll have a lot of $WPFButtons to keep track of, if we’re not careful. From now on, when we add elements, lets take the time to give them a logical name, like ‘VerifyOnline_Button’ and ‘ComputerName_textbox’, etc.

04_e

Here’s my XAML if you want to be lazy. Just copy and paste this after the window declaration in Visual Studio.

<Grid Background="#FF0B4A80">
    <TextBlock TextWrapping="WrapWithOverflow" VerticalAlignment="Top" Height="89" Width="314" Background="#00000000" Foreground="#FFFFF7F7" Margin="22,21,101,0" >
    This is the FoxDeploy official Awesome Tool.  To begin using this tool, first verify that a system if online, and then progress to any of the above tabs.
</TextBlock>
    <TextBox x:Name="ComputerName" TextWrapping="Wrap" HorizontalAlignment="Left" Height="32" Margin="21,142,0,0" Text="TextBox" VerticalAlignment="Top" Width="135" FontSize="14.667"/>
    <Button x:Name="Verify_Button" Content="Verify Online" HorizontalAlignment="Left" Margin="174,142,0,0" VerticalAlignment="Top" Width="93" Height="32"/>
    <Image x:Name="image1" Stretch="UniformToFill" HorizontalAlignment="Left" Height="98" Margin="10,174,0,0" VerticalAlignment="Top" Width="245" Source="C:\Users\Stephen\Dropbox\Speaking\Demos\module 13\Foxdeploy_DEPLOY_large.png"/>
    </Grid>

Now, that I’ve got this configured the way that I like it, I’m going to copy this whole body of code, and paste it into the master .xaml file, the one with the tabs we made earlier.

When you’re selecting XAMl, you only need to copy and paste everything from

<Grid> to </Grid>
Overwrite the you see here
In case it’s not clear, you want to paste your code in between the TabItem tags

When you click away after pasting, the window will update in Visual Studio and you should see your same UI compressed into the tab. You may need to resize some things to make them fit, but this is looking good so far!

05
It’s the same UI, now within the tabbed screen!

Now, let’s flesh out the rest of the tabs. For System Info, I’m going to copy and paste the formatting we already did for Part II of this blog series, excerpted here, after renaming some elements and removing cruft. Don’t worry about looking up the post unless you want to, what I’ve got here will work just fine if you’re following along.

<Grid Background="#FF0B4A80">
                    <Button x:Name="Load_diskinfo_button" Content="get-DiskInfo" HorizontalAlignment="Left" Height="24" Margin="10,10,0,0" VerticalAlignment="Top" Width="77"/>
                    <ListView x:Name="disk_listView" HorizontalAlignment="Left" Height="115" Margin="10,40,0,0" VerticalAlignment="Top" Width="325" RenderTransformOrigin="0.498,0.169">
                        <ListView.View>
                            <GridView>
                                <GridViewColumn Header="Drive Letter" DisplayMemberBinding ="{Binding 'Drive Letter'}" Width="80"/>
                                <GridViewColumn Header="Drive Label" DisplayMemberBinding ="{Binding 'Drive Label'}" Width="80"/>
                                <GridViewColumn Header="Size(MB)" DisplayMemberBinding ="{Binding Size(MB)}" Width="80"/>
                                <GridViewColumn Header="FreeSpace%" DisplayMemberBinding ="{Binding FreeSpace%}" Width="80"/>
                            </GridView>
                        </ListView.View>
                    </ListView>
                </Grid>

Alright, pasting that in to our UI, we should see the following.

06

This is a great time to take this code and try and run it in PowerShell. If it works…you’re in business, and the next step is to hook up some of the UI elements with some Add_ methods.

Copied and paste and it worked in PowerShell!
Copied and paste and it worked in PowerShell!

At this point, we’ll move on to the coolest part, which is how to handle events.

Events and how to handle them

This was something that confused me for a LONG time., in fact it’s been the dreaded question of my bootcamps as well. This is how I used to handle questions like these.

Student: ‘Stephen, how do I do something when the textbox loses focus, or when the text changes, or when blahBlah happens?’.
Me:
Me:
Me: * runs to bathroom and hides till day is over*

Here’s how it works.

Every GUI element in a form has a collection of Events that get triggered on certain conditions. They have names which are pretty easy to understand too, things like MouseEnter, MouseLeave(this event name has always sounded kind of disappointed to me), texthasChanged, etc.

When we’re loading this our form code into memory, the c# compiler is transforming our XAML into intermediate language code, another phrase for code on it’s way to becoming machine executable code. When that happens, we get a whole lot of what developers call ‘syntax sugar’–which sounds awfully diabetic–but means that the tools help us do work and give us shortcuts.

One of those shortcuts is that the compiler snorts through our GUI like a pig looking for truffles, finds all of the events associated with the objects on our form, and sets up handlers for each of the events, in the form of methods that begin with Add_.  We can use these to setup a handler, so that when an event is triggered, something cool happens.

There are a metric pant load of events, for every element. You can see them by piping one of your objects into Get-Member like so:

 &nbsp;$WPFComputerName | Get-member Add* -MemberType Method -force
All we have to do is add some code after the event we want to handle, and that's it. Sugar baby, SUGAR!
All we have to do is add some code after the event we want to handle, and that’s it. Sugar baby, SUGAR!

So, let’s say you want to run a chunk of code when the user moves over to another element in your form (which is called LosingFocus, which happens to me constantly during the day…hey I wonder what’s on reddit…). For example, let’s echo out the value of a box when a user moves away from the box.

 &nbsp;$WPFComputerNameBox.Add_LostFocus({Write-Host $WPFComputerName.Text})

I’ll stage this in the context of a little mini GUI, here’s the XAML, and then below, the PowerShell code.

<Grid Background="Azure">
        <Button x:Name="button" Content="Click Me" HorizontalAlignment="Left" Height="32" Margin="62,186,0,0" VerticalAlignment="Top" Width="106"/>
        <TextBox x:Name="textBox" HorizontalAlignment="Left" Height="30" Margin="62,92,0,0" TextWrapping="Wrap" Text="Type stuff here" VerticalAlignment="Top" Width="106"/>

    </Grid>
</Window>

Method Code

$WPFbutton.add_MouseEnter({Write-host 'Yay, the mouse is over me!'})
$WPFbutton.Add_MouseLeave({Write-host 'he didnt click me...:('})

$WPFTextBox.Add_TextChanged({Write-host "the user typed something, something like this $($WPFtextBox.Text)"})

As I interact with my form, you can see the background updating in real time.

RESUME HERE – Giving the Form some Smarts

With that done, I’m now going to go back and set the tabs for tabs 2, 3 and 4 as disabled, so that the user will have to interact with tab 1 before proceeding. Do this by adding IsEnabled=”False” to each TabItem header, like this:

<TabItem Header="System Info" IsEnabled="False">

If you’re following along at home, your XAML should look something like this now:

<Window x:Class="Tab_Me_baby_one_more_time.TabMe" 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:Tab_Me_baby_one_more_time" mc:Ignorable="d" Title="TabMe" Height="258.4" Width="486.4">
    <Grid>
        <TabControl x:Name="tabControl">
            <TabItem Header="ComputerName">
                <Grid Background="#FF0B4A80">
                    <TextBlock TextWrapping="WrapWithOverflow" VerticalAlignment="Top" Height="89" Width="314" Background="#00000000" Foreground="#FFFFF7F7" Margin="10,10,150.4,0" >
    This is the FoxDeploy official Awesome Tool.  To begin using this tool, first verify that a system if online, and then progress to any of the above tabs.
                    </TextBlock>

                    <TextBox x:Name="ComputerName" TextWrapping="Wrap" HorizontalAlignment="Left" Height="32" Margin="20,67,0,0" Text="TextBox" VerticalAlignment="Top" Width="135" FontSize="14.667"/>
                    <Button x:Name="Button" Content="Verify Online" HorizontalAlignment="Left" Margin="173,67,0,0" VerticalAlignment="Top" Width="93" Height="32"/>
                    <Image x:Name="image1" Stretch="UniformToFill" HorizontalAlignment="Left" Height="98" Margin="9,99,0,0" VerticalAlignment="Top" Width="245" Source="C:\Users\Stephen\Dropbox\Speaking\Demos\module 13\Foxdeploy_DEPLOY_large.png"/>
                </Grid>
            </TabItem>
            <TabItem Header="System Info" IsEnabled="False">
                <Grid Background="#FF0B4A80">
                    <Button x:Name="Load_diskinfo_button" Content="get-DiskInfo" HorizontalAlignment="Left" Height="24" Margin="10,10,0,0" VerticalAlignment="Top" Width="77"/>
                    <ListView x:Name="disk_listView" HorizontalAlignment="Left" Height="115" Margin="10,40,0,0" VerticalAlignment="Top" Width="325" RenderTransformOrigin="0.498,0.169">
                        <ListView.View>
                            <GridView>
                                <GridViewColumn Header="Drive Letter" DisplayMemberBinding ="{Binding 'Drive Letter'}" Width="80"/>
                                <GridViewColumn Header="Drive Label" DisplayMemberBinding ="{Binding 'Drive Label'}" Width="80"/>
                                <GridViewColumn Header="Size(MB)" DisplayMemberBinding ="{Binding Size(MB)}" Width="80"/>
                                <GridViewColumn Header="FreeSpace%" DisplayMemberBinding ="{Binding FreeSpace%}" Width="80"/>
                            </GridView>
                        </ListView.View>
                    </ListView>
                </Grid>

            </TabItem>
            <TabItem Header="ComputerName" IsEnabled="False">
                <Grid Background="#FF0B4A80"/>
            </TabItem>
            <TabItem Header="Processes" IsEnabled="False">
                <Grid Background="#FF0B4A80"/>
            </TabItem>
        </TabControl>

    </Grid>
</Window>

We’re going to plug some logic in at the bottom of our XAML/WPF Snippet. When the user launches this form, we want the $WPFComputerName.Text to default to the user’s computer name. When the user clicks the $WPFVerify button, we’re going to ping the system, if it replies, we’ll activate the rest of the tabs.

When I said earlier that every object in our GUI has a slew of events, that includes the form (background) of our app itself.  We’ll use the Form’s own Add_Loaded({}) event handler to tell our form to run a snip of code when it finishes loading, which is a useful way to set a default value for a box, for instance.

Below that, we’ll give our Verify button some code to run when it throws a click event (when the user clicks the button), via the .Add_Click({}) event handler method.  The code will try to ping the computer in the ComputerName box; if it replies, it will then enable each of the tabs for the user to click them by stepping through each item in the TabControl, and setting the ‘IsEnabled’ property of each to $True, to turn the lights on.

$Form.Add_Loaded({$WPFComputerName.Text = $env:COMPUTERNAME})    

$WPFVerify.Add_Click({if (Test-Connection $WPFComputerName.Text -Count 1 -Quiet){
    write-host "$($WPFComputerName.Text ) responded, unlocking"
    $WPFtabControl.Items[1..3] | % {$_.IsEnabled = $true}
    }
    else{
    write-host "$($WPFComputerName.Text ) did not respond, staying locked"
    $WPFtabControl.Items[1..3] | % {$_.IsEnabled = $false}
    }
})

Now when the form loads, the user has to interact with tab 1 before they can go on to the rest of the UI.

User can't click these till they ping a system first
User can’t click these till they ping a system first

For our System / Disk Info panel, I’m liberally applying the principle of code reuse to recycle the code from Part II of this series.  There we covered how to instruct PowerShell to assign certain properties to certain columns in a GridView table, using Binding.  This code will grab the hard drive info, and display it in a table on tab II.

$WPFLoad_diskinfo_button.Add_Click({
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}}
                                                                 }

Get-DiskInfo -computername $WPFComputerName.Text | % {$WPFdisk_listView.AddChild($_)}

})

With all of these changes in place, when the user provides a valid computer name, the other tabs will all unlock, then they can click the Get-DiskInfo button and…get some disk info.

10

Wrapping up

Your next steps to flushing out the rest of the UI is to figure out how to apply the same principles from Tab II to the tabs for Services and Processes.  I’ve got you started here with the GUI for the Services Tab, it’s up to you to handle the GUI for the processes tab, and the logic for both.   If you pay close attention to one of the column headings in the Services Tab, you’ll find you have to think outside of the box to come up with the value I’m asking for there.

Should you come up with something you’re particularly proud of, please sanitize your code (remove company info) and share it in the comments.  I’d recommend making a Gist or putting your code on GitHub or PasteBin, rather than dumping in 30KB of PowerShell and XAML below 🙂   I’m always surprised by the creativity my readers display, particularly when proving me wrong :p

Part V – Building Responsive Apps using Runspaces and adding Progress Bars

Complete XAML

<Window x:Class="Tab_Me_baby_one_more_time.TabMe" 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:Tab_Me_baby_one_more_time" mc:Ignorable="d" Title="TabMe" Height="258.4" Width="486.4">
 <Grid>
 <TabControl x:Name="tabControl">
 <TabItem Header="ComputerName">
 <Grid Background="#FF0B4A80">
 <TextBlock TextWrapping="WrapWithOverflow" VerticalAlignment="Top" Height="89" Width="314" Background="#00000000" Foreground="#FFFFF7F7" Margin="10,10,150.4,0" >
 This is the FoxDeploy official Awesome Tool. To begin using this tool, first verify that a system if online, and then progress to any of the above tabs.
 </TextBlock>

 <TextBox x:Name="ComputerName" TextWrapping="Wrap" HorizontalAlignment="Left" Height="32" Margin="20,67,0,0" Text="TextBox" VerticalAlignment="Top" Width="135" FontSize="14.667"/>
 <Button x:Name="Verify" Content="Verify Online" HorizontalAlignment="Left" Margin="173,67,0,0" VerticalAlignment="Top" Width="93" Height="32"/>
 <Image x:Name="image1" Stretch="UniformToFill" HorizontalAlignment="Left" Height="98" Margin="9,99,0,0" VerticalAlignment="Top" Width="245" Source="C:\Users\Stephen\Dropbox\Speaking\Demos\module 13\Foxdeploy_DEPLOY_large.png"/>
 </Grid>
 </TabItem>
 <TabItem Header="System Info" IsEnabled="False">
 <Grid Background="#FF0B4A80">
 <Button x:Name="Load_diskinfo_button" Content="get-DiskInfo" HorizontalAlignment="Left" Height="24" Margin="10,10,0,0" VerticalAlignment="Top" Width="77"/>
 <ListView x:Name="disk_listView" HorizontalAlignment="Left" Height="115" Margin="10,40,0,0" VerticalAlignment="Top" Width="325" RenderTransformOrigin="0.498,0.169">
 <ListView.View>
 <GridView>
 <GridViewColumn Header="Drive Letter" DisplayMemberBinding ="{Binding 'Drive Letter'}" Width="80"/>
 <GridViewColumn Header="Drive Label" DisplayMemberBinding ="{Binding 'Drive Label'}" Width="80"/>
 <GridViewColumn Header="Size(MB)" DisplayMemberBinding ="{Binding Size(MB)}" Width="80"/>
 <GridViewColumn Header="FreeSpace%" DisplayMemberBinding ="{Binding FreeSpace%}" Width="80"/>
 </GridView>
 </ListView.View>
 </ListView>

 <Label x:Name="DiskLabel" Content="Disk info for system: " HorizontalAlignment="Left" Height="24" Margin="117,10,0,0" VerticalAlignment="Top" Width="197" Foreground="#FFFAFAFA"/>
 </Grid>

 </TabItem>
 <TabItem Header="Services" IsEnabled="False">
 <Grid Background="#FF0B4A80">
 <Button x:Name="Load_services" Content="Load Services" HorizontalAlignment="Left" Height="24" Margin="10,10,0,0" VerticalAlignment="Top" Width="77"/>
 <ListView x:Name="service_listView" HorizontalAlignment="Left" Height="115" Margin="10,40,0,0" VerticalAlignment="Top" Width="355">
 <ListView.View>
 <GridView>
 <GridViewColumn Header="Name" DisplayMemberBinding ="{Binding ServiceName}" Width="80"/>
 <GridViewColumn Header="DisplayName" DisplayMemberBinding ="{Binding 'DisplayName'}" Width="100"/>
 <GridViewColumn Header="Status" DisplayMemberBinding ="{Binding 'Status'}" Width="80"/>
 <GridViewColumn Header="AutoStart" DisplayMemberBinding ="{Binding 'AutoStart'}" Width="80"/>
 </GridView>
 </ListView.View>
 </ListView>
 <Button x:Name="Stop_service" Content="Stop Service" HorizontalAlignment="Left" Height="24" Margin="129,10,0,0" VerticalAlignment="Top" Width="77"/>
 <Button x:Name="Start_service" Content="Start Service" HorizontalAlignment="Left" Height="24" Margin="258,10,0,0" VerticalAlignment="Top" Width="77"/>
 </Grid>
 </TabItem>
 <TabItem Header="Processes" IsEnabled="False">
 <Grid Background="#FF0B4A80">
 <TextBlock Name="processes_text" TextWrapping="WrapWithOverflow" VerticalAlignment="Top" Height="89" Width="314" Background="#00000000" Foreground="#FFFFF7F7" Margin="10,10,150.4,0" >
 Do something cool 🙂
 </TextBlock>
 </Grid>
 </TabItem>
 </TabControl>

 </Grid>
</Window>

My syntax highlighter was causing issues with the PowerShell code I posted. Instead, you’ll find the completed script here on the Github page for this post.

For additional reading on Events, I’d recommend checking out Keith Hill’s Blog Post here, and June Blender’s excellent post here. Both helped me to finally understand how it is that this works, and I truly appreciate it.

If you have any features you’d like to see me include next time, drop a comment here, hit me up on Twitter, or send me an e-mail at myFirstName At Foxdeploy.com. (You do have to replace MyFirstName with Stephen, by the way!)

If you have a particular scenario and it’s interesting enough, I might write a whole post on it. So if you want to recreate the ConfigMgr 2012 UI in PowerShell XAML, let me know 🙂

Who knows where we’ll go from here…

!! GUI Progress bars? Must have!
!! GUI Progress bars? Must have!

62 thoughts on “Part IV – PowerShell GUIs – how to handle events and create a Tabbed Interface

  1. code_man65 September 8, 2015 / 2:43 pm

    So Foxdeploy has already seen this, but I did a little bit more cleanup on it. I took inspiration from his GUI Toolmaking series and created a GUI front-end new user onboarding script. The generic version is available here: https://gist.github.com/codeman65/24777394b180c1bb9afe

    Things that would be useful to add in the future: A second tab to select groups for the new user but I am unsure how to have a group of check-boxes that dynamically change the new-aduser cmdlet parameters at the moment.

  2. EM September 8, 2015 / 9:11 pm

    Not sure what’s going on. But your code is not working for me.
    On a Windows 10240 Build, I get the following…………..

    H:\FOX_TabExample.ps1
    WARNING: We ran into a problem with the XAML code. Check the syntax for this control…
    Exception calling “Load” with “1” argument(s): “Cannot create unknown type ‘{http://schemas.microsoft.com/winfx/2006/xaml/presentation}span’.”
    You cannot call a method on a null-valued expression.
    At H:\FOX_TabExample.ps1:94 char:62
    + … electNodes(“//*[@Name]”) | %{Set-Variable -Name “WPF$($_.Name)” -Valu …
    + ~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

    You cannot call a method on a null-valued expression.

      • EM September 10, 2015 / 4:10 pm

        Cheers!

        I tried to quickly add a progress bar to yours to post back, but for some reason some of the classes were not reconised like $ProgressBar.PerformStep() I checked the MSDN site a thousand times and the method is there, but ISE didn’t like it and the parser didn’t either. Maybe things work differently when using XAML? I use it every day in my hand coded GUIs.

        Anyway, awesome work. Let the GUI games begin.

        • FoxDeploy September 10, 2015 / 4:26 pm

          There’s an extra assembly you have to load to get the progress bar to work, getting it animated requires a custom object of a funky type (a duration object) to control the animation.

          That’s gonna be in part V. Following part V will be ‘doing this powershell stuff in c#’

      • Mattski September 15, 2015 / 7:40 am

        Stephen, will post Part V compile the ps script into an executable? That would be cool to show/explain. Awaiting eagerly!!! 🙂

  3. EM September 10, 2015 / 3:38 am

    Thank you for taking the time to write the awesome series. I had issues at first, because Visual Studio was giving me some bum code. I fixed it up manually and now it’s working well.

    Maybe your code that removes some of the characters needs to also remove the ‘class’ part, as the errors were partly related to the first few lines about the <window x: class part.

    Have you considered using ISESteroids? It will help point out bad code, and show you your variables change as you walk through your code.

    Can't wait for the next part. I'd like to see something to bounce a series of servers, that does it one at a time and waits for each successive server to start responding before it bounces the next one. There are some powershell commands where you specify a service to check to consider the server up.

  4. Tom Alessi September 18, 2015 / 3:25 pm

    Posted this on Part I. But I probably should have posted int he most-recent section. Since now that’s this one (awesome, BTW!), here’s my post. Thanks again! Any help here would be GREAT!

    So this is simply excellent. I really appreciate it! So long ASCII menus!
    BUT! I’m getting the following error. Crashes PS or ISE. Crashes on multiple machines. Thing is, it’s not at ALL consistent. Errors out 1 in 20 times maybe. Almost always the 1st run of the day (weather still logged in or not). Any help would be GREAT! The error is:

    Problem signature:
    Problem Event Name: PowerShell
    NameOfExe: PowerShell_ISE.exe
    FileVersionOfSystemManagementAutomation: 6.3.9600.17090
    InnermostExceptionType: System.ComponentModel.Win32Exception
    OutermostExceptionType: System.ComponentModel.Win32Exception
    DeepestPowerShellFrame: unknown
    DeepestFrame: MS.Win32.UnsafeNativeMethods.PostMessage
    ThreadName: Pipeli..ution Thread
    OS Version: 6.3.9600.2.0.0.256.48
    Locale ID: 1033

    • FoxDeploy September 18, 2015 / 5:35 pm

      You mind putting your code on github or paste bin and putting down a link here? I’ll take a look. Github woudl be preferred

      • Tom Alessi September 21, 2015 / 7:56 am

        http://pastebin.com/90K3y9K7

        Thanks for taking a look. As you’ll see, it’s a really simple little script (that saves quite a bit of time for us). It usually crashes the first run of the day on each machine. Subsequent executions seem fine except for maybe 1 in 20 runs where the same thing happens. Thing is, I’d really like to rewrite many of my PS scripts to include GUI items. So I want to use this going forward. But only if I can get it more stable. 🙂

        Thanks!

        • FoxDeploy September 21, 2015 / 1:46 pm

          I looked up the error message and it’s related to a form taking too long to respond to an action. I’m not a dotnet dev, so it’s hard for me to go too far into this, but what if you just move all of the actions to the end of the body of the function, like I’ve done here.

          http://pastebin.com/1DG3kN3t

          If it still fails, when does it fail? When you first run the .ps1, or when you click Create or Cancel?

          Hope this helps!

      • Tom Alessi September 21, 2015 / 2:47 pm

        Thanks. Unfortunately, no change in behavior. It immediately crashed. Same error. Subsequent execution worked fine.

        As for where it fails.. It never displays the form. Basically crashes immediately.

  5. FoxDeploy September 21, 2015 / 4:13 pm

    Is there a Windows Event created when it crashes? I’ve read about certain dotnet versions having an issue like this one.

    • Tom Alessi September 22, 2015 / 8:16 am

      So there’s an error in the App log:

      Fault bucket , type 0
      Event Name: PowerShell
      Response: Not available
      Cab Id: 0

      Problem signature:
      P1: PowerShell_ISE.exe
      P2: 6.3.9600.17090
      P3: System.ComponentModel.Win32Exception
      P4: System.ComponentModel.Win32Exception
      P5: unknown
      P6: MS.Win32.UnsafeNativeMethods.PostMessage
      P7: Pipeli..ution Thread
      P8:
      P9:
      P10:

      Attached files:

      These files may be available here:
      C:\Users\tom\AppData\Local\Microsoft\Windows\WER\ReportArchive\Critical_PowerShell_ISE.e_a3b47b9f707a429bcbe5a61a7adabce41a567a3_00000000_28154eba

      Analysis symbol:
      Rechecking for solution: 0
      Report Id: 37baebc4-612b-11e5-be8a-98588a02c9ed
      Report Status: 0
      Hashed bucket:

      However, what I did find interesting in the Windows PowerShell log is that the “Engine state is changed from None to Available” 1 second AFTER the failure. I wonder if PowerShell’s just not ready at the time?

      • FoxDeploy September 22, 2015 / 8:32 am

        The powershell engine runs at all times, using dotnet. For some reason, there is a fatal error in dotnet when it is running this code. The specific error is in unsafe native method, I’ll look into it and see what I find.

  6. Sudhakar September 24, 2015 / 4:24 am

    Hi Stephen,

    Based on your concept I am creating an GUI using Visual Studio, but the only problem I am facing is I cannot link a button which will which will make me move in between the tab-form like a next and back button.

    Can you please help me out in this?

  7. Luke September 24, 2015 / 7:08 am

    Only a suggestion:

    Now, that I’ve fallen down the WPF XAML rabbit hole, I was wondering if in future articles you could cover the importance of using binding to control how your GUI behaves and to minimise the amount of code required. I’m trying to use as much GUI control in the XAML as possible to minimise the amount of PowerShell code. I’m sure others will appreciate learning these techniques. I’m enjoying the series and looking forward to the next instalment.

    Example:

  8. Andy (@AndyInv) October 6, 2015 / 3:58 am

    I’m still surprised, even with VS2015, that you still have to jump through hoops like this. Does it not make more sense to update VS2015 with a proper Powershell GUI editor, and implement the whole IDE GUI process as you would for a normal forms application?

    • FoxDeploy October 6, 2015 / 6:53 am

      It’s actually possible to code powershell entirely within visual Studio. I’m just not skilled with it yet so I’m written what I know.

  9. Brian Steele October 14, 2015 / 2:26 pm

    I’d love to see some of the more advanced elements like TreeView and GridView used. Ideally, I’d like to see how to work with selected items. For example, Make a TreeView that displays folders on a drive, and when you select a folder, have a ListView or GridView displays the files contained within the folder. And finally, be able to select a specific file, or files, in the GridView and click a button to perform an action on it(like delete, rename, etc)

  10. Chad White November 4, 2015 / 5:28 pm

    Big fan of this series! I was wondering for the services tab, could you demonstrate a way to be able to sort the columns asc, desc by name or status? And is there a way to bring a different tab into focus when you click on an acknowledge button?

  11. lordvacano February 11, 2016 / 11:00 pm

    Can you please post an example of textbox validation? I am looking for a way to validate the textbox and letting the user know about the validation error with a highlighted border around the textbox. I have seem examples in c# but nothing in Powershell.

  12. Bill March 9, 2016 / 4:16 am

    Hi Stephen, and all off them that follow your Blog.

    I’m create an GUI using Visual Studio, and that works well, thanks for all your work, to share this with us.
    My only problem is,
    My script, used the form variable to change some system settings on a remote system.
    Without the Form, this works fine, while the variable are applied in the Pssession section.
    When I used the Form to insert the Variable’s I need found a way to export them to the remote session.

    So my question is, if one off you know how can I forward the Form Variable’s to an pssession?

    • FoxDeploy March 9, 2016 / 4:21 pm

      Hey Bill, you need to provide us a gist or pastebin link to your code. Otherwise, I’ve got no idea what your code looks like and can’t even begin to guess.

      • Bill March 10, 2016 / 4:49 pm

        “@

        $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

        function NewTest_Item
        {

        $WPFmytest.Text
        $WPFouname.Text
        $WPFServerName.Text
        $WPFcomboBox.text

        Everything works fine, as long I run this on the local device, but as soon I use these variable in an remote session,

        $s = New-PSSession -ComputerName testserver.example.com
        Enter-PSSession -session $s

        The remote session is not aware from the local variable. So the question is how can I export, move, or import the variable?

        • James A. Wall May 19, 2016 / 7:13 am

          Define the variable with in the new session, I presume that would work.

  13. James A. Wall May 18, 2016 / 12:11 pm

    Just want to say this is amazing work, You really are an asset! I have some question about how to initiate a a query against a table field as I type into a box. So if I have a table called trucks and a column called name. I want to link a box in the GUI, so once I start typing a name in the box it access and shows whats available in that column from the table.

    Here is the paste bin of my code
    http://pastebin.com/50dumDnA

    Thank you!

    • FoxDeploy May 18, 2016 / 12:19 pm

      When you run Tque | export-csv c:\path\file.csv, does it create a file with the values you’d expect? Thats the first thing to figure out. I can’t help w/ your SQL aspects, having no such db or tables to query against.

  14. James A. Wall May 18, 2016 / 11:33 pm

    Wow, I didn’t see you responded so quick! thanks!

    So I have progress into knowning more what I need. I need to have an autocomplete text box. Tque works great as it populates the table fine with in the app. I can send you the database to your email if you would like.

    I got my table and column query down that I want to auto complete my text box from. There are 7500 +/- rows in the table.

    $adOpenStatic = 3
    $adLockOptimistic = 3
    $objConnection = New-Object -comobject ADODB.Connection
    $objRecordset = New-Object -comobject ADODB.Recordset
    $objConnection.Open(“Provider=Microsoft.ACE.OLEDB.12.0; Data Source = c:\Database\Gen3Data.mdb”)
    $objRecordset.Open(“Select Truck_ID from Trucks”, $objConnection,$adOpenStatic,$adLockOptimistic)

    $Mytrucks = $objRecordset.GetRows()
    $objRecordset.Close()
    $objConnection.Close()
    $comboitems = @()
    foreach($trck in $Mytrucks){$comboitems += $trck}

    $comboitems ………………. Is an array and it will have all the truck names that I want to auto complete from. I am now stumped, but I know what I want. An autocomplete text box, lol , against all the rows in the array We just built.

    Again, thanks a million!

    • FoxDeploy May 19, 2016 / 7:20 am

      Looks great! To setup autocomplete, check out post 2 and 3 in this series, I cover what you need to do there.

  15. James A. Wall May 19, 2016 / 10:08 am

    Thanks a million! I will report the results!

  16. EM May 26, 2016 / 6:17 pm

    My biggest issue with doing the XAML in PS thing is…. Errors that only occur when using XAML in this manner.

    I have an issue with a TabControl that I can’t seem to fix. Errors like
    Error: “The ‘Grid’ start tag on line 7 position 6 does not match the end tag of ‘TabItem’. Line 24, position 11.”

    Works in Visual Studio with no problem. I sometimes find that VS places crap in the XAML which I’ve had to fish out to get stuff working.

    If I cut our the whole Tabcontrol section the script works.

    Stephen, maybe have a tips n tricks section? Assuming others are having issues with their xaml.

    • FoxDeploy May 26, 2016 / 6:36 pm

      Share your code in a gist or paste?

      • EM May 26, 2016 / 6:48 pm

        All those kinds of sites are blocked. My try later at home.

        • FoxDeploy May 26, 2016 / 7:00 pm

          I couldn’t do my job without github and stack overflow

        • EM May 26, 2016 / 7:20 pm

          OK, so while I was troubleshooting this “Exception calling “Load” with “1” argument(s): “Cannot create unknown type ‘{http://schemas.microsoft.com/winfx/2006/xaml/presentation}Null’.”

          I starting going through all the end tags and placed ‘/’ at the ends.

          Turns out that ‘TabItem’ doesn’t have ‘/’ at the end.

          Once I removed all traces of Background=”{x:Null}” and the trailing ‘/’ from the TabItems, it all worked.

          Moral is, be very careful what properties you start playing with in Visual Studio, cause PS might not like them.

        • FoxDeploy May 26, 2016 / 8:16 pm

          Yep, visual Studio has a different debugger and catches a lot of errors for you which powershell just gives up on. Good job finding the problem!

  17. EM May 26, 2016 / 6:58 pm
  18. RM June 14, 2016 / 10:19 am

    Hi,
    Based on your work, i’ve tried different things and there’s one particular stuff that i can’t make work !

    I’m trying to populate the “system Info” tab directly with the tab change:

    $WPFtabControl.add_SelectionChanged({
    if ($WPFsys.IsSelected){
    Get-DiskInfo2
    }
    })

    Doing this works but if i click on any line of the listview, i receive the following message:
    “The calling thread cannot access this object because a different thread owns it.”

    Thanks by advance,

    Renaud.

  19. Peter Kriegel June 17, 2016 / 3:12 am

    Hi Owen!

    I like to intoduce my Asyncronous WPF GUI work to you.

    My example show: How to create a non blocking Graphical User Interface (GUI) with Windows Presentation Foundation (WPF) and how to write Data with PowerShell to GUI controls correctly from a different thread (Runspace) over the Dispatcher.

    Take a Look at: http://poshcode.org/6321

    Greets
    Peter Kriegel

  20. chasen July 12, 2016 / 4:18 pm

    Is it possible to do this with different windows instead of tabs? I am trying to determine how I would open the next window after clicking the button. In your example, you just set the tab variable to false. However, there is never a variable stored for the actual window.

    Does anyone know how to do this?

  21. bettingerchasen July 12, 2016 / 4:21 pm

    I am trying to create a GUI that opens a new window as it progresses through the application. Is there a way to do this and not use the tab method? The problem that I am running into is: a variable does not get stored for the window. Therefore, I don’t know how I would call upon the window to open or close it once the button is pushed.

    I don’t like using the tabs because I don’t think it looks aesthetically good. I would like to have multiple windows so that my user can seamlessly progress onto the next page by just clicking a button.

    Does anyone have any direction on how to achieve this vision?

    • FoxDeploy July 12, 2016 / 4:36 pm

      You want multiple Windows? That’s not hard, store each window in a function at the top of your script, like show-window1, etc. Then when you dismiss one window, call the next. I could blog this approach if you want

      • bettingerchasen July 13, 2016 / 1:45 pm

        That would be great. I am interested to see the approach. I understand your methodology; however, I do not have the ‘add_click’ method. Is it because I’m using an outdated version of powershell? I’m quite confused.

  22. Philip P. August 29, 2016 / 5:04 pm

    Ok guys I don’t often ask for help but I am truly stuck and anything you could offer would be useful. This is the code that is failing:

    $inputXML = @”

    This is the error Message:

    WARNING: We ran into a problem with the XAML code. Check the syntax for this control…
    Exception calling “Load” with “1” argument(s): “Cannot set unknown member ‘{http://schemas.microsoft.com/expression/blend/2008}LayoutOverrides’.”
    You cannot call a method on a null-valued expression.
    At P:\Shared\IT\Powershell\DCSC Toolkit GUI\DCSC GUI Build.ps1:122 char:62
    + … electNodes(“//*[@Name]”) | %{Set-Variable -Name “WPF$($_.Name)” -Valu …

    This was created using the standard foxdeploy setup and pasted from my GUI XAML build in VS 2015

    • FoxDeploy August 29, 2016 / 5:10 pm

      No problem, we’re happy to help, can you paste your code on a gist on github and send me the link?

      Nine times out of ten, someone double clicks on visual studio and it injects something powershell struggles with.

      • Philip P. August 29, 2016 / 5:16 pm

        The only error is in the loading of the expression/blend/2008

        If you need more of the code I can post it but it fails before it can get to them. Somehow it passed it once and generated all the WPF_Name items but now it just continues to error.

        • FoxDeploy August 29, 2016 / 5:18 pm

          The full code please 😌

      • Philip P. August 29, 2016 / 5:17 pm

        It erases my website link:
        “https://gist.github.com/Pepeterson/f1fb1df5c56f5547e4b3c981f3abd1ba”

      • Philip P. August 29, 2016 / 5:21 pm

        Full code updated.

      • Philip P. August 29, 2016 / 5:59 pm

        I got it. VS was adding layoutoverrides and process_textchanges that PS could not handle.

  23. David September 8, 2016 / 3:35 am

    Hi and thanx for sharing your works!
    Is there a way to add dinamically a new tab and fill with many object.
    In my scenario, i’ve a list of computer hostname and i want to add a tab with many infos on a click.
    For now, i can create the new tab with $NewTab = New-Object System.Windows.Controls.TabItem and add to the WPFtabControl.
    But how create the inner tab?
    Any ideas?

    • FoxDeploy September 8, 2016 / 5:42 am

      I cover this in my next post. Check back next week.

  24. rainer-michael kennedy October 27, 2016 / 5:35 pm

    Hi Stephen,

    Much appreciate the replies – I have got the control panel to (sounding a lot more funky than it actually is!).
    I’m trying to add the ability to click on the button, which then actions something else for example – another script.

    I’m not getting any errors, however the function just does not seem to be working.

    Code is here – http://pastebin.com/MkNCvh1q

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s