Чтение 64-битного реестра из 32-битного приложения

98

У меня есть проект модульного тестирования С #, который скомпилирован для AnyCPU. Наш сервер сборки - это 64-битная машина, на которой установлен 64-битный экземпляр SQL Express.

В тестовом проекте используется код, аналогичный приведенному ниже, для определения пути к файлам .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();
    }

Этот код отлично работает на наших 32-битных рабочих станциях и работал нормально на сервере сборки, пока я недавно не включил анализ покрытия кода с помощью NCover. Поскольку NCover использует 32-битный COM-компонент, средство запуска тестов (Gallio) работает как 32-битный процесс.

Проверяя реестр, нет ключа "Instance Names" под

HKEY_LOCAL_MACHINE \ SOFTWARE \ Wow6432Node \ Microsoft \ Microsoft SQL Server

Есть ли способ для приложения, работающего в 32-битном режиме, получить доступ к реестру за пределами Wow6432Node?

Дэвид Гардинер
источник

Ответы:

21

вы должны использовать параметр KEY_WOW64_64KEY при создании / открытии ключа реестра. Но AFAIK это невозможно с классом Registry, а только при прямом использовании API.

Это может помочь вам начать работу.

Стефан
источник
151

Существует еще нативная поддержка доступа реестра в 64 - разрядной ОС Windows с использованием .NET Framework 4.x . Следующий код протестирован с   Windows 7, 64 бит,   а также с   Windows 10, 64 бит .

Вместо использования "Wow6432Node", который имитирует узел, отображая одно дерево реестра в другое, делая его виртуальным, вы можете сделать следующее:

Решите, нужен ли вам доступ к 64-битному или 32-битному реестру, и используйте его, как описано ниже. Вы также можете использовать код, о котором я упоминал позже (раздел «Дополнительная информация»), который создает запрос на объединение для получения ключей реестра от обоих узлов в одном запросе, чтобы вы по-прежнему могли запрашивать их, используя их реальный путь.

64-битный реестр

Для доступа к 64-битному реестру вы можете использовать RegistryView.Registry64следующее:

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-битный реестр

Если вы хотите получить доступ к 32-битному реестру , используйте RegistryView.Registry32следующее:

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

Не путайте, обе версии используются в Microsoft.Win32.RegistryHive.LocalMachineкачестве первого параметра, вы определяете, использовать ли 64-битную или 32-битную версию по второму параметру (по RegistryView.Registry64сравнению с RegistryView.Registry32).

Обратите внимание, что

  • В 64-битной Windows HKEY_LOCAL_MACHINE\Software\Wow6432Nodeсодержит значения, используемые 32-битными приложениями, работающими в 64-битной системе. Только настоящие 64-битные приложения хранят свои значения HKEY_LOCAL_MACHINE\Softwareнапрямую. Поддерево Wow6432Nodeполностью прозрачно для 32-битных приложений, 32-битные приложения по-прежнему видят то, HKEY_LOCAL_MACHINE\Softwareчто ожидают (это своего рода перенаправление). В более старых версиях Windows, а также в 32-битной Windows 7 (и 32-битной Vista) поддерево Wow6432Nodeявно не существует.

  • Из-за ошибки в Windows 7 (64-разрядная версия) 32-разрядная версия исходного кода всегда возвращает «Microsoft» независимо от того, какую организацию вы зарегистрировали, в то время как 64-разрядная версия исходного кода возвращает правильную организацию.

Возвращаясь к приведенному вами примеру, сделайте это следующим образом для доступа к 64-битной ветке:

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

Дополнительная информация - для практического использования:

Я хотел бы добавить интересный подход, который Джонни Сковдал предложил в комментариях, которые я выбрал для разработки некоторых полезных функций, используя его подход: в некоторых ситуациях вы хотите вернуть все ключи, независимо от того, 32-битные они или 64 бит. Имена экземпляров SQL являются таким примером. В этом случае вы можете использовать запрос на объединение следующим образом (C # 6 или выше):

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

Теперь вы можете просто использовать указанные выше функции следующим образом:

Пример 1: получить имена экземпляров 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}");
}

предоставит вам список имен значений и значений в sqlRegPath.

Примечание. Вы можете получить доступ к значению ключа по умолчанию (отображается в инструменте командной строки REGEDT32.EXEкак (Default)), если вы опустите ValueNameпараметр в соответствующих функциях выше.

Чтобы получить список вложенных ключей в разделе реестра, используйте функцию GetRegKeyNamesили GetAllRegKeyNames. Вы можете использовать этот список для просмотра дополнительных разделов реестра.

Пример 2: Получить информацию об удалении установленного программного обеспечения

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

получит все 32-битные и 64-битные ключи удаления.

Обратите внимание на нулевую обработку, требуемую в функциях, потому что SQL-сервер может быть установлен как 32-битный или как 64-битный (Пример 1 выше). Функции перегружены, поэтому вы все равно можете передать 32-битный или 64-битный параметр, если требуется - однако, если вы его опустите, он попытается прочитать 64-битный, если это не удается (нулевое значение), он читает 32-битные значения.

Здесь есть одна особенность: поскольку GetAllRegValueNamesобычно используется в контексте цикла (см. Пример 1 выше), он возвращает пустой перечислимый тип, а не nullдля упрощения foreachциклов: если бы он не был обработан таким образом, цикл должен был бы иметь префикс ifпроверка личных данных для nullкоторых будет обременительной необходимости сделать это - так , что рассматривается один раз в функции.

Зачем беспокоиться о нуле? Потому что, если вам все равно, у вас будет намного больше головной боли, выясняя, почему в вашем коде было выбрано исключение с нулевой ссылкой - вы потратите много времени на выяснение, где и почему это произошло. И если это произошло в производственной среде, вы будете очень заняты изучением файлов журналов или журналов событий (я надеюсь, что у вас реализовано ведение журналов) ... лучше избегать нулевых проблем, если это можно сделать защитным способом. Операторы ?., ?[... ]и ??могут вам очень помочь (см. Приведенный выше код). Есть хорошая связанная статья, в которой обсуждаются новые ссылочные типы, допускающие значение NULL, в C # , которую я рекомендую прочитать, а также эта статья об операторе Элвиса.


Подсказка: вы можете использовать бесплатную версию Linqpad для тестирования всех примеров под Windows. Не требует установки. Не забудьте нажать F4и войти Microsoft.Win32на вкладку импорта пространства имен. В Visual Studio требуется, using Microsoft.Win32;чтобы в верхней части кода.

Совет: чтобы познакомиться с новыми операторами обработки null , попробуйте (и отладьте) следующий код в LinqPad:

Пример 3: Демонстрация операторов обработки 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>"
}

Попробуйте с помощью .Net fiddle

Если вам интересно, вот несколько примеров, которые я собрал, показывающих, что еще вы можете делать с этим инструментом.

Мэтт
источник
2
Спасибо за исчерпывающий ответ. По памяти я думаю, что использовал .NET 3.5, когда разместил вопрос, но приятно видеть, что .NET 4 улучшил ситуацию
Дэвид Гардинер
2
Пожалуйста. Недавно у меня была аналогичная проблема с 64-битным реестром, которую я уже решил, поэтому я подумал, что стоит поделиться решением.
Мэтт
2
Это именно то, что я искал. Я делаю это в Windows 9.1, и он отлично работает.
Michiel Bugher
1
@AZ_ - спасибо за правку, вы правы, ключ нужно закрыть!
Мэтт
1
@JohnySkovdal - Я изменил заголовок, чтобы было ясно, что я просто предоставляю дополнительную (необязательную) информацию - для тех, кто хочет глубже разобраться в этом вопросе.
Мэтт
6

У меня недостаточно комментариев для комментариев, но стоит отметить, что он работает при открытии удаленного реестра с помощью OpenRemoteBaseKey. Добавление параметра RegistryView.Registry64 позволяет 32-разрядной программе на компьютере A получить доступ к 64-разрядному реестру на компьютере B. До того, как я передал этот параметр, моя программа считывала 32-разрядную версию после OpenRemoteBaseKey и не нашла ключ I было после.

Примечание. В моем тесте удаленная машина была на самом деле моей машиной, но я обращался к ней через OpenRemoteBaseKey, как и на другой машине.

Сандра
источник
4

попробуйте это (из 32-битного процесса):

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

(нашел это здесь ).

акира
источник
1
Хорошая подсказка, это позволяет управлять реестром в пакетном режиме. Используйте, reg.exe /?чтобы получить больше информации ...
Мэтт
4

Если вы не можете использовать .NET 4 с ним RegistryKey.OpenBaseKey(..., RegistryView.Registry64), вам необходимо напрямую использовать Windows API.

Минимальное взаимодействие выглядит так:

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

Используйте это как:

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

Из того, что я прочитал, и из моих собственных тестов, мне кажется, что реестр следует проверять по этому пути «SOFTWARE \ Microsoft \ Windows \ CurrentVersion \ Uninstall». Потому что в других путях регистры не удаляются после удаления программы.

Таким образом я получил 64 регистра с 32-битной конфигурацией.

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

Для 32 регистров это:

registryKey = @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall";
key = Registry.LocalMachine.OpenSubKey(registryKey);
Сильный ТоЯ
источник