如何从32位的Powershell实例中访问64位注册表?

22
如果你启动一个32位的PowerShell实例(%SystemRoot%\syswow64\WindowsPowerShell\v1.0\powershell.exe),那么注册表提供程序只能看到注册表中受限制的32位部分。
**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

有没有办法强制提供程序进入64位模式? 我可以使用[Microsoft.Win32] .Net APIs或者WMI,但我不想这样做。如果可能的话,我想使用Powershell v2 CTP3。

9个回答

31

使用.NET API,您可以像这样读取64位值:

$key = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, [Microsoft.Win32.RegistryView]::Registry64)
$subKey =  $key.OpenSubKey("SOFTWARE\Microsoft\.NETFramework")
$root = $subKey.GetValue("InstallRoot")

2
请注意,这仅适用于.NET 4.0+。 - Koraktor
1
非常简单/干净的解决方案,适用于访问注册表的32位和64位视图。我甚至都没费心去阅读/理解被接受的答案。但只支持.NET 4.0+,正如@Koraktor指出的那样(我认为这意味着PowerShell 3.0+)。 - James Johnston
2
主要的缺点是你不能再利用Get-Item或者Get-ChildItem,但至少它能正常工作。 - jpmc26
不支持64位注册表键,32位进程无法看到它们(例如SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock),GetValue会静默失败。使用带有/reg:64标志的32位Reg命令可以工作,但缺乏PowerShell集成。 - whenrybruce
1
@whenrybruce 不确定您测试的 PowerShell 版本是哪个。对我来说,它运行良好(PS 5.1)- https://pastebin.com/irLQ4ksm - Peter Schneider
对我来说也很好用,从一个32位的PowerShell 5.1实例:[Microsoft.Win32.RegistryKey]::OpenBaseKey('LocalMachine', 'Registry64').OpenSubKey('SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock').GetValue('AllowAllTrustedApps') - zett42

12
当Powershell作为32位进程运行时,我不知道有什么机制可以将其“切换”到64位模式。64位系统中虚拟化支持的整个重点是使32位进程相信它们正在运行在32位操作系统中...
然而,我过去使用了以下技术,对我非常有效(以下代码在带有Powershell v1的Vista SP1 x64上进行了测试)。该技术依赖于.NET的“任何 CPU”可执行文件,即使从32位进程调用,也将作为64位进程运行。我们将执行以下步骤:
1.编译一个简短的C#程序,该程序将启动Powershell(即非常简单的“fork”实现 :-)) 2.运行已编译的C#程序 3.已编译的C#程序将启动Powershell,但因为它是“Any CPU”,所以将作为64位进程运行,因此它将启动64位Powershell(请注意,因为这只是概念验证,我希望Powershell在您的'path'中) 4.新的64位Powershell将运行我们选择的命令
这是上述操作的屏幕截图(请注意进程的位数): Process tree http://img3.imageshack.us/img3/3248/powershellfork.png 以下程序期望所有列出的文件驻留在同一目录中。我建议创建一个测试目录,例如C:\Temp\PowershellTest,并将所有文件存储在那里。
程序的入口点将是一个简单的命令。
# 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)

我会运行csc(32位,但这并不重要:-)),然后运行csc编译器的结果,传递一个参数,即visibility.ps1的完整路径(这是我们想要在64位Powershell中运行的命令)。

C#代码也非常简单:

// 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;
        }
    }
}

最后,你的“visibility”脚本:

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

现在从32位PowerShell运行入口脚本可以产生期望的结果(仅为展示我没有作弊,我首先直接运行可见性脚本,然后使用我们的分支技术):

程序运行 http://img3.imageshack.us/img3/2766/powershellrunc.png


7

我相信内置的cmdlet Start-Job可以让你从32位实例中检查64位注册表。

如果不行,使用Invoke-Command回路到本地机器。64位机器将有两个端点(64位和32位),64位端点将是默认值。

示例创建一个注册表值,并确保它在64位路径中,而不是32位。

Invoke-Command -scriptblock {
    New-ItemProperty -Path HKLM:\SOFTWARE\Acme 
                     -Name NameofNewReg 
                     -PropertyType String -Value "1"
    } -computername .

1
太棒了!“invoke-command -scriptblock {gci "<your 64-bit reg key>"} -computername .”的确有效。谢谢! - Ameer Deen

7
如果环境变量PROCESSOR_ARCHITEW6432存在且其值为AMD64,则表示您在64位计算机上运行32位程序。 此时您需要在虚拟的64位路径%windir%\sysnative下运行powershell。
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
}

2
最简单的方法是使用此快捷方式: C:\Windows\sysnative 它等同于 C:\Windows\System32 -- 但关键区别在于该进程作为64位进程启动。因此,从32位powershell访问64位注册表的最简单方法是通过C:\Windows\sysnative调用reg.exe 例如:
C:\Windows\sysnative\reg.exe QUERY HKLM\SOFTWARE\JavaSoft\JDK

来源:https://stackoverflow.com/a/25103599 如果出于某种原因,您需要从64位命令行访问32位注册表,请使用C:\Windows\syswow64。
C:\Windows\syswow64\reg.exe QUERY HKLM\SOFTWARE\JavaSoft

2

REG.EXE命令可以写入64位注册表,因此下面的内容对于从powershell运行的32/64位系统是安全的。

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

这个方案似乎比其他解决方案简单得多,而且错误率更低。不过需要注意的是,这可能是在几年之后才出现的。


1
Milan的答案有一点变化,可以按照Bart De Smet的blog使用C#程序来托管powershell。尽管该博客文章侧重于针对.NET 4.0进行编译,但您也可以将其编译为.NET 3.5。结果是一个二进制文件,它是一个PowerShell主机,当从32位进程调用时可以访问64位注册表项:
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
            );

        }
    }
}

1

扩展Milan Gardian的答案,对于小的代码块,请使用此函数:

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';
    }
}

现在使用此函数在64位模式下运行任何脚本块(只要它不太大),当64位模式可用时。

0

试试这个。(REG query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" /reg:64) 使用任何你喜欢的键。我在搜索卸载密钥。

我注意到其他类似的答案,但它们没有包括/reg:64参数。还要注意,在HKLM后面没有“:”


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接