Lettura del registro a 64 bit da un'applicazione a 32 bit


98

Ho un progetto di unit test ac # compilato per AnyCPU. Il nostro server di compilazione è una macchina a 64 bit e ha un'istanza SQL Express a 64 bit installata.

Il progetto di test utilizza un codice simile al seguente per identificare il percorso dei file .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();
    }

Questo codice funziona bene sulle nostre workstation a 32 bit e ha funzionato bene sul server di compilazione fino a quando non ho abilitato di recente l'analisi della copertura del codice con NCover. Poiché NCover utilizza un componente COM a 32 bit, il test runner (Gallio) viene eseguito come un processo a 32 bit.

Controllando il registro, non è presente alcuna chiave "Instance Names"

HKEY_LOCAL_MACHINE \ SOFTWARE \ Wow6432Node \ Microsoft \ Microsoft SQL Server

Esiste un modo per un'applicazione in esecuzione in modalità a 32 bit per accedere al registro all'esterno di Wow6432Node?

Risposte:


21

devi usare il parametro KEY_WOW64_64KEY durante la creazione / apertura della chiave di registro. Ma AFAIK ciò non è possibile con la classe Registry ma solo quando si utilizza direttamente l'API.

Questo potrebbe aiutarti a iniziare.


151

C'è ancora il supporto nativo per l'accesso al registro in Windows a 64 bit utilizzando 4.x .NET Framework . Il codice seguente viene testato con   Windows 7, 64 bit   e anche con   Windows 10, 64 bit .

Invece di usare "Wow6432Node", che emula un nodo mappando un albero del registro in un altro facendolo apparire virtualmente, puoi fare quanto segue:

Decidi se devi accedere al registro a 64 bit oa 32 bit e usalo come descritto di seguito. Puoi anche utilizzare il codice che ho menzionato più avanti (sezione Informazioni aggiuntive), che crea una query di unione per ottenere le chiavi di registro da entrambi i nodi in una query, quindi puoi comunque interrogarli usando il loro percorso reale.

Registro a 64 bit

Per accedere al registro a 64 bit , è possibile utilizzare RegistryView.Registry64quanto segue:

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 a 32 bit

Se desideri accedere al registro a 32 bit , utilizza RegistryView.Registry32quanto segue:

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

Non essere confuso, entrambe le versioni utilizzano Microsoft.Win32.RegistryHive.LocalMachinecome primo parametro, fai la distinzione se utilizzare 64 bit o 32 bit dal 2 ° parametro ( RegistryView.Registry64contro RegistryView.Registry32).

Nota quello

  • Su Windows a 64 bit, HKEY_LOCAL_MACHINE\Software\Wow6432Nodecontiene i valori utilizzati dalle applicazioni a 32 bit in esecuzione sul sistema a 64 bit. Solo le vere applicazioni a 64 bit memorizzano HKEY_LOCAL_MACHINE\Softwaredirettamente i propri valori . La sottostruttura Wow6432Nodeè completamente trasparente per le applicazioni a 32 bit, le applicazioni a 32 bit vedono ancora HKEY_LOCAL_MACHINE\Softwarecome si aspettano (è una sorta di reindirizzamento). Nelle versioni precedenti di Windows e in Windows 7 a 32 bit (e Vista a 32 bit) la sottostruttura Wow6432Nodeovviamente non esiste.

  • A causa di un bug in Windows 7 (64 bit), la versione del codice sorgente a 32 bit restituisce sempre "Microsoft" indipendentemente dall'organizzazione registrata, mentre la versione del codice sorgente a 64 bit restituisce l'organizzazione corretta.

Tornando all'esempio che hai fornito, fallo nel modo seguente per accedere al ramo a 64 bit:

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

Informazioni aggiuntive - per uso pratico:

Vorrei aggiungere un approccio interessante che Johny Skovdal ha suggerito nei commenti, che ho raccolto per sviluppare alcune funzioni utili utilizzando il suo approccio: in alcune situazioni si desidera recuperare tutte le chiavi indipendentemente dal fatto che sia a 32 bit o 64 bit. I nomi delle istanze SQL sono un esempio di questo tipo. In tal caso è possibile utilizzare una query di unione come segue (C # 6 o versione successiva):

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

Ora puoi semplicemente usare le funzioni sopra come segue:

Esempio 1: ottenere i nomi delle istanze 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}");
}

ti fornirà un elenco dei nomi dei valori e dei valori in sqlRegPath.

Nota: è possibile accedere al valore predefinito di una chiave (visualizzato dallo strumento della riga di comando REGEDT32.EXEcome (Default)) se si omette il ValueNameparametro nelle funzioni corrispondenti sopra.

Per ottenere un elenco di sottochiavi all'interno di una chiave di registro, utilizzare la funzione GetRegKeyNameso GetAllRegKeyNames. È possibile utilizzare questo elenco per attraversare ulteriori chiavi nel registro.

Esempio 2: ottenere informazioni sulla disinstallazione del software installato

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

otterrà tutte le chiavi di disinstallazione a 32 bit e 64 bit.

Notare la gestione null richiesta nelle funzioni perché SQL Server può essere installato a 32 bit o 64 bit (esempio 1 sopra). Le funzioni sono sovraccariche in modo da poter ancora passare il parametro a 32 bit o 64 bit se necessario, tuttavia, se lo si omette, proverà a leggere 64 bit, se fallisce (valore nullo), legge i valori a 32 bit.

C'è una specialità qui: poiché GetAllRegValueNamesviene solitamente utilizzato in un contesto di ciclo (vedere l'Esempio 1 sopra), restituisce un enumerabile vuoto piuttosto che nullsemplificare i foreachcicli: se non fosse gestito in questo modo, il ciclo dovrebbe essere preceduto da un ifcontrollo economico nullche sarebbe ingombrante dover fare - così che è oggetto di una volta in funzione.

Perché preoccuparsi di null? Perché se non ti interessa, avrai molti più mal di testa a scoprire perché quell'eccezione di riferimento nullo è stata lanciata nel tuo codice: passeresti molto tempo a scoprire dove e perché è successo. E se è accaduto in produzione sarai molto impegnato a studiare i file di log o i log degli eventi (spero che tu abbia implementato il log) ... meglio evitare problemi nulli dove puoi in modo difensivo. Gli operatori ?., ?[... ]e ??possono aiutarti molto (vedi il codice fornito sopra). C'è un bell'articolo correlato che discute i nuovi tipi di riferimento nullable in C # , che consiglio di leggere e anche questo sull'operatore Elvis.


Suggerimento: puoi usare l'edizione gratuita di Linqpad per testare tutti gli esempi sotto Windows. Non richiede installazione. Non dimenticare di premere F4e accedere Microsoft.Win32alla scheda di importazione dello spazio dei nomi. In Visual Studio, è necessario using Microsoft.Win32;nella parte superiore del codice.

Suggerimento: per familiarizzare con i nuovi operatori di gestione dei valori nulli , provare (ed eseguire il debug) del codice seguente in LinqPad:

Esempio 3: dimostrazione di operatori di gestione null

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>"
}

Provalo con .Net fiddle

Se sei interessato, ecco alcuni esempi che ho messo insieme che mostrano cos'altro puoi fare con lo strumento.


2
Grazie per quella risposta esauriente. A memoria penso che stavo usando .NET 3.5 quando ho postato la domanda, ma è bello vedere che .NET 4 ha migliorato la situazione
David Gardiner

2
Prego. Recentemente ho avuto un problema simile con il registro a 64 bit che avevo già risolto, quindi ho pensato che valesse la pena condividere la soluzione.
Matt

2
Questo è esattamente quello che stavo cercando. Lo sto facendo in Windows 9.1 e funziona alla grande.
Michiel Bugher

1
@AZ_ - grazie per la modifica, hai ragione, la chiave deve essere chiusa!
Matt

1
@ JohnySkovdal - Ho cambiato il titolo per chiarire che sto solo fornendo informazioni aggiuntive (opzionali) - per coloro che vogliono approfondire la questione.
Matt

6

Non ho abbastanza rappresentante per commentare, ma vale la pena sottolineare che funziona quando si apre un registro remoto utilizzando OpenRemoteBaseKey. L'aggiunta del parametro RegistryView.Registry64 consente a un programma a 32 bit sulla macchina A di accedere al registro a 64 bit sulla macchina B. Prima di passare quel parametro, il mio programma stava leggendo il 32 bit dopo OpenRemoteBaseKey e non ha trovato la chiave I era dopo.

Nota: nel mio test, la macchina remota era in realtà la mia macchina, ma l'ho acceduta tramite OpenRemoteBaseKey, proprio come avrei fatto per una macchina diversa.


4

prova questo (da un processo a 32 bit):

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

(trovato qui ).


1
Bel suggerimento, permette di manipolare il registro in un batch. Usa reg.exe /?per avere maggiori informazioni ...
Matt

4

Se non puoi usare .NET 4 con il suo RegistryKey.OpenBaseKey(..., RegistryView.Registry64), devi usare direttamente l'API di Windows.

L'interoperabilità minima è come:

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

Usalo come:

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

0

Da quanto ho letto e dai miei test, mi sembra che il registro debba essere controllato in questo percorso "SOFTWARE \ Microsoft \ Windows \ CurrentVersion \ Uninstall". Perché in altri percorsi i registri non vengono cancellati dopo aver disinstallato il programma.

In questo modo ho ottenuto 64 registri con configurazione a 32 bit.

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

Per 32 registri è:

registryKey = @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall";
key = Registry.LocalMachine.OpenSubKey(registryKey);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.