Lecture du registre 64 bits à partir d'une application 32 bits

98

J'ai un projet de test unitaire ac # qui est compilé pour AnyCPU. Notre serveur de build est une machine 64 bits et une instance SQL Express 64 bits est installée.

Le projet de test utilise un code similaire au suivant pour identifier le chemin d'accès aux fichiers .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();
    }

Ce code fonctionne correctement sur nos stations de travail 32 bits et fonctionnait correctement sur le serveur de construction jusqu'à ce que j'active récemment l'analyse de la couverture du code avec NCover. Étant donné que NCover utilise un composant COM 32 bits, le lanceur de test (Gallio) s'exécute comme un processus 32 bits.

Vérification du registre, il n'y a pas de clé "Noms d'instance" sous

HKEY_LOCAL_MACHINE \ SOFTWARE \ Wow6432Node \ Microsoft \ Microsoft SQL Server

Existe-t-il un moyen pour une application exécutée en mode 32 bits d'accéder au registre en dehors de Wow6432Node?

David Gardiner
la source

Réponses:

21

vous devez utiliser le paramètre KEY_WOW64_64KEY lors de la création / ouverture de la clé de registre. Mais AFAIK ce n'est pas possible avec la classe Registry mais uniquement lors de l'utilisation directe de l'API.

Cela pourrait vous aider à démarrer.

Stefan
la source
151

Il y a encore un support natif pour l' accès au Registre sous Windows 64 bits en utilisant 4.x .NET Framework . Le code suivant est testé avec   Windows 7, 64 bits   et également avec   Windows 10, 64 bits .

Au lieu d'utiliser "Wow6432Node", qui émule un nœud en mappant une arborescence de registre dans une autre en la faisant apparaître virtuellement, vous pouvez faire ce qui suit:

Décidez si vous devez accéder au registre 64 bits ou 32 bits et utilisez-le comme décrit ci-dessous. Vous pouvez également utiliser le code que j'ai mentionné plus tard (section Informations supplémentaires), qui crée une requête d'union pour obtenir les clés de registre des deux nœuds en une seule requête - afin que vous puissiez toujours les interroger en utilisant leur chemin réel.

Registre 64 bits

Pour accéder au registre 64 bits , vous pouvez utiliser RegistryView.Registry64comme suit:

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

Registre 32 bits

Si vous souhaitez accéder au registre 32 bits , procédez RegistryView.Registry32comme suit:

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

Ne soyez pas confus, les deux versions utilisent Microsoft.Win32.RegistryHive.LocalMachinecomme premier paramètre, vous faites la distinction entre utiliser 64 bits ou 32 bits par le 2ème paramètre ( RegistryView.Registry64versus RegistryView.Registry32).

Notez que

  • Sur un Windows 64 bits, HKEY_LOCAL_MACHINE\Software\Wow6432Nodecontient les valeurs utilisées par les applications 32 bits exécutées sur le système 64 bits. Seules les vraies applications 64 bits stockent leurs valeurs HKEY_LOCAL_MACHINE\Softwaredirectement. Le sous Wow6432Node- arbre est entièrement transparent pour les applications 32 bits, les applications 32 bits voient toujours HKEY_LOCAL_MACHINE\Softwarecomme elles l'attendent (c'est une sorte de redirection). Dans les anciennes versions de Windows ainsi que dans Windows 7 32 bits (et Vista 32 bits), le sous-arbre Wow6432Noden'existe évidemment pas .

  • En raison d'un bogue dans Windows 7 (64 bits), la version de code source 32 bits renvoie toujours "Microsoft" quelle que soit l'organisation que vous avez enregistrée tandis que la version de code source 64 bits renvoie la bonne organisation.

Pour revenir à l'exemple que vous avez fourni, procédez de la manière suivante pour accéder à la branche 64 bits:

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

Informations complémentaires - pour une utilisation pratique:

J'aimerais ajouter une approche intéressante que Johny Skovdal a suggérée dans les commentaires, que j'ai choisie pour développer des fonctions utiles en utilisant son approche: dans certaines situations, vous voulez récupérer toutes les clés, qu'elles soient 32 bits ou 64 bits. Les noms d'instances SQL en sont un exemple. Vous pouvez utiliser une requête Union dans ce cas comme suit (C # 6 ou supérieur):

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

Maintenant, vous pouvez simplement utiliser les fonctions ci-dessus comme suit:

Exemple 1: obtenir les noms d'instances 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}");
}

vous donnera une liste des noms de valeur et des valeurs dans sqlRegPath.

Remarque: Vous pouvez accéder à la valeur par défaut d'une touche (affichée par l'outil de ligne de commande REGEDT32.EXEsous la forme (Default)) si vous omettez le ValueNameparamètre dans les fonctions correspondantes ci-dessus.

Pour obtenir une liste des sous- clés dans une clé de registre, utilisez la fonction GetRegKeyNamesouGetAllRegKeyNames . Vous pouvez utiliser cette liste pour parcourir d'autres clés dans le registre.

Exemple 2: obtenir des informations de désinstallation du logiciel installé

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

obtiendra toutes les clés de désinstallation 32 bits et 64 bits.

Notez le traitement nul requis dans les fonctions car le serveur SQL peut être installé en 32 bits ou en 64 bits (exemple 1 ci-dessus). Les fonctions sont surchargées afin que vous puissiez toujours passer le paramètre 32 bits ou 64 bits si nécessaire - cependant, si vous l'omettez, il essaiera de lire 64 bits, si cela échoue (valeur nulle), il lit les valeurs 32 bits.

Il y a une spécialité ici: comme il GetAllRegValueNamesest généralement utilisé dans un contexte de boucle (voir l'exemple 1 ci-dessus), il retourne un énumérable vide plutôt que nullde simplifier les foreachboucles: s'il ne serait pas géré de cette façon, la boucle devrait être préfixée par une ifdéclaration vérifiantnull lequel il serait fastidieux de devoir faire cela - donc cela est traité une fois dans la fonction.

Pourquoi se soucier de null? Parce que si vous ne vous en souciez pas, vous aurez beaucoup plus de maux de tête pour savoir pourquoi cette exception de référence nulle a été lancée dans votre code - vous passeriez beaucoup de temps à découvrir où et pourquoi cela s'est produit. Et si cela s'est produit en production, vous serez très occupé à étudier les fichiers journaux ou les journaux d'événements (j'espère que vous avez implémenté la journalisation) ... mieux vaut éviter les problèmes de nullité lorsque vous le pouvez de manière défensive. Les opérateurs ?., ?[... ]et ??peuvent beaucoup vous aider (voir le code fourni ci-dessus). Il y a un bel article connexe traitant des nouveaux types de référence nullable en C # , que je recommande de lire et aussi celui-ci sur l'opérateur Elvis.


Astuce: vous pouvez utiliser l'édition gratuite de Linqpad pour tester tous les exemples sous Windows. Il ne nécessite pas d'installation. N'oubliez pas d'appuyer sur F4et d'entrer Microsoft.Win32dans l'onglet d'importation d'espace de noms. Dans Visual Studio, vous avez besoin using Microsoft.Win32;en haut de votre code.

Conseil: pour vous familiariser avec les nouveaux opérateurs de gestion des valeurs nulles , essayez (et déboguez) le code suivant dans LinqPad:

Exemple 3: démonstration d'opérateurs de gestion nuls

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

Essayez-le avec .Net Fiddle

Si vous êtes intéressé, voici quelques exemples que j'ai rassemblés montrant ce que vous pouvez faire d'autre avec l'outil.

Mat
la source
2
Merci pour cette réponse complète. De mémoire, je pense que j'utilisais .NET 3.5 lorsque j'ai posté la question, mais c'est bon de voir .NET 4 a amélioré la situation
David Gardiner
2
De rien. J'ai récemment eu un problème similaire avec le registre 64 bits que j'avais déjà résolu, alors j'ai pensé qu'il valait la peine de partager la solution.
Matt
2
C'est exactement ce que je cherchais. Je fais cela dans Windows 9.1 et cela fonctionne très bien.
Michiel Bugher
1
@AZ_ - merci pour la modification, vous avez raison, la clé doit être fermée!
Matt le
1
@JohnySkovdal - J'ai changé le titre pour indiquer clairement que je ne fais que fournir des informations supplémentaires (facultatives) - pour ceux qui veulent approfondir la question.
Matt le
6

Je n'ai pas assez de représentants pour commenter, mais il convient de souligner que cela fonctionne lors de l'ouverture d'un registre distant à l'aide d'OpenRemoteBaseKey. L'ajout du paramètre RegistryView.Registry64 permet à un programme 32 bits sur la machine A d'accéder au registre 64 bits sur la machine B.Avant de passer ce paramètre, mon programme lisait le 32 bits après OpenRemoteBaseKey, et n'a pas trouvé la clé I était après.

Remarque: Dans mon test, la machine distante était en fait ma machine, mais j'y ai accédé via OpenRemoteBaseKey, comme je le ferais pour une autre machine.

Sandra
la source
4

essayez ceci (à partir d'un processus 32 bits):

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

(trouvé que ici ).

Akira
la source
1
Belle astuce, cela permet de manipuler le registre par lots. Utilisez reg.exe /?pour obtenir plus d'informations ...
Matt
4

Si vous ne pouvez pas utiliser .NET 4 avec son RegistryKey.OpenBaseKey(..., RegistryView.Registry64), vous devez utiliser directement l'API Windows.

L'interopérabilité minimale est comme:

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

Utilisez-le comme:

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
la source
0

D'après ce que j'ai lu et mes propres tests, il me semble que le registre doit être vérifié dans ce chemin "SOFTWARE \ Microsoft \ Windows \ CurrentVersion \ Uninstall". Parce que dans d'autres chemins, les registres ne sont pas supprimés après la désinstallation du programme.

De cette façon, j'ai 64 registres avec une configuration 32 bits.

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

Pour 32 registres, c'est:

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