Développer des applications sous Windows Vista - partie 1

Louis-Guillaume MORAND

Avec l'arrivée du nouveau système d'exploitation Windows Vista, un grand nombre de fonctionnalités ont été présentées aux utilisateurs, tant au niveau de la sécurité que d'améliorations graphiques ou interactives. Pour les développeurs, il est alors possible d'intégrer ces nouveautés au sein de leurs applications et c'est ce que cet article va tâcher de présenter.

N'hésitez pas à commenter cet article ! Commentez ♪

- Titre : Développer des applications sous Windows Vista - partie 1

- Auteur : Louis-Guillaume MORAND

- Parution : 11 janvier 2008

- Licence : Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.

Sommaire

- I. Introduction

- II. Écrire une application certifiée pour Windows Vista

- II-A. Introduction

- II-B. Test 1 : Vérifier que chaque exécutable de l'application contient un fichier manifeste qui définit son niveau d'exécution

- II-C. Test 30 : Vérifier que l'application est « Restart Manager Aware »

- II-D. Test 32 : Vérifier que l'application gère automatiquement les exceptions connues ou attendues

- III. Application recovery sous Vista

- III-A. Présentation de l'API

- III-B. Implémentation

- III-B-1. Enregistrement de l'application (Restart Manager)

- III-B-2. Sauvegarde des données avant le crash (RecoveryCallback)

- III-B-3. Récupération des données

- III-C. Ressources complémentaires

- IV. Gérer le bootloader de Vista

- IV-A. Architecture du Boot Loader

- IV-B. Représentation objet du boot loader

- IV-C. Création de notre application

- IV-D. Conclusion

- IV-E. Ressources complémentaires

- V. Amélioration des interfaces graphiques

- V-A. Gérer la transparence dans vos applications

- V-B. Ajouter le bouclier sur vos boutons

- V-B-1. Implémentation

- V-B-2. Téléchargement

- V-B-3. Seconde solution

- VI. Remerciements

I - Introduction

Pratiquement un an après la sortie officielle de Windows Vista, j'ai eu l'idée de rédiger un article sur les différentes nouvelles fonctionnalités du système d'exploitation qu'il est possible d'intégrer au sein de vos applications. Ainsi, dans cet article qui sera découpé en plusieurs parties, nous apprendrons tout d'abord à réaliser une application désignée pour fonctionner pleinement sur Windows Vista, puis nous verrons comment utiliser les composants graphiques de Vista et enfin nous verrons comment utiliser certains composants ou fonctionnalités, propres à Windows Vista.

II - Écrire une application certifiée pour Windows Vista

II-A - Introduction

Windows Vista, bien que les mauvaises langues disent le contraire, représente un grand pas en avant concernant la sécurité et la stabilité d'un système d'exploitation, tout particulièrement par rapport à ses anciennes versions. Ainsi, en plus d'avoir des drivers qui ne sont pas lancés en mode kernel (=>pouvant entrainer des écrans bleus) et d'outils de sécurité comme Windows Defender (antimalware) , Windows Vista implémente un système de moindre privilège et de token (UAC) qui permet de bloquer toute action sensible qui n'aurait pas été autorisée par l'utilisateur.
Tout ceci entraine différentes contraintes de développement qu'il est nécessaire de respecter si l'on veut développer une application capable de fonctionner pleinement sur ce système d'exploitation. Pour avoir une application réellement certifiée pour Windows Vista (et ainsi obtenir le logo « Certified for Vista »), il vous faut d'abord suivre les recommandations décrites dans ce guide-là. Mais surtout, il faut réussir une série de tests qui sont décrits dans un livre blanc bien précis.

Il y a au total 32 cas tests qu'il faut passer et ceux-ci concernent la sécurité, la fiabilité et l'installation. Je n'expliquerai pas ici tous les tests, car certains sont suffisamment décrits dans le livre blanc pour ne pas nécessiter que l'on s'y attarde. Je n'expliquerai pas non plus comment configurer la machine pour réaliser tous les tests à l'aide du SDK, du débogueur et des composants tiers spécifiques, nous nous contenterons ici de regarder les points qui permettent d'avoir une application respectant la philosophie de codage de Windows Vista.

II-B - Test 1 : Vérifier que chaque exécutable de l'application contient un fichier manifeste qui définit son niveau d'exécution

Il est demandé pour chaque application, de définir dans un fichier « de configuration » les privilèges dont elle a besoin pour exécuter toutes ses fonctions tout en respectant le principe de moindre privilège. Ainsi, par défaut, une application aura les droits minimaux, mais pourra demander une élévation de privilèges pour par exemple écrire dans le dossier Windows ou à certains autres endroits sensibles. Même si une application est une application toute simple, sans fonctionnalités nécessitant des droits élevés, il est demandé de créer le fichier manifeste dans lequel il sera écrit que, justement, cette application peut être exécutée avec les droits minimaux.

Nous allons supposer que vous vouliez prendre une application déjà compilée et que vous aimeriez la rendre « plus compatible » avec Windows Vista. Nous allons donc extraire son manifeste, le modifier et le réintégrer grâce à l'utilitaire mt.exe (que vous pouvez trouver ici). Voici la commande pour extraire le manifeste

Extraction du manifeste

mt.exe -inputresource:NomAppli.exe;#1 -out:temp.manifest

Collez-y quelque chose ressemblant à cela :

Fichier de manifeste

<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>

Dans notre exemple, nous précisons que notre application doit avoir des droits administrateurs. Parfois, ils sont nécessaires pour simplement écrire dans le registre pour installer un logiciel au démarrage. Voyons maintenant ce que nous aurions pu mettre dans notre partie « security » du manifeste. Tout d'abord, bien que le XML soit extensible et que l'on puisse y mettre différents types de balises, c'est uniquement la balise requestedExecutionLevel qui nous intéresse. Elle a deux attributs « level » et « uiAccess » qui ont chacun différentes valeurs possibles.

Format de la balise requestedExecutionLevel

<requestedExecutionLevel
level="asInvoker | highestAvailable | requireAdministrator"
uiAccess="true|false"/>

Les plus couramment utilisés sont asInvoker pour utiliser le niveau de l'utilisateur courant et qui ne peut pas demander d'élévation de privilèges, et requireAdministrator qui comme son nom l'indique va demander l'obtention d'un token avec droits élevés. Pour finir, highestAvailable permet de lancer l'application avec le plus haut niveau de droits que l'utilisateur peut avoir. L'attribut uiAccess sert quant à lui à interagir avec l'interface protégée de certaines applications et est souvent utilisé dans le cadre de l'accessibilité. Vous pouvez maintenant réinsérer le manifeste dans l'application

Réinsertion du fichier manifeste

mt.exe -manifest temp.manifest -outputresource:NomAppli.exe;#1

Remarque: il est important de signaler que Windows Vista, possède un système de détection automatique de droits utilisé par les applications n'ayant de pas manifeste, principalement les installeurs. Il utilise différents systèmes pour le faire :
- le nom de l'application contient les mots-clés « install », « setup » ou « update » ;
- les mots-clés placés dans le fichier de version, la ressource StringTable, ou dans les fichiers de ressources liés à l'applicatif
Mais normalement, les installeurs doivent être signés également.

II-C - Test 30 : Vérifier que l'application est « Restart Manager Aware »

Je rassure tout le monde, cela n'a strictement rien à voir avec Jean-Claude Vandamme. Par « Restart Manager Aware », il est question de deux choses bien distinctes. La première n'a strictement rien à voir avec le principe de redémarrage, mais simplement d'extinction de l'application. Par exemple, si jusqu'à maintenant, vous aviez un raccourci clavier pour fermer votre application (alt+q ou ctrl+F4 par exemple), celle-ci devra également se fermer dès qu'elle recevra le raccourci CTRL+C. (Note personnelle : je me demande alors comment gérer le ctrl+c (copier) à l'intérieur de l'application).
Il faut également que l'application ait un listener qui écoute les messages Windows comme WM_QUERYENDSESSION et WM_ENDSESSION qui, tous deux, signalent une demande de fermeture de session. L'application doit alors se fermer d'elle même si possible et surtout engager si nécessaire ses propres méthodes de sauvegarde de données (par exemple, un éditeur de texte sauvegardera les documents ouverts).

Mais il est également question (bien que ce ne soit pas ouvertement précisé dans le livre blanc des guidelines) d'un nouveau mécanisme implémenté sur Windows Vista qui permet à une application de s'enregistrer pour redémarrer automatiquement en cas d'erreur grave. Ainsi, si par erreur, une erreur mémoire arrive, l'application plante et le système se charge de la relancer automatiquement. Il s'agit ici d'utiliser les API RestartManager que j'expliquerai plus en détail à l'aide d'exemples dans une prochaine partie de cet article.

II-D - Test 32 : Vérifier que l'application gère automatiquement les exceptions connues ou attendues

Comme tout développeur sérieux, vous avez sûrement appris qu'il fallait toujours cacher les erreurs fatales pour l'utilisateur, ou du moins afficher un simple message compréhensible, mais gérer l'erreur en interne. Ici, il est question de ne pas simplement « cacher » une erreur fatale qu'on ne sait pas gérer, mais au contraire de la laisser être gérée par un outil dédié. Ainsi, il est conseillé de laisser l'application planter totalement (oui oui) afin que soit appelé Windows Error Reporting qui se chargera d'analyser l'erreur et d'en faire un rapport sur un site dédié (Winqual).

Ce n'est pas très dur à coder puisqu'il suffit de ne pas « catcher » les exceptions que l'on peut pas corriger et pire, il faut si possible, refaire un throw pour que l'exception arrive jusqu'au Windows Error Reporting. Ce système, en partenariat avec Microsoft, permet pour l'éditeur du logiciel d'avoir un suivi des bugs chez les clients et celui-ci s'engage à en corriger une dizaine par mois et 60 % du total. Cela garantit qu'au fur et à mesure des mises à jour, l'application soit de plus en plus stable.

Je vous conseille également les webcasts en français se trouvant à cette adresse.

III - Application recovery sous Vista

Tout bon développeur qui se respecte sait que malgré ses efforts pour créer une application exempte de bugs il pourra toujours apparaître une exception non ou mal gérée ayant pour conséquence un arrêt fatal de l'application. Heureusement, pour le développeur les API de Windows Vista permettent de mettre en application une technique de récupération de données et de redémarrage en cas d'erreur fatale (Application Recovery and Restart (ARR)).
Partant d'un principe très simple qui consiste à sauvegarder les données importantes juste avant que l'application ne soit plus contrôlable, nous pouvons lui ordonner de se relancer d'elle-même puis de récupérer les données qu'elle utilisait juste avant le crash.

Nous allons voir par l'exemple comment mettre en place ce concept de récupération de crash.

III-A - Présentation de l'API

Nous allons pour cela, nous intéresser à l'API ApplicationRecovery qui contient neuf méthodes, mais dont seulement certaines seront utilisées dans notre exemple.
ApplicationRecoveryCallback
Cette méthode est la plus importante, car c'est elle qui permet au moment du crash de sauvegarder les données désirées afin de garder un « état » de l'application juste avant le plantage.

WORD WINAPI ApplicationRecoveryCallback(
PVOID pvParameter
);

ApplicationRecoveryFinished
Comme son nom l'indique, cette méthode permet d'indiquer si la récupération de données est finie ou non.

VOID WINAPI ApplicationRecoveryFinished(
__in          BOOL bSuccess
);

ApplicationRecoveryInProgress
Cette méthode permet de spécifier si l'application est en train de récupérer des données. Le paramètre (out) permet de savoir si l'utilisateur veut annuler la récupération des données ou pas.

HRESULT WINAPI ApplicationRecoveryInProgress(
__out         PBOOL pbCanceled
);

GetApplicationRecoveryCallback
Permet de récupérer un pointeur vers la stratégie de récupération du process spécifié.

HRESULT WINAPI GetApplicationRecoveryCallback(
__in          HANDLE hProcess,
__out         APPLICATION_RECOVERY_CALLBACK* pRecoveryCallback,
__out         PVOID* ppvParameter,
__out         DWORD dwPingInterval,
__out         DWORD dwFlags
);

GetApplicationRestartSettings
Permet d'obtenir des informations sur le processus de récupération de données du process spécifié.

HRESULT WINAPI GetApplicationRestartSettings(
__in          HANDLE hProcess,
__out_opt     PWSTR pwzCommandline,
__in_out      PDWORD pcchSize,
__out_opt     PDWORD pdwFlags
);

RegisterApplicationRecoveryCallback

HRESULT WINAPI RegisterApplicationRecoveryCallback(
__in          APPLICATION_RECOVERY_CALLBACK pRecoveryCallback,
__in_opt      PVOID pvParameter,
__in          DWORD dwPingInterval,
__in          DWORD dwFlags
);

RegisterApplicationRestart
Cette méthode enregistre l'instance de l'application dans une liste de démarrage puis lorsque le système de redémarrage est appelé, elle la relance automatiquement. Il est alors important de noter deux choses. Tout d'abord, il est possible que le crash se déroule au démarrage de l'application et ainsi nous aurions un cycle plantage - redémarrage - plantage - redémarrage infini et capable de monopoliser toutes les ressources de l'ordinateur. Pour se prémunir de cela, la fonction de redémarrage ne relance que les applications ayant été lancées au moins pendant 60 secondes. (Espérez donc que votre application ne plante pas pendant ce laps de temps). La seconde chose est que dans certains cas il se peut que les données sauvegardées soient telles (une chaine de caractère au lieu d'un chiffre par exemple) que leur réutilisation lors de la récupération de données au démarrage entraine irrémédiablement un autre plantage. Il convient alors de désactiver ce redémarrage automatique et d'utiliser la méthode décrite ci-après, UnregisterApplicationRestart

HRESULT WINAPI RegisterApplicationRestart(

__in_opt      PCWSTR pwzCommandline,
__in          DWORD dwFlags
);

UnregisterApplicationRecoveryCallback
Cette méthode permet de désinscrire l'application de la liste des applications pour lesquelles la récupération de données est activée.

HRESULT WINAPI UnregisterApplicationRecoveryCallback(void);

UnregisterApplicationRestart
Comme expliqué juste avant, cette méthode permet de désinscrire l'application de la liste des applications à redémarrer suite à un crash

HRESULT WINAPI UnregisterApplicationRestart(void);

III-B - Implémentation

Comme vous vous en doutez, ces méthodes sont typiques au système (Windows Vista et Windows postérieurs) et sont pour la plupart écrites en C/C++. Il nous faut donc les importer grâce à DllImport

[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern uint RegisterApplicationRestart(string pwsCommandLine, RsFlags dwFlags);

[DllImport("kernel32.dll")]
private static extern uint RegisterApplicationRecoveryCallback(IntPtr pRecoveryCallback, IntPtr pvParameter, int dwPingInterval, int dwFlags);

[DllImport("kernel32.dll")]
private static extern uint ApplicationRecoveryInProgress(out bool pbCancelled);

[DllImport("kernel32.dll")]
public static extern uint ApplicationRecoveryFinished(bool bSuccess);

Pour la méthode RegisterApplicationRestart, il vous faudra également décrire une énumération de flags bien particuliers :

[Flags]
private enum RsFlags
{
NONE = 0,
RESTART_NO_CRASH = 1,
RESTART_NO_HANG = 2,
RESTART_NO_PATCH = 4,
RESTART_NO_REBOOT = 8
}

Maintenant que nous avons les méthodes nécessaires, nous allons pouvoir coder notre application.
Pour avoir un exemple concret, nous allons prendre une application très simple contenant un simple formulaire et on supposera que les informations saisies servent à un calcul qui sera ensuite envoyé dans une base de données.

Il est temps pour moi de bien préciser ce qui est réalisé. Nous allons implémenter notre propre système d'exception pour détecter le crash de l'application pour implémenter nous-mêmes notre système de sauvegarde ainsi qu'enregistrer notre application pour qu'elle soit redémarrée automatiquement. Enfin, au redémarrage, nous récupèrerons les données sauvegardées pour les utiliser de nouveau.

Pour pouvoir tirer profit de cette API, il faut faire quelque chose de peu orthodoxe en désactivant la gestion des exceptions non gérées afin de laisser l'exception se propager jusqu'au système. Sans cela, l'API de Recovery est inutilisable. Pour cela, rajoutez une simple ligne dans la méthode main() de votre application

Désactivation de la gestion des exceptions non gérées

[STAThread]
static void Main()
{
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);
...
}

III-B-1 - Enregistrement de l'application (Restart Manager)

Vous pouvez alors enregistrer votre application dans la liste des applications à redémarrer

Enregistrement de l'application

RegisterApplicationRestart("mon appli", RsFlags.NONE);

Votre application doit être lancée depuis plus de 60 secondes pour pouvoir redémarrer. C'est une protection du système pour éviter le redémarrage en boucle.

III-B-2 - Sauvegarde des données avant le crash (RecoveryCallback)

Nous devons maintenant sauvegarder les données avant que l'application ne soit fermée. Certains logiciels (exemple : Office) font des sauvegardes régulières toutes les minutes, mais il est ici question de ne faire la sauvegarde qu'une fois (au moment du crash).
Déclarons notre méthode qui sauvegarde nos données sans oublier d'y appeler à l'intérieur de celle-ci la méthode ApplicationRecoveryFinished(true), qui indique lorsque toutes les données sont sauvegardées. Si vous l'oubliez, l'application sera gelée et ne pourra même pas être fermée.

private int Sauvegarder(IntPtr pvParameter)
{
// code de sauvegarde des données importantes
ApplicationRecoveryFinished(true);
return 0;
}

Il est à noter que c'est au développeur de gérer la notification de progression de la récupération de données, surtout si celle-ci est longue. Ainsi il sera nécessaire d'appeler plus ou moins régulièrement la méthode ApplicationRecoveryInProgress(). Cette méthode ne donne pas le pourcentage d'avancement, elle indique seulement que la récupération est encore en cours.

III-B-3 - Récupération des données

Et pour le redémarrage alors? Concernant la réutilisation des données sauvegardées, c'est à vous de le gérer entièrement. Lors de mes recherches, j'ai remarqué que certaines personnes le géraient à l'intérieur de la méthode main() tandis que d'autre préféraient gérer cela dans une fenêtre spécifique. Dans notre cas par exemple, nous allons gérer cela directement dans notre formulaire. Le code est ici simplifié au maximum en supposant qu'à part notre cas de redémarrage automatique, l'application n'accepte jamais de paramètres de ligne de commande.

Récupération des données au démarrage

string[] args = Environment.GetCommandLineArgs();
if (args.Length > 1)
{
RecoverData();
}

Ici, nous voyons qu'il y a un paramètre et l'on sait donc qu'il y a eu crash et donc sauvegarde de données, il ne nous reste plus qu'à appeler notre méthode de récupération de données. En temps normal, vu que nous avons spécifié que le redémarrage avait pour paramètre le mot « mon appli » nous aurions pu vérifier que l'argument détecté est bien celui-là. Il est ainsi possible d'avoir différents types de crash, de spécifier différents paramètres lors de l'enregistrement des données et ainsi proposer différentes actions en fonction du paramètre passé. Par exemple, vous utiliseriez le paramètre « mon appli-loading » pour une erreur survenant au démarrage de l'application et le paramètre « mon appli-transfert » pour une erreur survenant au moment d'une fonctionnalité bien spécifique. Il est ainsi possible de cette façon d'agir en fonction du moment où l'application a planté.

III-C - Ressources complémentaires

Source de l'application de démonstration : ici

MSDN : Application recovery and restart

IV - Gérer le bootloader de Vista

J'avais il y a quelques mois, écrit un article indiquant comment utiliser le bootloader de Vista depuis l'invite de commande. Nous allons voir dans cette partie qu'il est possible de contrôler ce bootloader directement depuis le code .Net même s'il est nécessaire de passer par la couche WMI (Windows Management Instrumentation) dont Laurent Dardenne fait une présentation très claire dans cet article. Avant de commencer à travailler sur une technologie puisque c'en est une, il convient de rassembler des documentations relatives pouvant servir à comprendre le fonctionnement de cette technologie et les possibilités qui nous sont offertes. Ainsi donc, je vous conseille fortement de lire, au moins la première partie, du livre blanc sur le boot loader de Windows Vista qui se trouve ici. Même si je réexpliquerai certaines choses dans cet article, vous pourriez y trouver des informations que je n'aurais pas pu placer ou que j'aurais jugées suffisamment pertinentes.

IV-A - Architecture du Boot Loader

Le boot loader a une architecture extrêmement simple. Nous avons un rangement (BCD Store) dans lequel vous placerez vos différents objets (BCD Objects) qui chacun possèdent un certain nombre d'éléments (BCD Elements). Maintenant, cela ne nous dit pas à quoi cela correspond dans la réalité. Pour comparer, lancez une invite de commande avec droits administrateur et tapez « bcdedit ». Le contenu du fichier de boot devrait alors être affiché de manière linéaire, mais suffisamment clair pour que vous puissiez comparer avec le diagramme suivant.

Il y a déjà plusieurs mois, tandis que Vista n'était alors qu'en bêta, et qu'il n'existait pas d'utilitaire tiers comme VistaBootPro, j'étais parti dans l'idée de créer un logiciel graphique de gestion de boot. La documentation de Microsoft n'étant pas encore bien claire à l'époque, j'allais partir dans des manipulations obscures de l'utilitaire bcdedit en pilotant une invite de commande. Ce n'est que bien plus tard et par hasard, alors que je cherchais une simple information WMI, je suis tombé sur cet article qui présente un utilitaire permettant de générer automatiquement des classes .Net afin de les manipuler facilement depuis WMI. Sachant déjà qu'il était possible de faire un grand nombre de choses à l'aide de WMI, je me suis demandé s'il était possible de contrôler le bootloader. Cette question trouva vite une réponse, dans le livre blanc puis des exemples de manipulation du BCD via sur script WMI est donné en exemple. Il ne me restait plus qu'à trouver comment mixer tout cela, pour finalement depuis du code .Net, utiliser WMI facilement afin de gérer mon bootloader.

IV-B - Représentation objet du boot loader

Commençons donc par représenter notre BCD en objet. Nous allons en quelques « clics », créer les classes qui nous permettront de modifier facilement les éléments du BCD. Pour cela, nous allons utiliser l'utilitaire Mgmtclassgen.exe et générer trois classes. Lancez une invite de commande et tapez les trois commandes suivantes (c'est un /L le second paramètre) :

Commandes de création des classes .Net

mgmtclassgen BcdStore /l CS /p c:\BcdStore.cs  /n root\wmi
mgmtclassgen BcdElement /l CS /p c:\BcdElement.cs  /n root\wmi
mgmtclassgen BcdObject /l CS /p c:\BcdObject.cs  /n root\wmi

Vous devriez avoir cela à l'écran de votre invite de commande, ainsi que trois nouveaux fichiers à la racine de C:\. Si par hasard, vous n'arriviez pas à générer les classes, vous les trouverez dans cette archive.

Tapez /l vb pour avoir du VB.Net (/L pour language).

J'ai trouvé le fichier Mgmtclassgen.exe dans le répertoire C:\Program Files\Microsoft SDKs\Windows\V6.0A\Bin.

IV-C - Création de notre application

Créez une interface de votre choix et ajoutez dans les propriétés dans votre solution, une référence vers System.Management afin d'utiliser WMI ainsi qu'une référence vers « Microsoft BCD constants library » afin de connaître les constantes utilisées par les objets du BCD.

Côté interface, nous aurons deux ou trois fonctionnalités :
- lister toutes les entrées du boot ;
- permettre de modifier des paramètres généraux comme le timeout ;
- afficher toutes les infos possibles sur les entrées du boot.
Pour chaque partie, je me limiterai aux champs les plus utilisés, mais en fin d'article, je fournirai un tableau avec différentes constantes qui vous permettront de faire des modifications plus poussées.

Pour avoir une idée générale du fonctionnement de WMI, je vous conseille cette très bonne introduction par Laurent Dardenne et pour l'utilisation de WMI en .Net, l'article de Christian TORREZE

Je vais donc aller très vite sur les explications.
Tout d'abord, nous créons notre connexion au scope

ConnectionOptions connectionOptions = new ConnectionOptions();
connectionOptions.Impersonation = ImpersonationLevel.Impersonate;
connectionOptions.EnablePrivileges = true;
ManagementScope managementScope = new ManagementScope(@"root\WMI", connectionOptions);

Nous allons ensuite créer un « pointeur » (pardon de l'abus de langage) sur notre BCDStore. Comme indiqué dans le livre son identifiant (GUID) est connu et est égal à

const string SYSTEM_STORE_GUID = "{9dea862c-5cdd-4e70-acc1-f32b344d4795}";
...
myStore = new BcdObject(managementScope, SYSTEM_STORE_GUID, "");

Maintenant que nous avons une instance du BCDStore, nous allons pouvoir l'interroger.
Tout d'abord, nous allons pouvoir demander des informations du plus haut niveau comme le timeout

ManagementBaseObject myObj;
myStore.GetElement((uint)BCDConstants.BcdBootMgrElementTypes.BcdBootMgrInteger_Timeout, out myObj);
txtTimeout.Text = myObj.GetPropertyValue("Integer").ToString();

La méthode GetElement nous oblige à passer un paramètre par référence pour y stocker l'objet visé. Puis sur cet objet, nous récupérons sa valeur en spécifiant son type (ici un entier).

Ici, le type de valeur est important, si vous vous trompez, vous aurez une erreur (à cause d'un mauvais cast). Pour savoir le type nécessaire, il suffit de voir dans l'énumération, le nom choisi. ici: BcdBootMgrInteger_Timeout. Dans la partie Liens de ce chapitre, vous trouverez la liste exhaustive des valeurs, leur type et leurs significations

Passons aux choses plus intéressantes, listons les entrées du bootloader. Il faut tout d'abord savoir qu'une entrée est représentée par un ID unique, ID qui sera utilisé lorsque vous voudrez modifier telle ou telle entrée.
Voici le code qui va lister les ID du bootloader

ManagementBaseObject myObj;
bool ok = myStore.GetElement((uint)BCDConstants.BcdBootMgrElementTypes.BcdBootMgrObjectList_DisplayOrder, out myObj);
// on teste pour être sûr que myObj va bien contenir un objet
if (ok)
{
string[] osIdList = (string[])myObj.GetPropertyValue("Ids");
foreach (string osGuid in osIdList)
{
lsvOs.Items.Add(osGuid);
}
}

Ce qui donne l'affichage suivant :

J'aurais pu ne pas avoir besoin de vous montrer une capture pour dire que lister les ID n'est pas très utile à première vue. Néanmoins dans mon application de démonstration, ils vont nous servir. Ainsi, ajoutons un événement lorsque l'utilisateur clique sur l'un des ID.
Puis collons-lui le code suivant :

ManagementBaseObject myObj;
BcdObject myOsObj = new BcdObject(managementScope, lsvOs.SelectedItems[0].Text, "");

foreach (string item in Enum.GetNames(typeof(BCDConstants.BcdLibraryElementTypes)))
{
string txt = contains(item);
if (txt != "")
{
int value = (int)Enum.Parse(typeof(BCDConstants.BcdLibraryElementTypes), item);
string pro = (myOsObj.GetElement((uint)value, out myObj)) ? myObj.GetPropertyValue(txt).ToString() : "";
if (pro != "")
listView1.Items.Add(new ListViewItem(new String[] { item,pro }));
}
}

Ce code va tester toutes les propriétés possibles qu'un objet peut avoir et lorsque la propriété contient quelque chose, lister sa valeur dans un tableau. En effet, un grand nombre de propriétés sont nulles par défaut; ce n'est que lorsqu'on veut leur attribuer une valeur particulière (taille mémoire max, DEP, etc.) qu'elles sont alors non nulles.
Voici le résultat que j'obtiens sur ma machine (j'ai une entrée de mon Vista à moi plus une autre que j'ai créée moi-même et qui se nomme « test »).

Vous noterez également dans le code, la présence d'une méthode contains qui retourne simplement le type nécessaire en fonction de la propriété choisie

private string contains(string pro)
{
if (pro.IndexOf("Integer") > -1) return "Integer";
if (pro.IndexOf("Boolean") > -1) return "Boolean";
if (pro.IndexOf("String") > -1) return "String";
return "";
}

Et pour finir, puisqu’afficher les informations n'est pas toujours suffisant, nous allons voir comment éditer la valeur d'une entrée ou simplement une information du bootloader lui-même.

Pour changer le timeout, une petite ligne suffit :

myStore.SetIntegerElement(ulong.Parse(txtTimeout.Text), (uint)BCDConstants.BcdBootMgrElementTypes.BcdBootMgrInteger_Timeout);

Tandis que pour modifier une entrée (ici sa description), il suffit d'utiliser le code suivant :

ManagementBaseObject myObj;
BcdObject myOsObj = new BcdObject(managementScope, lsvOs.SelectedItems[0].Text, "");
myOsObj.SetStringElement("ma nouvelle description", (uint)BCDConstants.BcdLibraryElementTypes.BcdLibraryString_Description);

IV-D - Conclusion

S'il est clair que modifier le boot loader de Windows Vista n'est pas aussi aisé que la modification d'autres points systèmes, il n'en reste pas que le boot loader peut être entièrement géré par une application avec interface graphique plutôt que l'outil en ligne de commande Bcdedit.exe.

IV-E - Ressources complémentaires

Liste des propriétés existantes pour l'objet BCDStore
Liste des propriétés existantes pour l'objet BCDElement

Sources du programme de démonstration pour ce chapitre : ici

V - Amélioration des interfaces graphiques

V-A - Gérer la transparence dans vos applications

Vous savez que Windows Vista intègre un nouveau système d'affichage appelé Aero Glass qui donne un effet de transparence à vos bordures de fenêtres. Il est possible de l'utiliser pour l'intégrer à vos applications pour avoir une application transparente comme le montre l'image suivante :

Par contre, puisque Florian Casabianca a déjà rédigé un excellent article sur le sujet, je vous encourage à aller le lire ici.

V-B - Ajouter le bouclier sur vos boutons

Si vous avez l'UAC d'activé sur votre système, vous avez déjà sûrement dû remarquer des boutons avec un petit bouclier dessus. Ces boutons étant pour la plupart du temps attachés à des fonctions administratives, vous avez lors d'un clic sur ces derniers, dû recevoir la fenêtre d'acceptation suivante :

V-B-1 - Implémentation

Vu que notre souhait est d'apprendre à développer une application destinée à Vista, il est normal que nous affichions ce même bouclier pour nos tâches nécessitant des droits élevés. « Facile ! », pourriez-vous me dire. Depuis la version 2.0 du framework .Net il est possible de placer une image sur un bouton. Effectivement, cela pourrait marcher, mais est-ce que cela serait du code propre pour autant ?
Second problème, ce bouclier sert à indiquer de plus hauts droits que ceux possédés par l'utilisateur seront nécessaires, mais que se passerait-il si l'utilisateur est déjà un utilisateur avec droits admin. Il convient donc de trouver une solution « plus simple » (à maintenir) et surtout, d'utiliser les outils qui sont fournis avec Windows Vista

Il existe donc une seconde solution : l'utilisation d'une API bien connue : la méthode SendMessage.

[DllImport("user32")]

public static extern UInt32 SendMessage (IntPtr hWnd, UInt32 msg, UInt32 wParam, UInt32 lParam);

Nous déclarons également deux constantes
internal const int BCM_FIRST = 0x1600; // normal
internal const int BCM_SETSHIELD = (BCM_FIRST + 0x000C); // avec bouclier

if (IsAdmin())
{
lblStatut.Text = "Compte admin";
btnAction.FlatStyle = FlatStyle.System;
SendMessage(btnAction.Handle, BCM_SETSHIELD, 0, 0xFFFFFFFF); // permet de mettre un bouclier
}
else
{
lblStatut.Text = "Compte normal";
//SendMessage(btnAction.Handle, BCM_FIRST, 0, 0xFFFFFFFF); // permet de rendre le bouton normal
}

Et voici notre application finie :

V-B-2 - Téléchargement

Vous pouvez télécharger le code source de l'application d'exemple ici (VSTudio 2008).

V-B-3 - Seconde solution

Florian Casabianca m'a signalé qu'il était possible d'afficher ce bouclier avec les icônes du framework 3.5 (et dans le SP1 du Framework 2.0) où il suffit de faire ceci :

int h = button1.ClientSize.Height / 2;
Icon ico = new Icon(SystemIcons.Shield, h, h);
Bitmap bitmap1 = Bitmap.FromHicon(ico.Handle);
button1.Image = bitmap1;
button1.ImageAlign = ContentAlignment.MiddleLeft;

VI - Remerciements

Je tiens à remercier l'équipe .Net pour son aide à l'élaboration de cet article, tout particulièrement Thomas Lebrun et Benjamin Roux. Un remerciement tout spécial à Adrien Artero pour avoir corrigé mon français.