How to access the 64-bit registry from a 32-bit Powershell instance?
Asked Answered
T

9

22

If you launch a 32-bit instance of Powershell (%SystemRoot%\syswow64\WindowsPowerShell\v1.0\powershell.exe), then the registry provider only sees the limited 32-bit parts of the registry.

**32-bit console**
PS> (dir HKLM:\SOFTWARE | measure).count - (dir HKLM:\SOFTWARE\wow6432node | measure).count

0

**64-bit console**
PS> (dir HKLM:\SOFTWARE | measure).count - (dir HKLM:\SOFTWARE\wow6432node | measure).count

-5

Is there any way to force the provider into 64-bit mode? I could drop down to [Microsoft.Win32] .Net APIs, or maybe WMI, but I'd rather not. I'm using Powershell v2 CTP3 if that expands the possibilities at all.

Touchmenot answered 10/3, 2009 at 14:11 Comment(0)
H
12

When Powershell is running as a 32 bit process, I am not aware of a mechanism to "switch it" to 64bit mode. The whole point of virtualization support in 64bit systems is to make 32bit processes believe they are living in a 32bit OS...

However, that said I used the following technique in the past and it worked very nicely for me (the following code was tested on Vista SP1 x64 with Powershell v1). The technique relies on the fact that .NET's "Any CPU" executables will run as 64bit process even when invoked from a 32bit process. The steps we will be performing:

  1. Compile a short C# program that will start powershell (i.e. a very simple "fork" implementation :-) )
  2. Run the compiled C# program
  3. The compiled C# program will start Powershell, but because it's "Any CPU", it will be running as a 64bit process so it will start 64bit Powershell (note that because this is just a proof-of-concept, I expect powershell to be in your 'path')
  4. The new 64bit Powershell will run a commandlet of our choice

This is a screenshot of the above in action (notice bit-ness of the processes): Process tree http://img3.imageshack.us/img3/3248/powershellfork.png

The following program expects all the files listed to reside in the same directory. I recommend creating a test directory, e.g. C:\Temp\PowershellTest, and storing all the files there).

Entry point to the program will be a simple commandlet:

# file "test.ps1"
$basePath = Split-Path -resolve $myInvocation.MyCommand.Path
$exe = Join-Path $basePath test.exe
&"$env:SystemRoot\Microsoft.NET\Framework\v3.5\csc.exe" /nologo /target:exe /out:$exe (Join-Path $basePath test.cs)
&$exe (Join-Path $basePath visibility.ps1)

It runs csc (32bit, but it doesn't matter :-) ) and then runs result of csc compiler, passing one argument, (full path to) visibility.ps1 (this is the commandlet we want to run in 64bit Powershell).

The C# code is very simple as well:

// file "test.cs"
using System.Diagnostics;
static class Program {
    static int Main(string[] args) {
        ProcessStartInfo i = new ProcessStartInfo("powershell", args[0]);
        i.UseShellExecute = false;
        using(Process p = Process.Start(i)) {
            p.WaitForExit();
            return p.ExitCode;
        }
    }
}

And finally, your "visibility" script:

# file "visibility.ps1"
(dir HKLM:\SOFTWARE).count - (dir HKLM:\SOFTWARE\wow6432node).count

Running the entry script from 32bit Powershell now yields desired result (just to show I was not cheating I run the visibility script directly first, then using our fork technique):

Program run http://img3.imageshack.us/img3/2766/powershellrunc.png

Howl answered 10/3, 2009 at 15:30 Comment(0)
A
31

With .NET API you can read 64-bit values like this:

$key = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, [Microsoft.Win32.RegistryView]::Registry64)
$subKey =  $key.OpenSubKey("SOFTWARE\Microsoft\.NETFramework")
$root = $subKey.GetValue("InstallRoot")
Aliaalias answered 15/10, 2013 at 12:19 Comment(6)
Please note that this will only work with .NET 4.0+.Sportive
Very simple/clean solution that works well for accessing both 32-bit and 64-bit views of registry. I didn't even bother trying to read/understand the accepted answer. But only .NET 4.0+ as @Sportive points out (I think that means PowerShell 3.0+).Catamount
The major downside is you can no longer leverage Get-Item or Get-ChildItem, but at least it just works.Intyre
Doesn't work for 64 bit registry keys not visible to 32 bit processes (e.g. SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock), GetValue fails silently. 32 bit Reg command with /reg:64 flag does work, but lacks the PowerShell integration.Twyla
@Twyla Not sure which version of powershell you have tested. For me it's running fine (PS 5.1) - pastebin.com/irLQ4ksmStott
Works fine for me too, from a 32-bit PowerShell 5.1 instance: [Microsoft.Win32.RegistryKey]::OpenBaseKey('LocalMachine', 'Registry64').OpenSubKey('SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock').GetValue('AllowAllTrustedApps')Benzidine
H
12

When Powershell is running as a 32 bit process, I am not aware of a mechanism to "switch it" to 64bit mode. The whole point of virtualization support in 64bit systems is to make 32bit processes believe they are living in a 32bit OS...

However, that said I used the following technique in the past and it worked very nicely for me (the following code was tested on Vista SP1 x64 with Powershell v1). The technique relies on the fact that .NET's "Any CPU" executables will run as 64bit process even when invoked from a 32bit process. The steps we will be performing:

  1. Compile a short C# program that will start powershell (i.e. a very simple "fork" implementation :-) )
  2. Run the compiled C# program
  3. The compiled C# program will start Powershell, but because it's "Any CPU", it will be running as a 64bit process so it will start 64bit Powershell (note that because this is just a proof-of-concept, I expect powershell to be in your 'path')
  4. The new 64bit Powershell will run a commandlet of our choice

This is a screenshot of the above in action (notice bit-ness of the processes): Process tree http://img3.imageshack.us/img3/3248/powershellfork.png

The following program expects all the files listed to reside in the same directory. I recommend creating a test directory, e.g. C:\Temp\PowershellTest, and storing all the files there).

Entry point to the program will be a simple commandlet:

# file "test.ps1"
$basePath = Split-Path -resolve $myInvocation.MyCommand.Path
$exe = Join-Path $basePath test.exe
&"$env:SystemRoot\Microsoft.NET\Framework\v3.5\csc.exe" /nologo /target:exe /out:$exe (Join-Path $basePath test.cs)
&$exe (Join-Path $basePath visibility.ps1)

It runs csc (32bit, but it doesn't matter :-) ) and then runs result of csc compiler, passing one argument, (full path to) visibility.ps1 (this is the commandlet we want to run in 64bit Powershell).

The C# code is very simple as well:

// file "test.cs"
using System.Diagnostics;
static class Program {
    static int Main(string[] args) {
        ProcessStartInfo i = new ProcessStartInfo("powershell", args[0]);
        i.UseShellExecute = false;
        using(Process p = Process.Start(i)) {
            p.WaitForExit();
            return p.ExitCode;
        }
    }
}

And finally, your "visibility" script:

# file "visibility.ps1"
(dir HKLM:\SOFTWARE).count - (dir HKLM:\SOFTWARE\wow6432node).count

Running the entry script from 32bit Powershell now yields desired result (just to show I was not cheating I run the visibility script directly first, then using our fork technique):

Program run http://img3.imageshack.us/img3/2766/powershellrunc.png

Howl answered 10/3, 2009 at 15:30 Comment(0)
L
8

If the enviroment variable PROCESSOR_ARCHITEW6432 exists and has the value AMD64 then you are running 32bit on a 64bit machine. Than you have to run the powershell in the virtual 64bit path %windir%\sysnative.

if ($env:PROCESSOR_ARCHITEW6432 -eq "AMD64") {
    write-warning "changing from 32bit to 64bit PowerShell..."
    $powershell=$PSHOME.tolower().replace("syswow64","sysnative").replace("system32","sysnative")

    if ($myInvocation.Line) {
        &"$powershell\powershell.exe" -NonInteractive -NoProfile $myInvocation.Line
    } else {
        &"$powershell\powershell.exe" -NonInteractive -NoProfile -file "$($myInvocation.InvocationName)" $args
    }

    exit $lastexitcode
}
Laundes answered 5/1, 2012 at 20:34 Comment(1)
This saved me. Best answer imho.With
M
7

I believe the built in cmdlet Start-Job will allow you to check the 64 bit registry from a 32-bit instance.

If not, using Invoke-Command to loop back to the local machine wil. A 64-bit machine will have two endpoints (64-bit and 32-bit), and the 64-bit endpoint will be the default.

Example creating a registry value and ensuring it is in the 64-bit path, not 32.

Invoke-Command -scriptblock {
    New-ItemProperty -Path HKLM:\SOFTWARE\Acme 
                     -Name NameofNewReg 
                     -PropertyType String -Value "1"
    } -computername .
Megohm answered 10/1, 2011 at 6:31 Comment(1)
Great trick! 'invoke-command -scriptblock {gci "<your 64-bit reg key>"} -computername .' works. Thanks!Adabelle
B
2

The easiest way is to use this shortcut: C:\Windows\sysnative which is equivalent to C:\Windows\System32 -- but the key difference is that the process is launched as a 64-bit process. Therefore, the easiest way to access the 64-bit registry from a 32-bit powershell is to call reg.exe via C:\Windows\sysnative For example:

C:\Windows\sysnative\reg.exe QUERY HKLM\SOFTWARE\JavaSoft\JDK

source: https://stackoverflow.com/a/25103599

If, for some reason, you needed to access the 32-bit registry from a 64-bit command-line use C:\Windows\syswow64

C:\Windows\syswow64\reg.exe QUERY HKLM\SOFTWARE\JavaSoft
Breeden answered 28/3, 2018 at 14:20 Comment(0)
C
2

The REG.EXE command can write to 64-bit registries so the below should be safe for 32/64-bit from powershell.

&REG.EXE @('ADD','HKLM\YOURPATH\...','/v','KEY','/t','REG_DWORD','/d','12c','/f','/reg:64')

Seems quite a bit simpler and less error prone than the other solutions. This may have came out years after mind you.

Cellulosic answered 2/7, 2020 at 20:30 Comment(0)
A
1

A slight variation of the answer from Milan is to host powershell using the C# program as per Bart De Smet's blog. Although that blog entry focusses on compiling against .NET 4.0, you can compile the same against .NET 3.5 as well. The result is a binary that is a PowerShell host that can access 64bit registry entires when invoked from a 32-bit process:

using System;
using System.Management.Automation.Runspaces;
using Microsoft.PowerShell;

namespace PSHost
{
    class Program
    {
        static void Main(string[] args)
        {

            var config = RunspaceConfiguration.Create();
            ConsoleShell.Start(
                config,
                "Windows PowerShell - Compiled for ANY CPU",
                "",
                args
            );

        }
    }
}
Adabelle answered 9/1, 2011 at 23:21 Comment(0)
M
1

To extend Milan Gardian's answer, use this function for small code blocks:

function RunAs-64Bit ([ScriptBlock]$scriptblock)
{
    [string]$code = 'using System.Diagnostics; static class Program { static int Main(string[] args) { ProcessStartInfo i = new ProcessStartInfo("powershell", args[0]); i.UseShellExecute = false; using(Process p = Process.Start(i)) { p.WaitForExit(); return p.ExitCode; } } }'
    [string]$exeName = $env:temp + '\' + [System.IO.Path]::GetRandomFileName() + '.exe';
    $params = New-Object 'System.CodeDom.Compiler.CompilerParameters'; 
    @('mscorlib.dll',  'System.dll', ([System.Reflection.Assembly]::GetAssembly([PSObject]).Location)) | %{ $params.ReferencedAssemblies.Add( $_ ) } | Out-Null
    $params.GenerateExecutable      = $true
    $params.GenerateInMemory        = $false;
    $params.CompilerOptions         = '/optimize';
    $params.OutputAssembly          = $exeName;
    $params.TreatWarningsAsErrors   = $false;
    $params.WarningLevel            = 4;

    $csprovider = New-Object 'Microsoft.CSharp.CSharpCodeProvider'; #disposable
    try {
        $compileResults = $csprovider.CompileAssemblyFromSource($params, $code)
        $errors = $compileResults.Errors | ?{ -not $_.IsWarning }
        if ($errors.Count -gt 0) 
        {
            Write-Host -f red 'Compiler errors are found.'
            foreach ($output in $compileResults.Output) { Write-Host -$output }
            foreach ($err in $errors) { Write-Host -f red $('Compile Error: ' + $err); }            
        }
        else 
        {
            $compileResults.Errors | %{ Abr-Write-UtilLog 'Util Get assembly from code' $('Compile Warning: ' + $_); }            
            $assembly = $compileResults.CompiledAssembly
            $commandParam = '-encodedCommand  ' + [System.Convert]::ToBase64String([System.Text.Encoding]::UNICODE.GetBytes($scriptblock));
            &$exeName $commandParam
        }
        Remove-Item -force $exeName -ErrorAction 'SilentlyContinue';
    } finally{
        $csprovider.Dispose();
        Remove-Variable 'csprovider';
    }
}

Now use this function to run any scriptblock (as long as it is not too big) in 64-bit mode when 64-bit mode is available

Mousterian answered 13/8, 2013 at 8:31 Comment(0)
N
0

Try this. (REG query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" /reg:64) Use whatever key you'd prefer. I was searching the Uninstall keys.

I noticed other similar answers but they didn't include the /reg:64 param. Also note, no ":" after HKLM

Noctule answered 29/7, 2021 at 11:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.