Scripting Games – Puzzle 1

The first puzzle called for a one-liner that produces the following output:

PSComputerName ServicePackMajorVersion Version  BIOSSerial                                
-------------- ----------------------- -------  ----------                                
win81                                0 6.3.9600 VMware-56 4d 09 1 71 dd a9 d0 e6 46 9f

and set the following challenges:

  • Try to use no more than one semicolon total in the entire one-liner
  • Try not to use ForEach-Object or one of its aliases
  • Write the command so that it could target multiple computers (no error handling needed) if desired
  • Want to go obscure? Feel free to use aliases and whatever other shortcuts you want to produce a teeny-tiny one-liner.

Now, I will be the first to admit that one-liners are not my strong point. My scripts tend to be pretty verbose; I don’t often use aliases, and even after several years of using PowerShell I still stumble when reading code like this:

gc computers.txt | % {gwmi Win32_operatingSystem | ? {$_.Caption -like '*8.1*'}}

So, with verbosity in mind and with the realisation that at least two WMI classes would need to be queried to solve this puzzle, I first thought about how I might solve this if I wasn’t restricted to one line of code. The solution is pretty straight forward:

Query the two WMI classes, make a custom object with the required properties and then output the object.

$os = Get-WmiObject Win32_OperatingSystem
$bios = Get-WmiObject Win32_BIOS

$obj = [PSCustomObject]@{
    PSComputerName = $os.PSComputerName
    ServicePackMajorVersion = $os.ServicePackMajorVersion
    Version = $os.Version
    BIOSSerial = $bios.SerialNumber
}

Write-Output $obj

The next step was to condense this down into one line of code.

Because of the challenge to use no more than one semicolon, I was keen to try and avoid using semicolons to separate the statements. The alternative I found, which at the time I thought was pretty clever, was to separate the commands using parentheses and commas.
What I later realised is that what this is doing is generating an array where each array member is the result of the command run in the parentheses. This is why I had to use Out-Null to suppress PowerShell’s desire output all three elements to the display.

(($os = Get-WmiObject Win32_OperatingSystem),($bios = Get-WmiObject Win32_BIOS) | Out-Null),
    ([PSCustomObject]@{
        PSComputerName = $os.PSComputerName;
        ServicePackMajorVersion = $os.ServicePackMajorVersion;
        Version = $os.Version;
        BIOSSerial = $bios.SerialNumber})

So, I was down to one line but still had too many semicolons. Next up was to find an alternative way to create the object. Rather than use the [PSCustomObject] accelerator, I opted for the old-school cmdlet New-Object and its friend Add-Member. Format-Table was used to format the output exactly as specified in the puzzle, without it there were too many tabs.

(($os = Get-WmiObject Win32_OperatingSystem),($bios = Get-WmiObject Win32_BIOS) | Out-Null),
    (New-Object -TypeName PSObject |
     Add-Member -MemberType NoteProperty -Name PSComputerName -Value $os.PSComputerName -PassThru |
     Add-Member -MemberType NoteProperty -Name ServicePackMajorVersion -Value $os.ServicePackMajorVersion -PassThru |
     Add-Member -MemberType NoteProperty -Name Version -Value $os.Version -PassThru |
     Add-Member -MemberType NoteProperty -Name BIOSSerial -Value $bios.SerialNumber -PassThru |
     Format-Table -AutoSize)

The next step was to use aliases to reduce the amount of text. I also took advantage of PowerShell’s ability to recognise parameters using the fewest number of unique characters to shorten them as far as possible:

(($os = gwmi Win32_OperatingSystem),($bios = gwmi Win32_BIOS) | Out-Null),
    (New-Object -t PSObject |
     Add-Member -m NoteProperty -na PSComputerName -va $os.PSComputerName -pa |
     Add-Member -m NoteProperty -na ServicePackMajorVersion -va $os.ServicePackMajorVersion -pa |
     Add-Member -m NoteProperty -na Version -va $os.Version -pa |
     Add-Member -m NoteProperty -na BIOSSerial -va $bios.SerialNumber -pa |
    ft -a)

Finally, I had to handle multiple computers. I opted for Read-Host for this, and just used a while loop to enable the user to keep entering computer names until they get bored and use ctrl + c to exit.

while ($true){
     ($computer = Read-Host 'Enter a computer name or ctrl + c to quit'),
        (($os = gwmi Win32_OperatingSystem -co $computer),($bios = gwmi Win32_bios -co $computer) | Out-Null),
            (New-Object -t PSObject | Add-Member -m NoteProperty -na PSComputerName -va $os.PSComputerName -pa |
             Add-Member -m NoteProperty -na ServicePackMajorVersion -va $os.ServicePackMajorVersion -pa |
             Add-Member -m NoteProperty -na Version -va $os.Version -pa |
             Add-Member -m NoteProperty -na BIOSSerial -va $bios.SerialNumber -pa | ft -a)}

There is no doubt that this is a very awkward solution to a fairly simple problem; clearly it’s not the kind of solution that the puzzle setter had in mind. That said, with the exception of its phenomenal length, it does meet the brief :).

Note: All code can be entered on a single line with out pressing ENTER until you’ve typed the last character, I’ve wrapped the lines at natural points to aid readability.

This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *