Summary: Microsoft Scripting Guy, Ed Wilson, talks about extending his ASCII encode/decode Windows PowerShell script.
Microsoft Scripting Guy, Ed Wilson, is here. I am a huge advocate of learning by playing around. Maybe that is why I did such a good job back when I was in kindergarten—and why I had such a tough time in high school. Who knows…
Anyway, what have I learned from playing around with hash tables and doing a simple ASCII lookup and replace?
Note This is an extension of my previous posts on the subject. Before you read today's post, you should read Automatically Create Hash Tables and Reverse Values and Converting Words to ASCII Numbers and Back with PowerShell.
I have learned the following:
- How to copy hash tables, and to reverse the keys and values from one hash table so that the values of one become the keys of the other and vice versa.
- How there are limitations on an ordered dictionary.
- How to cast between [char] and string.
- How I cannot use a string to look up a [char] in a hash table.
- How to break up a word in to a series of [char] types, and how to put them back together when I am finished manipulating them.
- How to use one hash table to look up a value and another hash table (based on the first, but with reversed key and value assignments) to quickly decode a value from that lookup table.
All of these become useful tidbits in my arsenal of Windows PowerShell knowledge and techniques.
Extending my capability
Thus far, I can encode and decode a single word. Now I want to be able to encode and decode several words (a sentence, for example)—and if possible, a paragraph or even two paragraphs. When I first thought about this, I thought, "I will need to add the uppercase letters in addition to the lowercase letters. I will also need to add some punctuation. In addition, the white space might be a problem."
At first, I set about picking up the range for uppercase and for lowercase letters. I even experimented with how to create that range. Here is the example I came up with to create two non-sequential ranges of numbers:
PS C:\> 1..3; 6..7
1
2
3
6
7
Cool. That looks exactly like what I want. So I tried to add it to a variable, and this is what happened:
PS C:\> $a = 1..3; 6..7
6
7
Bummer. But it makes sense. This is two separate commands, and only the first range was assigned.
PS C:\> $a
1
2
3
So I tried a second idea:
PS C:\> $a = 1..3; $a +=6..7
PS C:\> $a
1
2
3
6
7
…and it works perfectly.
But then, I thought a little more, "Hmmmm, the ASCII table automatically contains all the punctuation, uppercase letters, lowercase letters, and numbers, in addition to other things that I don’t need. But hey, it will be easier to just grab the whole thing."
So that is what I did.
Add all the ASCII characters
I decided to add all the ASCII characters. The only change in my code is going from 0-255. The rest of the Foreach-Object construction is exactly the same as in the previous scripts. This is shown here:
0..255 |
Foreach-Object {
$ASCIIFirst.Add($_,([char]$_).ToString())}
Clean up a bit of the output code
Because I know my script works as I want it to, I can clean up some of the output I was working on yesterday. At this point, I don’t want to delete anything, so I simply comment out the lines. Here is the output section now:
#$w.ToCharArray() | % {$ltrFirst["$_"]}
$n = $w.ToCharArray() | % {$ltrFirst["$_"]}
#$n | % {$ASCIIFirst[$_]}
$c = $n | % {$ASCIIFirst[$_]}
$n -join ''
$c -join ''
While I am cleaning up stuff, I also add an output to see the numbers on a line. So the only output will be a line of numbers and a line of characters.
Change the input line
Instead of displaying a single word, I want to see what happens when I input an entire line of text. I also want to try uppercase and lowercase letters.
$w = "This is a big old hairy dog!"
The line to encode includes uppercase, lowercase, and a “special” character (the exclamation point). The following output sort of works, and sort of does not. What works? Well, the exclamation point, and the space were correctly identified. Unfortunately, the capital letter did not translate. Here is my output:
The error message states that the key has already been added. In fact, this same error message appears over and over again.
So, I look at my $ltrFirst hash table, and it seems that a whole bunch of stuff did not get added. To be clear, many of the items from the ASCII table are non-printing characters, so I would expect to see some blank spaces. But closer examination tells me that none of the capital letters were added.
It then dawns on me that most of Windows PowerShell is case insensitive. I bet the default Windows PowerShell hash table is also case insensitive. This is why none of the capital letters added to the hash table, and why I got so many error messages about duplicate keys—it is a case insensitivity thing.
I open MSDN and look up System.Collections.Hashtable. I find that by default, a hash table is case sensitive! So this is a very easy fix. I manually create them. Here is my adjusted code:
$ASCIIFirst = New-Object System.Collections.Hashtable
$ltrFirst = New-Object System.Collections.Hashtable
I rerun the script, and voila! It works perfectly. Here is the script and the associated output:
That worked so well that I decide to see if it will take a paragraph. I change my input to be a Here-String as shown here:
$w = @"
This is a big old hairy dog!
The dog is old.
The dog is big.
And, the dog is hairy.
"@
I run the script again, and once again, it works perfectly. Here is the script and output:
So in a moment of exhilaration, I decide to see if it will also take blank lines. I change the Here-String once again:
$w = @"
This is a big old hairy dog!
The dog is old.
The dog is big.
And, the dog is hairy.
And in a new paragraph, i add a cat.
The cat is small.
The cat has gray hair.
It is a gray haired cat, that is small and a feline.
"@
Indeed, it works. Here is the output:
Dude, dude, dude! This is cool, cool, cool. So here is my script as it currently appears in my Windows PowerShell ISE:
#AsciiEncodeDecodeWordsCaseSensitive.ps1
# ed wilson
# hsg-10-8-14
# —————————————————————————–
$ASCIIFirst=$ltrFirst=$null
$ASCIIFirst = New-Object System.Collections.Hashtable
$ltrFirst = New-Object System.Collections.Hashtable
#$ASCIIFirst = @{}
#$ltrFirst = @{}
0..255 |
Foreach-Object {
$ASCIIFirst.Add($_,([char]$_).ToString())}
foreach($k in $ASCIIFirst.Keys)
{$ltrFirst.add($ASCIIFirst[$k],$k)}
#$w = "This is a big old hairy dog!"
$w = @"
This is a big old hairy dog!
The dog is old.
The dog is big.
And, the dog is hairy.
And in a new paragraph, i add a cat.
The cat is small.
The cat has gray hair.
It is a gray haired cat, that is small and a feline.
"@
#$w.ToCharArray() | % {$ltrFirst["$_"]}
$n = $w.ToCharArray() | % {$ltrFirst["$_"]}
#$n | % {$ASCIIFirst[$_]}
$c = $n | % {$ASCIIFirst[$_]}
$n -join ''
$c -join ''
That is all there is to using my ASCII encode/decode script. Join me tomorrow when I will talk about adding further functionality to it, such as reading and exporting files.
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
0 comments