Ответ 1
вы должны использовать параметр KEY_WOW64_64KEY при создании/открытии раздела реестра. Но AFAIK это невозможно с классом Registry, но только при непосредственном использовании API.
У меня есть проект С# unit test, который компилируется для 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-битный процесс.
Проверяя реестр, нет ключа "Имена экземпляров" в
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Microsoft SQL Server
Есть ли способ для приложения, работающего в 32-битном режиме, получить доступ к реестру вне Wow6432Node?
вы должны использовать параметр KEY_WOW64_64KEY при создании/открытии раздела реестра. Но AFAIK это невозможно с классом Registry, но только при непосредственном использовании API.
Все еще есть встроенная поддержка доступа к реестру в 64-битной Windows с использованием .NET Framework 4.x. Следующий код протестирован с 64-битной Windows 7, а также с 64-битной Windows 10. Для доступа к 64-битному реестру вы можете использовать:
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-битному реестру, используйте:
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-битный по 2-му параметру (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 (и Vista 32-битной) поддерево 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 являются таким примером. В этом случае вы можете использовать запрос на объединение следующим образом (С# 6 или выше):
// using Microsoft.Win32;
public static IEnumerable<string> GetRegValueNames(RegistryView view, string regPath,
RegistryHive hive = RegistryHive.LocalMachine)
{
return RegistryKey.OpenBaseKey(hive, view)
?.OpenSubKey(regPath)?.GetValueNames();
}
public static IEnumerable<string> GetAllRegValueNames(string RegPath,
RegistryHive hive = RegistryHive.LocalMachine)
{
var reg64 = GetRegValueNames(RegistryView.Registry64, RegPath, hive);
var reg32 = GetRegValueNames(RegistryView.Registry32, 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)?.GetValue(ValueName);
}
public static object GetRegValue(string RegPath, string ValueName="",
RegistryHive hive = RegistryHive.LocalMachine)
{
return GetRegValue(RegistryView.Registry64, RegPath, ValueName, hive)
?? GetRegValue(RegistryView.Registry32, 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.Registry32, 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 [email protected]"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
в соответствующих функциях выше.
Чтобы получить список SubKeys в разделе реестра, используйте функцию GetRegKeyNames
или GetAllRegKeyNames
. Вы можете использовать этот список для просмотра дальнейших ключей в реестре.
Пример 2: Получить информацию об удалении установленного программного обеспечения
var currentVersionRegPath = @"SOFTWARE\Microsoft\Windows\CurrentVersion";
var uninstallRegPath = [email protected]"{currentVersionRegPath}\Uninstall";
var regKeys = Registry.GetAllRegKeyNames(RegPath: uninstallRegPath);
получит все 32-битные и 64-битные ключи удаления.
Обратите внимание на нулевую обработку, требуемую в функциях, поскольку сервер SQL может быть установлен как 32-разрядный или как 64-разрядный (Пример 1 выше). Функции перегружены, поэтому вы все равно можете передать 32-битный или 64-битный параметр, если это необходимо - однако, если вы его опустите, он попытается прочитать 64-битный код, в случае сбоя (нулевое значение) он прочитает 32-битные значения.
Здесь есть одна особенность: поскольку GetAllRegValueNames
обычно используется в контексте цикла (см. Пример 1 выше), он возвращает пустое перечисляемое значение, а не null
чтобы упростить циклы foreach
: если это не будет обработано таким образом, цикл должен будет быть префиксом оператора if
проверяющего null
что было бы обременительно, if
бы это делалось, так что это делается один раз в функции.
Зачем беспокоиться о нуле? Потому что, если вам все равно, у вас будет гораздо больше головной боли, когда вы узнаете, почему в вашем коде возникло исключение с нулевой ссылкой - вы потратите много времени на выяснение, где и почему это произошло. И если это произойдет на производстве, вы будете очень заняты изучением файлов журналов или журналов событий (надеюсь, у вас реализовано ведение журналов)... лучше избегать нулевых проблем, когда вы можете защититься. Операторы ?.
, ?[
... ]
и ??
может вам сильно помочь (см. код, приведенный выше). Существует хорошая родственный статья обсуждает новые NULLABLE ссылочных типов в С#, которые я рекомендую прочитать, а также это один об операторе Элвиса.
Подсказка: вы можете использовать бесплатную версию Linqpad для тестирования всех примеров под Windows. Не требует установки. Не забудьте нажать F4 и ввести Microsoft.Win32
на вкладке импорта пространства имен. В Visual Studio вам требуется using Microsoft.Win32;
в верхней части вашего кода.
Совет: Чтобы ознакомиться с новыми операторами обработки нулей , попробуйте (и отладьте) следующий код в LinqPad:
Пример 3: демонстрация нулевых операторов обработки
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>"
}
Если вам интересно, вот несколько примеров, которые я собрал, показывающих, что еще можно сделать с помощью инструмента.
У меня недостаточно комментариев для комментариев, но стоит отметить, что он работает при открытии удаленного реестра с использованием OpenRemoteBaseKey. Добавление параметра RegistryView.Registry64 позволяет 32-разрядной программе на компьютере A получить доступ к 64-разрядному реестру на компьютере B. Прежде чем передать этот параметр, моя программа читала 32-разрядную версию после OpenRemoteBaseKey и не нашла ключа я был после.
Примечание. В моем тесте удаленная машина была на самом деле моей машиной, но я обратился к ней через OpenRemoteBaseKey, как и для другой машины.
попробуйте это (из 32-битного процесса):
> %WINDIR%\sysnative\reg.exe query ...
(найдено, что здесь).
Если вы не можете использовать .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);
}
}