Membaca 64bit Registry dari aplikasi 32bit

98

Saya memiliki proyek uji unit ac # yang dikompilasi untuk AnyCPU. Server build kami adalah mesin 64bit, dan telah menginstal instance SQL Express 64bit.

Proyek uji menggunakan kode yang mirip dengan berikut ini untuk mengidentifikasi jalur ke 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();
    }

Kode ini berfungsi dengan baik di workstation 32-bit kami, dan berfungsi dengan baik di server build sampai saya baru-baru ini mengaktifkan analisis cakupan kode dengan NCover. Karena NCover menggunakan komponen COM 32bit, runner pengujian (Gallio) berjalan sebagai proses 32bit.

Memeriksa registri, tidak ada kunci "Nama Instance" di bawah

HKEY_LOCAL_MACHINE \ SOFTWARE \ Wow6432Node \ Microsoft \ Microsoft SQL Server

Apakah ada cara untuk aplikasi yang berjalan dalam mode 32bit untuk mengakses registri di luar Wow6432Node?

David Gardiner
sumber

Jawaban:

21

Anda harus menggunakan parameter KEY_WOW64_64KEY saat membuat / membuka kunci registri. Tetapi AFAIK itu tidak mungkin dengan kelas Registry tetapi hanya jika menggunakan API secara langsung.

Ini mungkin membantu Anda memulai.

Stefan
sumber
151

Masih ada dukungan asli untuk akses registri di bawah 64 bit Windows menggunakan Framework 4.x . Kode berikut diuji dengan   Windows 7, 64 bit   dan juga dengan   Windows 10, 64 bit .

Alih-alih menggunakan "Wow6432Node", yang mengemulasi node dengan memetakan satu pohon registri ke yang lain sehingga muncul di sana secara virtual, Anda dapat melakukan berikut ini:

Putuskan, apakah Anda perlu mengakses registri 64 bit atau 32 bit, dan gunakan seperti yang dijelaskan di bawah ini. Anda juga dapat menggunakan kode yang saya sebutkan nanti (bagian Informasi tambahan), yang membuat kueri gabungan untuk mendapatkan kunci registri dari kedua node dalam satu kueri - jadi Anda masih dapat menanyakannya dengan menggunakan jalur sebenarnya.

Registri 64 bit

Untuk mengakses registri 64 bit , Anda dapat menggunakan RegistryView.Registry64sebagai berikut:

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

Registri 32 bit

Jika Anda ingin mengakses registri 32bit , gunakan RegistryView.Registry32sebagai berikut:

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

Jangan bingung, kedua versi tersebut menggunakan Microsoft.Win32.RegistryHive.LocalMachinesebagai parameter pertama, Anda membuat perbedaan apakah akan menggunakan 64 bit atau 32 bit dengan parameter ke - 2 ( RegistryView.Registry64versus RegistryView.Registry32).

Catat itu

  • Pada Windows 64 bit, HKEY_LOCAL_MACHINE\Software\Wow6432Nodeberisi nilai yang digunakan oleh aplikasi 32 bit yang berjalan pada sistem 64 bit. Hanya aplikasi 64 bit yang benar yang menyimpan nilainya HKEY_LOCAL_MACHINE\Softwaresecara langsung. Subpohon Wow6432Nodesepenuhnya transparan untuk aplikasi 32 bit, aplikasi 32 bit masih terlihat HKEY_LOCAL_MACHINE\Softwareseperti yang diharapkan (ini semacam pengalihan). Pada versi Windows yang lebih lama dan juga Windows 7 32 bit (dan Vista 32 bit) subpohon tersebut Wow6432Nodejelas tidak ada.

  • Karena bug di Windows 7 (64 bit), versi kode sumber 32 bit selalu mengembalikan "Microsoft" terlepas dari organisasi mana yang telah Anda daftarkan sementara versi kode sumber 64 bit mengembalikan organisasi yang benar.

Kembali ke contoh yang Anda berikan, lakukan dengan cara berikut untuk mengakses cabang 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");

Informasi tambahan - untuk penggunaan praktis:

Saya ingin menambahkan pendekatan menarik yang disarankan Johny Skovdal dalam komentar, yang saya ambil untuk mengembangkan beberapa fungsi berguna dengan menggunakan pendekatannya: Dalam beberapa situasi Anda ingin mendapatkan kembali semua kunci terlepas dari apakah itu 32 bit atau 64 bit. Nama contoh SQL adalah contohnya. Anda dapat menggunakan kueri gabungan dalam kasus tersebut sebagai berikut (C # 6 atau lebih tinggi):

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

Sekarang Anda cukup menggunakan fungsi di atas sebagai berikut:

Contoh 1: Dapatkan nama instance 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}");
}

akan memberi Anda daftar nama dan nilai nilai di sqlRegPath.

Catatan: Anda dapat mengakses nilai default kunci (ditampilkan oleh alat baris perintah REGEDT32.EXEsebagai (Default)) jika Anda menghilangkan ValueNameparameter dalam fungsi terkait di atas.

Untuk mendapatkan daftar subkunci dalam kunci registri, gunakan fungsi GetRegKeyNamesatau GetAllRegKeyNames. Anda dapat menggunakan daftar ini untuk menelusuri kunci lebih lanjut di registri.

Contoh 2: Dapatkan informasi uninstall perangkat lunak yang diinstal

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

akan mendapatkan semua kunci uninstall 32 bit dan 64 bit.

Perhatikan penanganan null yang diperlukan dalam fungsi karena SQL server dapat diinstal sebagai 32 bit atau 64 bit (Contoh 1 di atas). Fungsinya kelebihan beban sehingga Anda masih dapat meneruskan parameter 32 bit atau 64 bit jika diperlukan - namun, jika Anda menghilangkannya maka ia akan mencoba membaca 64 bit, jika gagal (nilai null), ia membaca nilai 32 bit.

Ada satu kekhususan di sini: Karena GetAllRegValueNamesbiasanya digunakan dalam konteks perulangan (lihat Contoh 1 di atas), ia mengembalikan enumerabel kosong daripada nullmenyederhanakan foreachperulangan: jika tidak ditangani seperti itu, perulangan harus diawali dengan sebuah ifpemeriksaan pernyataan untuk nullyang akan menjadi rumit karena harus melakukan itu - sehingga yang ditangani sekali dalam fungsi.

Mengapa repot-repot tentang null? Karena jika Anda tidak peduli, Anda akan lebih pusing mencari tahu mengapa pengecualian referensi nol itu dimasukkan ke dalam kode Anda - Anda akan menghabiskan banyak waktu untuk mencari tahu di mana dan mengapa hal itu terjadi. Dan jika itu terjadi dalam produksi, Anda akan sangat sibuk mempelajari file log atau log peristiwa (saya harap Anda telah menerapkan logging) ... lebih baik hindari masalah null di mana Anda bisa dengan cara defensif. Operator ?., ?[... ]dan ??dapat banyak membantu Anda (lihat kode yang disediakan di atas). Ada artikel terkait yang bagus membahas jenis referensi nullable baru di C # , yang saya sarankan untuk dibaca dan juga yang ini tentang operator Elvis.


Petunjuk: Anda dapat menggunakan edisi gratis Linqpad untuk menguji semua contoh pada Windows. Itu tidak membutuhkan instalasi. Jangan lupa untuk menekan F4dan masuk Microsoft.Win32di tab impor Namespace. Dalam Visual Studio, Anda memerlukan using Microsoft.Win32;di bagian atas kode Anda.

Tip: Untuk membiasakan diri Anda dengan operator penanganan null baru , coba (dan debug) kode berikut di LinqPad:

Contoh 3: Mendemonstrasikan operator penanganan 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>"
}

Cobalah dengan biola .Net

Jika Anda tertarik, berikut beberapa contoh yang saya kumpulkan untuk menunjukkan apa lagi yang dapat Anda lakukan dengan alat tersebut.

Matt
sumber
2
Terima kasih atas jawaban komprehensif itu. Dari memori saya pikir saya menggunakan .NET 3.5 ketika saya memposting pertanyaan, tetapi bagus untuk dilihat. NET 4 telah memperbaiki situasi
David Gardiner
2
Sama-sama. Saya memiliki masalah serupa dengan registri 64 bit baru-baru ini yang telah saya selesaikan, jadi saya pikir ada baiknya berbagi solusinya.
Matt
2
Inilah yang saya cari-cari. Saya melakukan ini di windows 9.1 dan itu berfungsi dengan baik.
Michiel Bugher
1
@AZ_ - terima kasih atas pengeditannya, Anda benar, kuncinya harus ditutup!
Matt
1
@JohnySkovdal - Saya telah mengubah judul untuk memperjelas bahwa saya hanya memberikan informasi tambahan (opsional) - bagi mereka yang ingin menggali lebih dalam masalah ini.
Matt
6

Saya tidak memiliki cukup perwakilan untuk berkomentar, tetapi perlu ditunjukkan bahwa ini berfungsi saat membuka registri jarak jauh menggunakan OpenRemoteBaseKey. Menambahkan parameter RegistryView.Registry64 memungkinkan program 32-bit pada Mesin A untuk mengakses registri 64-bit pada Mesin B. Sebelum saya meneruskan parameter itu, program saya membaca 32-bit setelah OpenRemoteBaseKey, dan tidak menemukan kunci I adalah setelah.

Catatan: Dalam pengujian saya, mesin jarak jauh sebenarnya adalah mesin saya, tetapi saya mengaksesnya melalui OpenRemoteBaseKey, seperti yang saya lakukan untuk mesin lain.

Sandra
sumber
4

coba ini (dari proses 32bit):

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

(menemukannya di sini ).

akira
sumber
1
Petunjuk bagus, ini memungkinkan untuk memanipulasi registri dalam batch. Gunakan reg.exe /?untuk mendapatkan info lebih lanjut ...
Matt
4

Jika Anda tidak dapat menggunakan .NET 4 dengan itu RegistryKey.OpenBaseKey(..., RegistryView.Registry64), Anda perlu menggunakan Windows API secara langsung.

Interop minimalnya seperti:

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

Gunakan seperti:

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

Dari apa yang telah saya baca dan dari pengujian saya sendiri, menurut saya registri harus diperiksa di jalur ini "SOFTWARE \ Microsoft \ Windows \ CurrentVersion \ Uninstall". Karena di jalur lain register tidak dihapus setelah menghapus program.

Dengan cara ini saya mendapat 64 register dengan konfigurasi 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();
}

Untuk 32 register adalah:

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