Accessing x64 registry keys from PowerShell x86

Crossing the 32-bit and 64-bit architecture barrier in PowerShell

You might find yourself in a predicament where you need to run powershell x86, or the 32-bit, version but you need to read or write to a path in the registry that should be reversed for the x64, or 64-bit, version. The inverse of this is not a problem for PowerShell as it will natively let you select the proper path. You're also going to get a bonus lesson on converting DWord and QWord entries into integers :)

Let's create our registry entries first and we'll see how this plays out nicely when using a 64-bit console to read both values. Next we'll look at the issues we face running from a 32-bit console and how I went about resolving it.

Lab Set Up

First, I created 2 paths in the registry using regedit.exe. The 64-bit will be a QWord, while the 32-bit will be a DWord.

The 64-bit path:
Key: HKLM:\Software\Wes
QWord Value: test
Value Data: 1b69b4be052fab1

The 32-bit path:
Key: HKLM:\Software\WOW6432Node\Wes
DWORD Value: test
Value Data: 75bcd15

If we're looking at the regedit GUI in the Data column, what we'll see for the 64-bit is "0x1b69b4be052fab1 (123456789987654321)" and for the 32-bit we'll see "0x75bcd15 (123456789)"

64-bit console to read 64-bit and 32-bit values


# The 64-bit info:

Get-ItemPropertyValue -Path "HKLM:\Software\Wes" -Name "test"

# Output: 123456789987654321

# The 32-bit info:

Get-ItemPropertyValue -Path "HKLM:\Software\WOW6432Node\Wes" -Name "test"

# Output: 123456789

The 32-bit console

If we try something similar in a 32-bit console, the second command will return what we'd expect (the 32-bit value). However, the first command will also return the same 32-bit value!

# The 64-bit info:

Get-ItemPropertyValue -Path "HKLM:\Software\Wes" -Name "test"

# Output: 123456789

# The 32-bit info:

Get-ItemPropertyValue -Path "HKLM:\Software\WOW6432Node\Wes" -Name "test"

# Output: 123456789

Essentially, this was Microsoft's way of putting backwards compatibility for older applications making registry calls to the HKLM:\Software path. The 32-bit application don't need to change their code and they'll just continue to work. Pretty clever idea and necessary to make a smooth transition from 32-bit to 64-bit machines.

I'd argue it'd be a rare occurrence today, and will continue to become even more rare in the future, but you might very well be forced into using the PowerShell x86 console but need to do something in the registry on the 64-bit side. Sadly, there's no fully native PowerShell calls to help you out here but you can still invoke a native windows application and if needed, work with the output in PowerShell. For the sake of this demostration, I'm only going to be querying data but you can modify this method to update or delete information as well.


# We need to leverage the reg.exe application and pass a specific flag to read the 64-bit information. We're going to assign this into a variable to make it easier to work with.

$Query64 = reg.exe query 'HKEY_LOCAL_MACHINE\Software\Wes' /v 'test' /reg:64

# If we were to look at the data, we'll see there's an empty line, the path we wanted the query, and line 3 would contain the value, type, data. Here's an example:

# 
# HKEY_LOCAL_MACHINE\Software\Wes
#     test    REG_QWORD    0x1b69b4be052fab1

# The next few steps will be to trim and split this up to capture the value (which will then need to be converted if using QWORD or DWORD.)

$Query64RawArray = $Query64[2].Trim() # This captures that 3rd line, which has the info we want and it removes any spaces at the beginning and end of the line.

$Query64Array = $Query64RawArray -split "\s{3,}" # This splits the data where 3 or more spaces are present. In my testing, it's always been 4 spaces but just playing it safe with 3. We don't want to just use 1 space in case the value or data contains a space.

$Query64Data = $Query64Array[2] # Gets the data.

# If we just stopped here, the $Query64Data would give us "0x1b69b4be052fab1". If this is all you need, then you skip ahead but I'm expecting to see "123456789987654321" like I did before. 

$Query64Data = [System.Convert]::ToUInt64($Query64Data, 16)

# $Query64Data now contains the value of "123456789987654321"

Let's put it all together with less comments to see how it looks and works.

$Query64 = reg.exe query 'HKEY_LOCAL_MACHINE\Software\Wes' /v 'test' /reg:64

$Query64RawArray = $Query64[2].Trim()
$Query64Array = $Query64RawArray -split "\s{3,}"
$Query64Data = $Query64Array[2]

$Query64Data = [System.Convert]::ToUInt64($Query64Data, 16)

$Query32Data = Get-ItemPropertyValue -Path "HKLM:\Software\Wes" -Name "test"

Write-Host "The value of the 64-bit is '$Query64Data'"
# Output: The value of the 64-bit is '123456789987654321'

Write-Host "The value of the 32-bit is '$Query32Data'"
# Output: The value of the 64-bit is '123456789'

We could pass the /reg:32 without changing anything else to pull out the 32-bit information as well but since we do this natively with PowerShell commands and it's much easier to work with, I don't see a need for the extra work. Because I know the type is a QWORD, which is capable of being much larger than its 32-bit counterpart, DWORD, I chose to go with an 64-bit unsigned integer (::ToUInt64). Additionally, this will handle DWORD data without needing to changing anything. You could also even use ToUInt32 but make sure the data is within the safe boundaries. If your data is a negative number, then use either ::ToInt32 or ::ToInt64 as unsigned integers's range is only positive numbers!

Integer Ranges

Int32: -2,147,483,648 to 2,147,483,647
Int64: -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
UInt32: 0 to 4,294,967,295
UInt64: 0 to 18,446,744,073,709,551,615

Bonus Lesson!

If you need to convert a number to DWORD or QWORD values to put back into the registry, then you'll want to read on. I was originally using one method but found it does not support unsigned integers! I have a way that supports uints and I'm going to share both methods but I'd only recommend one as it works for all cases.

# Converting int32 or int64 my old way:
$i32 = [System.Convert]::ToString(123456789, 16)
$i64 = [System.Convert]::ToString(123456789987654321, 16)

# If you tried with a number larger than int64 but within the uint64 bounds, you'd get an error like this:

# Cannot convert argument "value", with value: "18446744073709551614", for "ToString" to type "System.Int64": "Cannot convert value "18446744073709551614" to type "System.Int64". Error: "Value was either too large or too small for an Int64.""

# My recommended method is to appropriately cast the number while assigning it to a variable and then call the ToString method.

$i32 = [int32]-21474648 # Showing how to do a negative number
$i64 = [int64]92233726854775807
$ui32 = [uint32]3123456789
$ui64 = [uint64]10123456789012345

$i32.ToString("x") # feb852a8
$i64.ToString("X") # 147AE15FD7D43FF
$ui32.ToString("X") # BA2C2B15
$ui64.ToString("x") # 23f73af5cedf79

# Using a lower case "x" will generate lower case letters in the output.
# An upper case "X" will output upper case letters. Either will work for registry.