The PowerShell Iron Scripter: My solution to prequel puzzle 1

Each week leading up to the PowerShell + DevOps Global Summit 2018, PowerShell.org will be posting an iron scripter prequel puzzle on their website. As their website states, think of the iron scripter as the successor to the scripting games.

I’ve taken a look at the different factions and it was a difficult choice for me to choose between the Daybreak and Flawless faction. While I try to write code that’s flawless, perfection is in the eye of the beholder and it’s also a never-ending moving target. Today’s perfect code is tomorrow’s hot mess because one’s knowledge and experience are both constantly increasing, or at least they should be if you want to remain relevant in this industry. I used some of the comments I saw in a tweet from Joel Bennett to help me choose between the two and I ended up choosing the Daybreak faction.

In the first puzzle, you’re given some code that simply does not work due to numerous errors. The instructions even state there are errors in the code. I started out by cleaning up the supplied code a bit to at least make it work so I’d have a better understanding of what it’s trying to accomplish.

If I were part of the Battle faction, I would probably quit here and say it works so that’s good enough.

Throwing a prototype function together to query the local system was simple enough. Using the CIM cmdlets instead of the older WMI ones allows it to run on PowerShell Core 6.0 in addition to Windows PowerShell 4.0+. Although the CIM cmdlets were introduced in PowerShell version 3.0, I choose to use the ForEach method which wasn’t introduced until PowerShell version 4.0.

In case you didn’t already know, PowerShell Core version 6.0 is not an upgrade or replacement to Windows PowerShell version 5.1. It installs side by side on Windows systems. Based on the response to a tweet of mine from Don Jones, it appears that I’m not the only one who thought PowerShell Core should have been version 1.0 instead of 6.0 to help differentiate it and eliminate some of the confusion.

Specifying the Property parameter with Get-CimInstance to limit the properties returned makes my Get-MonitorInfo function shown in the following example more efficient. After all, there’s no reason whatsoever to retrieve data that’s never going to be used. I decided to keep things as simple as possible and write a regular function instead of an advanced one. It could be turned into an advanced function by simply adding cmdlet binding which also requires a param block even if it’s empty.

This function works as expected against a local system with multiple monitors, but it’s run against a VM with a single monitor in this example.

While the function seems to meet the requirements of the solution, I wanted to take it a step further and write something that I would use in a production environment. After all, if you’re going to go to all of this effort, why not write something useful.

Functions belong in modules so I’ll start out by using my New-MrScriptModule function from my MrToolkit module to create a new module named MrIronScripter.

Once the module is created, I use New-MrFunction to create a function named Get-MrMonitorInfo.

I wanted the function to be able to run against remote computers. Since it uses the CIM cmdlets, I decided to rely on CIM sessions for remote connectivity. Replying on them eliminates the need to code remote connectivity and the ability to specify alternate credentials into my function. All of that is taken care of when establishing a CIM session with the New-CimSession cmdlet. My New-MrCimSession function could also be used to establish a CIM session using WSMAN with automated negotiation down to DCOM depending on which one is available.

Most functions iterate though each remote computer one at a time, but why limit your function to querying one computer at a time? One of my goals was to query all of the remote computers at once which would be much more efficient from a performance standpoint.

Can a read-only property be modified in PowerShell? Keep reading while keeping an open mind to find out.

The PSComputerName property isn’t populated when running against the local system. That’s one of the problems I ran into when trying to put the pieces back together from the different WMI classes to return the results from each individual system. That property is also read-only and generates an error when trying to update the value it contains in the BIOS variable. I found a solution thanks to a blog article by Boe Prox. Due to differences in the names and where the PSComputerName property is actually pulled from, my example wasn’t quite as straight forward as the one in Boe’s example, but his article pointed me in the right direction.

I also discovered that I couldn’t use traditional error handling because it wouldn’t return results for any systems if any single one of them failed. What I ended up doing was to iterate through the error variable and output the errors as warnings if any were generated.

I also decided to return more information than was specified in the Iron Scripter event. I created custom formatting so only the properties specified in their provided code example would be returned by default in either a table or a list view. One change I did make is to use the word “Model” instead of “Type” for the computer and monitor model.

The following example demonstrates how those five properties default to a table and only those five properties are displayed when piping to Format-List unless a wildcard or other properties are specified via the Property parameter.

If you’re not already, you should consider competing in these Iron Scripter prequel events and the official Iron Scripter competition if you’re attending the PowerShell + DevOps Global Summit 2018. Competing isn’t about playing a game, winning, or losing. It’s about having fun while learning real world skills with a little friendly competition. I’ve found that I learn something every time I work through a scripting games scenario. The two big takeaways for me during this event are how to update a read-only property and splatting the same command twice or double-splatting as I’ll call it as shown in the following example.

In the end, I decided not to splat these commands twice in my function, but it’s nice to know something like that is possible and probably not something I would have thought of if I hadn’t been trying to solve this particular iron scripter puzzle.

Last, but not least, your solution isn’t complete until you’ve added it to a source control system such as Git and shared it with the community on a site such as GitHub.

Prior to running the commands shown in the previous example, I created an empty and uninitialized repository on GitHub named IronScripter.

Publishing it to the PowerShell Gallery so others can easily install it probably wouldn’t be a bad idea either.

The code in this blog article can be installed from the PowerShell Gallery using the Install-Module function which is part of the PowerShellGet module that ships with PowerShell version 5.0 and higher. The PowerShellGet module can be downloaded and installed on PowerShell 3.0+.

In the future, I plan to start using Plaster instead of my functions to create script modules and functions. I also plan to look into using platyPS for creating MAML based help as an alternative to comment based help.

If you want to learn how to write PowerShell functions and script modules from a former winner of the advanced category in the scripting games and a multiyear recipient of both Microsoft’s MVP and SAPIEN Technologies MVP award, then you should definitely consider attending my “Writing award winning PowerShell functions and script modules” session at the PowerShell + DevOps Global Summit 2018.

µ

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: