Lendo o registro de 64 bits de um aplicativo de 32 bits

98

Eu tenho um projeto de teste de unidade c # que é compilado para AnyCPU. Nosso servidor de compilação é uma máquina de 64 bits e possui uma instância do SQL Express de 64 bits instalada.

O projeto de teste usa um código semelhante ao seguinte para identificar o caminho para os arquivos .MDF:

    private string GetExpressPath()
    {
        RegistryKey sqlServerKey = Registry.LocalMachine.OpenSubKey( @"SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL" );
        string sqlExpressKeyName = (string) sqlServerKey.GetValue( "SQLEXPRESS" );
        RegistryKey sqlInstanceSetupKey = sqlServerKey.OpenSubKey( sqlExpressKeyName + @"\Setup" );
        return sqlInstanceSetupKey.GetValue( "SQLDataRoot" ).ToString();
    }

Este código funciona bem em nossas estações de trabalho de 32 bits e funcionou bem no servidor de compilação até que recentemente habilitei a análise de cobertura de código com o NCover. Como o NCover usa um componente COM de 32 bits, o executor de teste (Gallio) é executado como um processo de 32 bits.

Verificando o registro, não há nenhuma chave "Nomes de Instância" em

HKEY_LOCAL_MACHINE \ SOFTWARE \ Wow6432Node \ Microsoft \ Microsoft SQL Server

Existe uma maneira de um aplicativo em execução no modo de 32 bits acessar o registro fora do Wow6432Node?

David Gardiner
fonte

Respostas:

21

você deve usar o parâmetro KEY_WOW64_64KEY ao criar / abrir a chave de registro. Mas AFAIK isso não é possível com a classe Registry, mas apenas ao usar a API diretamente.

Isso pode ajudar você a começar.

Stefan
fonte
151

Há ainda suporte nativo para acesso ao registro em 64 bits do Windows usando o .NET Framework 4.x . O código a seguir foi testado com   Windows 7 de 64 bits   e também com   Windows 10 de 64 bits .

Em vez de usar o "Wow6432Node", que emula um nó mapeando uma árvore de registro em outra fazendo-a aparecer virtualmente, você pode fazer o seguinte:

Decida se você precisa acessar o registro de 64 bits ou de 32 bits e use-o conforme descrito abaixo. Você também pode usar o código que mencionei posteriormente (seção de informações adicionais), que cria uma consulta de união para obter as chaves do registro de ambos os nós em uma consulta - assim, você ainda pode consultá-los usando seu caminho real.

Registro de 64 bits

Para acessar o registro de 64 bits , você pode usar RegistryView.Registry64o seguinte:

string value64 = string.Empty; 
RegistryKey localKey = 
    RegistryKey.OpenBaseKey(Microsoft.Win32.RegistryHive.LocalMachine, 
        RegistryView.Registry64); 
localKey = localKey.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion"); 
if (localKey != null) 
{ 
    value64 = localKey.GetValue("RegisteredOrganization").ToString(); 
    localKey.Close();
} 
Console.WriteLine(String.Format("RegisteredOrganization [value64]: {0}",value64));

Registro de 32 bits

Se você deseja acessar o registro de 32 bits , use RegistryView.Registry32o seguinte:

string value32 = string.Empty; 
RegistryKey localKey32 = 
    RegistryKey.OpenBaseKey(Microsoft.Win32.RegistryHive.LocalMachine, 
        RegistryView.Registry32); 
localKey32 = localKey32.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion"); 
if (localKey32 != null) 
{ 
    value32 = localKey32.GetValue("RegisteredOrganization").ToString(); 
    localKey32.Close();
} 
Console.WriteLine(String.Format("RegisteredOrganization [value32]: {0}",value32));

Não se confunda, ambas as versões estão usando Microsoft.Win32.RegistryHive.LocalMachinecomo primeiro parâmetro, você faz a distinção entre usar 64 bits ou 32 bits pelo segundo parâmetro ( RegistryView.Registry64versus RegistryView.Registry32).

Observe que

  • Em um Windows de 64 bits, HKEY_LOCAL_MACHINE\Software\Wow6432Nodecontém valores usados ​​por aplicativos de 32 bits em execução no sistema de 64 bits. Apenas os verdadeiros aplicativos de 64 bits armazenam seus valores HKEY_LOCAL_MACHINE\Softwarediretamente. A subárvore Wow6432Nodeé totalmente transparente para aplicativos de 32 bits, os aplicativos de 32 bits ainda veem HKEY_LOCAL_MACHINE\Softwarecomo esperam (é um tipo de redirecionamento). Em versões anteriores do Windows, bem como no Windows 7 de 32 bits (e no Vista de 32 bits), a subárvore Wow6432Nodeobviamente não existe.

  • Devido a um bug no Windows 7 (64 bits), a versão do código-fonte de 32 bits sempre retorna "Microsoft", independentemente da organização que você registrou, enquanto a versão do código-fonte de 64 bits retorna a organização certa.

Voltando ao exemplo que você forneceu, faça da seguinte maneira para acessar o branch de 64 bits:

RegistryKey localKey = 
    RegistryKey.OpenBaseKey(Microsoft.Win32.RegistryHive.LocalMachine, 
        RegistryView.Registry64); 
RegistryKey sqlServerKey = localKey.OpenSubKey(
    @"SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL");
string sqlExpressKeyName = (string) sqlServerKey.GetValue("SQLEXPRESS");

Informações adicionais - para uso prático:

Eu gostaria de adicionar uma abordagem interessante que Johny Skovdal sugeriu nos comentários, que peguei para desenvolver algumas funções úteis usando sua abordagem: Em algumas situações, você deseja recuperar todas as chaves, independentemente de serem de 32 bits ou 64 bits. Os nomes das instâncias SQL são um exemplo. Você pode usar uma consulta de união nesse caso da seguinte maneira (C # 6 ou superior):

// using Microsoft.Win32;
public static IEnumerable<string> GetRegValueNames(RegistryView view, string regPath,
                                  RegistryHive hive = RegistryHive.LocalMachine) 
{ 
    return RegistryKey.OpenBaseKey(hive, view)
                     ?.OpenSubKey(regPath)?.G‌​etValueNames();
}

public static IEnumerable<string> GetAllRegValueNames(string RegPath,
                                  RegistryHive hive = RegistryHive.LocalMachine) 
{
    var reg64 = GetRegValueNames(RegistryView.Registry64, RegPath, hive);
    var reg32 = GetRegValueNames(RegistryView.Re‌​gistry32, RegPath, hive);
    var result = (reg64 != null && reg32 != null) ? reg64.Union(reg32) : (reg64 ?? reg32);
    return (result ?? new List<string>().AsEnumerable()).OrderBy(x => x);
}

public static object GetRegValue(RegistryView view, string regPath, string ValueName="",
                                 RegistryHive hive = RegistryHive.LocalMachine)
{
    return RegistryKey.OpenBaseKey(hive, view)
                       ?.OpenSubKey(regPath)?.G‌​etValue(ValueName);
}

public static object GetRegValue(string RegPath, string ValueName="",
                                 RegistryHive hive = RegistryHive.LocalMachine)
{   
    return GetRegValue(RegistryView.Registry64, RegPath, ValueName, hive) 
                     ?? GetRegValue(RegistryView.Re‌​gistry32, RegPath, ValueName, hive);
}

public static IEnumerable<string> GetRegKeyNames(RegistryView view, string regPath,
                   RegistryHive hive = RegistryHive.LocalMachine)
{
    return RegistryKey.OpenBaseKey(hive, view)
        ?.OpenSubKey(regPath)?.GetSubKeyNames(); 
}

public static IEnumerable<string> GetAllRegKeyNames(string RegPath,
                                  RegistryHive hive = RegistryHive.LocalMachine)
{
    var reg64 = GetRegKeyNames(RegistryView.Registry64, RegPath, hive);
    var reg32 = GetRegKeyNames(RegistryView.Re‌​gistry32, RegPath, hive);
    var result = (reg64 != null && reg32 != null) ? reg64.Union(reg32) : (reg64 ?? reg32);
    return (result ?? new List<string>().AsEnumerable()).OrderBy(x => x);
}

Agora você pode simplesmente usar as funções acima da seguinte maneira:

Exemplo 1: obter nomes de instância SQL

var sqlRegPath=@"SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL";
foreach (var valueName in GetAllRegValueNames(sqlRegPath))
{
    var value=GetRegValue(sqlRegPath, valueName);
    Console.WriteLine($"{valueName}={value}");
}

fornecerá uma lista dos nomes e valores dos valores em sqlRegPath.

Nota: Você pode acessar o valor padrão de uma chave (exibida pela ferramenta de linha de comando REGEDT32.EXEcomo (Default)) se omitir o ValueNameparâmetro nas funções correspondentes acima.

Para obter uma lista de subchaves em uma chave de registro, use a função GetRegKeyNamesou GetAllRegKeyNames. Você pode usar essa lista para percorrer outras chaves no registro.

Exemplo 2: Obtenha informações de desinstalação do software instalado

var currentVersionRegPath = @"SOFTWARE\Microsoft\Windows\CurrentVersion";
var uninstallRegPath = $@"{currentVersionRegPath}\Uninstall";
var regKeys = Registry.GetAllRegKeyNames(RegPath: uninstallRegPath);

obterá todas as chaves de desinstalação de 32 e 64 bits.

Observe a manipulação de nulos necessária nas funções porque o servidor SQL pode ser instalado como 32 ou 64 bits (Exemplo 1 acima). As funções estão sobrecarregadas, então você ainda pode passar o parâmetro de 32 bits ou 64 bits se necessário - no entanto, se você omiti-lo, ele tentará ler 64 bits; se falhar (valor nulo), ele lerá os valores de 32 bits.

Há uma especialidade aqui: Como GetAllRegValueNamesgeralmente é usado em um contexto de loop (consulte o Exemplo 1 acima), ele retorna um enumerável vazio em vez de nullsimplificar os foreachloops: se não fosse tratado dessa forma, o loop teria que ser prefixado por uma ifverificação de declaração para nullqual seria complicado ter que fazer isso - portanto, isso é tratado uma vez na função.

Por que se preocupar com null? Porque, se você não se importar, terá muito mais dores de cabeça em descobrir por que essa exceção de referência nula foi lançada em seu código - você gastaria muito tempo descobrindo onde e por que isso aconteceu. E se isso aconteceu na produção, você estará muito ocupado estudando arquivos de log ou logs de eventos (espero que tenha implementado o log) ... melhor evitar problemas nulos onde você pode de uma forma defensiva. Os operadores ?., ?[... ]e ??podem te ajudar muito (veja o código fornecido acima). Há um bom artigo relacionado discutindo os novos tipos de referência anuláveis ​​em C # , que recomendo ler e também este sobre o operador Elvis.


Dica: você pode usar a edição gratuita do Linqpad para testar todos os exemplos no Windows. Não requer instalação. Não se esqueça de pressionar F4e entrar Microsoft.Win32na guia de importação de Namespace. No Visual Studio, você precisa using Microsoft.Win32;na parte superior do código.

Dica: para se familiarizar com os novos operadores de manipulação de nulos , experimente (e depure) o seguinte código no LinqPad:

Exemplo 3: Demonstrando operadores de manipulação de nulos

static string[] test { get { return null;} } // property used to return null
static void Main()
{
    test.Dump();                    // output: null
    // "elvis" operator:
    test?.Dump();                   // output: 
    // "elvis" operator for arrays
    test?[0].Dump();                // output: 
    (test?[0]).Dump();              // output: null
    // combined with null coalescing operator (brackets required):
    (test?[0]??"<null>").Dump();    // output: "<null>"
}

Experimente com .Net fiddle

Se você estiver interessado, aqui estão alguns exemplos que juntei mostrando o que mais você pode fazer com a ferramenta.

Matt
fonte
2
Obrigado por essa resposta abrangente. De memória, acho que estava usando .NET 3.5 quando postei a pergunta, mas é bom ver que .NET 4 melhorou a situação
David Gardiner
2
De nada. Recentemente, tive um problema semelhante com o registro de 64 bits que já havia resolvido, então achei que vale a pena compartilhar a solução.
Matt
2
Isso é exatamente o que estou procurando. Estou fazendo isso no windows 9.1 e funciona muito bem.
Michiel Bugher
1
@AZ_ - obrigado pela edição, você tem razão, a chave precisa ser fechada!
Matt
1
@JohnySkovdal - mudei o título para deixar claro que estou apenas fornecendo informações adicionais (opcionais) - para aqueles que desejam se aprofundar no assunto.
Matt,
6

Não tenho representantes suficientes para comentar, mas vale ressaltar que funciona ao abrir um registro remoto usando OpenRemoteBaseKey. Adicionar o parâmetro RegistryView.Registry64 permite que um programa de 32 bits na Máquina A acesse o registro de 64 bits na Máquina B. Antes de passar esse parâmetro, meu programa estava lendo o 32 bits após OpenRemoteBaseKey e não encontrou a chave I estava depois.

Nota: Em meu teste, a máquina remota era na verdade minha máquina, mas eu a acessei via OpenRemoteBaseKey, assim como faria em uma máquina diferente.

Sandra
fonte
4

tente isto (a partir de um processo de 32 bits):

> %WINDIR%\sysnative\reg.exe query ...

(encontrei isso aqui ).

Akira
fonte
1
Bela dica, permite manipular o registro em lote. Use reg.exe /?para obter mais informações ...
Matt
4

Se você não puder usar o .NET 4 com o seu RegistryKey.OpenBaseKey(..., RegistryView.Registry64), precisará usar a API do Windows diretamente.

A interoperabilidade mínima é como:

internal enum RegistryFlags
{
    ...
    RegSz = 0x02,
    ...
    SubKeyWow6464Key = 0x00010000,
    ...
}

internal enum RegistryType
{
    RegNone = 0,
    ...
}

[DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int RegGetValue(
    UIntPtr hkey, string lpSubKey, string lpValue, RegistryFlags dwFlags, 
    out RegistryType pdwType, IntPtr pvData, ref uint pcbData);

Use-o como:

IntPtr data = IntPtr.Zero;
RegistryType type;
uint len = 0;
RegistryFlags flags = RegistryFlags.RegSz | RegistryFlags.SubKeyWow6464Key;
UIntPtr key = (UIntPtr)((uint)RegistryHive.LocalMachine);

const string subkey= @"SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL";
const string value = "SQLEXPRESS";

if (RegGetValue(key, subkey, value, flags, out type, data, ref len) == 0)
{
    data = Marshal.AllocHGlobal((int)len);
    if (RegGetValue(key, subkey, value, flags, out type, data, ref len) == 0)
    {
        string sqlExpressKeyName = Marshal.PtrToStringUni(data);
    }
}
Martin Prikryl
fonte
0

Pelo que li e pelos meus próprios testes, parece-me que o registro deve ser verificado neste caminho "SOFTWARE \ Microsoft \ Windows \ CurrentVersion \ Uninstall". Porque em outros caminhos os registros não são deletados após a desinstalação do programa.

Desta forma obtive 64 registradores com configuração de 32 bits.

string registryKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
RegistryKey key64 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
RegistryKey key = key64.OpenSubKey(registryKey);
if (key != null)
{
    var list = key.GetSubKeyNames().Select(keyName => key.OpenSubKey(keyName).GetValue("DisplayName")).ToList();

    key.Close();
}

Para 32 registros é:

registryKey = @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall";
key = Registry.LocalMachine.OpenSubKey(registryKey);
Silny ToJa
fonte