Lesen der 64-Bit-Registrierung aus einer 32-Bit-Anwendung

97

Ich habe ein AC # Unit-Test-Projekt, das für AnyCPU kompiliert wurde. Unser Build-Server ist ein 64-Bit-Computer, auf dem eine 64-Bit-SQL Express-Instanz installiert ist.

Das Testprojekt verwendet Code ähnlich dem folgenden, um den Pfad zu den MDF-Dateien zu identifizieren:

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

Dieser Code funktioniert auf unseren 32-Bit-Workstations einwandfrei und auf dem Build-Server einwandfrei, bis ich kürzlich die Analyse der Codeabdeckung mit NCover aktiviert habe. Da NCover eine 32-Bit-COM-Komponente verwendet, wird der Testläufer (Gallio) als 32-Bit-Prozess ausgeführt.

Bei der Überprüfung der Registrierung befindet sich kein Schlüssel "Instanznamen" unter

HKEY_LOCAL_MACHINE \ SOFTWARE \ Wow6432Node \ Microsoft \ Microsoft SQL Server

Gibt es eine Möglichkeit für eine Anwendung, die im 32-Bit-Modus ausgeführt wird, auf die Registrierung außerhalb von Wow6432Node zuzugreifen?

David Gardiner
quelle

Antworten:

21

Sie müssen den Parameter KEY_WOW64_64KEY verwenden, wenn Sie den Registrierungsschlüssel erstellen / öffnen. Aber AFAIK ist mit der Registry-Klasse nicht möglich, sondern nur, wenn die API direkt verwendet wird.

Dies kann Ihnen den Einstieg erleichtern.

Stefan
quelle
151

Es gibt immer noch die native Unterstützung für Zugriff auf die Registry unter 64 - Bit - Windows mit .NET Framework 4.x . Der folgende Code wird mit Windows 7, 64 Bit   und auch mit   Windows 10, 64 Bit getestet   .

Anstatt zu verwenden "Wow6432Node", wodurch ein Knoten emuliert wird, indem ein Registrierungsbaum einem anderen zugeordnet wird, sodass er dort virtuell angezeigt wird, können Sie Folgendes tun:

Entscheiden Sie, ob Sie auf die 64-Bit- oder die 32-Bit-Registrierung zugreifen müssen, und verwenden Sie sie wie unten beschrieben. Sie können auch den später erwähnten Code verwenden (Abschnitt "Zusätzliche Informationen"), mit dem eine Vereinigungsabfrage erstellt wird, um Registrierungsschlüssel von beiden Knoten in einer Abfrage abzurufen. Sie können sie also weiterhin unter Verwendung ihres tatsächlichen Pfads abfragen.

64-Bit-Registrierung

Um auf die 64-Bit-Registrierung zuzugreifen , können Sie RegistryView.Registry64Folgendes verwenden:

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

32-Bit-Registrierung

Wenn Sie auf die 32-Bit-Registrierung zugreifen möchten , verwenden Sie RegistryView.Registry32Folgendes:

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

Seien Sie nicht verwirrt, beide Versionen verwenden Microsoft.Win32.RegistryHive.LocalMachineals ersten Parameter. Sie unterscheiden anhand des zweiten Parameters ( versus ), ob 64-Bit oder 32-Bit verwendet werden soll .RegistryView.Registry64RegistryView.Registry32

Beachten Sie das

  • HKEY_LOCAL_MACHINE\Software\Wow6432NodeEnthält unter 64-Bit- Windows Werte, die von 32-Bit-Anwendungen verwendet werden, die auf dem 64-Bit-System ausgeführt werden. Nur echte 64-Bit-Anwendungen speichern ihre Werte HKEY_LOCAL_MACHINE\Softwaredirekt in. Der Teilbaum Wow6432Nodeist für 32-Bit-Anwendungen vollständig transparent. 32-Bit-Anwendungen sehen immer noch so aus, HKEY_LOCAL_MACHINE\Softwarewie sie es erwarten (es handelt sich um eine Art Umleitung). In älteren Windows-Versionen sowie 32-Bit-Windows 7 (und Vista 32-Bit) ist der Teilbaum Wow6432Nodeoffensichtlich nicht vorhanden.

  • Aufgrund eines Fehlers in Windows 7 (64 Bit) gibt die 32-Bit-Quellcodeversion immer "Microsoft" zurück, unabhängig davon, welche Organisation Sie registriert haben, während die 64-Bit-Quellcodeversion die richtige Organisation zurückgibt.

Gehen Sie wie folgt vor, um auf den 64-Bit-Zweig zuzugreifen:

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

Zusätzliche Informationen - für den praktischen Gebrauch:

Ich möchte einen interessanten Ansatz hinzufügen, den Johny Skovdal in den Kommentaren vorgeschlagen hat, den ich aufgegriffen habe, um mithilfe seines Ansatzes einige nützliche Funktionen zu entwickeln: In einigen Situationen möchten Sie alle Schlüssel zurückerhalten, unabhängig davon, ob es sich um 32-Bit oder handelt 64 Bit. Die SQL-Instanznamen sind ein solches Beispiel. In diesem Fall können Sie eine Vereinigungsabfrage wie folgt verwenden (C # 6 oder höher):

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

Jetzt können Sie die obigen Funktionen einfach wie folgt verwenden:

Beispiel 1: SQL-Instanznamen abrufen

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

gibt Ihnen eine Liste der Wertnamen und Werte in sqlRegPath.

Hinweis: Sie können auf den Standardwert einer Taste zugreifen (angezeigt vom Befehlszeilentool REGEDT32.EXEals (Default)), wenn Sie den ValueNameParameter in den entsprechenden Funktionen oben weglassen .

Verwenden Sie die Funktion oder, um eine Liste der SubKeys in einem Registrierungsschlüssel abzurufen . Mit dieser Liste können Sie weitere Schlüssel in der Registrierung durchlaufen.GetRegKeyNamesGetAllRegKeyNames

Beispiel 2: Informationen zur Deinstallation der installierten Software abrufen

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

erhält alle 32-Bit- und 64-Bit-Deinstallationsschlüssel.

Beachten Sie die in den Funktionen erforderliche Nullbehandlung, da SQL Server als 32-Bit oder als 64-Bit installiert werden kann (Beispiel 1 oben). Die Funktionen sind überlastet, sodass Sie den 32-Bit- oder 64-Bit-Parameter bei Bedarf weiterhin übergeben können. Wenn Sie ihn jedoch weglassen, wird versucht, 64-Bit zu lesen. Wenn dies fehlschlägt (Nullwert), werden die 32-Bit-Werte gelesen.

Hier gibt es eine Besonderheit: Da sie GetAllRegValueNamesnormalerweise in einem Schleifenkontext verwendet wird (siehe Beispiel 1 oben), gibt sie eine leere Aufzählung zurück, anstatt Schleifen nullzu vereinfachen foreach: Wenn dies nicht so gehandhabt würde, müsste der Schleife ein Präfix vorangestellt werden eine ifAnweisungsprüfung, für nulldie es umständlich wäre, dies zu tun - so dass dies einmal in der Funktion behandelt wird.

Warum sich um Null kümmern? Denn wenn es Sie nicht interessiert, werden Sie viel mehr Kopfschmerzen haben, wenn Sie herausfinden, warum diese Nullreferenzausnahme in Ihrem Code ausgelöst wurde - Sie würden viel Zeit damit verbringen, herauszufinden, wo und warum sie passiert ist. Und wenn es in der Produktion passiert ist, sind Sie sehr damit beschäftigt, Protokolldateien oder Ereignisprotokolle zu studieren (ich hoffe, Sie haben die Protokollierung implementiert) ... vermeiden Sie besser Nullprobleme, wenn Sie dies defensiv tun können. Die Bediener ?., ?[... ]und ??können Ihnen sehr helfen (siehe den oben angegebenen Code). Es gibt einen schönen verwandten Artikel über die neuen nullbaren Referenztypen in C # , den ich zum Lesen empfehle, und auch diesen über den Elvis-Operator.


Tipp: Sie können die kostenlose Version von Linqpad verwenden , um alle Beispiele unter Windows zu testen. Es ist keine Installation erforderlich. Vergessen Sie nicht, auf der Registerkarte Namespace-Import zu drücken F4und einzugeben Microsoft.Win32. In Visual Studio benötigen Sie using Microsoft.Win32;oben in Ihrem Code.

Tipp: Um sich mit den neuen vertraut machen null Handhabung Betreiber , ausprobieren (und debug) den folgenden Code in LinqPad:

Beispiel 3: Demonstrieren von Nullbehandlungsoperatoren

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

Versuchen Sie es mit .Net Geige

Wenn Sie interessiert sind, habe ich hier einige Beispiele zusammengestellt, die zeigen, was Sie sonst noch mit dem Tool tun können.

Matt
quelle
2
Vielen Dank für diese umfassende Antwort. Aus dem Gedächtnis denke ich, dass ich .NET 3.5 verwendet habe, als ich die Frage gepostet habe, aber gut zu sehen, dass .NET 4 die Situation verbessert hat
David Gardiner
2
Bitte. Ich hatte kürzlich ein ähnliches Problem mit der 64-Bit-Registrierung, das ich bereits gelöst hatte. Ich dachte, es lohnt sich, die Lösung zu teilen.
Matt
2
Genau das habe ich gesucht. Ich mache das in Windows 9.1 und es funktioniert großartig.
Michiel Bugher
1
@AZ_ - danke für die Bearbeitung, du hast recht, der Schlüssel muss geschlossen werden!
Matt
1
@JohnySkovdal - Ich habe die Überschrift geändert, um zu verdeutlichen, dass ich nur zusätzliche (optionale) Informationen bereitstelle - für diejenigen, die tiefer in die Angelegenheit eintauchen möchten.
Matt
6

Ich habe nicht genügend Repräsentanten, um Kommentare abzugeben, aber es ist erwähnenswert, dass dies beim Öffnen einer Remote-Registrierung mit OpenRemoteBaseKey funktioniert. Durch Hinzufügen des RegistryView.Registry64-Parameters kann ein 32-Bit-Programm auf Computer A auf die 64-Bit-Registrierung auf Computer B zugreifen. Bevor ich diesen Parameter übergeben habe, hat mein Programm das 32-Bit-Programm nach OpenRemoteBaseKey gelesen und den Schlüssel I nicht gefunden war nach.

Hinweis: In meinem Test war der Remote-Computer tatsächlich mein Computer, aber ich habe über OpenRemoteBaseKey darauf zugegriffen, genau wie bei einem anderen Computer.

Sandra
quelle
4

Versuchen Sie dies (aus einem 32-Bit-Prozess):

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

(fand das hier ).

Akira
quelle
1
Netter Hinweis, es erlaubt, die Registrierung in einem Stapel zu manipulieren. Verwenden Sie reg.exe /?, um weitere Informationen zu erhalten ...
Matt
4

Wenn Sie .NET 4 nicht mit seiner verwenden können RegistryKey.OpenBaseKey(..., RegistryView.Registry64), müssen Sie die Windows-API direkt verwenden.

Das minimale Interop ist wie folgt:

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

Verwenden Sie es wie:

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
quelle
0

Nach dem, was ich gelesen habe und nach meinen eigenen Tests, sollte die Registrierung unter diesem Pfad "SOFTWARE \ Microsoft \ Windows \ CurrentVersion \ Uninstall" überprüft werden. Denn in anderen Pfaden werden die Register nach der Deinstallation des Programms nicht gelöscht.

Auf diese Weise habe ich 64 Register mit 32-Bit-Konfiguration erhalten.

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

Für 32 Register gilt:

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