I was working today on trying to write some scripts to help me better monitor our various installations of Neverfail. The command line tools they provide don’t provide all the performance information I need but (strangely enough) they do write some of it into the Registry where it gets updated very frequently. In writing a Powershell script to read these values I was having issues with a set of the values I was reading. Some values would be handled correctly and others were returning weird results. Below is an example of the “weird” ones.
PS C:\Users\cars> foreach ($value in $badKey.GetValueNames()) {
>> Write-Host "$value | " -nonewline
>>Write-Host $badKey.GetValue("$value")
>> }
>>
PerfCtr1 | 0 0 0 0
PerfCtr2 | 0 13 136 8
PerfCtr3 | 0 0 3 101
PS C:\Users\cars> $example=$badKey.GetValue("PerfCtr1")
PS C:\Users\cars> $example | gm
TypeName: System.Byte
Name MemberType Definition
---- ---------- ----------
CompareTo Method System.Int32 CompareTo(Object value), System.Int32 CompareTo(Byte value)
Equals Method System.Boolean Equals(Object obj), System.Boolean Equals(Byte obj)
GetHashCode Method System.Int32 GetHashCode()
GetType Method System.Type GetType()
GetTypeCode Method System.TypeCode GetTypeCode()
ToString Method System.String ToString(), System.String ToString(String format), System.String ...
So what it it turns out I was getting from the GetValue method was a sequence of 4 bytes as an array. This made me wonder what the heck was going on so I fired up RegEdit to take a look. The contents of the Data column looked okay but the Type was different, so I expanded the column to see what was different and discovered REG_DWORD_BIG_ENDIAN as a type. This was one I don’t recall having seen before.
TechNet has the following to say about DWORD and it’s brethren:
REG_DWORD
A 32-bit (4-byte) number. Boolean (“True” or “False”) values and many entries for device drivers and services use this data type. REG_DWORD data can be displayed and entered in hexadecimal or decimal format in the registry editor Regedit.exe. For an example, see the ActivityLogFlag entry.
REG_DWORD_BIG_ENDIAN
Same as REG_DWORD. A 32-bit number in which the most significant byte is displayed as the leftmost (or high-order) byte. This is the most common format for storing numbers in computers that are running Windows Server 2003.
REG_DWORD_LITTLE_ENDIAN
A 32-bit number in which the most significant byte is displayed as the rightmost (or low-order) byte. This is opposite of the order in which bytes are stored in the REG_DWORD and REG_DWORD_BIG_ENDIAN data types.
If you’re not paying attention it could be easy to miss the difference when using RegEedit since they appear almost identical to DWORD values. The only obvious difference is the “Type” field.
So in the example above GetValue returns different values for “PerfCtr2″ and “PerfCtr2 DWORD” which are nominally the same value (at least according to RegEdit).
0 13 136 8
PS C:\>Write-Host $key.GetValue("PerfCtr2 DWORD")
886792
To help me figure out how to get the info I was looking for I put together a test and created a couple of dummy registry keys with each of the types of reg keys and some examples.
If we try to see what Powershell tells us about each of these keys we see that for our BIG_ENDIAN friend GetValueKind returns “unknown.”
PS C:\>foreach ($value in $key1.GetValueNames()) { Write-Host $value " | " $key1.GetValueKind($value) "|" $key1.GetValue($value) }
REG_SZ Example | String | This is a test of the emergency broadcast system
REG_DWORD Example | DWord | 1
REG_MULTI_SZ Example | MultiString | This is a test This is another Test
REG_EXPAND_SZ | ExpandString | C:\ProgramData\Fred
REG_DWORD_BIG_ENDIAN | Unknown | 161 178 195 212
PS C:\>
GetValue converts each byte to a decimal value. Our key REG_DWORD_BIG_ENDIAN (0xa1b2c3d4) can be expressed as 4 bytes “a1″ “b2″ “c3″ “d4″ which when converted become the values “161″ “178″ “195″ “212″. While this is mildly useful it doesn’t help us easily get the value we want 2712847316. While it is possible to get the right value by doing some math [ (byte1 * 256^3) + (byte2 * 256^2) + (byte3 * 256) + byte4 ] I thought my resulting attempts to write a snippet to do this were ugly since it doesn’t appear Powershell has any easy way to do exponentiation.
PS C:\>$bytes= $key.GetValue("REG_DWORD_BIG_ENDIAN")
PS C:\>$number=0
PS C:\>for ($idx=0;$idx -lt $bytes.length;$idx++) {
>> $pwr = $bytes.length-$idx-1;
>> $number+= $bytes[$idx] * [math]::pow(256,$pwr)
>>}
>>
PS C:\>Write-Host "The Value via method 1 is [$number]"
The Value via method 1 is [2712847316]
The second version uses the Math::Pow method to do help do the exponentiation:(byte1 * 256^3) + (byte2 * 256^2) + (byte3 * 256) + byte4
[cc lang="powershell" tab_size="3"]PS C:\>$key=Get-Item "HKLM:\Software\CRTCorp\Product_A"
PS C:\>$bytes= $key.GetValue("REG_DWORD_BIG_ENDIAN")
PS C:\>$number=0
PS C:\>$number = ($bytes[0] * [Math]::pow(256,3)) + ($bytes[1] * [Math]::Pow(256,2)) + ($bytes[2] * 256) + $bytes[3]
PS C:\>Write-Host "The Value via Method 2 is [$number]"
The Value via method 3 is [2712847316]
The third method was to write out the formula a little more explicitly so that (byte1 * 256^3) + (byte2 * 256^2) + (byte3 * 256^1) + (byte4 *256^0) becomes (byte1 * 16777216) + (byte2 * 65536) + (byte3 * 256) + byte4
PS C:\>$bytes= $key.GetValue("REG_DWORD_BIG_ENDIAN")
PS C:\>$number=0
PS C:\>$number= ($bytes[0] * 16777216) + ($bytes[1] * 65536) + ($bytes[2] * 256) + $bytes[3]
PS C:\>Write-Host "The Value via method 3 is [$number]"
The Value via method 3 is [2712847316]
I was curious though as to why it was we could get the individual bytes converted but there wasn’t (to me) an obvious way to do the whole value. I came across a mention of the Convert Class on MSDN which does make it possible. Convert class in the .Net framework. After playing around some I was able to come up with a different way using the Convert Class that to me seems a little cleaner. The Convert class has several overloaded methods. One version of the ToInt64 method converts a string version of a number into 64-bit signed integer. Either this or ToUInt32 will work for our purposes here. ToInt32 won’t work because of the value may incorrectly (for our purposed) return a negative (i.e. signed) integer. The call to the ToInt64 method requires an argument that specifies the base of the number the string represents (in this case hex= base 16). So in theory that would work if we were able to represent the bytes as a hex string.. i.e. 0xA1B2C3D4. That’s when I came across a VBScript to Powershell page describing converting numbers from decimal to hex that helped fill in the missing piece. Putting these two things together I was able to come up with a function to use in my script which seemed to work.
#Convert a Reg_DWORD_BIG_ENDIAN value to a number that makes sense to a human
# $convertee should be a byte array
#--------------------------------------------
function Convert-RBEToDecimal($convertee){
$tmpString = "0x"
foreach ($byte in $convertee){
$tmpString += "{0:X}" -f $byte
}
Return [Convert]::ToInt64($tmpString,16)
}
#############################################################################
The operative word here was “seemed”. I noticed in testing that there were instances where I got a very wrong answer. As an example if the Registry value were (0xA102C4D4, or 2701312980 decimal) the function would return. 169001940 decimal as the value…I realized that in building $tmpString if the value of a particular byte was less than 16 it’d spit out a single character 0xC rather than 0x0C. This was fine if I was interested in the value of a single byte but when concatenating the values together makes for a big difference in the resulting value as 0xA102C3D4 would become 0xA12C3D4. The resolution for this was to change the format string to pad the value with a leading 0 if necessary. So $tmpString += “{0:X}” -f $byte became $tmpString += “{0:X2}” -f $byte.
So the final function I ended up using looks like this:
#Convert a Reg_DWORD_BIG_ENDIAN value to a number that makes sense to a human
# $convertee should be a byte array
#--------------------------------------------
function Convert-RBEToDecimal($convertee){
$tmpString = "0x"
foreach ($byte in $convertee){
$tmpString += "{0:X2}" -f $byte
}
Return [Convert]::ToInt64($tmpString,16)
}
###############################################################################


[...] especially since all the counters involved are of the REG_DWORD_BIG_ENDIAN variety (you can see a previous entry related to BIG_ENDIAN here). I ended up settling on using the Reg.exe util available in Windows. This utility let’s [...]
[Translate]