Monthly Archive

Categories

Scripting Games

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 Smile - 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

Scripting Games countdown

The countdown to the 2014 Winter Scripting Games has started. The ideal way to brighten up the depths of winter.

This Games is for teams of least two. You can learn more by downloading and reading the players guide:

http://powershell.org/wp/2013/12/09/2014-winter-scripting-games-players-guide/

The games start January 2014 so don’t go looking for events just yet. Now would be a good time to look at the events from past games and get in some practice.