2008 Winter Scripting Games

Solution to Beginner Windows PowerShell Event 2: True Type

Event 2 Solution


Windows PowerShell solution to Event 2 in the 2008 Winter Scripting Games.

Solutions are also available for VBScript and Perl.

*

Event 2 – True Type

This event covered two separate areas of scripting. One was reading from the registry, the other was parsing a string. Because this is the Beginners Division, we decided to accept two different answers to this event. We’re going to take a look at both and explain why, in spite of the fact that one doesn’t really return the correct solution, we decided to accept it anyway. Let’s start by looking at a solution that returns the correct number of fonts:

$total = 0

$a = get-item "hklm:\\Software\Microsoft\Windows NT\CurrentVersion\Fonts"
$f = $a.GetValueNames()

foreach ($i in $f)
{
    if ($i.contains("TrueType"))
    {
        $total++
        $i
    }
}

" "
"TrueType: " + $total
"Total: " + $f.length

We start out by initializing a variable that we’ll use to keep track of the total number of true type fonts:

$total = 0

Next we use the Get-Item cmdlet to retrieve all the registry values within the registry key hklm:\\Software\Microsoft\Windows NT\CurrentVersion\Fonts:

$a = get-item "hklm:\\Software\Microsoft\Windows NT\CurrentVersion\Fonts"

This returna a reference to the registry key. This reference is a .NET object named RegistryKey. The RegistryKey object just happens to have a method name GetValueNames, which is what we use next:

$f = $a.GetValueNames()

GetValueNames returns an array of the names of all the values within the key. As it turns out, that’s exactly what we want.

Well, okay, it’s almost what we want. We don’t want all the names, we only want the names of the true type fonts. To find those particular fonts we need to set up a foreach loop to loop through the array:

foreach ($i in $f)

We identify a true type font by the fact that the name of the font contains the string TrueType. Hmmm, the name contains the string… You know what would be a good idea? Using the contains method to find the string:

if ($i.contains("TrueType"))

If the name of the font contains the string “TrueType” we need to display that font name, and also increment the counter that counts the number of true type fonts:

$total++
$i

Each time through the loop we check the font name to see if it contains the string “TrueType”. If it does, we add 1 to our counter (which is what $total++ does), display that name, then move on to the next item in the array of font names.

When we’ve gone through the entire array we display the number of true type fonts, plus the total number of fonts:

"TrueType: " + $total
"Total: " + $f.length

Notice we used the Length property to find the length of the array of font names.

That script returns the correct number of fonts. However, many people may have done things a little differently. Another way to retrieve the values within a key is to use the Get-ItemProperty cmdlet rather than the Get-Item property. We’ll show you a script that does that, then point out what the problem is. Keep in mind that if you went this route, your count of total fonts was off, but if it was off by the correct amount and you displayed everything else properly, you received the points anyway. Here’s the script:

$total = 0

$path = "hklm:\\Software\Microsoft\Windows NT\CurrentVersion\Fonts"
$a = get-itemproperty $path | get-member -membertype noteproperty

foreach ($i in $a)
{
    if (($i.name).contains("TrueType"))
    {
        $total++
        $i.name
    }
}

" "
"TrueType: " + $total
"Total: " + $a.length

We started our script by initializing the counter variable $total:

$total = 0

Now we need to read values from the registry. You can do that in Windows PowerShell using only one line of code, although we’ve broken this into two lines just to make it easier to read:

$path = "hklm:\\Software\Microsoft\Windows NT\CurrentVersion\Fonts"
$a = get-itemproperty $path | get-member -membertype noteproperty

In the first line we’re simply assigning the path to the registry key to a variable; in this case, a variable named $path. We don’t need to do this, but it makes the next line a lot shorter and easier to read. Speaking of the next line, that’s where we actually retrieve the registry values. The first part of this command calls Get-ItemProperty on the $path variable, which returns an object containing all the values related to the key at this path:

get-itemproperty $path

We don’t actually want everything PowerShell retrieves here. That’s because PowerShell will return more than just the values found in this key, and we don’t want those “bonus” items. To get what we really want, we pipe this object to the Get-Member cmdlet:

get-member -membertype noteproperty

By specifying a –membertype of noteproperty, we end up with a list of values for the specified key, which we’ve assigned to the variable $a.

Unfortunately, at this point we still have a little more than we want, and this is why many people turned in scripts where the count was off. And, in every case, that count was off by an addition of 5. That’s because, along with the list of key values (fonts), PowerShell returns these five values:

PSChildName
PSDrive
PSParentPath
PSPath
PSProvider

Why are these five PowerShell elements returned? Well, we’re not sure and we didn’t take the time to figure it out. But this is the reason the count of fonts is off by five. And because this is far from obvious and, well, even the Scripting Guys initially started out with this as our solution, we’re giving everyone points if their total came out to 5 more than the actual font count.

To continue on with this script… Now that we have our list of registry values (and then some) we can start reading through them. We do that with a foreach loop:

foreach ($i in $a)

This loop will loop us through the collection of value names (font names) one at a time. Now we want to display all the font names. Wait, not all the font names. If you displayed all the fonts, you didn’t receive any points for this event. What we really wanted were the true type fonts. It’s pretty obvious which fonts are the true type fonts; they all have (TrueType) appended to their names, like this:

Lucida Bright (TrueType)

That means that in order to find the true type fonts, we simply need to find the fonts with “TrueType” in their names. We do that by using the contains method:

($i.name).contains("TrueType")

The contains method returns true if a given substring is contained within a string, and false if it isn’t. Here we’re getting the name of the value ($i.name) and calling the contains method on that string. We pass one parameter to the contains method, which is the substring we’re looking for (TrueType). Because contains returns true or false, we can place the entire thing within an If statement:

if (($i.name).contains("TrueType"))

If we fall into the if statement, that means we’ve found a font name that contains the string TrueType – in other words, we’ve found a true type font. And since we want to display all the true type fonts, we display the name of the font here:

$i.name

Oh yes, and because we want to count the total number of true type fonts, we increment our counter (a ++ following a variable name adds 1 to that variable):

$total++

Once we’ve looped through all the values we display a blank line (just to separate our count output from our list of fonts) and the total number of true type fonts found:

" "
"TrueType: " + $total

We also need to display the total number of fonts on the machine. The total number of fonts is the number of elements we read from the registry, which is contained in the variable $a. So all we need to do to get the number of fonts is check the length property of the array $a:

"Total: " + $a.length

And we’re done.


Top of pageTop of page