Odczytywanie rejestru 64-bitowego z aplikacji 32-bitowej

98

Mam projekt testu jednostkowego AC #, który jest kompilowany dla AnyCPU. Nasz serwer kompilacji jest maszyną 64-bitową i ma zainstalowaną 64-bitową instancję SQL Express.

Projekt testowy używa kodu podobnego do poniższego, aby zidentyfikować ścieżkę do plików .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();
    }

Ten kod działa dobrze na naszych 32-bitowych stacjach roboczych i działał dobrze na serwerze kompilacji, dopóki niedawno nie włączyłem analizy pokrycia kodu za pomocą NCover. Ponieważ NCover używa 32-bitowego komponentu COM, program uruchamiający testy (Gallio) działa jako proces 32-bitowy.

Podczas sprawdzania rejestru nie ma klucza „Nazwy instancji”

HKEY_LOCAL_MACHINE \ SOFTWARE \ Wow6432Node \ Microsoft \ Microsoft SQL Server

Czy istnieje sposób, aby aplikacja działająca w trybie 32-bitowym mogła uzyskać dostęp do rejestru poza Wow6432Node?

David Gardiner
źródło

Odpowiedzi:

21

musisz użyć parametru KEY_WOW64_64KEY podczas tworzenia / otwierania klucza rejestru. Ale AFAIK nie jest to możliwe w przypadku klasy Registry, ale tylko przy bezpośrednim korzystaniu z interfejsu API.

To może pomóc Ci zacząć.

Stefan
źródło
151

Jest jeszcze natywne wsparcie dla dostępu do rejestru w 64-bitowym systemie Windows przy użyciu .NET Framework 4.x . Poniższy kod jest testowany w   systemie Windows 7 w wersji 64-bitowej,   a także w   systemie Windows 10 w wersji 64-bitowej .

Zamiast używać "Wow6432Node" , które emuluje węzeł, mapując jedno drzewo rejestru na inne, dzięki czemu pojawia się tam wirtualnie, możesz wykonać następujące czynności:

Zdecyduj, czy potrzebujesz dostępu do rejestru 64-bitowego, czy 32-bitowego, i użyj go w sposób opisany poniżej. Możesz również użyć kodu, o którym wspomniałem później (sekcja Informacje dodatkowe), który tworzy zapytanie składające, aby uzyskać klucze rejestru z obu węzłów w jednym zapytaniu - dzięki czemu nadal możesz zapytać o nie, używając ich rzeczywistej ścieżki.

Rejestr 64-bitowy

Aby uzyskać dostęp do rejestru 64-bitowego , możesz użyć RegistryView.Registry64następujących poleceń:

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

Rejestr 32-bitowy

Jeśli chcesz uzyskać dostęp do rejestru 32-bitowego , użyj RegistryView.Registry32następujących poleceń:

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

Nie należy się mylić, obie wersje używają Microsoft.Win32.RegistryHive.LocalMachinejako pierwszego parametru, rozróżniasz, czy użyć 64-bitowego czy 32-bitowego parametru przez drugi parametr (w RegistryView.Registry64porównaniu RegistryView.Registry32).

Zwróć na to uwagę

  • W 64-bitowym systemie Windows HKEY_LOCAL_MACHINE\Software\Wow6432Nodezawiera wartości używane przez aplikacje 32-bitowe działające w systemie 64-bitowym. Tylko prawdziwe 64-bitowe aplikacje przechowują swoje wartości HKEY_LOCAL_MACHINE\Softwarebezpośrednio w. Poddrzewo Wow6432Nodejest całkowicie przezroczyste dla aplikacji 32-bitowych, aplikacje 32-bitowe nadal widzą to, HKEY_LOCAL_MACHINE\Softwareczego oczekują (jest to rodzaj przekierowania). W starszych wersjach systemu Windows, a także w 32-bitowym systemie Windows 7 (i w systemie Vista w wersji 32-bitowej) poddrzewo Wow6432Nodeoczywiście nie istnieje.

  • Z powodu błędu w systemie Windows 7 (64-bitowym), 32-bitowa wersja kodu źródłowego zawsze zwraca „Microsoft” niezależnie od zarejestrowanej organizacji, podczas gdy 64-bitowa wersja kodu źródłowego zwraca właściwą organizację.

Wracając do podanego przykładu, zrób to w następujący sposób, aby uzyskać dostęp do gałęzi 64-bitowej:

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

Dodatkowe informacje - do praktycznego zastosowania:

Chciałbym dodać interesujące podejście, które zasugerował Johny Skovdal w komentarzach, które zebrałem, aby opracować kilka przydatnych funkcji za pomocą jego podejścia: W niektórych sytuacjach chcesz odzyskać wszystkie klawisze, niezależnie od tego, czy jest to 32-bitowy, czy 64-bitowy. Przykładem są nazwy instancji SQL. W takim przypadku można użyć zapytania składającego w następujący sposób (C # 6 lub nowszy):

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

Teraz możesz po prostu użyć powyższych funkcji w następujący sposób:

Przykład 1: pobieranie nazw instancji 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}");
}

poda listę nazw wartości i wartości w sqlRegPath.

Uwaga: Możesz uzyskać dostęp do domyślnej wartości klucza (wyświetlanej przez narzędzie wiersza poleceń REGEDT32.EXEjako (Default)), jeśli pominiesz ValueNameparametr w odpowiednich funkcjach powyżej.

Aby uzyskać listę kluczy podrzędnych w kluczu rejestru, użyj funkcji GetRegKeyNameslubGetAllRegKeyNames . Możesz użyć tej listy do przechodzenia przez kolejne klucze w rejestrze.

Przykład 2: Uzyskaj informacje o odinstalowaniu zainstalowanego oprogramowania

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

otrzyma wszystkie 32-bitowe i 64-bitowe klucze deinstalacji.

Zwróć uwagę na obsługę zerową wymaganą w funkcjach, ponieważ serwer SQL można zainstalować jako 32-bitowy lub 64-bitowy (przykład 1 powyżej). Funkcje są przeciążone, więc w razie potrzeby nadal można przekazać parametr 32-bitowy lub 64-bitowy - jednak jeśli go pominiesz, spróbuje odczytać 64-bitowy, jeśli to się nie powiedzie (wartość zerowa), odczytuje wartości 32-bitowe.

Jest tu jedna specjalność: ponieważ GetAllRegValueNamesjest zwykle używana w kontekście pętli (patrz przykład 1 powyżej), zwraca raczej puste wyliczalne niż nulluproszczenie foreachpętli: jeśli nie byłoby obsługiwane w ten sposób, pętla musiałaby być poprzedzona przedrostkiem ifoświadczenie sprawdzanienull co byłoby uciążliwe konieczności zrobić - tak, aby zajmował się kiedyś w tej funkcji.

Po co zawracać sobie głowę nullem? Ponieważ jeśli cię to nie obchodzi, będziesz miał dużo więcej bólu głowy, gdy dowiesz się, dlaczego ten wyjątek zerowego odwołania został wrzucony do twojego kodu - spędzisz dużo czasu, aby dowiedzieć się, gdzie i dlaczego to się stało. A jeśli zdarzyło się to w środowisku produkcyjnym, będziesz bardzo zajęty studiowaniem plików dziennika lub dzienników zdarzeń (mam nadzieję, że masz zaimplementowane logowanie) ... lepiej unikaj błędów zerowych, gdzie możesz w obronny sposób. Operatorzy ?., ?[... ]i ??może pomóc partii (patrz kod podany powyżej). Jest fajny powiązany artykuł omawiający nowe typy odwołań dopuszczających wartość null w C # , który polecam przeczytać, a także ten o operatorze Elvisa.


Wskazówka: możesz skorzystać z bezpłatnej edycji Linqpada, aby przetestować wszystkie przykłady w systemie Windows. Nie wymaga instalacji. Nie zapomnij nacisnąć F4i wejść Microsoft.Win32na karcie Import przestrzeni nazw. W programie Visual Studio potrzebujesz using Microsoft.Win32;górnej części kodu.

Porada: Aby zapoznać się z nowymi operatorami obsługi wartości null , wypróbuj (i debuguj) następujący kod w LinqPad:

Przykład 3: Demonstracja operatorów obsługi wartości 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>"
}

Spróbuj tego ze skrzypcami .Net

Jeśli jesteś zainteresowany, oto kilka przykładów, które zebrałem, pokazując, co jeszcze możesz zrobić za pomocą tego narzędzia.

Matt
źródło
2
Dzięki za wyczerpującą odpowiedź. Z pamięci myślę, że korzystałem z .NET 3.5, kiedy opublikowałem pytanie, ale dobrze widzieć .NET 4 poprawił sytuację
David Gardiner,
2
Nie ma za co. Niedawno miałem podobny problem z rejestrem 64-bitowym, który już rozwiązałem, więc pomyślałem, że warto się nim podzielić.
Matt,
2
To jest dokładnie to, czego szukałem. Robię to w Windows 9.1 i działa świetnie.
Michiel Bugher
1
@AZ_ - dziękuję za edycję, masz rację, klucz musi być zamknięty!
Matt,
1
@JohnySkovdal - Zmieniłem nagłówek, aby było jasne, że podaję tylko dodatkowe (opcjonalne) informacje - dla tych, którzy chcą zagłębić się w sprawę.
Matt,
6

Nie mam wystarczającej liczby przedstawicieli, aby komentować, ale warto zaznaczyć, że działa to podczas otwierania zdalnego rejestru za pomocą OpenRemoteBaseKey. Dodanie parametru RegistryView.Registry64 umożliwia 32-bitowemu programowi na komputerze A dostęp do 64-bitowego rejestru na komputerze B. Przed przekazaniem tego parametru mój program odczytywał 32-bitowy kod po OpenRemoteBaseKey i nie znalazł klucza I. był po.

Uwaga: W moim teście zdalna maszyna była w rzeczywistości moją maszyną, ale uzyskałem do niej dostęp za pośrednictwem OpenRemoteBaseKey, tak jak w przypadku innej maszyny.

Sandra
źródło
4

spróbuj tego (z procesu 32-bitowego):

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

(znalazłem to tutaj ).

akira
źródło
1
Fajna wskazówka, pozwala manipulować rejestrem wsadowo. Użyj, reg.exe /?aby uzyskać więcej informacji ...
Matt
4

Jeśli nie możesz używać .NET 4 z jego RegistryKey.OpenBaseKey(..., RegistryView.Registry64), musisz bezpośrednio użyć Windows API.

Minimalna interop jest następująca:

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

Używaj go jak:

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
źródło
0

Z tego, co przeczytałem i z własnych testów, wydaje mi się, że należy sprawdzić rejestr w tej ścieżce „SOFTWARE \ Microsoft \ Windows \ CurrentVersion \ Uninstall”. Ponieważ w innych ścieżkach rejestry nie są usuwane po odinstalowaniu programu.

W ten sposób otrzymałem 64 rejestry z 32-bitową konfiguracją.

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

Dla 32 rejestrów to:

registryKey = @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall";
key = Registry.LocalMachine.OpenSubKey(registryKey);
Silny ToJa
źródło