Lectura del registro de 64 bits desde una aplicación de 32 bits

98

Tengo un proyecto de prueba de unidad ac # que está compilado para AnyCPU. Nuestro servidor de compilación es una máquina de 64 bits y tiene instalada una instancia de SQL Express de 64 bits.

El proyecto de prueba utiliza un código similar al siguiente para identificar la ruta a los archivos .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 bien en nuestras estaciones de trabajo de 32 bits y funcionó bien en el servidor de compilación hasta que habilité recientemente el análisis de cobertura de código con NCover. Debido a que NCover utiliza un componente COM de 32 bits, el corredor de pruebas (Gallio) se ejecuta como un proceso de 32 bits.

Al comprobar el registro, no hay ninguna clave "Nombres de instancia" debajo

HKEY_LOCAL_MACHINE \ SOFTWARE \ Wow6432Node \ Microsoft \ Microsoft SQL Server

¿Existe alguna forma de que una aplicación que se ejecuta en modo de 32 bits acceda al registro fuera de Wow6432Node?

David Gardiner
fuente

Respuestas:

21

debe usar el parámetro KEY_WOW64_64KEY al crear / abrir la clave de registro. Pero AFAIK, eso no es posible con la clase Registry, sino solo cuando se usa la API directamente.

Esto podría ayudarlo a comenzar.

Stefan
fuente
151

Todavía hay soporte nativo para el acceso al registro de 64 bits bajo Windows utilizando 4.x de .NET Framework . El siguiente código se prueba con   Windows 7, 64 bit   y también con   Windows 10, 64 bit .

En lugar de usar "Wow6432Node", que emula un nodo al mapear un árbol de registro en otro para que aparezca allí virtualmente, puede hacer lo siguiente:

Decida si necesita acceder al registro de 64 bits o de 32 bits y utilícelo como se describe a continuación. También puede usar el código que mencioné más adelante (sección de información adicional), que crea una consulta de unión para obtener claves de registro de ambos nodos en una consulta, por lo que aún puede consultarlos utilizando su ruta real.

Registro de 64 bits

Para acceder al registro de 64 bits , puede utilizar RegistryView.Registry64lo siguiente:

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

Si desea acceder al registro de 32 bits , utilice RegistryView.Registry32lo siguiente:

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));

No se confunda, ambas versiones están usando Microsoft.Win32.RegistryHive.LocalMachinecomo primer parámetro, usted hace la distinción entre usar 64 bits o 32 bits por el segundo parámetro ( RegistryView.Registry64versus RegistryView.Registry32).

Nota que

  • En Windows de 64 bits, HKEY_LOCAL_MACHINE\Software\Wow6432Nodecontiene valores utilizados por aplicaciones de 32 bits que se ejecutan en el sistema de 64 bits. Solo las verdaderas aplicaciones de 64 bits almacenan sus valores HKEY_LOCAL_MACHINE\Softwaredirectamente. El subárbol Wow6432Nodees completamente transparente para aplicaciones de 32 bits, las aplicaciones de 32 bits todavía ven HKEY_LOCAL_MACHINE\Softwarecomo lo esperan (es una especie de redirección). En versiones anteriores de Windows, así como en Windows 7 de 32 bits (y Vista de 32 bits), el subárbol Wow6432Nodeobviamente no existe.

  • Debido a un error en Windows 7 (64 bits), la versión del código fuente de 32 bits siempre devuelve "Microsoft" independientemente de la organización que haya registrado, mientras que la versión del código fuente de 64 bits devuelve la organización correcta.

Volviendo al ejemplo que ha proporcionado, hágalo de la siguiente manera para acceder a la rama 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");

Información adicional - para uso práctico:

Me gustaría agregar un enfoque interesante que Johny Skovdal ha sugerido en los comentarios, que he recogido para desarrollar algunas funciones útiles utilizando su enfoque: En algunas situaciones, desea recuperar todas las claves independientemente de si es de 32 bits o 64 bits. Los nombres de las instancias de SQL son un ejemplo. Puede usar una consulta de unión en ese caso de la siguiente manera (C # 6 o 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);
}

Ahora puede simplemente usar las funciones anteriores de la siguiente manera:

Ejemplo 1: obtener nombres de instancias 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}");
}

le dará una lista de los nombres y valores de los valores en sqlRegPath.

Nota: Puede acceder al valor predeterminado de una clave (mostrado por la herramienta de línea de comandos REGEDT32.EXEcomo (Default)) si omite el ValueNameparámetro en las funciones correspondientes anteriores.

Para obtener una lista de subclaves dentro de una clave de registro, use la función GetRegKeyNameso GetAllRegKeyNames. Puede utilizar esta lista para recorrer más claves en el registro.

Ejemplo 2: obtener información de desinstalación del software instalado

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

obtendrá todas las claves de desinstalación de 32 y 64 bits.

Observe el manejo de nulos requerido en las funciones porque el servidor SQL se puede instalar como 32 bits o como 64 bits (ejemplo 1 anterior). Las funciones están sobrecargadas, por lo que aún puede pasar el parámetro de 32 bits o 64 bits si es necesario; sin embargo, si lo omite, intentará leer 64 bits, si eso falla (valor nulo), lee los valores de 32 bits.

Aquí hay una especialidad: debido a GetAllRegValueNamesque generalmente se usa en un contexto de bucle (consulte el Ejemplo 1 anterior), devuelve un enumerable vacío en lugar de nullsimplificar los foreachbucles: si no se manejara de esa manera, el bucle debería tener el prefijo una ifcomprobación de sentencias para la nullque sería engorroso tener que hacer eso, de modo que se trata una vez en la función.

¿Por qué preocuparse por null? Porque si no le importa, tendrá muchos más dolores de cabeza al descubrir por qué se incluyó esa excepción de referencia nula en su código: pasaría mucho tiempo averiguando dónde y por qué sucedió. Y si sucedió en producción, estará muy ocupado estudiando archivos de registro o registros de eventos (espero que haya implementado el registro) ... mejor evite los problemas de nulos cuando pueda de manera defensiva. Los operadores ?., ?[... ]y ??pueden ayudarlo mucho (consulte el código proporcionado arriba). Hay un buen artículo relacionado sobre los nuevos tipos de referencia que aceptan valores NULL en C # , que recomiendo leer y también este sobre el operador de Elvis.


Sugerencia: puede usar la edición gratuita de Linqpad para probar todos los ejemplos en Windows. No requiere instalación. No olvide presionar F4e ingresar Microsoft.Win32en la pestaña Importar espacio de nombres. En Visual Studio, necesita using Microsoft.Win32;en la parte superior de su código.

Sugerencia: para familiarizarse con los nuevos operadores de manejo de nulos , pruebe (y depure) el siguiente código en LinqPad:

Ejemplo 3: demostración de operadores de manejo 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>"
}

Pruébalo con .Net violín

Si está interesado, aquí hay algunos ejemplos que reuní y que muestran qué más puede hacer con la herramienta.

Mate
fuente
2
Gracias por esa respuesta completa. De memoria, creo que estaba usando .NET 3.5 cuando publiqué la pregunta, pero es bueno ver que .NET 4 ha mejorado la situación
David Gardiner
2
De nada. Recientemente tuve un problema similar con el registro de 64 bits que ya había resuelto, así que pensé que vale la pena compartir la solución.
Matt
2
Esto es exactamente lo que estaba buscando. Estoy haciendo esto en Windows 9.1 y funciona muy bien.
Michiel Bugher
1
@AZ_ - gracias por la edición, tienes razón, ¡la clave debe estar cerrada!
Matt
1
@JohnySkovdal: he cambiado el título para dejar en claro que solo estoy proporcionando información adicional (opcional), para aquellos que quieran profundizar en el asunto.
Matt
6

No tengo suficiente representante para comentar, pero vale la pena señalar que funciona al abrir un registro remoto usando OpenRemoteBaseKey. Agregar el parámetro RegistryView.Registry64 permite que un programa de 32 bits en la Máquina A acceda al registro de 64 bits en la Máquina B. Antes de pasar ese parámetro, mi programa estaba leyendo 32 bits después de OpenRemoteBaseKey y no encontró la clave que yo fue después.

Nota: En mi prueba, la máquina remota era en realidad mi máquina, pero accedí a ella a través de OpenRemoteBaseKey, tal como lo haría con una máquina diferente.

Sandra
fuente
4

prueba esto (de un proceso de 32 bits):

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

(encontré eso aquí ).

Akira
fuente
1
Buena pista, permite manipular el registro en un lote. Úselo reg.exe /?para obtener más información ...
Matt
4

Si no puede usar .NET 4 con él RegistryKey.OpenBaseKey(..., RegistryView.Registry64), debe usar la API de Windows directamente.

La interoperabilidad mínima es 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);

Úselo 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
fuente
0

Por lo que he leído y por mis propias pruebas, me parece que el registro debería comprobarse en esta ruta "SOFTWARE \ Microsoft \ Windows \ CurrentVersion \ Uninstall". Porque en otras rutas los registros no se borran después de desinstalar el programa.

De esta manera obtuve 64 registros con configuración 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 es:

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