Iron Scripter prequels
One of the new things for Summit 2018 is the iron Scripter competition on the last afternoon. As a warm up for the competition we’re running a number of Iron Scripter prequels.
A puzzle will be published every week – first 2 are on powershell.org.
A forum exists to discuss the solutions in the context of the Iron Scripter factions.
Even if you aren’t going to Summit you can solve the puzzles and contribute to the solution.
Think of the puzzles as a Scripting Games lite. We provide a commentary on the puzzle including an example solution but we DON’T grade any submissions.
Join in and enjoy.
Scripting Game puzzle – – January 2016
Here’s how I’d solve the puzzle
function get-starttime {
[CmdletBinding()]
param(
[parameter(
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[Alias('CN', 'Computer')]
[ValidateNotNullOrEmpty()]
[string[]]$computername = $env:COMPUTERNAME
)
PROCESS {
foreach ($computer in $computername){
$props = [ordered]@{
ComputerName = $computer
StartTime = ''
'UpTime (Days)' = 0.0
Status = 'OFFLINE'
}
if (Test-WSMan -ComputerName $computer -ErrorAction SilentlyContinue) {
$lbt = Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $computer -ErrorAction SilentlyContinue
if ($lbt) {
$props['StartTime'] = $lbt.LastBootUpTime
$upt = [math]::round(((Get-Date) - $lbt.LastBootUpTime).TotalDays, 1)
$props['UpTime (Days)'] = $upt
$props['Status'] = 'OK'
}
else {
$props['Status'] = 'ERROR'
}
} ## endif
New-Object -TypeName PSObject -Property $props
} ## end foreach
} ## end PROCESS
}
Create an advanced function. Yes I know I’ve used lower case for the function name. I always do to visually separate my code from cmdlets and other functions.
Use the [parameter] decorator to enable pipeline input. Only a single parameter so don’t need to bother woth positional parameters. Function is supposed to default to local machien so can’t make parameter mandatory.
Requirement to process multiple computers at once presumably means the computername parameter has to take an array – sumultaneous processing implies a work flow which negates the initial requirement to create a function
Use the PROCESS block to run a foreach loop that iterates over the collection of computernames.
Create a hash table for the results – I’ve used an ordered hash table to preserve the property order. Set the values to a failed connection.
use Test-Wsman to see if can reach the computer. If can’t the output object is created. If you can reach the machine then run Get-CimInstance - preferred over Get-WmiObject because it returns the date ready formatted
Assuming that works set the start time and status properties. Calculate the uptime in days. I’d prefer to see just an integer here – tenths of days doesn’t mean anything to most people
If the call to Get-CimInstance fails then set the status to ERROR
Output the object.
The requirement to add a proeprty for patching is not clear but I’m assuming it means if the machine has been up for more than 30 days with the 1/10 month as a typo
if you want to add that then
Add a property
MightNeedPatching = $false
to the hash table when you create it
and add this line
if ($upt -ge 30){$props['MightNeedPatching'] = $true}
after
$upt = [math]::round(((Get-Date) - $lbt.LastBootUpTime).TotalDays, 1)
$props['UpTime (Days)'] = $upt
2015 December Scripting Games Puzzle
The December 2015 puzzle was based on the 12 Days of Christmas song. Starting with this here-string.
$list = @"
1 Partridge in a pear tree
2 Turtle Doves
3 French Hens
4 Calling Birds
5 Golden Rings
6 Geese a laying
7 Swans a swimming
8 Maids a milking
9 Ladies dancing
10 Lords a leaping
11 Pipers piping
12 Drummers drumming
"@
A here-string is a multiline string. It is NOT an array – its a single string. The first task is to split into an array of individual lines:
## split into individual lines
$s = $list -split '\n'
Each line of the multiline here string ends with a new line – so that becomes the split point.
The array can be sorted by length of line. Ascending or descending wasn’t specified so here’s both options:
'Sort by length ascending'
$s | Sort-Object -Property Length
"`n "
'sort by length descending'
$s | Sort-Object -Property Length -Descending
Removing the numbers to give justthe text and sorting by length of text. I trmmed the strings as some empty spaces had appeared in the arrays. I think because I copied the here-string
"`n "
'remove numbers sort by length ascending'
$s.Trim() -replace '\d{1,2}\s', '' | Sort-Object -Property Length #| group length | ft -a -wrap
"`n "
'remove numbers sort by length descending'
$s.Trim() -replace '\d{1,2}\s', '' | Sort-Object -Property Length -Descending
Create objects. Split on white space and restrict output to 2 elements – number and text in this case. Create object using New-object
"`n "
#'create objects'
$items = @()
$s.Trim() | foreach {
$item = $psitem -split '\s',2
$items += New-Object -TypeName PSObject -Property @{
Count = $item[0] -as [int]
Item = $item[1]
}
}
Count the number of birds
"`n "
'count of birds'
$birdcount = ($items -match '(Partridge|Doves|Hens|Birds|Geese|Swans)' | Measure-Object -Property Count -Sum).Sum
$birdcount
Count all items
"`n "
'count of items'
$itemcount = ($items | Measure-Object -Property Count -Sum).Sum
$itemcount
If you treat the song as stating the gifts are cumulative then how many gifts are given in total. Each item is given (13 – the day on which its given) times i.e. 12 to 1 times respectively. The total number of items can be calculated like this
"`n "
'cumulative count of items'
$total = 0
$items | foreach {$total += $psitem.Count * (13-$psitem.Count) }
$total
As a bonus here’s how you calculate the cumulative number of each type of item.
"`n "
'cumulative number of each item'
$totalitems =@()
$items | foreach {
$totalitems += New-Object -TypeName PSObject -Property @{
Count = $psitem.Count * (13-$psitem.Count)
Item = $psitem.Item
}
}
$totalitems
Scripting Games September 2015
The puzzle for September is here http://powershell.org/wp/2015/09/05/september-2015-scripting-games-puzzle/
with the write up published here
http://powershell.org/wp/2015/10/03/2015-september-scripting-games-wrap-up/
Personally I don’t like the approach as it leads to code that’s difficult to read and maintain. I much prefer a simpler more verbose approach that is maintainable.
Scripting Games–August 2015
The August 2015 Scripting Games puzzle was a simple ‘get data from a web service’ request. You had to retrieve data from a given web service
£> Invoke-RestMethod -Uri 'http://www.telize.com/geoip' |
Select-Object -Property longitude, latitude, continent_code, timezone
longitude latitude continent_code timezone
--------- -------- -------------- --------
-0.13 51.5 EU Europe/London
Apart from the inaccuracy in the continent information – England isn’t part of Europe - its pretty straight forward. The trick is remembering that Invoke-RestMethod exists and selecting out the properties you need.
If you wanted to productionise this you end up with the follwoing function as a minimum
function Get-GeoInformation
{
[CmdletBinding(SupportsShouldProcess=$true)]
param (
[Parameter(Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[Alias('ip', 'address')]
[ValidateScript({([System.Net.IPAddress]::TryParse($_, [ref]'127.0.0.1')) -eq $true})]
[string[]]$ipaddress
)
BEGIN {} #end begin
PROCESS {
try {
if ($ipaddress) {
foreach ($ip in $ipaddress){
if ($psCmdlet.ShouldProcess("$ip", 'Retreiving geo-location data')) {
Write-Verbose -Message "Retrieving data for $ip"
Invoke-RestMethod -Method GET -Uri "http://www.telize.com/geoip/$ip" -ErrorAction Stop
}
} # end foreach ($ip in $ipaddress)
} # end if ($ipaddress)
else {
Write-Verbose -Message 'Retrieving data'
if ($psCmdlet.ShouldProcess('default ip address', 'Retreiving geo-location data')) {
Invoke-RestMethod -Uri 'http://www.telize.com/geoip'-ErrorAction Stop
}
} #end else
} # end try
catch [System.Net.WebCmdletWebResponseException] {
throw 'Error connecting to web service'
}
catch {
throw 'Unresolved error'
} # end catch
} #end process
END {} # end end
<#
.SYNOPSIS
Get-GeoInformation returns data from the geoip web service
at http://www.telize.com/
.DESCRIPTION
Get-GeoInformation returns data from the geoip web service
at http://www.telize.com/
If an IP address is presented to the function that will be sent to
the web service otherwise the current public IP address of the user,
or their ISP, is used.
.PARAMETER ipaddress
IP address to be tested. The IP address will be validated on input
and an error thrown if invalid
.EXAMPLE
£> Get-GeoInformation
longitude : -0.13
latitude : 51.5
asn : AS9105
offset : 1
ip : 88.108.92.247
area_code : 0
continent_code : EU
dma_code : 0
timezone : Europe/London
country_code : GB
isp : Tiscali UK
country : United Kingdom
country_code3 : GBR
No IP address is presented so the user's public (ISP address) is used
.EXAMPLE
£> Get-GeoInformation -ipaddress 88.108.92.247
longitude : -0.13
latitude : 51.5
asn : AS9105
offset : 1
ip : 88.108.92.247
area_code : 0
continent_code : EU
dma_code : 0
timezone : Europe/London
country_code : GB
isp : Tiscali UK
country : United Kingdom
country_code3 : GBR
An IP address is used
.NOTES
Written in response to the August 2015 Scripting Games puzzle
from PowerShell.org
.LINK
http://www.telize.com
http://powershell.org/wp/2015/08/01/august-2015-scripting-games-puzzle/
#>
}
The function takes one, or more, IP addresses as an optional parameter and after testing its a valid IP address proceeds to call the web service for each IP address.
The –Confirm and –WhatIf parameters are available on the function via the CmdletBinding:
[CmdletBinding(SupportsShouldProcess=$true)]
I’ve also added comment based help.
Its interesting that a single PowerShell pipeline goes to over 100 lines of code when you add in the validation, error handling and other aspects of producing production code
Scripting Games July 12015 thoughts on the solution
The July 2015 puzzle can be found here:
http://powershell.org/wp/2015/07/04/2015-july-scripting-games-puzzle/
Write a one-liner that produces the following output (note that property values will be different from computer to computer; that’s fine).
PSComputerName ServicePackMajorVersion Version BIOSSerial
-------------- ----------------------- ------- ----------
win81 0 6.3.9600 VMware-56 4d 09 1 71 dd a9 d0 e6 46 9f
By definition, a one-liner is a single, long command or pipeline that you type, hitting Enter only at the very end. If it wraps to more than one physical line as you’re typing, that’s OK. But, in order to really test your skill with the parser, try to make your one-liner as short as technically possible while still running correctly.
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.
By definition, a one-liner is a single, long command or pipeline that you type, hitting Enter only at the very end. If it wraps to more than one physical line as you’re typing, that’s OK. But, in order to really test your skill with the parser, try to make your one-liner as short as technically possible while still running correctly.
Initial thoughts.
One liner is a mis-nomer that has caused more problems than enough for the PowerShell community. The requirement is correctly stated as a single pipline. One pipeline can spread over many lines but is called a one-liner. This is one line of code:
get-service; get-process;
but is not a one-liner because the semi-colon is a line termination character so you’ve combined 2 lines but into 1 but they’ll execute as 2 pipelines.
Obviously need to use CIM (WMI) to retrieve the data. Standard approach would be Win32_OperatingSystem & Win32_Bios
£> Get-CimClass *Bios
NameSpace: ROOT/cimv2
CimClassName
------------
Win32_BIOS
Win32_SystemBIOS
£> Get-CimClass *OperatingSystem
NameSpace: ROOT/cimv2
CimClassName
------------
CIM_OperatingSystem
Win32_OperatingSystem
Win32_SystemOperatingSystem
If you want to shave off a couple of characters you could use the CIM_OperatingSystem class as Boe suggests http://powershell.org/wp/2015/07/29/2015-july-scripting-games-wrap-up/
Be aware that the CIM_ classes adhere to the standard definition from the DMTF - http://www.dmtf.org/standards/cim and Win32_ classes are the Microsoft version from when WMI was introduced. The win32_ classes are often subtly different to the CIM_ classes so check carefully with Get-CimClass before using.
My first though was to use Get-CimInstance
Get-CimInstance -ClassName Win32_OperatingSystem |
select PSComputerName, ServicePackMajorVersion, Version,
@{N='BIOSSerial'; E={(Get-CimInstance -ClassName Win32_Bios).SerialNumber}}
But for the local machine that won’t return the PSComputerName attribute unless you use the –ComputerName parameter and point to the local machine or pipe the computer name to the cmdlet
Get-WmiObject doesn’t have that problem
Get-WmiObject -ClassName Win32_OperatingSystem |
select PSComputerName, ServicePackMajorVersion, Version,
@{N='BIOSSerial'; E={(Get-CimInstance -ClassName Win32_Bios).SerialNumber}}
The trick here is to use a calculated field to get the BIOS serial number
I prefer using Invoke-Command to get the data from a remote machine. I get the RunSpaceId as well but that can be filtered out.
The last part of the puzzle was to shrink the code to the smallest number of characters possible. I’ve never seen the point of that in production use so never bother – if I wanted to write something unreadable I’d use Perl. The time taken to shrink is never regained and I can use tab completion to get my code input quickly and working. Boe has done an excellent job of showing that could be done so I’ll point you in that direction
Scripting Games July 2015 puzzle
Head over to http://powershell.org/wp/2015/07/04/2015-july-scripting-games-puzzle/ for our inaugural Scripting Games puzzle.
I’ll publish a solution at the end of the month
Scripting Games
See what’s happening with the Scripting Games - http://blogs.technet.com/b/heyscriptingguy/archive/2015/06/27/powershell-spotlight-yeah-it-s-the-scripting-games.aspx
Thank about the requirements and offer to help as outlined in the article.
The next Scripting Games
No – we’re not announcing the start of the next games just yet.
There is however a chance for you to shape the next games – head over to http://powershell.org/wp/2014/02/17/what-should-the-scripting-games-look-like-next-time/ and tell us what you would like to see in the next games
Countdown to the Scripting Games–32 days and counting
The countdown to the Winter 2014 Scripting Games has started.
Officially starting at 1am (UTC or GMT) 19 January 2014 the following dates should be noted:
2 January 2014 – registration opens
6 January 2014 – a practice event becomes available
19 January 2014 – event 1 starts
26 January 2014 – event 2 starts
2 February 2014 – event 3 starts
9 February 2014 – event 4 starts
Each event lasts one (1) week
As explained previously the events are designed to be tackled by teams of 2-6 people.
Coaching will be available during the Games if teams would like to use it.
The judging and coaching teams are separate.
Details from http://powershell.org/wp/2013/12/16/2014-winter-scripting-games-schedule/
£> (Get-Date -Day 19 -Month 1 -Year 2014 -Hour 1 -Minute 0 -Second 0 ) - (Get-Date) | Format-List Days, Hours, Minutes,
Seconds
Days : 32
Hours : 13
Minutes : 47
Seconds : 33
Enjoy