ConfigMgr Preflight with XAML and PowerShell

There are so many of these out and about, but I love making things myself, so I’ve written a preflight tool to run a number of checks before a ConfigMgr build can progress.

Most of the tools I’ve seen that provide this functionality, use a HTA or winforms. I didn’t want to use either of these because I felt they were old technologies. So I decided to figure out if it was possible to generate a XAML UI using PowerShell as a “backend”. As PowerShell is based on .Net, it was pretty easy 🙂

If you just want to take a look at the script, its here on GitHub. Although I’m happy with how it is currently, I’m still adding functionality. For now I’ll try and walk through how it currently works.

To get started, I decided what functionality I wanted out of the script and decided on the following:

  • Check whether the ‘Ethernet’ adapter is active
  • Check whether the device is laptop and if so check if the power cable is plugged in
  • Write some user friendly verbose logging to a textbox
  • Only let the user continue if all preflight checks pass

I fired up Visual Studio community edition and created a WPF solution. Then using the drag and drop editor I drew up my UI. Once happy, I copied the XAML it generated and you can see that in my “XAML” region.

To allow PowerShell to process it correctly, I put the XAML into here string and passed that into a variable to use later. I then tidied up the XAML, removing any parts PowerShell couldnt read using the following code:

$ixaml = $ixaml -replace 'mc:Ignorable="d"','' -replace "x:N",'N' -replace '^<Win.*', '<Window'

I added the assemblies needed from .NET for WPF and then generated a XAML reader. Using the XAML reader, I created a variable for each object of the UI that I could manipulate and prefixed each with “WPF”.

$xaml.SelectNodes("//*[@Name]") | % {
Set-Variable -Name "WPF$($_.Name)" -Value $form.FindName($_.Name)
}

You can take a look at what the reader has found with the following:

Get-Variable | ? Name -like "WPF*"

At this point, if you add [void]$form.ShowDialog() to the end of your code, you should be able to run your script and it should load up your WPF UI. This show dialog method should always be at the end of your script.

So now I had a script that generates a UI, I started gathering data I wanted to use from the device, the script was running on. So I wrote a few functions using WMI queries to get the status of the Ethernet adaptor, get the IP of the device, get the device type and get the battery status. Then with these functions in place, I thought about the logic of the script.

As I wanted to ensure that you could retry the checks if you wanted to, I wrapped the entire logic in a Run-StatusChecks function, this meant I could call this function later if I needed to. As this is a user facing script, it was important that it handled failures (exceptions) nicely, so I wrapped every check in an if-else. This allowed me to print a success or failure message to the status pane, indicating to the user exactly what they needed to fix. This is so much better than wrapping the whole thing in a try-catch and throwing a generic exception.

function Run-StatusChecks{
$WPFstatusTextBox.Appendtext("Starting network checks" + [char]13)
if((Get-EthStatus).ToString() -eq "2") {
$WPFstatusTextBox.Appendtext("Completing network status check" + [char]13)
$WPFstatusTextBox.Appendtext("Getting IP address assigned to Ethernet adapter" + [char]13)
$WPFstatusTextBox.Appendtext((Get-EthernetIP).ToString() + [char]13)
$LANOK = "pass"
}
else{
$WPFstatusTextBox.Appendtext("Error: No network connection found" + [char]13)
$LANOK = "fail"
}
if($LANOK -eq "pass"){
$WPFstatusTextBox.Appendtext("Querying device type" + [char]13)
if((Get-DeviceType).ToString() -eq "10" -or (Get-DeviceType).ToString() -eq "9" -or (Get-DeviceType).ToString() -eq "14"){
$WPFstatusTextBox.Appendtext("The device is a Laptop" + [char]13)
if((Get-BatteryStatus).ToString() -eq "True"){
$WPFstatusTextBox.Appendtext("The device is connected to a power source" + [char]13)
$PowerOK = "pass"
}
else{
$WPFstatusTextBox.Appendtext("Please connect your device to a power source" + [char]13)
$PowerOK = "fail"
}
}
else{
$WPFstatusTextBox.Appendtext("The device is a Desktop or VM" + [char]13)
$PowerOK = "pass"
}
}
else{
$WPFstatusTextBox.Appendtext([char]13 + "Preflight checks failed. Please resolve the above issues and reboot" + [char]13)
}
if($LANOK -and $PowerOK -eq "pass"){
$WPFtsContinueButton.IsEnabled = "True"
}
}

Next I added functionality to the buttons.

The continue button created a file for me to confirm that a build had been set away. At some point I may change this to an email, then add some try-catch logic to my task sequence to email me the result of a build. Finally it uses [Environment]::Exit(1) to completely exit out of the PowerShell script. You may have noticed right at the end of my logic region I made sure that the continue button is only enabled if you pass all checks. To make this work I made sure my button was disabled by default:

if($LANOK -and $PowerOK -eq "pass"){
$WPFtsContinueButton.IsEnabled = "True"
}

<Button x:Name="tsContinueButton" Content="Continue" Background="#FF383636" Foreground="#FFF9F7F7" Margin="8,0,8,0" BorderThickness="0" IsEnabled="False"/>

My retry button will clear the status pane and then restart the checks, this is really useful if the technician forgot to add the ethernet cable or power cable and wants to retry.

I had so much fun writing this script, check it out and let me know if you think I could have done anything better. My next project will be creating an application that will import devices into ConfigMgr and add them to the right collection.

Advertisements
ConfigMgr Preflight with XAML and PowerShell

One thought on “ConfigMgr Preflight with XAML and PowerShell

  1. I was recommended this blog by my cousin. I’m not sure whether this post is
    written by him as nobody else know such detailed about my problem.
    You are wonderful! Thanks!

    Like

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