Tuesday, May 1, 2007

Scripting Games MMVII: A Second Look At PowerShell Beginner Events

Greetings! It’s been months since the 2007 Scripting Games have ended. Solutions to the problems are already published. However, I’m still learning PowerShell and would like to share my experience to the community as I undergo the process. I think one of the best ways to demonstrate my current knowledge is to test-drive it against problems. I still consider myself as a beginner in this field, so I decided to try answering the PowerShell Beginner Events with my own version of the solutions. By the way, I skipped Events 4, 6, and 10, because my solution would also be the same as the scripting guys’ version.

Practice Event: Never, Ever on a Sum Day

Before the Scripting Games started, a practice event was released. This is how the scripting guys described “Sum Day”:

What's a "Sum Day?" Well, May 9, 2014 (that is 5/9/2014) is a Sum Day. That's because, if you take the numeric value for the month (5) and add the numeric value for that day (9), you happen to get the numeric value for the last two digits of the year: 14. See how that works? Is January 1, 2003 a Sum Day? No, because the month (1) plus the day (1) adds up to 2, and the last two digits of the year happen to be 03. Is September 6, 2015 a Sum Day? You bet it is. That's because the month (9) plus the day (6) add up to the last two digits of the year (15).

To complete the practice event, a script must be written that does the following:

  1. Starts with January 1, 2000
  2. Reports back the first 100 Sum Days

Scripting Guys’ Solution:

$i = 0
$startdate = get-date("1/1/2000")  

do

    $day = $startdate.Day 
    $month = $startdate.month 
    $year = $startdate.year 
    $sum = $day + $month 
    $year = [string] $year 
    $x = $year.substring(2,2
    $y = [int] $x

    if ($sum -eq $y
   
        $startdate.toshortdatestring() 
        $i++ 
    }

    $startdate = $startdate.adddays(1)
}

until ($i -eq 100)

Scripting Kid’s Solution:

$date = Get-Date("1January2000");
$sumdays = 0;

while($sumdays -lt 100)
{
    $date = $date.AddDays(1);

    if (($date.day + $date.month) -eq ($date.year%100))
    {
        $sumdays++;
        Write-Host $sumdays :  $date.ToShortDateString();
    }
}

My solution is kinda similar to the Scripting Guys’. However, I designed it to be a little bit shorter. Instead of using “do-until” loop, I use the “while” version. I also tried to eliminate unnecessary variables. As you may have noticed, I only used 2: $date and $sumdays. This may not be the best solution. However, my main objective here is just to test-drive my current skills as neophyte scripter. I came from the Software Development space, so I kinda brought some of my practices to scripting. One of my favorite methods in software development is the minimalistic approach. I believe programming is one of the practices in which less means more. If a certain feature is not necessary, then we might as well take it away.

Beginner Event 1: Mother, May I?

This event is about permissions. You ask permission using a message box.
Here are the instructions that the scripting guys gave:

Create a script that opens a message box that looks like this:

Your script must also display the following messages when the corresponding button is clicked.
If the Yes button is clicked, display this message:

Yes – Processing will continue...

And display this message if the No button is clicked:

No – Processing stopped

Scripting Guys’ Solution:

$a = new-object -comobject wscript.shell
$b = $a.popup("Do you want to continue?",0,"Continue Processing", 4)

 

if ($b -eq 7)
{
    "Processing stopped."
}
else
{
    "Processing will continue..."
}

Scripting Kid’s Solution:

if ([Windows.Forms.MessageBox]::Show("Do you want to continue?", `
"Continue Processing" , 'YesNo' ) -eq "Yes")
{
    Write-Host "Processing will continue..."
}
else
{
    Write-Host "Processing stopped."
}

One of the things I like about PowerShell is that it enables me to leverage my existing knowledge about the .NET Framework. As a .NET developer, I’m more familiar with the Windows Forms solution rather than the wscript.shell approach. When I hear about “implementing message boxes”, two things register in my mind: Windows Forms and alert() method of javascript. Since, the problem takes on the windows desktop space using PowerShell, I tinkered on the Windows Forms approach.

Beginner Event 2: Scriptomatic Search

This event is about matching a class to its corresponding property.
Here are the instructions that the scripting guys gave:

Match the class with the property that belongs to that class.

For example (and yes, this is just an example, not the correct answer):

The first line says that the class at position 1 (Win32_ComputerSystem) goes with the property at position 10 (EventType). The second line matches the class at position 2 (Win32_DesktopMonitor) with the property at position 4 (MACAddress). And so on.

Scripting Guys’ Solution:

Here are the answers:

To arrive at the answers above, one may manually browse/search using Scriptomatic or the WMI SDK on MSDN.

Scripting Kid’s Solution:

$classList = new-Object -type Collections.Hashtable;
$classList
.Add(1, "Win32_ComputerSystem");
$classList
.Add(2, "Win32_DesktopMonitor");
$classList
.Add(3, "Win32_NetworkAdapter");
$classList
.Add(4, "Win32_OperatingSystem");
$classList
.Add(5, "Win32_Process");
$classList
.Add(6, "Win32_Service");
$classList
.Add(7, "CIM_DataFile");
$classList
.Add(8, "Win32_DiskDrive");
$classList
.Add(9, "Win32_LogicalDisk");
$classList
.Add(10, "Win32_NTLogEvent");

$propertyList = new-Object -type Collections.Hashtable;
$propertyList
.Add(1, "MACAddress");
$propertyList
.Add(2, "BytesPerSector");
$propertyList
.Add(3, "ServiceType");
$propertyList
.Add(4, "NumberOfProcessors");
$propertyList
.Add(5, "EventType");
$propertyList
.Add(6, "BlockSize");
$propertyList
.Add(7, "WindowsDirectory");
$propertyList
.Add(8, "HandleCount");
$propertyList
.Add(9, "ScreenWidth");
$propertyList
.Add(10, "Extension");

for($i = 1; $i -le $classList.Count; $i++)
{
    for($j = 1; $j -le $propertyList.Count; $j++)
    {
        if ($i -ne 7)
        {
            if ((Get-WmiObject $classList[$i] | Get-Member $propertyList[$j]).Name -eq $propertyList[$j])
            {
                Write-Host $i : $j
            }
        }
    }
}

## For CIM_DataFile, use Scriptomatic.
## But since it's the only one left,
## you might as well know the answer already.

In my solution above, I used HashTable to store the data. I wanna use generics, but at the time of writing I’m still exploring it. PowerShell has a different mechanism when it comes to generics. Anyway, I could have searched them through Scriptomatic and MSDN, but that would defeat my purpose. That is to test-drive my current scripting skills. So, instead of manually searching, I wrote a script that does that for me. I believe one of the reasons why PowerShell (or any scripting language) was made is because of automation. Time is gold. Anyway, you may also notice that I skipped CIM_DataFile. Man, my computer took so long in searching for that. It seems like forever. So, I decided to skip it.

Beginner Event 3: One of These Things is not Like the Others

I admire the scripting guys in taking a creative approach in making these puzzles.
It takes scripting to a more enjoyable dimension. Anyway, this event is about being unique.
Oh! I just remembered a story from a friend. He said, when he was small,
his classmates used to laugh at him because he was so different from them.
But then, he smiled back, because they’re all the same. I believe each of us is unique.
Being unique is special. That means our Architect took significant time in designing the way we are.
Here are the instructions that the scripting guys gave:

In this event you’ll take groups of three elements and determine which element is different from the others. You’ll start with five arrays, with three elements in each array:

Write a script that reports which of the three elements in each array is different from the other two. For example, the second element in array 1 is different, so your output would look something like this:

Scripting Guys’ Solution:

$a1 = "monday", "MONDAY", "monday"
$a2
= "TUESDAY", "tuesday", "tuesday"
$a3
= "WEDNESDAY", "wednesday", "wednesday"
$a4
= "thursday", "thursday", "THURSDAY"
$a5
= "friday", "FRIDAY", "friday"

$i = 0

if ($a1[$i] -ceq $a1[$i+1])
{
    $diffa1 = "third"
}
elseif
($a1[$i] -ceq $a1[$i+2])
{
    $diffa1 = "second"
}
else

{
    $diffa1 = "first"
}

if ($a2[$i] -ceq $a2[$i+1])
{
    $diffa2 = "third"
}
elseif
($a2[$i] -ceq $a2[$i+2])
{
    $diffa2 = "second"
}
else

{
    $diffa2 = "first"
}

if ($a3[$i] -ceq $a3[$i+1])
{
    $diffa1 = "third"
}
elseif
($a3[$i] -ceq $a3[$i+2])
{
    $diffa3 = "second"
}
else

{
    $diffa3 = "first"
}

if ($a4[$i] -ceq $a4[$i+1])
{
    $diffa4 = "third"
}
elseif
($a4[$i] -ceq $a4[$i+2])
{
    $diffa4 = "second"
}
else

{
    $diffa4 = "first"
}

if ($a5[$i] -ceq $a5[$i+1])
{
    $diffa5 = "third"
}
elseif
($a5[$i] -ceq $a5[$i+2])
{
    $diffa5 = "second"
}
else

{
    $diffa5 = "first"
}

"a1: " + $diffa1
"a2: "
+ $diffa2
"a3: "
+ $diffa3
"a4: "
+ $diffa4
"a5: "
+ $diffa5

Scripting Kid’s Solution:

$a1 = "monday", "MONDAY", "monday"
$a2
= "TUESDAY", "tuesday", "tuesday"
$a3
= "WEDNESDAY", "wednesday", "wednesday"
$a4
= "thursday", "thursday", "THURSDAY"
$a5
= "friday", "FRIDAY", "friday"

function DisplayUnique([string]$label, [System.Array]$list)
{
    $result = "";
    if
($list[0] -ceq $list[1])
    {
        $result = "third"
    }
    elseif ($list[0] -ceq $list[2])
    {
        $result = "second"
    }
    elseif ($list[1] -ceq $list[2])
    {
        $result = "first"
    }

    return $label + $result
}

DisplayUnique "a1: " $a1
DisplayUnique
"a2: " $a2
DisplayUnique
"a3: " $a3
DisplayUnique
"a4: " $a4
DisplayUnique
"a5: " $a5


My approach above is kinda similar to the Scripting Guys’. However, I tried to use a function, because I’ll be using the same logic for the remaining arrays. This results to a lesser code. One discipline I learned in software development is the use of refactoring. This is taking the time to improve existing code by asking ourselves questions such as “How can I make this piece of code more reusable?”. I love the concept of accomplishing more with lesser effort.

Beginner Event 5: ABCs and 123s

This event is about type conversions and formatting.
Here are the instructions that the scripting guys gave:

In this event you’ll be working with this string of letters:

"It was the best of times...you know the rest."

Turn the letters of this string into the hexadecimal equivalents of their ASCII values. For example, the decimal ASCII value for the first letter, a capital I, is 73. You’ll need to determine this value, and then find the hexadecimal equivalent. Make sure that your script displays each hexadecimal number on a separate line, like this:

9
10
A
B
D

For additional points, turn the string back from hexadecimal numbers to a string. Again, display the string on a single line.

Scripting Guys’ Solution:

$str = "It was the best of times...you know the rest"
$len
= $str.length

for($i=0;$i -lt $len;$i++)
{
    [
int[]]$a = $a + [int] $str[$i]
    [
object[]]$b = $b + "{0:X}" -f $a[$i]
}
$b

for($i=0;$i -lt $len;$i++)
{
    $b[$i] = "0x" + $b[$i]
    $b[$i] = [int] $b[$i]
    $c = $c + [char] $b[$i]
}
$c

Scripting Kid’s Solution:

[int[]] [char[]] "It was the best of times...you know the rest." | `
foreach
`
{
    [
string[]]$hexOutput += "{0:X}" -f $_
}
$hexOutput

$hexOutput | `
foreach
`
{
    Write-Host -NoNewline ([char]([int]("0x" + $_)))
}

Whenever it’s ok to write foreach rather than for loop, I choose foreach. I like it, because I don’t have to deal with indices if I don’t really have to. Since the output string will be displayed in a single line, I didn’t use additional variables for this. Instead, I used “-NoNewline” parameter for the “Write-Host” cmdlet.

Beginner Event 7: Playing Fair

“To err is human, and to forgive is divine.”
This event is about handling exceptions.
Here are the instructions that the scripting guys gave:

Start with this script:

When you run this script, you’ll receive the following error message:

Add code that allows the script to run without displaying an error. Wait, there’s a trick – you must do this without changing any lines of code. You can add lines, but you can’t modify or delete lines. Keep in mind that you’re not trying to fix any errors in the script, you’re just trying to make the existing script run without displaying an error message. For another task, determine whether an error has occurred and if so display the message “An error has occurred.”

Scripting Guys’ Solution:

$erroractionpreference = "SilentlyContinue"
$error
[0] = ""

$a
= 5
$b
= 6
$c
= "seven"
$d
= 8

$x = $a + $b
$x
= $x + $c
$x
= $x + $d

if ($error[0])
{
    "An error has occurred"
}
$x

Scripting Kid’s Solution:

& {
    $erroractionpreference
= "silentlycontinue"
    trap {
"An error has occurred."; }

    $a = 5
    $b
= 6
    $c
= "seven"
    $d
= 8

    $x = $a + $b
    $x
= $x + $c
    $x
= $x + $d
    $x

}

Both of us set the $erroractionpreference variable to “silentlycontinue”. This allows the script to run without displaying an error. However, I wrapped my version with a scriptblock. I just want to set the $erroractionpreference variable within the block. After that, I want it to go back to its original setting which is “continue”. The code that’s quite unique with my version is the use of trap. This is the equivalent of try-catch in other languages. When an error occurs in the code, the trap block gets executed. Thus, the message is displayed.

Beginner Event 8: Jacks

I remembered this game way back in grade school.
My female classmates are fond of playing these games along with chinese garters and stuffs.
Hmmm… I wonder if these games are still popular nowadays. Most of the kids I know today are into computer gaming.
Anyway, here are the instructions that the scripting guys gave for this event:

If you’ve ever seen the kids’ game called Jacks, you know that the game is played with 10 jacks and one rubber ball. The idea is that, after throwing the jacks on the floor, you bounce the ball once, pick up a jack, then catch the ball in the same hand before the ball has bounced a second time. You do this for each of the 10 jacks. After you’ve done that successfully, you throw the jacks down again, but this time you pick up two jacks at a time, once again catching the ball before the second bounce. Then you do this all over again with three jacks (on the last pick-up, of course, you’ll only have one jack left to pick up). This continues until you’ve gone all the way to picking up all ten jacks at once, at which point the game is over.

For this event, write a script that does two things: 1) determines how many total jacks you will have picked up by the end of the game; and, 2) figures out how many times you had to pick up the jacks. (Oh: and the script must display both of these numbers.) Here’s an example of what your output should look like:

Scripting Guys’ Solution:

for($i = 1; $i -le 10; $i++)
{
    $t = $t + 10
}
"Total = "
+ $t

for($i = 1; $i -le 10; $i++)
{
    for ($j = 1; $j -le 10; $j+=$i)
    {
        $p = $p + 1
    }
}
"Pickups = "
+ $p

Scripting Kid’s Solution:

for ($round = 1; $round -le 10; $round++)
{
    for ( $jacksOnGround = 10; $jacksOnGround -gt 0; $jacksOnGround -= $round )
    {
        $totalPickups++;
    }

    $totalJacks += 10;
}
"Total jacks:"
+ $totalJacks
"Total pick-ups:"
+ $totalPickups

I think the key solution for this problem is the use of nested loops. My approach is kinda similar to the Scripting Guys’. However, I only used two loops.

Beginner Event 9: Hopscotch

I really wonder why this event is called Hopscotch.
I looked in the dictionary and it says, “Hopscotch - A game in which a child tosses a stone into an area drawn on the ground and then hops through it and back to regain the stone”.
Anyway, here are the instructions that the scripting guys gave for this event:

There are a lot of people we need to keep in touch with on a regular basis. Admittedly, we could keep all that contact information in Outlook and store it in our PDA or cell phone. Instead, we’ve taken the simpler route and stored our contact information in a text file that looks something like this:

Ken Myer
100 Main Street
Seattle, WA 99999

Pilar Ackerman
100 Elm Street
Los Angeles, CA 99999

No we want to see who we have in our address list. You need to read the text file List.txt and return only the names for each entry, like this:

Ken Myer
Pilar Ackerman

We happen to have another list of contacts, List2.txt. In this list some of the addresses – but not all – are multi-line addresses, like this one:

Ken Myer
200 Oak Street
Apartment 10
Seattle, WA

Retrieve only the names from List2.

The files List.txt and List2.txt are available in the Scripting Games 2007 Competitors’ Pack.

Scripting Guys’ Solution:

$arrFile = get-content c:\scripts\list.txt

if ($arrFile.length -gt 0)
{
    $arrFile[0]
}

$newEntry = $False

for ($i = 1; $i -le ($arrFile.length - 1); $i++)
{
    if ($newEntry)
    {
        $arrFile[$i]
        $newEntry = $False
    }

    if ($arrFile[$i] -eq "")
    {
        $newEntry = $True
    }
}

Scripting Kid’s Solution:

function DisplayName ($fileName)
{
    $display = $true
    gc
$fileName | `
    foreach `
    {
        if ($display)
        {
            Write-Host
$_
            $display = $false
        }
        elseif ($_.Trim() -eq "")
        {
            $display = $true
        }
    }
}

cd "c:\scripts"
"From List.txt: "
;
DisplayName(
"List.txt");
"From List2.txt: "
;
DisplayName (
"List2.txt");


My approach is kinda similar to the Scripting Guys’. However, I just wrapped it in a function, because I’ll be using it in another list. I really use foreach loop whenever it’s ok to use. It’s simpler.

Summary

I learned that there are many ways to kill a cat. But, I don’t kill them. I’m kind to animals.
This post only has two generic purposes:

  1. Provide alternative solutions to the above problems
  2. Share my experience to the community as I practice scripting

Learning is a lifetime journey. The best solution now may not be the best one in the future.
That is why it’s really a nice thing to keep those scripting blades sharp by practicing.
Not just any ordinary practice. My violin teacher once taught me that constant practice isn’t the key to perfection.

But, constant correct practice makes perfect.

Thank you very much for spending time with me.

Happy Scripting!!!

No comments: