Solved: Cisco AnyConnect 'Session Ended' error

This was kicking my butt today, but turns out that it had an easy work around.
I learned a long-time ago that if youâre running Hyper-V on your Device, you should not install a VPN client on the host, but rather should be doing this within child VMs. Â The reason for this is that sometimes the drivers associated with a VPN adapter donât play nicely with a hypervisor, and can often result in a blue screen error when they attempt to make changes to the virtual adapters displayed to the parent partition.
So, I made a Windows 10 VM to run my VPN clientâŚhowever, I was getting errors of âSession Endedâ, along with tons of murky stuff in my Event Viewer, related to missing devices, etc.  It looked pretty scary.
As it turns out this is a simple resolution.
Symptom
A VPN connection is immediately dropped when connecting on a Windows 8 or higher VM
Validation
Launch AnyConnect and click the cog icon, then click the Mesage History tab.
Look for an error of âVPN establishment capability from a remote desktop is disabled. Â A VPN connection will not be establishedâ.
Cause
When connecting to Windows 8.1 and newer child OSes in Hyper-V, Virtual Machine Connection will actually attempt to connect via RDP, rather than through the secured backchannel that VMC normally offers. Â This will appear as an RDP session on the remote machine, and AnyConnect is often configured to prohibit this behavior.
Resolution
While connecting to the VPN, use basic connection instead of âEnhanced Sessionâ Â You can use this button here to toggle between the two, and itâs okay to jump back into enhanced session after the VPN connection in completed.
Continue Reading...Listen to me on Coding101!

Last week, I had an amazing time at the Microsoft MVP Summit, it was a dream come true! Â Speaking of true, I even got to meet Jim Truher and Bruce Payette! Â Simply a wonderful, wonderful time.
Probably my favorite part about being there was getting to be on a live podcast recording for TWiT networkâs Coding 101 show, along with Sarah Dutkiewicz, Adam Bertram, Jeremy Clark, June Blender, &Â Jeff Wouters! Â We got to talk about what got us into coding, how to become an MVP, and our favorite (and worst) interview questions. Â I feel like I did pretty well, despite my heart trying to pound out of my chest.
https://twit.tv/shows/coding-101/episodes/90?autostart=false
Let me know how you think I did!
Continue Reading...Using PowerShell and oAuth

Like most of my posts here, Iâm going to try to make something sound easy, when in reality Iâve spent months crying into my coffee trying to understand it. In truth, Iâve been trying to get oAuth to work for more than a year now.
It all started with a simple goal, I just wanted to check my blog stats easily using WordPressâs REST API, and I wanted to do it from PowerShell, should be simple, right? WRONG
My initial issue was that I didnât want to understand oAuth, I just wanted to copy and paste some stuff and hope that it worked. I can tell you now that Iâve worked it out, it really isnât that difficult, but knowing whatâs happening will make it all much easier.
What is oAuth?
oAuth was made to solve a problem, that of sharing information between two different web services. We need oAuth because a user may want to right click a file from Dropbox.com and post it on Facebook with one click, for instance. Or have their Twitter send a Tweet when they update their Blog on WordPress. oAuth is a crucial verification step when tying two services together, and itâs worth the time to spend learning how it works. Furthermore, most of the coolest REST APIs out there require you to authenticate using oAuth in order to even use them.
Now, letâs see what the steps are to get your application (or script) linked to a service that uses oAuth.
The Core Steps of oAuth
oAuth isnât too complicated. Hereâs what a normal oAuth exchange looks like. This happens the first time a user links two services (or instead links PowerShell or Python to a web service) and generally is a one time thing.
Prompt the User for permission
Weâll prompt the user with a login window, theyâll sign in and if they agree with the permissions weâre asking for, we receive an Access Code
Exchange the Access Code for an Authorization Token
Weâll submit that code in exchange for an authorization token. This is a one-time thing, as most auth tokens last quite a long time.
Use the Authorization Token for future requests
Now that weâve got an Authorization Token, we can use this over and over to make action on behalf of our user.
The last piece of info to know is that there are special URLs to use for each stage of the authorization process. For the rest of this post, assume Iâm talking about Wordpress.com, which requires you to use oAuth for authentication before you can start using their API.
All of the code Iâm referring to here can be found in the PSWordPress repo on GitHub. In my implementation of oAuth for WordPress, all of the following steps (except signing up for the API) are performed within the Connect-WordPress Account cmdlet, if youâd like to follow along.
How to begin - Register for the API
Any service which uses oAuth as the gateway to a REST API is going to have some mechanism in place for you to register for the API. If you want to use the WordPress.com API, for instance, youâll register for it here. Â Just to be clear, youâre not signing up for oAuth, rather oAuth is simply the mechanism that letâs your users trust your app or service with a bit of access to their account from something else, say, Twitter or FaceBook.
Signing up for an API will normally involve you picking out a name for your project, providing a thumbnail image, and providing the URL of your application. Because the primary use case of oAuth is to allow services to talk to each other, even though weâll be using PowerShell, we still must provide a URL. Iâm using the URL of my blog, FoxDeploy.com, but you could pick literally any URL in the world.
The key things you need to copy when you sign up are in this list below. Â Youâll need to use them when you send the user off to the approval page, and then youâll also need it when you exchange the access code for an authorization token:
- ClientID, a numerical ID WordPress assigned us.
- ClientSecret, a randomly generated strong password assigned to us. Generally, keep this secret. Itâs not such a big deal for WordPress but some APIs will require payment, so this could be as costly as giving out a credit card number.
- A list of URLs we need to query for different things
Copy all of this stuff down somewhere safe (donât put it in a GitHub project, for instance!)
Get an Access Token
The first real step in implementing oAuth, once youâve got your Client details and the URLs, is to display a logon box to a user.
This step is mostly prompting a user to provide you their credentials to a given service. We can grab the URL that we saw when we signed up for the API, and send the user there, providing a few values to represent your application/project, and then the user will login. When the user signs in, theyâll be redirected to the URL we specify, and the access token will be embedded at the end of the address they go to; we need to simply substring out the Access token and weâre set.
Taking a look at the oAuth page for WordPress, we see where we need to provide our values to make the login box appear.
If you were to plop in values for clientID, and blogID, youâd see a standard oAuth sign in window, like this:
To display a login box from PowerShell, you should use the cmdlet Iâve got here, Show-oAuthWindow. In this next section, weâll dig into what this is actually doing, so that you can swap parts in and out as needed to fit whatever youâre working on.
Showing a login window from PowerShell
Show-oAuthWindow is where the magic happens. I found this example on the Hey! Scripting Guy Blog, written by Microsoft PowerShell PFE (Wow, talk about my dream job!) Chris Wu. Itâs really beautifully simple.
An assembly is loaded into PowerShell, to allow us to display some .net forms objects. Then the window size is specified, and we provide the URL for the browser window to go to (the âAuthorizeâ URL from my project screenshot above). We watch the process and instruct the window to close when the user is redirected to an address with Error= or code= in the URL.
Function Show-OAuthWindow { Add-Type -AssemblyName System.Windows.Forms $form = New-Object -TypeName System.Windows.Forms.Form -Property @{Width=440;Height=640} $web = New-Object -TypeName System.Windows.Forms.WebBrowser -Property @{Width=420;Height=600;Url=($url -f ($Scope -join "%20")) } $DocComp = { $Global:uri = $web.Url.AbsoluteUri if ($Global:Uri -match "error=\[^&\]\*|code=\[^&\]\*") {$form.Close() } }
Show-OAuthWindow -URL "https://public-api.wordpress.com/oauth2/authorize?client\_id=$clientID&redirect\_uri=$blogURL&scope=global&response\_type=code"
If you were to run this code, youâd see a login box like this one:
The user clicks Approve, and then our Access Code comes back in the form of some characters appended to the URL
Assuming the user interacts with this successfully, they will be redirected to the $redirectURL, and our Access Code will be within the URL, looking something like the following: [https://foxdeploy.com/?code=cw9hk1xG9k](https://developer.wordpress.com/?code=cw9hk1xG9k)
.  Weâll store the address in $uri, make it a global variable and then close the window, and weâre left with a $URI value that includes our access code.  We merely need to regex out the Access Code.
$regex = '(?<=code=)(.\*)(?=&)' $authCode = ($uri | Select-string -pattern $regex).Matches\[0\].Value $global:accessCode = $accessCode Write-output "Received an accessCode, $accessCode"
At this point, we have the Access Code, and itâs time to cash it in for an Authorization Code.
Trade the Access Token to get an Auth Token
Now that we have the time sensitive Access Token, itâs time to cash that in for a mostly-permanent authToken. Looking back at the WordPress API Docs again, they say the following (referring to our accessCode):
They showed us how to do it in curl. Scroll down to see how to do it in PowerShell
They URL listed there is actually the Token URL we saw earlier when we sign up, and tell us to do a cUrl to that URL and add our (the ClientSecret), the project ID (Client ID) and your redirect URL, along with the Access Token we just got . Take all of these, present them as the body for one last Post and weâll get back our Auth Token. Once weâve done this, we donât need to repeat the steps again until our Authorization Token expires.
If youâre looking at my Connect-WordPressAccount cmdlet, weâre now up to line 46. The code calls Get-WordPressAuthToken (gitHub link), and passes in params it already had, like $clientSecret, $clientID and $blogURL. It also passes in $authCode, which we received in the last step.  Iâve excerpted this bit below:
#Continuing in Connect-WordPress Account Get-WordPressAuthToken -ClientID $ClientID -clientSecret $clientSecret -blogURL $blogURL -authCode $authCode -Debug
#store the token $password = ConvertTo-SecureString $accessToken -AsPlainText -Force $password | ConvertFrom-SecureString | Export-Clixml $configDir -Force
Now, to look within Get-WordPressAuthToken, here is how to post an access code and get back a token with PowerShell, instead of the cURL example in their screen shot.
Function Get-WordPressAuthToken{ \[CmdletBinding()\] param($ClientID,$blogURL,$clientSecret,$authCode)
try { $result = Invoke-RestMethod https://public-api.wordpress.com/oauth2/token \` -Method Post -ContentType "application/x-www-form-urlencoded" \` -Body @{client\_id=$clientId; client\_secret=$clientSecret; redirect\_uri=$blogURL; grant\_type="authorization\_code"; code=$authCode} -ErrorAction STOP }
We call the oAuth2/token endpoint, and just provide a list of parameters, including the Access Token we received when the user authorized our app. If this request worked, weâll see something like the following.
{ "access\_token": "YOUR\_API\_TOKEN",
"blog\_id": "blog ID",
"blog\_url": "blog url",
"token\_type": "bearer"
}
We simply need to take the Access Token and save it for future use. Weâre done with authorizing now!
Storing the Token Safely
The user has now trusted us to partially act on their behalf for WordPress/whatever service.
This token we have is the functional equivalent to them just handing us their computer logged in, with Chrome open and signed in as them on this service. Anything you can do as a person using the service, someone with your Auth Token could do as well. This means you need to find a safe place to store it.
I opted to use the secure string cmdlets to store them safely within the userâs appdata folder, so if youâre looking at my own example, I use the system security API to safely store this info. Â It can only be read (by default) by that user, or administrators of that PC, so it is fairly safe. Â When the user loads any cmdlet from my PowerShell module, the variable is reloaded into $accessToken so that all of the PowerShell cmdlets that need the token âjust workâ.
#store the token $password = ConvertTo-SecureString $accessToken -AsPlainText -Force $password | ConvertFrom-SecureString | Export-Clixml $configDir -Force
Using THE token to actually query the API
The final thing to do is to try this token out against one of the many, many endpoints available. The awesome thing about Rest APIs is that they almost always have some totally kick-butt documentation, including pages and pages of addresses we can query for nifty info. Hereâs a snippet of just a few, listed on developer.wordpress.com/docs/api. Iâll pick one out, like /me
append any of these to /rest/v1.1 and youâre in business
According to this, I just need to query the URL listed and provide my Authorization Token as a header.
In PowerShell, this looks something like this:
Invoke-RestMethod https://public-api.wordpress.com/rest/v1.1/me -Method Get -Headers @{"Authorization" = "Bearer $accessToken"}
Getting data back from a REST API, awesome!
Thatâs really all there is to it! Â The data will come back already serialized, in most cases, and youâre in business.
Letâs make some cmdlets!
At this point, all you need to do to make a slue of cmdlets is to look at the endpoints, find nifty ones, and then just setup functions that ask for the right info. This is the fruit of my labors, right here.
Function Get-WordPressStats { \[Cmdletbinding()\] param( \[Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true, Position=0)\] \[Alias("domainName")\] $ID, $accessToken=$Global:accessToken)
Invoke-RestMethod https://public-api.wordpress.com/rest/v1.1/sites/$ID/stats -Method Get \` -Headers @{"Authorization" = "Bearer $accessToken"} | Select-object -ExpandProperty Stats |Select-Object Views\*,Visit\*
}
Letâs REST
I hope you liked this post. Iâm very happy to have finally worked out an approach that makes sense to me for oAuth. If youâve got some specific requests or some REST APIs that are giving you fits, let me know and Iâll do my best to help you figure it out!
Sources
For helping me to understand how to present parameters for oAuth
Wordpress Reference for API, with actually really good documentation
Continue Reading...Windows vs Intel Raid Performance Smackdown

Update: Turns out I was pretty wrong about Storage Spaces and have been totally schooled and corrected about them here, on Reddit and at Redmond. Â Iâll be revising this post heavily, adding some iometer numbers. Â Stay tuned!
In this post, Iâll share my results comparing hardware to software RAID on modern Windows 10, then to take things up a notch, Iâll see how a Storage Spaces volume with Parity (maybe Microsoft is charged money for using the word âRaidâ, because they avoid that word like the plague!) compares to the hardware solution (Spoilers: it ainât pretty) in pretty much every configuration I could think of.
Iâve had a lot of issues in the FoxDeploy lab recently. Two months ago, I had a number of SSDs in a Storage pool on Server 2012 R2, then decided I should go to the desktop experience, and moved to Windows 10. Â Letâs call this rebuild #1.
Since then, Iâve had an SSD fail in a Windows Mirror, resulting in a total loss of VMs, as I didnât have backup working. Â Rebuild #2
Next, I decided to take three drives, two physical SSDs and a chunk of unpartioned space from a six year old spinner, and put them in a Raid-5.
As it turns out, this kills the drive. Goodbye, Big Betty, I will miss you. Rebuild #3.
At this point, I had it with these janky solutions. Iâve recently come into possession of some new SSD drives, and this time, Iâm going to do it right, damn it! I wanted to finally have some resiliency in case one of my drives died. But along the way, I needed to know how it would perform.
Findings - Raid 0 Testing
For starters, hereâs our benchmark. Â Itâs a single OCZ 120 GB SSD disk, connected via 3GBPS sata.
Single disk, sata 3
Single Disk Performance
Not bad, very respectable. Now, letâs see what happens when I use Windows to take two of these and stripe them. This is also called Raid 0, as in âyou have zero parity and are totally hosed if one of these drives diesâ.
Expectations: You gain the full capacity of both drives and speed to spare, as data is randomly written across drives. Â Speed should be roughly double that of a single drive. Â However, if one dies, you are up a creek, because you will not be able to recover your data. Youâll tend to get ~double the read and write performance when striping.
two disks, SATA 3, windows raid 0
WIndows Raid 0
If we compare the two, we see roughly double the performance as well. Â Windows raid is achieving parity via the OS. Since data striping (putting chunks of data on the disk) is handled by the operating system, there is a potential for disk performance to suffer when the rest of the OS is under heavy load. Â Microsoft has had years to work out the errors and speed things up though, so donât let this stop you if your motherboard doesnât support Raid.
Now, Letâs see how Windows Raid-0 stacks up to actual hardware raid.
TWO DISKS, SATA 3, Hardware RAID 0
Hardware Raid 0
To put those side-by-side, hereâs the difference you can expect when comparing hardware Raid-0 to Software Raid-0.
SIDE BY SIDE, INTEL %Â CHANGE VSÂ SOFTWARE RAID 0
Intel Performance Increase over Microsoft
Winner - Intel There is quite a performance edge to gain by using a hardware controller, even nowadays. I was very surprised to see such a disparity here. Â In every regard there were noticeable speed gains, at least on paper.
Findings - Raid 1 testing
Now, these big numbers were very fun, but as Iâm doing this to test my performance while operating safely with parity, I needed to see what would happen when actually using mirroring.
Expectations - With Raid 1, you should receive roughly double read speed, while writes will suffer, because of the need to create and distribute parity sets across disks. You lose the whole capacity of one drive, but have a full backup of your active volume in case it dies. Nothing is safer than Raid 1! First, I tested in Windows for Raid 1, also known as a âMirrored Setâ.
TWO DISKS, SATA 3, WINDOWS RAID 1
Windows Raid 1
Wow, the reads are still good, but boy does the writes take a huge faceplant. Writes were roughly half of the write speed for a single drive, which was surprisingly bad, in my opinion. Â Functionally, this makes sense, as the OS has to track syncing writes to two different disks. Â Who knew that it would levy such a heavy penalty though.
Now, to compare this to using dedicated Raid hardware.
TWO DISKS, SATA 3, hardware RAID 1
Intel Raid 1
While the Intel raid controller blows the Software Raid out of the water on sequential reads, surprisingly the Windows software Raid was better in nearly every other respect. It seems that no matter if you use a hardware or a software Raid controller, you should expect to lose performance when youâre duplicating every write, which makes sense.
We can compare this to our single disk performance though, and see something else happening.
Side by side, intel % change vs software raid 1
Intel % increase over Microsoft Software Raid
Winner - Windows If you need super fast sequential reads, like youâre working with a single big file that happens to be on one contiguous area of disk, if you could control that, then youâd want the Intel Controller. If not, Windows looks to be good enough, and takes the lead in most categories.
Findings - Raid 5 testing
Raid 5 was the defacto standard for parity across volumes smaller than a TB. Now, itâs been replaced with other Raid and parity options, but itâs still probably the most prevalent way of safely allowing for disk failures without a tremendous speed loss. With Raid-5, you lose the space of one drive. Â With that being said, you can get some beefy performance, and have parity!
Raid 5 in Windows 10, some notes before we begin
In this round, weâll be leaving the Disk Management wizard for Windows and instead go into the control panel to create a Storage Space! Â Previously, there was no way for end user running home or PRO Windows to do make a parity volume, which reserved that feature for Windows Server users only. Â Storage Spaces is Microsoftâs new, much easier to use approach to Disk Management with loads of options.
You can configure this any way you want to! Just spend a LOT of time reading documentation.
While Microsoft doesnât call it âRaid 5â, itâs possible using Storage Spaces to create a Parity Pool, which will effectively achieve the same results.
Raid-5 by any other name. Double Parity would be Raid-10!
It should be noted that I had to tune this array to get these results. Â First off, a default Storage Spaces drive will be created with a Write-back Cache of 1 GB. Â It should also be said that there IS a distinct performance penalty at play here. Â In order to play things safe, there is some mandatory disk journaling involved here, meaning that your writes arenât going to be cached very much. Â Instead, the storage subsystem will wait for confirmation of all writes, and it does so VERY conservatively.
Itâs not uncommon to see R/WR delays into the tens of thousands of milliseconds while writes are performed.  You can somewhat minimize this behavior by running these two PowerShell cmdlets.  The first GREATLY boosts the write cache amount, which Iâve seen to boost performance, while the second cmdlet specifies that there is Power protection (like a UPS).  To quote the help documentation âIf you specify a value of $True for this parameter, the storage pool does not perform flush operations, and the pool removes write-through attributes from commands.â  It also helps.
Get-StoragePool | select -First 1 | set-storagepool -WriteCacheSizeDefault 8GB Get-StoragePool -FriendlyName âStorage Poolâ | Set-StoragePool -IsPowerProtected $true |
Now, after that length pre-amble, letâs hop to it.
three DISKS, SATA 3, HARDWAREÂ RAID 5
Hardware Raid 5
As expected from hardware Raid 5, we receive roughly 100% read speed from all drives, for some truly mammoth read speeds.  Writes hang out around 2/3rdâs of the total write speed for all drives, making this the power-userâs option.  It should be noted that I had to specify a custom disk policy for this Raid 5 volume to enable write caching and disable flush operations.  This is definitely a power-mode configuration, but depends on battery backup to ensure there is no data loss.
Be warned, you will lose data if you have a power outtage. Only check these if youâve got reliable UPS in place.
If youâre using Raid 5, youâre probably data loss averse, so only specify these settings if youâve actually got a battery backup.
Now, onto the final option, Storage Spaces with Parity.  This is super secure for your data, howeverâŚwritesâŚwellâŚ
THREE DISKS, SATA 3, Storage spaces parity volume (raid 5)
Storage Spaces Journaling aggressively protects data, but at a cost
I reached out to some peers for help, to see if I was mistuning this array, because, frankly, damn.
According to Aidan Finn, Microsoft MVP of Datacenter and Hyper-V, Iâm doing it wrong. Â This is not meant for performance, nor is it a good idea for storing VHD files. Â You should be using parity volumes as a replacement for a super expensive NetApp or LeftHand SAN Solution, with wonderful configurability for hundreds of potential configuration. Â You use this to save money for your archival storage, but shouldnât expect to get equal performance.
Still, the numbers are pretty shocking. Â Hereâs Storage Spaces Parity volume compared to a single drive, and to a Raid 5 Volume.
Great for Reads versus a single drive, but writes suffer incredibly.
Winner - Intel
Now, Storage Spaces can do things you cannot do using a simple Intel Raid Controller. Â For instance, you could easily take four full JBOD (Just a bunch of disk) arrays, filled with 8 TB drives and also slap in a stack of PCI-e or SSD storage to create a wonderfully resilient storage system with excellent performance.
But itâs not the answer to every problem. Â In this case, the Intel Raid Controller was the clear winner, absolutely smoking the Storage Spaces Controller. Â This graph says it all
12,000% improvement in writes is mindblowing
In Conclusion
If you want maximum performance for a small number of disks, need some basic parity support, and donât have more than 13TB in a volume, and you have a hardware Raid Controller, then you should definitely use hardware Raid 5 over storage spaces.
You need maximum speed and will backup your data - Use Raid 0 and backup data
You need maximum speed and some resilience to disk failure - Use Raid 1 or Raid 5
You need to archive monstrous amounts of data across insane configurations of disk arrays - Use Storage Spaces.
Finally, some random charts.
Intel RAID-5 performance over Single Disk
Did I do it wrong? Â Am I missing something? Â Correct the author by e-mailing me at Stephen@foxdeploy.com, or commenting here.
Continue Reading...Hyper-V on Windows 10, how to fix a broken VHD

Iâm running Windows 10 w/ Hyper-V on my home lab pc, and found a strange Hyper-V error this week.
One of the core things you shouldnât do with differencing disks is to ever, ever move or edit the parent disk, or else you can break the child disk. Â I added some more storage and moved my VHDs around to an all SSD Raid, then found that Iâd broken the chain of VHDs for one of my VMs.
When trying to start the VM, this is the error youâll see.
The chain of virtual hard disks is broken. The system cannot locate the parent virtual hard disk for the differencing disk.
Normally, one runs âInspect Diskâ from Hyper-V to locate the parent disk of a child VHD, which will fix the chain of differencing and allow your VMs to access the disks again. Â However on Windows 10, clicking âInspect Diskâ will result in Hyper-V throwing a fatal error.
There was a problem with one of the command line parameters. Either âBEHEMOTHâ could not be found, or âX:\Virtual Machines\Virtual Hard Disks\VM01.vhdxâ is not a valid path.
Property âMaxInternalSizeâ does not exist in class âMsvm_VirtualHardDiskSettingDataâ.
The path is valid and exists. Iâve found a workaround, that of using PowerShell and the Hyper-V module to run Set-VHD, like so:
set-vhd -Path 'X:\\Virtual Machines\\Virtual Hard Disks\\VM01.vhdx' \` -ParentPath "X:\\Server2012Template\\Virtual Hard Disks\\Server2012Template.vhdx"
Part IV - PowerShell GUIs - how to handle events and create a Tabbed Interface

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 345x450), then add a Tab control from the toolbox.
I like to draw a somewhat wide tab, like this.
[
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.
[
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!
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.
[
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.
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!
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.
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!
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:
$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!
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.
$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.
https://www.youtube.com/watch?v=5cepMPD7j-s
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">
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
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.
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!
Continue Reading...