Last week I posted a PowerShell snippet on Twitter. My original post piped an array of integers as [CHAR] type using an OFS. Don't worry about that. As many people reminded me, it is much easier to use the -Join operator.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
-join [char[]](116,103,105,102)
I'll let you try that on your own. The [CHAR] type is used to represent a character as an integer value, like this:
PS C:\> [char]120 x
For this week's Friday Fun I thought it would be nice to translate a string of text into corresponding character values. It looks like a secret code! Or we could use the translation in a join scriptblock. So I put together a little script I call Translate-ToChar.ps1. The script takes a string of text and writes an array of [CHAR] objects.
Param( [Parameter(Position=0)] [ValidateNotNullOrEmpty()] [string]$Text="PowerShell Rocks!", [switch]$Scriptblock ) #create CHAR mapping hash table $map=@{} 33..125 | foreach { $map.Add([string]$_,[char]$_)} #create an empty array to hold the CHAR values $values=@() $text.ToCharArray() | foreach { #because $_ will change, save the current piped in character as a variable $ltr=$_ [int]$i=($map.getEnumerator() | where {$_.value -ceq $ltr }).Name #add the value to the array $values+=$i } if ($ScriptBlock) { #write a scriptblock to the pipeline #create the command $ofs="," #create a variable so we variable expansion of $values $t="-join [char[]]($values)" #put it back together as a script block and write to the pipeline [scriptblock]::Create($t) } else { #write value array $values }
The script begins by defining a map hash table for what I think are all characters you are likely to find on a US keyboard. These should be character values 33 through 125 which I get using the range (..) operator.
$map=@{} 33..125 | foreach { $map.Add([string]$_,[char]$_)}
Each number is piped to ForEach object where I add it to the hash table. In order to get the hash table key to work properly I cast the number as a string and the hash table value is the same number cast as a [CHAR]. Now we can begin breaking the string apart and "translating" it.
$text.ToCharArray() | foreach { #because $_ will change, save the current piped in character as a variable $ltr=$_
The script splits the string into a character array using the default delimiter of a space. Because I'm going to be using a pipelined expression, I save the current letter as a variable. It will keep things straight in a moment.
The next step is to find the character in the hash table values. I found the best way to accomplish this was to call the hash table's GetEnumerator() method. This way I can pipe it to Where-Object and find the corresponding key.
[int]$i=($map.getEnumerator() | where {$_.value -ceq $ltr }).Name #add the value to the array $values+=$i
Notice I'm using the case sensitive -ceq operator. The Name property of the hash table enumerator is the key value, or in other words the corresponding [CHAR] integer. With me still? This value is added to an array for the final result. In fact the default isĀ to simply write $values to the pipeline. But, I've included a -Scriptblock parameter to have the script write a scriptblock to the pipeline using the -Join operator I mentioned earlier. Now for the interesting part.
I have an array variable which needs to be expanded into the scriptblock. This won't work:
$sb={-join [char[]]($values)} write $sb
I'll end up with a scriptblock but have no way of resolving $values once the script ends. So instead I create a string with $values knowing that PowerShell will expand it. And because I want the array to be expanded as a comma separated string, I'll specify the $ofs variable as a comma. The default is a space.
$ofs="," #create a variable so we get variable expansion of $values $t="-join [char[]]($values)"
The variable $t is now a string with all of the integer values from $values as a comma separated list. I can turn this into scriptblock like this:
#put it back together as a script block and write to the pipeline [scriptblock]::Create($t)
The scriptblock gets written to the pipeline. So, I could run the script like this:
PS C:\scripts> $a=.\Translate-ToChar.ps1 PS C:\scripts> $a 80 111 119 101 114 83 104 101 108 108 0 82 111 99 107 115 33
Now I can use $a however I want. Or I could create a scriptblock.
PS C:\scripts> $b=.\Translate-ToChar.ps1 -Scriptblock PS C:\scripts> $b -join [char[]](80,111,119,101,114,83,104,101,108,108,0,82,111,99,107,115,33)
I can invoke $b anytime I want to see the message.
While I don't expect you to be running this in production I hope you picked up some tips on using hash tables, arrays, scriptblocks and casting variable types.
Download Translate-ToChar
Hi Jeffery,
When reading the sample above I am confused about your usage of the hashtable called map. If you map from char to number (instead of the other way around) you end up with simpler code that produces the same output for the given sample.
#create CHAR mapping hash table
$map=@{}
33..125 | foreach { $map.Add([Char]$_,[String]$_)}
#create an empty array to hold the CHAR values
$values=@()
$text.ToCharArray() | foreach {
$value = $map[$_]
if (-not $value) { $value = 0 }
$values += $value
}
Please correct me if I am wrong, since I have a feeling I missed something.
Thanks for a great blog anyway.
I started down a similar path but couldn’t get my head around it. It took a bit to work your code but now I see. In my mind I always saw the ToCharArray() method as splitting the string into individual letters or characters. But now I see that it is splitting the string into an array of [CHAR] objects. Because PowerShell formats them back as “real” characters, it’s easy to miss that, which I have for years. I appreciate the elegance here, although fundamentally it probably doesn’t make much difference.
Agreed, in terms of correctness it does not matter. However, if our task at hand is to convert strings to arrays of numeric values we can simplify even further:
# convert
$a = “Powershell Rocks!”.ToCharArray() | % { [int]$_ }
# convert back
-join [char[]]$a