Part IV - DSC - One-Click Domain Controller

June 11, 2015 FoxDeploy

IntroToDsc

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


Hey guys, I haven’t forgotten about you, but it’s been a very busy month here, with me traveling to Redmond for some exciting Microsoft meetings and then back and forth to Minneapolis for a new client!


I’ve been receiving your messages and have now released the final step in this one-click Domain controller DSC Build. To recap, we left off with a DSC Config that would make our machine a domain controller, but that was it.

We didn’t have functional DHCP for clients to find us, or any other bells and whistles. In this post, we’ll be adding on DHCP so clients can get an IP address, as well as DNS to help our Machines Domain Join to our new DC. If you tried Domain Joining with our build from last time, you would have been quite disappointed. Not this time!

What has to go :

Previously, we had a single config file that did some really good stuff for us, setting up a workgroup and then making a new local admin. We’re going to gut all of that stuff, because setting up a system with a DSC Config needs to be idempotent. That means that the config needs to be able to run over and over without breaking stuff, and the previous build would do just that. If we draft a config that will result in changes every time the config is validated, we’ve created a problem.

The issue in our last config stemmed from the fact that it would change the system’s name and also add it to a work group. Then, later in the config we make it a domain controller. What would happen if the DSC config were re-evaluated is that the machine would throw an error when it got to the portion of the .mof telling it to join a workgroup. Domain Controllers can’t leave a domain until they’ve lost all of their FSMO Roles, so this is an invalid config. Bad Stephen!

As it is, that bit about joining a workgroup turned out to be wasted space, so we’ll remove that too. The full code is available below, as well as here on my GitHub site.

What’s new and what does it do

For this post, you’ll need the xNetworking Resource, found here

  • https://gallery.technet.microsoft.com/scriptcenter/xNetworking-Module-818b3583

As well as the xDHCPServer Resource, found here

  • https://gallery.technet.microsoft.com/xDhcpServer-PowerShell-f739cf90#content

You’ll also need the xComputerManagement and xActiveDirectory Resources, which can be found here:

  • https://gallery.technet.microsoft.com/scriptcenter/xActiveDirectory-f2d573f3
  • https://gallery.technet.microsoft.com/scriptcenter/xComputerManagement-Module-3ad911cc

Prepare your DSC Target Node by copying the folders in the zips to the Modules folder, in this path

$env:ProgramFiles\\WindowsPowerShell\\Modules

The big new steps we’re adding are the following:

Set a Static IP Address

The end goal of this whole shebang was to have a working DHCP server, and DHCP can’t give out IP Addresses unless they have a fixed IP themselves. In this config block , we set a Static IP of 10.20.30.1 and rename the Adapter along the way.

xIPAddress NewIPAddress { 
  IPAddress = "10.20.30.1" 
  InterfaceAlias = "Ethernet" 
  SubnetMask = 24 
  AddressFamily = "IPV4"
}

Enable the DHCP Windows Feature

This one was kind of a pain, as it was hard to figure out what the name was of the DHCP Server Role! Turns out it’s DHCP not DHCPServer (as it is listed in Get-WindowsFeature). All we do here is make sure that the DHCP server is installed, and we do it after configuring the IP address to prevent an error which would shut down our config.

WindowsFeature DHCP { 
  DependsOn = '[xIPAddress]NewIpAddress' 
  Name = 'DHCP' 
  Ensure = 'PRESENT' 
  IncludeAllSubFeature = $true
}

Enable the DHCP Address Scope

You can get super complex with Windows Server Networking using DHCP and DNS, but I always like to keep things simple, especially in my testlab. This configuration resource essentially runs us through the DHCP Wizard and ensures that a DHCP Scope Exists, giving out IP addresses from 10.20.30.5 all the way up to 250.

xDhcpServerScope Scope
     {
      DependsOn = '[WindowsFeature]DHCP'
      Ensure = 'Present'
      IPEndRange = '10.20.30.250'
      IPStartRange = '10.20.30.5'
      Name = 'PowerShellScope'
      SubnetMask = '255.255.255.0'
      LeaseDuration = '00:08:00'
      State = 'Active'
      AddressFamily = 'IPv4'
     } 

Specify the DNS server for DHCP clients to use

If you don’t get this part, your DNS Clients will throw up an ‘ ERROR: Could not join to the domain VAS_ERR_DNS: unable to lookup any DNS SRV records for’, which should be your clue to ensure that you’ve specified option 6 in your DNS settings

xDhcpServerOption Option
     {
         Ensure = 'Present'
         ScopeID = '10.20.30.0'
         DnsDomain = 'fox.test'
         DnsServerIPAddress = '10.20.30.1'
         AddressFamily = 'IPv4'
     }

The Complete Config

If you’re following from home, go ahead and delete everything from the last DSC post from line 29 to 54.

Or, if you’re laz–uh, more efficiently minded, copy and paste this code instead.

Now that we know what we’re doing, let’s give it a whirl!

Testing it out

My network is laid out like this

Layout

I’ve got my future DHCP Server and a random client Windows 10 VM both sitting on the same network. Now we’re going to enforce the configuration on my DSC Client, and then watch and see my workstation pull down a DHCP Address!

You might be thinking:

‘Hey Stephen, why not just copy the files down with DSC!’

and that’s a great question. As it turns out, you can do something like that by using what’s called a DSC Partial Configuration…which I’m still figuring out. Once I understand it, I’ll write a post about it. The long and short of it now is that you can’t reference a resource and also copy a resource within the same config because…uh…reasons.

You’re boring me, let’s get to it!

The only thing that’s left is to hit F5 and watch as my machine gets config’d! First we apply the new computer name… which needs a reboot

SetComputerName

Now, we reboot and just relaunch the configuration. We could just wait…but it’s more fun to hit -Force and watch the whole thing happen in real time

MakingaDC

If all of this worked (and it looks like it did!) we should now be able to go over to our test machine and run a DHCP /renew and see an IP address come over.

DHCP worked!

An important piece of getting this Domain Controller accepting domain joins is to make sure that new PCs to the domain can find the DC. This means that they need to ask a DNS server for the SRV record of a Domain Controller holding the Global Catalog role. We’ll run a ipconfig /all and see if our DNS Server setting is registered.

DNS server came alone

Now is where I started to get excited. See, my whole reason for going down this path is that I think making a domain controller in a Lab environment can be very daunting for first-timers, and wanted to help lower that barrier to entry. If my client machine can see the DC, then it should be able to Domain Join now. So the moment of truth…

MomentOfTruth

Now…to hit OK…

Welcome to the FoxDeploy Domain!!! Welcome to the FoxDeploy Domain!!!

Wrapping it up

I will admit, I jumped up and down when I got this part to work. We now have a one-click, single-stop Domain Controller build, perfect to use a stepping stone into whatever else you’d want to do on a domain controller. It really is kind of amazing to me that this all works, knowing how hard it would be to do this level on configuration using something like SCCM/Configuration Manager.

From here? The next step in our series will have us joining clients to this domain as well, and from there, we can do whatever you’d want to do with DSC. If you’ve got an idea or a situation from work, send me a message, and you might just be the next blog post here in this series.

Continue Reading...

Part III - Using Advanced GUI Elements in PowerShell

May 14, 2015 FoxDeploy

Learning PowerShell GUIs

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


Welcome back to the GUI Series here on FoxDeploy.com! In the previous weeks, I’ve had a lot of feedback and requests from you guys (which I absolutely love! Assuming I don’t need sleep, I’m content to stay up till the wee hours of the morning helping you guys out and responding to your comments or requests, so keep ‘em coming!). As for where this blog is heading…I’ve really been bit hard by the maker bug, and I recently purchased a RaspBerry Pi 2, which is super awesome and incredibly powerful. Direct deploying a program from Visual Studio to this little guy and seeing it light up and project content to my HDMI monitor really makes it feel like I’m doing something tangible. I’ll be making a whole series of posts about cool things you can do with the Pi. If you have any ideas, send ‘em over!

We’re going to cover a number of topics in this one, namely ‘how do I do ex’ questions. We’ll have two somewhat silly examples to start, followed by an actually useful GUI to create a user to wrap us up here.

We’ll specifically be hitting all of these guys here, in order.

  • Hide a UI Element (by special request)
  • Use Checkboxes
  • Use radio buttons (by special request)
  • Populate a drop-down box automatically

Also, in a big departure from the past, I’ll not be posting full code on here anymore. From now on, code samples from here will always be linked and kept up-to-date on GitHub. You can find the code from this post here: https://github.com/1RedOne/Post_III

Alright, let’s get started. Power up Visual Studio and create a new project, specify ‘WPF’ as your type in the search box.

Hide a UI element

We’ll draw an image and set it to invisible, and then add a button that will make it appear or disappear. You can imagine and endless amount of examples where this could be useful

Now, let’s add a meaningful image (read:skybison) and then make it hidden. Finally, let’s add a button.

The XAML

<Window x:Class="BlogPostIII.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:BlogPostIII"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid x:Name="background" Background="#FF1D3245">
        <Image x:Name="image" HorizontalAlignment="Left" Height="100" Margin="171,154,0,0" VerticalAlignment="Top" Width="100" Source="C:\Users\sred13\Dropbox\My Code\Migmon\htdocs\Appa.png" Visibility="Hidden" />
        <Button x:Name="button" Content="Reveal Hidden Skybisons" HorizontalAlignment="Left" Height="34" Margin="10,277,0,0" VerticalAlignment="Top" Width="155"/>
 
    </Grid>
</Window>

This will give us a form with an image, and a button. To hook the button up to the image, we just need to add a few snippets of code:

$WPFbutton.Add_Click({
    if ($WPFimage.Visibility -ne 'Visible'){
      $WPFimage.Visibility = 'Visible'
    }
    else {
      $WPFimage.Visibility = 'Hidden'
    }
})

And…that’s it!

RevealUI Elements

Clicking the button will reveal or hide the image. You could use the same mechanism to set an item from Disabled to Enabled as well, we’re just illustrating the principles here. Moving right along…

Use Checkboxes

Alright, what we’ll do now is draw a check box, and add a second Sky Bison. We’ll make both Bisons invisible, and if you check the box, you’ll get TWO bisons for the price of one! If you uncheck the box, the Bison dies. I’m sorry kid, but that’s just a fact of life.

To save space, I’ll only show you the PowerShell behind making this work. Check the github link if you want the XAML or Project file.

This code is garbage and ripe to be simplified! Send a PR in to fix it, if you’d like!

$WPFbutton.Add_Click({
  if ($WPFimage.Visibility -ne 'Visible'){
    if ($WPFcheckBox.IsChecked -eq $true){
      $WPFimage.Visibility = "Visible"
      $WPFimage_Copy.Visibility = 'Visible'
    }
    else {
      $WPFimage.Visibility = 'Visible'
    }
  } 
  elseif ($WPFimage.Visibility -ne 'Visible' -and {$WPFcheckBox.IsChecked -eq $false})
    {
      $WPFimage.Visibility = 'Visible'
    }
  else{$WPFimage.Visibility,$WPFimage_Copy.Visibility = 'Hidden','Hidden'}
})
 
$WPFcheckBox.Add_Checked({
    if ($WPFimage.Visibility -eq 'Visible'){
        $WPFimage_Copy.Visibility = 'Visible'
      }
    else{
      $WPFimage_Copy.Visibility = 'Hidden'
    }
  })

And the result :

Clicking the checkbox makes TWO Appa's appear! Clicking the checkbox makes TWO Appa’s appear!

Use Radio Buttons and auto-populate a Combo Box/Dropdown Box

Beginning with this example, we’ll start by combining some of the above approaches and work towards making a more useful tool, a GUI that wraps around making a new user and makes it a bit easier to set an account expiration date. We’ll use this tool to wrap the New-ADUser Cmdlet, so we should first check the requirements for that command.

We’ll need to define the user’s Given (first name), their surName (last name), account name and PW.

For the components we’ll use:

  • Checkbox - click the checkbox to specify the user is a temp and should expire
  • ComboBox - a drop-down box filled with acceptable places for a new user to be placed
  • Radio buttons - use the radio buttons to specify 7 days, 30 days, 90 days account life
  • Textblock - we’ll use these as pretty labels for our tool

You can drag and drop them however you’d like, here’s what I came up with:

Ohhh, so gooey!

So, here we go. We’ve got a first name, last name, logon name and password textboxes. You’ll use those as you would, expect to use them. At first, we’ll be displaying the PW in plaintext but on the next revision, we’ll add the feature to display asterisks and require you to click to reveal the PW. Finally, we have two new classes here, the combo box and the radio button.

ComboBoxes are used to provide a user history (similar to auto-complete in your web browser of choice) or to constrict the user to only certain options. We’ll use the ComboBox to provide three or four places where a contractor can go.

Radio Buttons are used to present the user with a number of preset options, and allow them to only select one of them at a time (usually, there is a weird thing called a tri-state radio button, which means you can select two, but those are uniformly hated and loathed by users and devs alike, and should be killed with fire and/or the delete key).

Let’s code these up!

I wrote a few chunks of code here. First, when the user clicks the checkbox to enable a limited timespan user, I needed to add some logic to enable the radio buttons and pick one of them, which you see here. This is done by adding a script block to the Checkbox using the Add_Checked and Add_Unchecked methods:

 #Add logic to the checkbox to enable items when checked
$WPFcheckBox.Add_Checked({
    $WPFradioButton_7.IsEnabled=$true
   $WPFradioButton_30.IsEnabled=$true
   $WPFradioButton_90.IsEnabled=$true
    $WPFradioButton_7.IsChecked=$true
    })
#when this box is unchecked, make sure that none of the option bubbles are selected
$WPFcheckBox.Add_UnChecked({
    $WPFradioButton_7.IsEnabled=$false
   $WPFradioButton_30.IsEnabled=$false
   $WPFradioButton_90.IsEnabled=$false
    $WPFradioButton_7.IsChecked,$WPFradioButton_30.IsChecked,$WPFradioButton_90.IsChecked=$false,$false,$false})

Next, we need to link the Create User button up to the code to make a new user. Because the user may or may not be set to expire, I wanted a method to easily end up with an object called $Hash that contains the needed settings to make a new user account. I ended up writing a helper function called Get-FormField which will gather all of the settings the user specifies into the form, which is then used later on when you click the ‘Create User’ button like so:

$WPFMakeUserbutton.Add\_Click({ #Resolve Form Settings $hash = Get-FormFields New-ADUser @hash -PassThru $Form.Close()}) 

Finally,  populating the combo box, it’s actually super easy. The combo box name in this example is targetOu_ComboBox, which ends up becoming the PowerShell object $WPFtargetOU_ComboBox. We call its AddChild method to add entries to the list. I ran a quick LDAP query (thanks to this post on SuperUser for showing me the way!) to get the default OU for a new user and stored that in $defaultOU, and then manually typed in the Distinguished Name of the other OU I wanted to provide as an option.

$defaultOU = (get-adobject -filter 'ObjectClass -eq "domain"' -Properties wellKnownObjects).wellknownobjects.Split("\`n")\[-1\].Split(':') | select -Last 1

$defaultOU,"OU=Contractors,DC=FOXDEPLOY,DC=local" | ForEach-object {$WPFtargetOU\_comboBox.AddChild($\_)} 

This results in both names pre-populating our dropdown box, like so:

Just use $WPFCombo.AddChild() to add more items to the dropdown Just use $WPFCombo.AddChild() to add more items to the dropdown

The radio buttons are used to pick 7, 30 or 90 days as the expiration date for this account. We first check to see if the Checkbox for temporary user account is checked, and if so, we then check to see which bubble/radio button is checked. We then reset $Expiration date to equal get-date.AddDays($expirationDate) and pass that along too, using Get-FormFields, this is done in lines 7-10 below.

For the full source, click here!

Let’s use my favorite test-user, I like Ham.

My wife HATES ham

And in testing this in my environment…it works!

I titled this picture 'The Expiration of Ham', it cracks me  up

I titled this picture ‘The Expiration of Ham’, it cracks me up

What’s next?

I’d planned to include building a tabbed interface, making a progress bar and finally building an all-in-one management tool using PowerShell in this post, but I’ll be saving those now for part IV of the series, as my fingers are tired and me no-wanto-writey anymore! Until then, please let me know here, on e-mail, or on Twitter if there are any other GUI features you’d like to see me outline!

Part IV - Creating a Tabbed Interface and handling events

Continue Reading...

Quick How-To: Add an image to your Raspberry Pi Windows App

May 11, 2015 FoxDeploy

IntroToIoT

This is part of the Learning Raspberry Pi Series here on FoxDeploy.com.

Click the banner for more Raspberry Pi and Windows!


One of the first things you’ll want to do when you make a GUI to push out to your Raspberry Pi 2 with Windows 10 is to make a fancy smancy GUI. To do that, you’ll need an image!

Assuming you’ve followed the guide here to make your first HelloWorld app, you might want to add an image. This will be your first image embedded in a functional app, so you’d better make it a good one!

For me, the pursuit of a fine image is half of the fun. Most of the fun! I like to Google around for the best image, that perfect piece of clipart which accurately describes my project, inspires my end user and aligns our paradigms…and then discard it and pick a picture of a Fox or a Sky Bison (my wife and I are really enjoying Avatar the Last Airbender now!)

Now that I’ve got a folder of some high quality jpegs ready

high Quality Jpegs

Using the toolbox image control, you can draw the outline for an image to put it wherever you’d like and then you’ll get a…uh…

UglyX Wait, it’s an X, what gives?

Previously, we were using PowerShell implementing .net classes to draw WPF forms, meaning we could put together a GUI in real time using bits and pieces from all over our system, including files stored in relative paths.

Since we’re not dealing with the same sorts of apps we worked with before, we can’t just point to outside files anymore. We’re talking about compiled code now; we’ve gotta up our game, and that means including assets.

Give me the short answer

Fine, I’ll save you a ton of words here. If you want to include an image in your program, you’ll have to embed it as an asset. These things get embedded in your .exe/code when you compile and are the one true way to deliver resources within your code. It is possible to download your image when the tool runs…which is something I’ll cover later.

To embed an image, follow these instructions/gifs

Open Solution Explorer and click on the Assets Folder. Now, right click->Add->Add Existing Item.

Add Item Pick your file.

Now click your image placeholder again and check out the dropdown box.

Awesome!

Success! Our image was included!

You’re now on your way to building beautiful and functional Windows 10 Raspberry Pi apps, like this one:

Uh...this doesn't look very functional Stephen

Uh…that doesn’t look very functional Stephen…

Continue Reading...

Super-Fast walkthrough: running Win10 Preview on Raspberry Pi 2 and what's it like

May 04, 2015 FoxDeploy

IntroToIoT

This is part of the Learning Raspberry Pi Series here on FoxDeploy.com. Click the banner for more Raspberry Pi and Windows!


If you’re like me, you got really excited to hear about the possibilities of running Windows 10 IoT preview on your Raspberry Pi 2.  I actually bought one just for a blog series on PowerShell and Raspberry Pi, so stay tuned for more!  Note: I did all of this while drinking some brewskies the other night.  If I can do it mildly intoxicated, then you can definitely do it sober.

What you’ll need

  • Laptop / Desktop running Windows 10 Preview, can’t be a VM as you’ll need hardware access
  • RaspBerry Pi 2:
    • Base model
    • Fancy Smancy kit - I bought this one.  You’ll really want to put a heat sink on the CPU and GPU, this thing gets super hot.  This kit includes it.
  • 8 GB or higher Class 10 Micro SD Card.  Don’t buy an off-brand or you’re begging for pain!
  • Sign-up for the Windows Connect Program here and click through all of the EULAs to enable the download tab.  Download the .zip file and unzip it.
  • Optional : SD Card reader if you don’t have one

How to get it up and running

  1. Download the .ffu file listed above.
  2. Go to your Win 10 preview machine, use DiskPart to figure out which disk your SD Card is.  This will likely be the smallest disk when you run diskpart - List Disk .  Grab the disk number.

    List disk Whoa Stephen, 64 GB, you must be a baller. That’s right, I make literally upwards of $8 an hour.

  3. Change into the directory where you put your .ffu file
  4. Run this cmd

    dism.exe /Apply-Image /ImageFile:flash.ffu /ApplyDrive:\\\\.\\PhysicalDriveN /SkipPlatformCheck

  5. When you see 100% you’re done!  Put the SD Card in your Raspberry Pi 2 and boot her up.

What’s it like?

Plug in, connect network and boot.  Done.  It takes forever to boot, like five mins.  You’ll first see a random Blue debugging screen with some clickable elements that don’t actually work.

You can click these on the left, 'App' and 'MiraCast/View' but they don't actually do anything You can click these on the left, ‘App’ and ‘MiraCast/View’ but they don’t actually do anything

You can ignore that, and then the system will reboot, showing you the cool new logo of Windows 10 IoT.

Man, FoxDeploy, this is a terrible screen shot.  Were you drunk taking a picture of your TV?  Uh...yes.  Yes I was.

Man, FoxDeploy, this is a terrible screen shot. Were you drunk taking a picture of your TV? Uh…yes. Yes I was.

Eventually you’ll end up on a page like this, showing you the IP to manage this bad boy.

There is a program you can also find in the .zip from earlier, which runs only on Windows 10 PCs.  It looks like this.

IoTwatcher_NoContextMenu

You can right-click items here to jump into a network Share, or open a Web browser.

IoTwatcher

You can open that IP in your browser for a very useful web management tool.

 You can also check process performance, which shows you a very sexy looking performance graph viewer

How'd you put load on the Pi?  By launching a PowerShell session.

How’d you put load on the Pi? By launching a PowerShell session.

Remote PowerShell works, just add the PC to your trustedHosts like using the WSman Cmdlets, and if you’re remoting from Win10, disable the PS ReadLine module (it will cause errors).  For more info, follow the guide here.

Remoting performance to the Reaspberry Pi is very, very slow.  VERY slow.  But it’s a work in progress.

My transcript of how I connected:

set-Item WSMan:\\localhost\\Client\\TrustedHosts 10.63.30.54

WinRM Security Configuration. This command modifies the TrustedHosts list for the WinRM client. The computers in the TrustedHosts list might not be authenticated. The client might send credential information to these computers. Are you sure that you want to modify this list? \[Y\] Yes \[N\] No \[S\] Suspend \[?\] Help (default is "Y"): y 

PS C:\\WINDOWS\\system32> Enter-PSSession -ComputerName 10.63.30.54 -Credential 10.63.30.54\\Administrator \[10.63.30.54\]: PS C:\\Users\\Administrator\\Documents> 

Digging into the covers, you’ll see some weirdness…like no MSI class or Win32_Product classes :(

NoMSIClass

What’s next?

Well, from here on in, we’ll try to find some cool stuff on the Raspberry Pi.  My next to-do’s include finding temperature sensors, if they exist, and finding some other cool stuff we can run on the Pi from remote PowerShell.

Continue Reading...

Windows 10 for Raspberry Pi - Solving DISM 'the Drive can't find the sector requested'

May 03, 2015 FoxDeploy

IntroToIoT

This is part of the Learning Raspberry Pi Series here on FoxDeploy.com. Click the banner for more Raspberry Pi and Windows!


Symptom

You’re like me, and super excited to start playing with Windows 10 for the Internet of Things (IoT) on your Rasperry Pi 2.  But when running the DISM commands from this post, you see the following error:

The drive cannot find the sector requested

The drive cannot find the sector requested

Reason

This is sort of a very opaque error, and in this case, double-check that you’re using a big enough SD card.  As it turns out, I accidentally picked up a 4 GB card, which is too small!  I think the specific cause of this error comes from the fact that the image in question won’t actually fit on a card smaller than 8 GB, and thus the Deployment Image Servicing Management tool craps out trying to write to that sector.

Solution

Buy a bigger SD Card!  Here’s one which will work perfectly!  When you’re buying a Micro SD card, don’t cheap out.  The quality matters and the class (the bin rate) of the card definitely matters.  Smaller cards are virtually identical to the larger capacity SD cards and the only difference is physical imperfections in the card, which can mean an earlier fail rate and other problems.

Continue Reading...

Part II - Deploying PowerShell GUIs in Minutes using Visual Studio

April 16, 2015 FoxDeploy

Learning PowerShell GUIs

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!

ZAMMLE_pic0

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:

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

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:

Running Convert tool

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

That was SO EASY!

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'

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.

Driveinfo

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.

Layout pregridview

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.

Layout Click Gridview

In properties on the right, click ‘Columns’

Layout AddColumns

This will bring up the Column collection editor

Column 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:

Columns

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:

https://gist.github.com/1RedOne/482fbcfdb6a081bafa1625fdb9e21249

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.

Where-To-make-changes

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

Layout almost there

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.

https://gist.github.com/1RedOne/b206eb919f97f5df0036e2a6a4610b0d

And the finished product:

done

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

https://gist.github.com/1RedOne/effdf04ad4c9b199bd4767e1e2fca68f

Full PowerShell Code here


#place your XAML below
$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-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

Link to the ISE Snippet

Continue Reading...

Microsoft MVP

Five time Microsoft MVP, and now I work for the mothership


Need Help?

Get help much faster on our new dedicated Subreddit!

depicts a crowd of people in a night club with colored lights and says 'join the foxdeploy subrreddit today'


Blog Series
series_sml_IntroToDsc
series_sml_PowerShellGUI series_sml_IntroToRaspberryPi Programming series_sml_IntroToWindows Remote Management Series The Logo for System Center Configuration Manager is displayed here Depicts a road sign saying 'Learning PowerShell Autocomplete'




Blog Stats