Summary: Microsoft Scripting Guy Ed Wilson shares information about the PowerShell Saturday 002 Iron Scripter Event.
Microsoft Scripting Guy, Ed Wilson, is here. Today I will share the Iron Scripter event and winning script from PowerShell Saturday #002 in Charlotte NC. Jim Christopher, Power Shell MVP and leader of the Charlotte PowerShell Users Group provided the scenario and source files. Jonathan Tyler won the event and we will hear his thoughts on this event. This is timely because Jim will host another Iron Scripter event at Windows PowerShell Saturday #003 in Atlanta (Alpharetta Georgia) this Saturday – October 27, 2012. Tickets are still available – but are going fast. The trophy for the first Iron Scripter appears here (and yes, it is as hefty as it looks. Solid steel, it checks in at nearly 25 pounds).
The scenario, according to Jim :
CORRECT DATA FIELD FORMATTING IN XML FILES
Your script must process a collection of XML files. Each file contains information about a single user. The phone field has been manually entered and is not always in the correct format. Your script must validate the format of the phone field in each file, fix misformatted phone fields, and update the invalid XML files on disk.
The required format of the phone field is:
(###) ###-####
The XML files contain phone fields in a wide variety of formats. Some contain parenthesis, hyphens, spaces, periods, etc, and others do not.
In all cases, the digits of the phone number are in the correct order.
Example of an invalid phone field:
<user>
<id>User98</id>
<name>Dacia Charbonneau</name>
<phone>2389101449</phone>
<email>DChrb@contoso.com</email>
</user>
Example of a corrected phone field:
<user>
<id>User98</id>
<name>Dacia Charbonneau</name>
<phone>(238) 910-1449</phone>
<email>DChrb@contoso.com</email>
</user>
The sample data provided to the users during the event can be found here at the Script Gallery.
Judges are instructed to rate your scripts on the following criteria:
1) Functionality: does your script solve the problem?
2) Usability: does your script apply to new instances of the problem?
3) Readability: is your script easily understandable?
Judges ratings are final. No complaining allowed.
For posterity (and comparison), my 2-minute solution (typed at the console, not in a script file) is below:
dir *.xml | foreach {
$x = [xml](get-content $_);
if( $x.user.phone -notmatch '\(\d{3}\) \d{3}-\d{4}' )
{
$p = $x.user.phone -replace '\D+','';
$p = $p -replace '(\d{3})(\d{3})(\d{4})','($1) $2-$3';
$x.user.phone = $p
$x.save( $_.fullname );
}
}
In the figure appearing here, Jonathan Tyler receives the Iron Scripter trophy from the Scripting Wife at the packed house awards ceremony at the end of Windows PowerShell Saturday in Charlotte, North Carolina. The competition was hard fought, and a fraction of a point separated Jonathan from multiple Scripting Games winner Glen Sizemore.
The solution as supplied by Jonathan
Originally, I had not planned to go to the Powershell Saturday event, though I really wanted to go. My wife was scheduled to be out of town, and I don’t think the Microsoft Campus would be a good place for three small children while trying to learn some new techniques – much less at a scripting competition. Anyway, as time progressed, schedules changed and I was able to register to attend.
When I saw the presentation schedule, I saw the Iron Scripter! competition and thought it might be fun. The more I thought about it, the more I realized I wanted to try it. I had competed in the Official Scripting Games two years ago and had fun and learned a lot. (I do plan on entering the 2013 Games, by the way.)
When Jim Christopher (@beefarino) presented the scenario, I was a little relieved. I had recently done some work with Powershell and XML at work, so it was somewhat fresh in my mind. I have even written a couple of blog posts regarding some XML with SharePoint using Powershell. The scenario that was presented to us was to read in a series of XML files, validate the phone number field for a specific format, and then write the file back with the corrected format. This had to be completed within one hour.
Unfortunately, when I saw that, my mind went completely blank on how to work with XML…we’ll call it stage fright. To begin and get my bearings, I opened the Windows PowerShell ISE and loaded one of the sample data files. I began to play with the XML file in the console to see that the properties lined up from the elements in the XML file. When starting a new script, I normally will run through a series of piece-meal steps in the console window to make sure I am thinking correctly as I am writing snippets into the code editing window. It took a couple of minutes to figure out a direction, and the coding began.
I began scripting furiously and quickly developed a solid base from which to work. I felt pretty pleased with my initial work, and my script worked. The only thing, it was not exactly what the scenario required. I had run through several tests, restored the test files from the zip file, and run the script again just to make sure I was getting valid results. I was. I took some time to accept pipeline input as well as a string filename input. I had even taken some time to write in comment-based help for some extra brownie points. Then it happened. I looked up and re-read the requirements. “Validate” and “correct” were the key words (they were in bold type) that jumped off the screen. My initial solution simply forced all of the phone number fields to the correct format, even if they were already in the correct format. It worked, but not the requirements.
I looked at my watch and saw that I had about 20 minutes left in the submission window. I opened up my Regular Expression editor and began plugging in a quick format for the phone number. Once I got the format test ready, I modified my code to check the “Phone” field for the proper format. If it failed, I sent the Phone string up to a secondary function that did the conversion. It stripped the Phone string of any non-numeric characters, converted that resulting numeric string to Int64, and then formatted using the ToString() method with the required format. That newly formatted string was assigned back to the “Phone” element and the file was saved. After a few more tests against the data (and some debugging strings output to the console for verification which were later removed), I submitted the script to the server.
During the first half of the competition, I wasn’t so worried about the time. My first thoughts were to get something saved to the server. Once I got rolling with a workable solution, I spent all my time trying to get it formatted properly. I realized at one point that I was doing the same “work” in two different places. This prompted me to write the secondary function that I put in the BEGIN block of the script. Once I did that, it made the code look a lot cleaner (to me, anyways) which is always helpful when you need to figure out what is going on in your scripts. I was able to submit my final version with about five to ten minutes to spare.
Time was a factor in the solution. Had I had more time to work on the script, I would have done a few more things:
- Passed the XML data and the file name to another function that would have done nothing but validate, update, and save the information back.
- Set up file testing (Test-Path) to verify the file actually exists.
- Might also have included a parameter set to handle in-memory XML as well so that if the XML document
I had a blast with this competition. A friend and colleague of mine, David Mitchell (@surgeterrix), was able to go with me. I had mentioned to him about the competition before the event. He told me that he didn’t feel like he was knowledgeable enough about Windows PowerShell to be able to enter the competition. I finally talked him into entering. After the submissions were closed, he told me that he had fun working on it as well…and that he learned something new about Windows PowerShell. I believe this is the biggest key to these types of competitions. The things you learn from working out the scenarios well outweigh the prizes at the end of the competition. Don’t get me wrong, I am enjoying the bragging rights (Glenn Sizemore – @glnsize J ), but it is just plain fun to compete and learn at the same time. I dig in a little to Glenn, but he was formidable competition to say the least. The difference in our scores was only 0.1 point.
================ SOURCE CODE =====================
Function Update-PhoneNumber
{
<#
.SYNOPSIS
Updates the phone number format to a pre-defined format in an XML document.
.DESCRIPTION
This function will read a single or multiple XML files of user objects. The
phone number field will be read and forced to the format "(###) ###-####."
The XML data will be saved back to the original file when complete.
.PARAMETER File
Accepts System.IO.FileInfo objects from the pipeline to process multiple files.
.PARAMETER FileName
Loads the specified file and processes the phone number field.
.INPUTS
System.IO.FileInfo - Using the get-childitem cmdlet, you can specify a group of
XML files to process
String - Using a single fully-qualified file name to process a single file.
.OUTPUTS
XML document saved back to the original file name
.EXAMPLE
c:\PS> Update-PhoneNumber -FileName c:\temp\users\user0.xml
This example will read in the user0.xml file from the specified directory and
force the format for the phone number.
.EXAMPLE
c:\PS> Get-ChildItem -Path C:\Temp\Users -filter *.xml | Update-PhoneNumber
This example will read all XML files in the C:\Temp\Users directory and update the
phone number format and save the files back to the same location, overwriting the original
data.
#>
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true,Position=0, ParameterSetName="FileInfo", ValueFromPipeline=$true)]
[System.IO.FileInfo]$File,
[Parameter(Mandatory=$true,Position=0, ParameterSetName="FileName")]
[string]$FileName
)
BEGIN
{
Function Convert-PhoneString
{
Param([string]$phoneString)
$phoneNumber = ""
foreach ($char in $xmlFile.user.phone.ToCharArray())
{
if ($char -match "\d")
{
$phoneNumber += $char
}
}
return ([int64]$phoneNumber).ToString("(###) ###-####")
}
}
Process
{
switch ($PSCmdlet.ParameterSetName)
{
"FileInfo"
{
$xmlFile = [xml](get-content $file.FullName)
if (-not ($xmlFile.user.phone -match "^\(\d{3}\)\s\d{3}-\d{4}$"))
{
$xmlFile.user.phone = Convert-PhoneString $xmlFile.user.phone
$xmlFile.Save($file.FullName);
}
}
"FileName"
{
$xmlFile = [xml](get-content $FileName)
if (-not ($xmlFile.user.phone -match "^\(\d{3}\)\s\d{3}-\d{4}$"))
{
$xmlFile.user.phone = Convert-PhoneString $xmlFile.user.phone
$xmlFile.Save($FileName);
}
}
}
}
}
Well that is it. The first ever Iron Scripter event was a success, and as you can see, the competition was tremendous. Jim will be hosting the second Iron Scripter event at Windows PowerShell Saturday 003 in Atlanta (Alpharetta Georgia) this Saturday October 27, 2012. There are still tickets available. Come check it out – it will be a blast.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy