I. Introduction▲
Le but de ce document est de présenter la journalisation, les concepts utilisés ainsi que l'API de programmation de la journalisation dans le monde Microsoft.
Ce document est organisé en plusieurs chapitres.
- Le chapitre 2 présente le rôle de la journalisation de manière générale.
- Le chapitre 3 présente les spécificités introduites et les concepts manipulés par le sous-système EventLog Microsoft dans la fonctionnalité de journalisation.
- Le chapitre 4 présente l'API Microsoft permettant de manipuler la journalisation aussi bien en mode écriture/génération qu'en mode lecture/analyse.
- Le chapitre 5 présente un exemple complet de code permettant de générer des messages EventLog.
- Le chapitre 6 présente un exemple complet de code permettant de récupérer et analyser les messages EventLog.
II. La journalisation▲
II-A. Quelques définitions▲
La journalisation est l'action d'enregistrer dans un journal tout ou partie des évènements qui se produisent dans un système informatique pendant son fonctionnement.
Il est possible de définir deux types de journalisation :
- la journalisation système : ce type de journalisation désigne l'enregistrement chronologique des évènements survenant au niveau des composants du système. Le niveau de cette journalisation peut être paramétré, afin de filtrer les différents évènements selon leur catégorie de gravité ;
- la journalisation applicative : ce type de journalisation désigne l'enregistrement chronologique des opérations de la logique métier pendant le fonctionnement de l'application. Un journal applicatif est lui-même une exigence du métier. Il est donc défini comme une fonctionnalité faisant partie de la logique applicative. Par conséquent, il ne devrait pas être arrêté pendant le fonctionnement de l'application.
II-B. Pourquoi journaliser ?▲
Le rôle et les buts de la journalisation sont multiples et souvent liés ou imposés par des contraintes de sécurité.
- Légal : Dans ce cadre, le but de la journalisation est de fournir un élément de preuve juridique. Par exemple, les fournisseurs d'accès à Internet doivent conserver et archiver les journaux concernant les connexions de leurs utilisateurs afin de pouvoir répondre à une requête judiciaire.
- Statistique : La journalisation permet aussi de fournir des informations statistiques concernant l'usage. Un administrateur de serveur WWW par exemple peut légitimement se demander « Quelle est la page WWW de mon site qui est la plus visitée ? », ou encore, « D'où proviennent majoritairement les requêtes ? »
- Analyse : Enfin, la journalisation permet d'analyser un problème et de trouver les enchaînements qui ont provoqué le problème. « Que s'est-il passé sur le réseau à 11H27 juste avant que le serveur DNS ne s'arrête ? », ou encore, « Quelle requête HTTP a bien pu générer un plantage de mon serveur WWW ? »
Comme on peut le voir, les buts de la journalisation sont multiples. Suivant les buts recherchés, le profil des personnes qui liront les journaux sera différent.
II-C. Contraintes liées à la journalisation▲
Certaines contraintes doivent (ou devraient) s'appliquer à la journalisation.
- Intégrité : L'intégrité de la journalisation ne doit pas pouvoir être remise en cause. Ce qui est archivé dans les journaux doit être ce que l'application ou le système a journalisé. Il ne devrait pas être possible de modifier un élément de la journalisation a posteriori.
- Complétude : L'archivage de la journalisation doit impérativement comporter tous les évènements reçus. Si pour une raison quelconque (surcharge de la machine par exemple), un évènement venait à être perdu, il y aurait incomplétude de la journalisation. Un évènement perdu ou absent peut entrainer une mauvaise analyse d'un problème ou encore la non-prise en compte de la journalisation en tant que preuve juridique valide. Comme il n'est pas toujours possible d'avoir une complétude forte, il faut au moins que le système mis en place puisse détecter la perte ou l'altération d'un ou plusieurs évènements, on parlera dans ce cas-là de complétude faible.
- Cohérence : Afin de faciliter une éventuelle analyse, il est important que les évènements journalisés soient cohérents. Cela veut dire par exemple que la date système de la machine doit être synchronisée afin que la séquence des différents évènements conduisant au problème puisse être ordonnée.
- Utilité : Un évènement de journalisation doit contenir de l'information utile et riche. En effet, Erreur mémoire lors de l'allocation d'un buffer de 112 octets n'est pas très utile pour comprendre un éventuel problème s'il n'est pas couplé avec un message de plus haut niveau du genre : Erreur système lors du traitement de la connexion de l'utilisateur 'Administrateur'.
- Confidentialité : Les informations contenues dans les évènements présentent un certain caractère de confidentialité. Il importe donc que les moyens utilisés pour transporter et archiver ces évènements garantissent cette confidentialité.
II-D. Les différentes méthodes de journalisation▲
Il existe plusieurs méthodes permettant de transporter et d'archiver les différents évènements de journalisation. Le choix et l'usage de ces méthodes sont fonction de l'environnement et de l'application.
- Fichier plat : C'est probablement la plus simple des méthodes. Les évènements sont stockés dans un fichier en général au format texte. La gestion de ce fichier (droits du fichier, format d'écriture, durée de rétention …) est entièrement laissée à l'application ou même au système d'exploitation.
- Syslog : C'est la méthode de journalisation historique utilisée dans le monde Unix. Cette méthode comprend un logiciel de collecte et d'archivage (c'est le démon syslogd par exemple), un protocole réseau (défini par le RFC 3164) et une API de programmation permettant de s'interfacer avec le protocole Syslog (voir openlog(), syslog() et closelog()). Plus d'informations sur le protocole Syslog ici.
- Base de données : Les évènements de journalisation sont stockés dans une base de données.
- Le sous-système EventLog : Ce système de journalisation est utilisé dans le monde Microsoft.
Ce document a pour but de présenter la journalisation dans le monde Microsoft et le sous-système EventLog.
III. Le sous-système EventLog▲
La journalisation dans le monde Microsoft est réalisée par le sous-système EventLog. Ce sous-système est implémenté par le service Windows EventLog.
Jusqu'à Windows XP, il n'était pas possible d'arrêter ce sous-système. Toutefois, il est possible de modifier le Type de démarrage du service en mettant « Désactivé » et de redémarrer la machine. Le service ne sera alors pas lancé.
Sous Windows Seven (et peut-être Windows Vista), il semble qu'il soit possible d'arrêter le service bien que cela soit fortement déconseillé.
Microsoft propose deux API (Application Programming Interface) pour s'interfacer avec le sous-système EventLog.
- L'API Event Logging. Cette API est disponible depuis Windows 3.1.
- L'API Windows Event Log. Cette API est disponible depuis Windows Vista et Windows Server 2008. Cette API doit, à terme, remplacer l'API Event Logging.
Une difficulté introduite par ces deux API est que leurs noms sont très proches et qu'il est très facile de se tromper.
Dans la suite de ce document, nous ne parlerons que de l'API Event Logging. La nouvelle API Windows Event Log introduite à partir de Windows Vista fera l'objet d'une autre présentation ou d'une modification de ce document.
III-A. Architecture simplifiée▲
Le schéma suivant montre l'architecture simplifiée du système :
Les différentes sources ou applications envoient leurs messages de journalisation EventLog au sous-système. Le sous-système EventLog regarde dans la base de registre le journal associé à la source et enregistre l'évènement dans ce journal.
Par défaut, il existe trois journaux dans lesquels les messages EventLog sont enregistrés. Ces journaux sont :
- Application : il contient les messages EventLog générés par les applications et les services. Par exemple, un journal pourrait enregistrer un message d'erreur. C'est au développeur de l'application de décider les évènements à journaliser. L'emplacement par défaut du fichier d'archivage est %SystemRoot%\System32\Config\AppEvent.evt ;
- Sécurité : il contient les messages EventLog tels que les échecs et les réussites des connexions aussi bien que les évènements relatifs à l'usage des ressources comme l'ouverture, la création ou la destruction de fichiers ou autres objets. Un administrateur peut activer l'audit pour enregistrer ces évènements. L'emplacement par défaut du fichier d'archivage est %SystemRoot%\System32\Config\SecEvent.evt ;
- Système : il contient les messages EventLog générés par les composants du système, comme un problème lors du chargement d'un driver au démarrage. Les évènements journalisés par les composants du système sont prédéterminés. L'emplacement par défaut du fichier d'archivage %SystemRoot%\System32\Config\SysEvent.evt.
D'autres journaux peuvent être créés et utilisés si nécessaire (depuis Windows 2000). Par exemple, depuis Internet Explorer version 7, il existe le journal « Internet Explorer ». Un autre exemple : Windows 2003 Server définit les journaux nommés « Directory Service », « DNS Server » et « File Replication Service ».
III-B. Le programme « Event Viewer »▲
Le programme utilisé pour visualiser les messages EventLog est le programme « Event Viewer ». Ce programme est lancé par la commande eventvwr.exe et se trouve normalement dans le répertoire C:\Windows\system32.
Le programme « Event Viewer » permet de visualiser les différents journaux ainsi que les différents messages archivés dans ces journaux :
Ce programme est aussi accessible depuis le panneau de configuration dans les outils d'administration, c'est l'observateur d'évènements. La présence de ce programme à cet endroit montre bien l'importance de la journalisation.
III-C. Un message EventLog▲
Un message EventLog contient les informations suivantes.
- Le type du message.
- La source du message.
- L'identifiant du message.
- La catégorie du message.
- Le message lui-même.
- Des informations dynamiques facultatives.
- Un buffer de données facultatif.
- La date et l'heure.
- D'autres informations.
III-C-1. Le type du message▲
Il existe six types de messages EventLog. Le programme « Event Viewer » utilise ces types pour déterminer l'icône à utiliser pour afficher la liste des messages EventLog.
- Success : Il s'agit d'un message EventLog qui indique le succès d'une opération.
- Error : Il s'agit d'un message EventLog qui indique un problème significatif comme la perte de données ou de fonctionnalités. Par exemple, si un service ne parvient pas à démarrer, un message de type Error est généré.
- Warning : Il s'agit d'un message EventLog pas forcément significatif, mais qui pourrait indiquer un possible futur problème. Par exemple, quand l'espace disque devient faible, un message de type Warning est généré. En règle générale, si une application peut gérer ce type d'évènements sans perdre de fonctionnalités ou de données, l'évènement peut être classifié comme un EventLog de type Warning.
- Information : Il s'agit d'un message EventLog décrivant la réussite d'une opération pour une application, un service ou un driver. Par exemple, quand un driver réseau se charge avec succès, il est approprié de journaliser un message de type Information. À noter qu'il est généralement inapproprié pour une application utilisateur de générer un tel message à chaque lancement.
- Audit Success : Il s'agit d'un message EventLog qui enregistre le succès d'un accès à un objet dont la sécurité est auditée. Par exemple, la connexion avec succès d'un utilisateur sur le système est journalisée comme un évènement de type Audit Success.
- Audit Failure : Il s'agit d'un message EventLog qui enregistre l'échec d'une tentative d'accès à un objet dont la sécurité est auditée. Par exemple, si un utilisateur tente un accès à un disque réseau et échoue, cette tentative est journalisée comme un évènement de type Audit Failure.
Remarque : le programme « Event Viewer » ne fait pas de différences pour les icônes des types de messages Success, Audit success et Audit failure, il utilise l'icône des messages de type Information.
III-C-2. La source du message▲
La source du message EventLog est le nom du programme ou du service qui a généré cet évènement. C'est souvent le nom de l'application ou le nom d'un composant de cette application si celle-ci est vraiment importante. Une application ou un service peuvent utiliser leur propre nom. Cela peut être MSSQLSERVER ou Tcpip par exemple.
III-C-3. L'identifiant du message▲
L'identifiant du message EventLog est un nombre sur 32 bits qui identifie le message EventLog. Le programme « Event Viewer » affiche cet identifiant.
L'identifiant du message EventLog est un nombre sur 32 bits construit de la manière suivante.
- La partie code : c'est un nombre sur 16 bits qui identifie le message.
- La partie facility : c'est un nombre sur 12 bits. Il peut être utilisé pour identifier le sous-système ou le contexte qui a généré le message EventLog. Cette notion de fonctionnalité est totalement différente du concept de fonctionnalité utilisé par le protocole Syslog.
- La partie reserved bit : c'est un nombre sur 1 bit. Il est réservé pour un usage futur.
- La partie customer bit : c'est un nombre sur 1 bit. Il indique si le message EventLog est généré par le système ou bien par une autre application.
- La partie severity : c'est un nombre sur 2 bits. Il donne le niveau de sévérité du message EventLog. Cette notion de sévérité est totalement différente du concept de sévérité utilisé par le protocole Syslog.
III-C-4. La catégorie du message▲
La catégorie du message EventLog permet d'organiser et d'ajouter de l'information au message EventLog. Cette catégorie est une chaîne de caractères et chaque source de messages EventLog peut définir ses propres catégories. La signification de cette catégorie dépend de la source du message. Le programme « Event Viewer » montre la catégorie du message EventLog.
III-C-5. Le message EventLog▲
La signification et le contenu du message EventLog dépend de la source du message EventLog. Le programme « Event Viewer » montre le texte du message EventLog.
III-C-6. Les informations dynamiques▲
Le programme utilisateur peut fournir, lors de la génération du message EventLog, des paramètres dynamiques sous forme d'un tableau de chaînes de caractères. Ces chaînes de caractères sont ensuite insérées dans le message EventLog lors de l'affichage de celui-ci. Le contenu et l'ordre de ces paramètres dynamiques sont à la libre appréciation du développeur de l'application. Ce contenu doit toutefois être en cohérence avec le message à afficher.
Ces informations dynamiques sont optionnelles.
III-C-7. Le buffer de données▲
Le programme utilisateur peut fournir, lors de la génération du message EventLog, un buffer de données applicatives. Le contenu de ce buffer sera ensuite inséré sous une forme hexa décimale dans le message EventLog lors de l'affichage de celui-ci. Le contenu de ce buffer est à la libre appréciation du développeur de l'application. Ce contenu peut fournir une aide au diagnostic par exemple.
Ce buffer de données est optionnel.
III-C-8. La date et l'heure▲
Le message EventLog contient deux informations de date et d'heure.
- La date de génération : C'est la date à laquelle l'évènement a été transmis au sous-système EventLog. Cette date est au format UTC (Universal Coordinated Time).
- La date d'écriture : C'est la date à laquelle l'évènement a été reçu et traité par le sous-système EventLog et enregistré dans le journal des messages EventLog. Cette date est au format UTC (Universal Coordinated Time).
III-C-9. Les autres informations▲
D'autres informations moins importantes peuvent aussi être récupérées dans un message EventLog. Ainsi, il est possible de récupérer.
- L'utilisateur : C'est le nom de l'utilisateur qui a généré le message EventLog. Cette information peut ne pas être disponible si elle n'a pas de sens dans le contexte courant d'exécution.
- Le nom de la machine : C'est le nom de la machine sur laquelle le message EventLog a été généré.
- Le numéro d'enregistrement : Cette valeur est le numéro de l'enregistrement dans le journal des messages EventLog. Il peut être utilisé pour lire ou rechercher un enregistrement spécifique.
- La taille de l'enregistrement : C'est la taille de l'enregistrement dans le journal des messages EventLog. Ce n'est pas forcément la longueur du message lui-même.
III-D. La base de registre▲
Comme souvent pour les systèmes Microsoft, toutes les informations de configuration du sous-système EventLog se situent dans la base de registre.
III-D-1. Clé de registre racine▲
La clé de registre racine de tout ce qui concerne le sous-système EventLog est HKLM\SYSTEM\CurrentControlSet\Services\EventLog.
III-D-2. Les différents journaux▲
Le sous-système EventLog introduit la notion de journal d'enregistrement. La documentation MSDN utilise le terme anglais localisation. Dans un but de clarté, j'utiliserai dans ce document le terme français de journal ou journal d'enregistrement même si la traduction n'est pas parfaite, je trouve que ce terme a plus de sens.
Un journal d'enregistrement est un fichier géré par le sous-système EventLog dans lequel sont stockés les différents messages EventLog générés par les sources de messages qui appartiennent à ce journal.
Les noms des différents journaux gérés par le sous-système EventLog figurent dans la base de registre sous la racine du sous-système EventLog, par exemple :
- HKLM\SYSTEM\CurrentControlSet\Services\EventLog\Application\ ;
- HKLM\SYSTEM\CurrentControlSet\Services\EventLog\Security\ ;
- …
Chacun des journaux EventLog définis dans la base de registre contient les informations de configuration suivantes :
Clé de registre |
Type |
Description |
---|---|---|
CustomSD |
REG_SZ |
Cette valeur permet de définir l'accès au journal des évènements. La syntaxe utilisée SDDL (Security Descriptor Definition Language). Les droits suivants peuvent être accordés : |
DisplayNameFile |
REG_EXPAND_SZ |
Cette clé de Registre identifie le fichier contenant la ressource du nom localisé du journal EventLog. Ce fichier est utilisé par le programme « Event Viewer » pour afficher les noms localisés des différents journaux EventLog. |
DisplayNameID |
REG_DWORD |
Cette clé de registre stocke le numéro de la ressource du nom localisé du journal EventLog. |
File |
REG_SZ ou REG_EXPAND_SZ |
Cette clé de registre spécifie le nom complètement qualifié du fichier EventLog dans lequel les différents messages EventLog de ce journal sont sauvegardés. |
MaxSize |
REG_DWORD |
Cette clé de registre spécifie la taille maximale du journal EventLog. Cette valeur correspond à l'option Taille maximale du journal sur la page Propriétés affichée par le programme « Event Viewer ». |
PrimaryModule |
REG_SZ |
Cette clé de registre stocke le nom de la source contenant les valeurs par défaut pour les sources qui ne fournissent pas toutes les informations de configuration nécessaires (absence de clé ou mauvaise valeur). |
Retention |
REG_DWORD |
Cette clé de registre spécifie la durée de vie, exprimée en secondes, pendant laquelle les messages EventLog sont protégés d'un éventuel écrasement. Lorsque cette durée de vie est atteinte, le message peut être effacé. |
Sources |
REG_MULTI_SZ |
Cette clé de Registre spécifie les applications, services ou des groupes d'applications sources qui peuvent générer des messages EventLog dans ce journal. Chaque source répertoriée par cette entrée est aussi représentée par une sous-clé de registre. |
AutoBackupLogFiles |
REG_DWORD |
L'utilisation de cette clé de registre indique au sous-système EventLog de sauvegarder automatiquement les journaux EventLog avant de les effacer. Sur les machines avec l'option CrashOnAuditFail activée, le système continue à enregistrer des évènements (au lieu de s'arrêter à cause d'une défaillance de l'audit) si le journal EventLog peut être sauvegardé automatiquement. |
RestrictGuestAccess |
REG_DWORD |
Les messages EventLog pouvant contenir des informations sensibles, cette clé de registre permet de restreindre l'accès des journaux aux administrateurs et aux comptes système. |
Isolation |
REG_SZ |
Cette clé de registre définit les autorisations d'accès par défaut pour le journal. |
III-D-3. Les différentes sources▲
Toutes les sources EventLog pour un journal particulier sont listées dans la base de registre sous la clé HKLM\SYSTEM\CurrentControlSet\Services\EventLog\<journal>\<source>, par exemple :
- HKLM\SYSTEM\CurrentControlSet\Services\EventLog\Application\.NET Runtime\ ;
- HKLM\SYSTEM\CurrentControlSet\Services\EventLog\Security\SC Manager\ ;
- HKLM\SYSTEM\CurrentControlSet\Services\EventLog\System\acpi\ ;
- …
Chacune des sources EventLog définie dans la base de registre contient les informations de configuration suivantes :
Clé de registre |
Type |
Description |
---|---|---|
CategoryCount |
REG_DWORD |
Nombre de catégories utilisées par cette source de messages EventLog. |
CategoryMessageFile |
REG_SZ ou REG_EXPAND_SZ |
Chemin d'accès au fichier des différentes catégories EventLog définies pour cette source. Ce fichier contient les catégories localisées pour cette source. Il est possible d'indiquer plusieurs fichiers, séparés par des points virgules (« ; »). |
EventMessageFile |
REG_SZ ou REG_EXPAND_SZ |
Chemin d'accès au fichier des différents messages EventLog définis pour cette source. Ce fichier contient les messages localisés pour cette source. Il est possible d'indiquer plusieurs fichiers, séparés par des points virgules (« ; »). |
ParameterMessageFile |
REG_SZ ou REG_EXPAND_SZ |
Chemin d'accès au fichier des différents paramètres EventLog définis pour cette source. Ce fichier contient les paramètres statiques de cette source exprimés dans un langage neutre. Ces paramètres seront ajoutés dans le message EventLog. Il est possible d'indiquer plusieurs fichiers, séparés par des points virgules (« ; »). |
TypesSupported |
REG_DWORD |
Cette valeur spécifie le masque des types de messages EventLog supportés par cette source. Ce masque est construit (par un ou logique) avec les valeurs suivantes : |
ProviderGuid |
REG_SZ ou REG_EXPAND_SZ |
Cette clé est souvent renseignée par certaines sources et contient comme valeur un Guid. Le rôle exact de cette clé est inconnu. Si vous avez plus d'informations au sujet de cette clé, n'hésitez pas, 19 commentaires, ce document sera mis à jour. |
publisherGuid |
REG_EXPAND_SZ |
Cette clé est parfois renseignée par certaines sources. Le rôle exact de cette clé est inconnu. Si vous avez plus d'informations au sujet de cette clé, n'hésitez pas, 19 commentaires, ce document sera mis à jour. |
LoggingLevel |
REG_DWORD |
Cette clé est parfois renseignée par certaines sources. Le rôle exact de cette clé est inconnu. Si vous avez plus d'informations au sujet de cette clé, n'hésitez pas, 19 commentaires, ce document sera mis à jour. |
Version |
REG_DWORD |
Cette clé est parfois renseignée par certaines sources. Le rôle exact de cette clé est inconnu. Si vous avez plus d'informations au sujet de cette clé, n'hésitez pas, 19 commentaires, ce document sera mis à jour. |
III-E. Récapitulatif de l'organisation dans la base de registre▲
Les différentes clés de registres peuvent être vues de la manière suivante :
Répertoires dans la base de registre |
Clés dans la base de registre |
||
---|---|---|---|
HKLM\SYSTEM\CurrentControlSet\Services\EventLog |
|||
Application |
CustomSD, DisplayNameFile, DisplayNameID, File, MaxSize, PrimaryModule, Retention, Sources, AutoBackupLogFiles, RestrictGuestAccess, Isolation |
||
.NET Runtime |
CategoryCount, CategoryMessageFile, EventMessageFile, ParameterMessageFile, TypesSupported |
||
.NET Runtime Optimization Service |
CategoryCount, CategoryMessageFile, EventMessageFile, ParameterMessageFile, TypesSupported |
||
<autre source> |
CategoryCount, CategoryMessageFile, EventMessageFile, ParameterMessageFile, TypesSupported |
||
Security |
CustomSD, DisplayNameFile, DisplayNameID, File, MaxSize, PrimaryModule, Retention, Sources, AutoBackupLogFiles, RestrictGuestAccess, Isolation |
||
DSA |
CategoryCount, CategoryMessageFile, EventMessageFile, ParameterMessageFile, TypesSupported |
||
LSA |
CategoryCount, CategoryMessageFile, EventMessageFile, ParameterMessageFile, TypesSupported |
||
<autre source> |
CategoryCount, CategoryMessageFile, EventMessageFile, ParameterMessageFile, TypesSupported |
||
System |
CustomSD, DisplayNameFile, DisplayNameID, File, MaxSize, PrimaryModule, Retention, Sources, AutoBackupLogFiles, RestrictGuestAccess, Isolation |
||
ACPI |
CategoryCount, CategoryMessageFile, EventMessageFile, ParameterMessageFile, TypesSupported |
||
adp94xx |
CategoryCount, CategoryMessageFile, EventMessageFile, ParameterMessageFile, TypesSupported |
||
<autre source> |
CategoryCount, CategoryMessageFile, EventMessageFile, ParameterMessageFile, TypesSupported |
||
<autre journal> |
CustomSD, DisplayNameFile, DisplayNameID, File, MaxSize, PrimaryModule, Retention, Sources, AutoBackupLogFiles, RestrictGuestAccess, Isolation |
||
<autre source> |
CategoryCount, CategoryMessageFile, EventMessageFile, ParameterMessageFile, TypesSupported |
III-F. Reconstitution du texte d'un message EventLog▲
La reconstitution du message textuel (par le programme « Event Viewer » par exemple) suit un processus rigoureux, mais assez « tortueux ». Cette reconstitution est partiellement décrite dans la documentation du MSDN. Parfois, certains points de ce processus ont été déduits à partir du fonctionnement du programme « Event Viewer ». Si vous trouvez des informations plus complètes ou plus formelles, n'hésitez pas, 19 commentaires, ce document sera mis à jour.
Lorsqu'une application génère un message EventLog, elle fournit entre autres les informations suivantes.
- L'identifiant de la catégorie du message.
- L'identifiant du format de message.
- Un tableau optionnel de paramètres dynamiques.
- Un buffer optionnel de données complémentaires.
Le schéma suivant décrit le cheminement pour recréer le texte localisé complet du message EventLog :
III-F-1. Le nom de la catégorie▲
C'est l'élément le plus facile à récupérer et la méthode est la suivante.
- Il suffit de lire la clé de registre CategoryMessageFile de la source de données concernée. Cette clé indique le nom du fichier qui contient la liste des textes localisés des différents noms de catégories.
-
Ensuite, on récupère le texte de la ressource à l'aide de la fonction FormatMessage(). La ressource à utiliser est choisie par la fonction en utilisant le langage du programme ou du système. L'ordre de sélection du langage est le suivant :
- Le Langage neutral.
- Le langage spécifié pour le thread courant.
- Le langage spécifié pour l'utilisateur.
- Le langage spécifié pour le système.
- Le langage US English.
III-F-2. Le format du message▲
Le texte du message se fait en deux étapes :
- la première étape consiste à récupérer le format du message ;
- la seconde étape consiste à remplacer les codes d'insertion par leurs valeurs.
III-F-3. La récupération du format du message▲
La récupération du format du message suit le même procédé que celui de la récupération du nom de la catégorie :
- Il suffit de lire la clé de registre EventMessageFile de la source de données concernée. Cette clé indique le nom du fichier qui contient la liste des textes localisés des différents formats de messages.
-
Ensuite, on récupère le texte de la ressource à l'aide de la fonction FormatMessage(). La ressource à utiliser est choisie par la fonction en utilisant le langage du programme ou du système. L'ordre de sélection du langage est le suivant :
- Le Langage neutral.
- Le langage spécifié pour le thread courant.
- Le langage spécifié pour l'utilisateur.
- Le langage spécifié pour le système.
- Le langage US English.
III-F-4. Le remplacement des codes d'insertion▲
Le format du message peut contenir des codes d'insertion. Cette dernière étape consiste à remplacer ces codes d'insertion par leurs vraies valeurs. Les différents codes d'insertion sont les suivants :
Code d'insertion |
Signification |
---|---|
%n |
Insertion du paramètre dynamique n-1 fourni par le programme lors de l'appel à la fonction ReportEvent(). |
%%n |
Insertion du paramètre statique n. La lecture du paramètre statique se fait de la même manière que pour les noms de catégories, mais en utilisant le nom du fichier indiqué par la clé de registre ParameterMessageFile. |
%{S…} |
Security identifier (SID). Pas d'information trouvée au sujet du fonctionnement de ce code d'insertion. |
%{…} |
Globally unique identifier (GUID). Pas d'information trouvée au sujet du fonctionnement de ce code d'insertion. |
Le remplacement des codes d'insertion doit se faire de manière récursive. Dès qu'un remplacement est effectué, la chaîne de caractères obtenue doit à nouveau être analysée depuis le début pour rechercher une nouvelle substitution. Toutefois, afin d'éviter une récursivité infinie, le nombre de substitutions doit être limité à 100.
IV. L'API de programmation Event Logging▲
Rappel : Microsoft propose 2 API pour s'interfacer avec le sous-système EventLog :
- L'API Event Logging. Cette API est disponible depuis Windows 3.1.
- L'API Windows Event Log. Cette API est disponible depuis Windows Vista et Windows Server 2008. Cette API doit, à terme, remplacer l'API Event Logging.
Dans ce document, nous n'abordons que l'API Event Logging.
IV-A. L'API Event Logging▲
Les fonctions de l'API Event Logging disponibles pour accéder au sous-système EventLog sont les suivantes :
Fonction |
Description |
---|---|
Archive le journal spécifié dans un fichier de sauvegarde. |
|
Vide le journal spécifié avec un archivage éventuel et préalable dans un fichier de sauvegarde. |
|
Ferme le descripteur en lecture sur un journal EventLog préalablement obtenu par OpenEventLog() ou OpenBackupEventLog(). |
|
Ferme le descripteur en écriture sur un journal EventLog préalablement obtenu par RegisterEventSource(). |
|
Récupère les caractéristiques d'un journal en utilisant un descripteur obtenu par OpenEventLog() ou RegisterEventSource(). |
|
Récupère le nombre d'enregistrements dans le journal d'un journal en utilisant un descripteur obtenu par OpenEventLog() ou OpenBackupEventLog(). |
|
Récupère le numéro du plus vieil enregistrement du journal en utilisant un descripteur obtenu par OpenEventLog() ou OpenBackupEventLog(). |
|
Permet à une application de recevoir une notification quand un nouvel enregistrement EventLog est ajouté dans le journal en utilisant un descripteur obtenu par OpenEventLog(). |
|
Ouvre le journal archivé par BackupEventLog(). |
|
Ouvre en lecture le journal spécifié. |
|
Lit le nombre d'enregistrements dans le journal en utilisant un descripteur obtenu par OpenEventLog(). |
|
Ouvre en écriture le journal dont la source est spécifiée. |
|
Ajoute un nouvel enregistrement EventLog dans le journal en utilisant un descripteur obtenu par RegisterEventSource(). |
Toutes ces fonctions sont déclarées dans le fichier include windows.h (en fait, dans winbase.h). Ces fonctions sont implémentées dans la bibliothèque Advapi32.dll (et .lib). Toutes ces fonctions existent en version Unicode (xxxW) et Ansi (xxxA).
IV-B. La fonction ReportEvent()▲
La fonction ReportEvent() attend entre autres les paramètres suivants :
-
Le type : Il s'agit du code de gravité du message. Il n'y a aucun lien entre le message et son code de gravité. Ce sont le développeur et le projet, qui en fonction du contexte, déterminent le code de gravité. Les valeurs autorisées sont :
- EVENTLOG_SUCCESS (0x00)
- EVENTLOG_AUDIT_FAILURE (0x10)
- EVENTLOG_AUDIT_SUCCESS (0x08)
- EVENTLOG_ERROR_TYPE (0x01)
- EVENTLOG_INFORMATION_TYPE (0x04)
- EVENTLOG_WARNING_TYPE (0x02)
- La catégorie : Il s'agit d'un identifiant (sous forme de nombre) qui permet de qualifier le message en l'accompagnant d'un nom de sous-système par exemple. Ce nombre est l'identifiant d'une ressource chaîne de caractères qui est affichée par le programme « Event Viewer ». Il n'y a aucun lien entre le message et la catégorie, c'est-à-dire qu'un message peut être envoyé une première fois avec une catégorie et une seconde fois avec une autre catégorie sans que cela ne pose un problème. Cette catégorie de messages doit être unique pour une source donnée de messages EventLog. Les identifiants des catégories de messages doivent être séquentiels et commencer à 1. Ce sont le développeur et le projet, qui en fonction du contexte, déterminent la catégorie à utiliser.
-
Le message : Il s'agit d'un identifiant (sous forme de nombre) d'une ressource chaîne de caractères qui représente le format d'affichage du message d'erreur. Cet identifiant de message doit être unique pour une source de messages Eventlog donnée. Ce sont le développeur et le projet, qui en fonction du contexte, déterminent le message à utiliser. Cet identifiant de message est constitué de 4 parties :
- La racine de l'identifiant, c'est un nombre sur 16 bits.
- La facilité du message, c'est un nombre sur 12 bits. Les facilités 0 à 255 sont normalement réservées par Microsoft et définies dans les fichiers include du SDK Microsoft, les facilités 256 à 4095 sont à la libre disposition des développeurs.
- Le bit customer. Ce bit indique s'il s'agit d'un message système ou utilisateur.
- La sévérité du message, c'est un nombre sur 2 bits qui indique la gravité du message. Les valeurs autorisées sont Success (0), Informational (1), Warning (2) et Error (3).
- L'identifiant de sécurité : Il s'agit des informations permettant de connaitre le nom de l'utilisateur qui a généré le message. Cet identifiant peut être absent si cela n'a pas de sens dans le contexte courant (dans le cas d'un service par exemple).
- Les paramètres applicatifs : Il s'agit d'un tableau optionnel de chaînes de caractères fourni par l'application permettant de compléter le message en fonction du contexte. Ces paramètres applicatifs sont déterminés par le développeur et le projet en fonction du contexte du message. Ces paramètres applicatifs doivent être mis en cohérence par rapport à l'information qu'attend le format du message (il est incohérent de passer un nom d'utilisateur si le message doit afficher le chemin de fichier de configuration par exemple).
- Un buffer de données : Il s'agit d'un tableau optionnel d'octets permettant d'ajouter de l'information au message (dump d'une structure par exemple).
IV-C. Le fichier de messages▲
IV-C-1. Format du fichier▲
Les chaînes de caractères des catégories, les formats de messages et les paramètres statiques sont des ressources situées dans un ou plusieurs exécutables ou DLL. Ces fichiers sont identifiés dans la base de registre pour une source de messages EventLog donnée.
Ces fichiers de messages sont compilés et transformés en ressources par le programme mc.exe fourni avec le SDK. Ces ressources sont des chaînes de caractères localisées ou non d'un type spécial. Elles ne peuvent pas être générées, éditées ou modifiées par l'éditeur de ressources Visual Studio. Il est obligatoire de passer par un fichier texte source Unicode ou non (par habitude, l'extension de ce fichier est .mc) et le compilateur mc.exe.
La syntaxe du header d'un fichier est la suivante :
- Commentaire : Il s'agit d'un commentaire qui sera reporté dans le fichier include généré.
;// This is a single-line comment.
;/* This is a block comment.
; It spans multiple lines.
;*/
- MessageIdTypedef : Il s'agit du type vers lequel seront castés des identifiants de messages générés. Ce type ne peut être composé que d'un seul mot, les parenthèses sont interdites ainsi que les types comprenant plusieurs mots (unsigned long int par exemple). Cette définition peut se trouver à plusieurs endroits dans le fichier de définition, c'est la dernière valeur fixée qui est utilisée pour la génération du prochain message.
MessageIdTypedef = DWORD
- SeverityNames : Il s'agit de l'ensemble des valeurs qui permettent de définir la partie sévérité (sur 2 bits) de l'identifiant d'un message. Les valeurs par défaut suivantes sont définies : SeverityNames=(Success=0x0 Informational=0x1 Warning=0x2 Error=0x3)
SeverityNames =
(
Sev_Success = 0x0
Sev_Information = 0x1
Sev_Warning = 0x2
Sev_Error = 0x3
)
- FacilityNames : Il s'agit de l'ensemble des valeurs qui permettent de définir la partie fonctionnalité (sur 12 bits) de l'identifiant d'un message. Les valeurs par défaut suivantes sont définies : FacilityNames=(System=0x0FF Application=0xFFF). Les valeurs de 0 à 255 sont réservées au système, les valeurs 256 à 4095 sont disponibles.
FacilityNames =
(
FACILITY_NULL = 0
FACILITY_RPC = 1
FACILITY_DISPATCH = 2
FACILITY_STORAGE = 3
FACILITY_ITF = 4
)
- LanguageNames : Il s'agit de l'ensemble définissant les langages des différents messages. Ce mot clé permet de définir le nom logique du langage (LANG_FRA par exemple), l'identifiant du langage (0x40c pour le français par exemple) et le nom arbitraire d'un fichier intermédiaire (Messages_0x040c par exemple). La liste des identifiants de langages à utiliser se trouve ici.
LanguageNames =
(
LANGUE_FRA = 0x040c:Messages_0x040c
LANGUE_ENG = 0x0809:Messages_0x0809
)
- Outputbase : Il s'agit de la base utilisée pour générer les identifiants de messages dans le fichier include. Les valeurs autorisées sont 10 et 16. La valeur par défaut est 16. Cette définition peut se trouver à plusieurs endroits dans le fichier de définition, c'est la dernière valeur fixée qui est utilisée pour la génération du prochain message.
Outputbase = 10
Chacun des messages à définir est constitué de la forme suivante :
- MessageId : Il s'agit de l'identifiant du message (16 bits de poids faible). Ce mot clé est obligatoire, mais sa valeur est facultative. Si aucune valeur n'est fournie, c'est la valeur précédente +1 qui est utilisée (ou 1 s'il n'y a pas de valeur précédente). Si une valeur est fournie, c'est cette valeur qui est utilisée. Si la valeur fournie contient les signes + ou -, la valeur utilisée est la valeur précédente plus ou moins la valeur spécifiée.
MessageId = 1000
MessageId = +1
MessageId =
MessageId = +0
Attention, il y a une petite subtilité concernant la valeur précédente. Le compilateur de messages mc.exe gère une valeur précédente par fonctionnalité utilisée. Par exemple, si l'on a :
MessageId1 = 1000
Facility = FACILITY_NULL
...
MessageId2 = 1200
Facility = FACILITY_MBN
...
MessageId3 = +1
Facility = FACILITY_NULL
...
MessageId4 = +1
Facility = FACILITY_MBN
...
La valeur de MessageId3 sera 1001, car la dernière valeur utilisée pour la fonctionnalité FACILITY_NULL est 1000. La valeur de MessageId4 sera 1201, car la dernière valeur utilisée pour la fonctionnalité FACILITY_MBN est 1200.
- Severity : Il s'agit du code sévérité du message (2 bits de poids fort). Ce mot clé est optionnel.
Severity = Sev_Success
Severity = Sev_Information
- Facility : Il s'agit de la fonctionnalité du message (12 bits intermédiaires). Ce mot clé est optionnel. S'il est absent, c'est la facilité du message précédent (ou 0) qui est utilisée pour ce message. Bien que la documentation indique que la facilité par défaut soit Application (0xfff), il semble en fait que la valeur par défaut soit 0.
Facility = FACILITY_MBN
Facility = FACILITY_NULL
- SymbolicName : Il s'agit du nom symbolique du message. Ce nom pourra être utilisé en C ou C++ en incluant le fichier include généré.
SymbolicName = EVENT_STARTED_BY_1
SymbolicName = EVENT_BACKUP
SymbolicName = CATEGORY_ONE
- OutputBase : Il s'agit de la base utilisée pour générer les identifiants de messages dans le fichier include. Les valeurs autorisées sont 10 et 16. La valeur par défaut est 16. Il semble que le compilateur mc.exe n'accepte pas ce mot clé dans le bloc de définition d'un message.
- Language : Ce mot clé introduit le message dans la langue spécifiée. Ce mot clé doit être répété pour chacun des langages spécifiés par le mot clé LanguageNames.
- message text : Il s'agit de la définition sous forme de chaîne de caractères du message lui-même.
- . : le caractère « . » doit se trouver sur une ligne isolée juste après le message lui-même. Il termine la définition du contenu message.
Exemple de fichier de définition complet :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
;#ifndef __MESSAGES_EventMessageFile_H__
;#define __MESSAGES_EventMessageFile_H__
;
;// Ligne de compilation : mc.exe -u$(InputDir)\$(InputName).mc"
;// -u indique un fichier Unicode
;
MessageIdTypedef = DWORD
Outputbase = 16
LanguageNames =
(
LANGUE_FRA = 0x040c:Messages_0x040c
LANGUE_ENG = 0x0809:Messages_0x0809
)
FacilityNames =
(
FACILITY_NULL = 0
FACILITY_RPC = 1
FACILITY_DISPATCH = 2
FACILITY_STORAGE = 3
FACILITY_ITF = 4
FACILITY_WIN32 = 7
FACILITY_WINDOWS = 8
FACILITY_SSPI = 9
FACILITY_SECURITY = 9
FACILITY_CONTROL = 10
FACILITY_CERT = 11
FACILITY_INTERNET = 12
FACILITY_MEDIASERVER = 13
FACILITY_MSMQ = 14
FACILITY_SETUPAPI = 15
FACILITY_SCARD = 16
FACILITY_COMPLUS = 17
FACILITY_AAF = 18
FACILITY_URT = 19
FACILITY_ACS = 20
FACILITY_DPLAY = 21
FACILITY_UMI = 22
FACILITY_SXS = 23
FACILITY_WINDOWS_CE = 24
FACILITY_HTTP = 25
FACILITY_USERMODE_COMMONLOG = 26
FACILITY_USERMODE_FILTER_MANAGER = 31
FACILITY_BACKGROUNDCOPY = 32
FACILITY_CONFIGURATION = 33
FACILITY_STATE_MANAGEMENT = 34
FACILITY_METADIRECTORY = 35
FACILITY_WINDOWSUPDATE = 36
FACILITY_DIRECTORYSERVICE = 37
FACILITY_GRAPHICS = 38
FACILITY_SHELL = 39
FACILITY_TPM_SERVICES = 40
FACILITY_TPM_SOFTWARE = 41
FACILITY_UI = 42
FACILITY_PLA = 48
FACILITY_FVE = 49
FACILITY_FWP = 50
FACILITY_WINRM = 51
FACILITY_NDIS = 52
FACILITY_USERMODE_HYPERVISOR = 53
FACILITY_CMI = 54
FACILITY_USERMODE_VIRTUALIZATION = 55
FACILITY_USERMODE_VOLMGR = 56
FACILITY_BCD = 57
FACILITY_USERMODE_VHD = 58
FACILITY_SDIAG = 60
FACILITY_WEBSERVICES = 61
FACILITY_WINDOWS_DEFENDER = 80
FACILITY_OPC = 81
FACILITY_XPS = 82
FACILITY_RAS = 83
FACILITY_MBN = 84
)
SeverityNames =
(
Sev_Success = 0x0
Sev_Information = 0x1
Sev_Warning = 0x2
Sev_Error = 0x3
)
;////////////////////////////////////////
;// Events
;//
MessageId = 1000
SymbolicName = EVENT_STARTED_BY_1
Facility = FACILITY_MBN
Severity = Sev_Success
Language = LANGUE_FRA
Succès : L'application '%P02' (%2) a été démarrée par l'utilisateur%1"
.
Language = LANGUE_ENG
Success : The program '%P02' (%2) has been started by user%1"
.
MessageId = +0
SymbolicName = EVENT_STARTED_BY_2
Facility = FACILITY_NULL
Severity = Sev_Information
Language = LANGUE_FRA
Information : L'application '%P02' (%2) a été démarrée par l'utilisateur%1"
.
Language = LANGUE_ENG
Information : The program '%P02' (%2) has been started by user%1"
.
MessageId = +0
SymbolicName = EVENT_STARTED_BY_3
Facility = FACILITY_NULL
Severity = Sev_Warning
Language = LANGUE_FRA
Warning : L'application '%P02' (%2) a été démarrée par l'utilisateur%1"
.
Language = LANGUE_ENG
Warning : The program '%P02' (%2) has been started by user%1"
.
MessageId = +0
SymbolicName = EVENT_STARTED_BY_4
Facility = FACILITY_NULL
Severity = Sev_Error
Language = LANGUE_FRA
Error : L'application '%P02' (%2) a été démarrée par l'utilisateur%1"
.
Language = LANGUE_ENG
Error : The program '%P02' (%2) has been started by user%1"
.
MessageId = +1
SymbolicName = EVENT_BACKUP
Facility = FACILITY_NULL
Severity = Sev_Information
Language = LANGUE_FRA
Vous devriez sauvegarder vos données régulièrement !
.
Language = LANGUE_ENG
You should regulary backup your data!
.
;
;#endif //__MESSAGES_EventMessageFile_H__
;
IV-C-2. Construction du fichier de ressources▲
La chaîne de construction des ressources de messages EventLog est la suivante :
IV-D. Les limitations de EventLog▲
Le sous-système EventLog présente tout de même quelques limitations :
- Il n'est pas prévu nativement de pouvoir transférer les messages EventLog vers un concentrateur de messages de logs. Les seules possibilités sont la sauvegarde des différents journaux dans un fichier de sauvegarde ou alors la lecture des messages générés à l'aide de la fonction ReadEventLog() qui permet de se connecter à une machine distante par protocole Netbios. Cette fonctionnalité nécessite toutefois que la machine qui récupère les messages ait un compte sur la machine distante (machines dans un même domaine ou alors login/password sur la machine distante).
- Les messages ne peuvent être générés que sur la machine locale, car les ressources messages sont situées dans des DLL (ou exe) sur la machine qui génère le message. Une machine distante qui récupèrerait le message (par ReadEventLog() ou alors par une copie du journal des évènements) devrait avoir les mêmes DLL (ou exe) en local ou alors devrait pouvoir lire les fichiers à distance (par protocole Netbios).
V. Génération de messages EventLog▲
Le but du programme de test EventLogWriter est de générer plusieurs évènements EventLog. La visualisation de l'évènement généré se fera avec le programme « Event Viewer ».
Attention, le fonctionnement du programme « Event Viewer » fait qu'il bloque le fichier dans lequel sont contenues les ressources texte des messages EventLog. Ce fonctionnement fait que la recompilation du programme peut poser des problèmes, car le fichier exécutable étant bloqué, l'environnement de développement ne peut finir de construire l'exécutable. Parfois même il n'y a pas de message d'erreur. Il est donc impératif lors d'une phase de recompilation d'arrêter toutes les instances du programme « Event Viewer » sous peine de prise de tête.
V-A. Choix de développement▲
Le choix qui a été fait pour ce projet à des fins purement didactiques est de réaliser une DLL pour chacune des familles de messages (catégories, messages et paramètres). Il est probable que dans un vrai projet, au moins les catégories et les messages seraient regroupés dans une seule DLL (avec peut-être aussi les paramètres).
Le programme utilise 3 DLL qui contiennent chacune les messages :
- EventCategorieFile.dll : cette DLL contient les ressources sous forme de chaîne de caractères des noms des différentes catégories en français et en anglais. Cette DLL définit 3 catégories dont les identifiants commencent à 1 et finissent à 3.
- EventMessageFile.dll : cette DLL contient les ressources sous forme de chaîne de caractères des formats d'affichage des différents messages en français et en anglais. Cette DLL définit 5 messages. Il est absolument impératif que les identifiants des messages commencent après le dernier identifiant de catégories. Si cette dernière règle n'est pas respectée, l'affichage des messages dans le programme « Event Viewer » sera incohérent.
- EventParameterFile.dll : cette DLL contient les ressources sous forme de chaîne de caractères des paramètres fixes des différents messages en langue neutre. Cette DLL définit un paramètre fixe dont l'identifiant est arbitrairement fixé à 5002. Il n'y a aucune contrainte sur le choix des identifiants des messages des paramètres.
Ces 3 DLL sont référencées dans la base de registre respectivement dans les clés de registre CategoryMessageFile, EventMessageFile et ParameterMessageFile.
Ce programme a été développé sur une machine Windows 7 professional en utilisant le « SDK Windows® Software Development Kit (SDK) for Windows 7 and .NET Framework 3.5 Service Pack 1 ».
Le programme est écrit volontairement en C afin que d'autres langages puissent utiliser les idées développées dans ce code.
Afin de ne pas alourdir le code inutilement, aucun contrôle de bon déroulement n'est effectué. Ces contrôles devront impérativement être mis en place dans un vrai développement.
V-B. Récupération des fichiers▲
Les différents binaires constituants ce projet sont :
- Le programme EventLogWriter.exe peut être récupéré ici.
- La DLL EventCategorieFile.dll peut être récupérée ici.
- La DLL EventMessageFile.dll peut être récupérée ici.
- La DLL EventParameterFile.dll peut être récupérée ici.
Ces 4 fichiers doivent être mis impérativement dans le même répertoire (et pas dans la racine d'un disque).
Si une erreur se produit lors du lancement du programme EventLogWriter.exe, il faut installer les redistribuables VS 2005. Ce setup peut être téléchargé ici.
Le projet complet au format Visual Studio 2005 peut être téléchargé ici.
V-C. Fonctionnement du programme▲
Le programme doit être appelé une première fois avec les privilèges d'administrateur. Ces privilèges sont nécessaires pour créer les différentes clés dans la base de registre.
Les clés qui vont être créées sont :
Clé |
Type |
Valeur |
---|---|---|
HKLM\SYSTEM\CurrentControlSet\services\eventlog\Application\TestApp\ |
Folder |
|
HKLM\SYSTEM\CurrentControlSet\services\eventlog\Application\TestApp\CategoryCount |
REG_DWORD |
3 |
HKLM\SYSTEM\CurrentControlSet\services\eventlog\Application\TestApp\CategoryMessageFile |
REG_EXPAND_SZ |
<répertoire d'installation>\EventCategorieFile.dll |
HKLM\SYSTEM\CurrentControlSet\services\eventlog\Application\TestApp\EventMessageFile |
REG_EXPAND_SZ |
<répertoire d'installation>\EventMessageFile.dll |
HKLM\SYSTEM\CurrentControlSet\services\eventlog\Application\TestApp\ParameterMessageFile |
REG_EXPAND_SZ |
<répertoire d'installation>\EventParameterFile.dll |
HKLM\SYSTEM\CurrentControlSet\services\eventlog\Application\TestApp\TypesSupported |
REG_DWORD |
7 |
Le fonctionnement du programme est le suivant :
- Il commence par appeler la fonction _add_event_source() qui enregistre le programme TestApp comme source de messages EventLog dans le journal Application. C'est pour cela que les privilèges d'administrateur sont nécessaires la première fois.
- Ensuite le programme appelle la fonction _log_event(). Cette fonction va ouvrir une connexion avec le sous-système EventLog et ensuite envoyer 7 messages EventLog avec des caractéristiques différentes.
- La fonction _GetSid() est utilisée pour récupérer le SID (Security Identifier) de l'utilisateur actuellement connecté et le transmettre dans le message EventLog.
V-D. Les sources du projet▲
V-D-1. Le fichier EventLogWritter.cpp▲
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
// EventLogConsole.cpp : définit le point d'entrée pour l'application console.
//
#ifndef _WIN32_WINNT // Autorise l'utilisation des fonctionnalités spécifiques à Windows XP ou version ultérieure.
#define _WIN32_WINNT 0x0501 // Attribuez la valeur appropriée à cet élément pour cibler d'autres versions de Windows.
#endif
#define _CRT_SECURE_NO_DEPRECATE
#define _CRT_NON_CONFORMING_SWPRINTFS
#include <Windows.h>
#include <stdio.h>
#include "MessageFile.h"
#include "CategorieFile.h"
#include "ParameterFile.h"
// quelques types bien utiles
typedef
wchar_t MCR_CHAR;
typedef
const
wchar_t *
MCR_CSTR;
typedef
unsigned
char
MCR_U1;
typedef
unsigned
long
MCR_U4;
static
void
_GetSID
(
SID *
Sid)
{
// Open the access token associated with the calling process.
HANDLE hToken =
NULL
;
OpenProcessToken
(
GetCurrentProcess
(
), TOKEN_QUERY, &
hToken);
// get the size of the memory buffer needed for the SID
DWORD dwBufferSize =
0
;
GetTokenInformation
(
hToken, TokenUser, NULL
, 0
, &
dwBufferSize);
PTOKEN_USER pTokenUser =
(
PTOKEN_USER)malloc
(
dwBufferSize);
memset
(
pTokenUser, 0
, dwBufferSize);
// Retrieve the token information in a TOKEN_USER structure.
GetTokenInformation
(
hToken, TokenUser, pTokenUser, dwBufferSize, &
dwBufferSize);
// copy the SID structure
memcpy
(
Sid, pTokenUser->
User.Sid, sizeof
(*
Sid));
// free everything
free
(
pTokenUser);
CloseHandle
(
hToken);
}
// nécessite les privilèges d'administration
// lancer le programme la première fois en tant qu'admin
static
void
_add_event_source
(
MCR_CSTR pszName, const
MCR_U4 dwCategoryCount)
{
HKEY hRegKey =
NULL
;
MCR_CHAR szPath[ MAX_PATH ];
swprintf
(
szPath, L"
SYSTEM
\\
CurrentControlSet
\\
Services
\\
EventLog
\\
Application
\\
%s
"
, pszName );
// Create the event source registry key
RegCreateKey
(
HKEY_LOCAL_MACHINE, szPath, &
hRegKey );
// Name of the PE module that contains the message resource
GetModuleFileName
(
NULL
, szPath, MAX_PATH );
// remove the last '\'
*
wcsrchr
(
szPath, '
\\
'
) =
0
;
// Register EventMessageFile
MCR_CHAR szFile[ MAX_PATH ];
swprintf
(
szFile, L"
%s
\\
%s
"
, szPath, L"
EventMessageFile.dll
"
);
RegSetValueEx
(
hRegKey, L"
EventMessageFile
"
, 0
, REG_EXPAND_SZ,
(
LPBYTE)szFile,
(
MCR_U4)(
wcslen
(
szFile ) +
1
) *
sizeof
szFile[0
] );
// Register ParameterMessageFile
swprintf
(
szFile, L"
%s
\\
%s
"
, szPath, L"
EventParameterFile.dll
"
);
RegSetValueEx
(
hRegKey, L"
ParameterMessageFile
"
, 0
, REG_EXPAND_SZ,
(
LPBYTE)szFile,
(
MCR_U4)(
wcslen
(
szFile ) +
1
) *
sizeof
szFile[0
] );
// Register CategoryMessageFile
swprintf
(
szFile, L"
%s
\\
%s
"
, szPath, L"
EventCategorieFile.dll
"
);
RegSetValueEx
(
hRegKey, L"
CategoryMessageFile
"
, 0
, REG_EXPAND_SZ,
(
LPBYTE)szFile,
(
MCR_U4)(
wcslen
(
szFile ) +
1
) *
sizeof
szFile[0
] );
// Register supported event types
MCR_U4 dwTypes =
EVENTLOG_ERROR_TYPE |
EVENTLOG_WARNING_TYPE |
EVENTLOG_INFORMATION_TYPE;
RegSetValueEx
(
hRegKey, L"
TypesSupported
"
, 0
, REG_DWORD, (
LPBYTE) &
dwTypes, sizeof
dwTypes );
// Register category count
RegSetValueEx
(
hRegKey, L"
CategoryCount
"
, 0
, REG_DWORD, (
LPBYTE)&
dwCategoryCount, sizeof
dwCategoryCount );
RegCloseKey
(
hRegKey );
}
static
void
_log_event
(
MCR_CSTR pszName)
{
// Open the eventlog
HANDLE hEventLog =
RegisterEventSource
(
NULL
, pszName );
// get the sid of the user
SID sid;
_GetSID
(&
sid);
PSID psid =
&
sid;
// make dynamic parameters array
MCR_CSTR aInsertions[] =
{
L"
Paramètre 1
"
, L"
Paramètre 2
"
}
;
// buffer parameters
MCR_U1 buffer[64
];
for
(
MCR_U1 boucle =
0
; boucle !=
sizeof
(
buffer); boucle++
)
buffer[boucle] =
'
'
+
boucle;
ReportEvent
(
hEventLog, // Handle to the eventlog
EVENTLOG_SUCCESS, // Type of event
CATEGORY_TWO, // Category (could also be 0)
EVENT_STARTED_BY_1, // Event id
psid, // User's sid (NULL for none)
2
, // Number of insertion strings
sizeof
(
buffer), // Number of additional bytes
aInsertions, // Array of insertion strings
buffer // Pointer to additional bytes
);
// And another one
ReportEvent
(
hEventLog, // Handle to the eventlog
EVENTLOG_INFORMATION_TYPE, // Type of event
CATEGORY_TWO, // Category (could also be 0)
EVENT_STARTED_BY_2, // Event id
psid, // User's sid (NULL for none)
2
, // Number of insertion strings
sizeof
(
buffer), // Number of additional bytes
aInsertions, // Array of insertion strings
buffer // Pointer to additional bytes
);
ReportEvent
(
hEventLog, // Handle to the eventlog
EVENTLOG_WARNING_TYPE, // Type of event
CATEGORY_TWO, // Category (could also be 0)
EVENT_STARTED_BY_3, // Event id
psid, // User's sid (NULL for none)
2
, // Number of insertion strings
sizeof
(
buffer), // Number of additional bytes
aInsertions, // Array of insertion strings
buffer // Pointer to additional bytes
);
ReportEvent
(
hEventLog, // Handle to the eventlog
EVENTLOG_ERROR_TYPE, // Type of event
CATEGORY_TWO, // Category (could also be 0)
EVENT_STARTED_BY_4, // Event id
psid, // User's sid (NULL for none)
2
, // Number of insertion strings
sizeof
(
buffer), // Number of additional bytes
aInsertions, // Array of insertion strings
buffer // Pointer to additional bytes
);
ReportEvent
(
hEventLog, // Handle to the eventlog
EVENTLOG_AUDIT_SUCCESS, // Type of event
CATEGORY_TWO, // Category (could also be 0)
EVENT_STARTED_BY_1, // Event id
psid, // User's sid (NULL for none)
2
, // Number of insertion strings
sizeof
(
buffer), // Number of additional bytes
aInsertions, // Array of insertion strings
buffer // Pointer to additional bytes
);
ReportEvent
(
hEventLog, // Handle to the eventlog
EVENTLOG_AUDIT_FAILURE, // Type of event
CATEGORY_ONE, // Category (could also be 0)
EVENT_STARTED_BY_2, // Event id
psid, // User's sid (NULL for none)
2
, // Number of insertion strings
sizeof
(
buffer), // Number of additional bytes
aInsertions, // Array of insertion strings
buffer // Pointer to additional bytes
);
ReportEvent
(
hEventLog, // Handle to the eventlog
EVENTLOG_SUCCESS, // Type of event
CATEGORY_ONE, // Category (could also be 0)
EVENT_BACKUP, // Event id
NULL
, // User's sid (NULL for none)
2
, // Number of insertion strings
0
, // Number of additional bytes
aInsertions, // Array of insertion strings
NULL
// Pointer to additional bytes
);
// Close eventlog
DeregisterEventSource
(
hEventLog );
}
int
wmain
(
int
/*argc*/
, wchar_t *
/*argv*/
[])
{
// register the source EventLog
// should be done during the setup procedure by the setup
// but in this sample, will be done every times
_add_event_source
(
L"
TestApp
"
, LAST_CATEGORY);
_log_event
(
L"
TestApp
"
);
return
0
;
}
V-D-2. Le fichier CategoryFile.mc▲
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
;#ifndef __MESSAGES_EventCategorieFile_H__
;#define __MESSAGES_EventCategorieFile_H__
;
;// Ligne de compilation : mc.exe -u "$(InputDir)\$(InputName).mc"
;// -u indique un fichier Unicode
;
MessageIdTypedef = DWORD
Outputbase = 10
LanguageNames =
(
LANGUE_FRA = 0x040c:Messages_0x040c
LANGUE_ENG = 0x0809:Messages_0x0809
)
;////////////////////////////////////////
;// Eventlog categories
;//
;// Categories always have to be the first entries in a message file!
;//
MessageId = +1
SymbolicName = CATEGORY_ONE
Language = LANGUE_FRA
Première catégorie d'évènements
.
Language = LANGUE_ENG
First category of events
.
MessageId = +1
SymbolicName = CATEGORY_TWO
Language = LANGUE_FRA
Seconde catégorie d'évènements
.
Language = LANGUE_ENG
Second category of events
.
MessageId = +1
SymbolicName = LAST_CATEGORY
Language = LANGUE_FRA
Repère pour la dernière catégorie d'évènements
.
Language = LANGUE_ENG
Tag for the last category of events
.
;
;#endif //__MESSAGES_EventCategorieFile_H__
;
V-D-3. Le fichier MessageFile.mc▲
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
;#ifndef __MESSAGES_EventMessageFile_H__
;#define __MESSAGES_EventMessageFile_H__
;
;// Ligne de compilation : mc.exe -u "$(InputDir)\$(InputName).mc"
;// -u indique un fichier Unicode
;
MessageIdTypedef = DWORD
Outputbase = 16
LanguageNames =
(
LANGUE_FRA = 0x040c:Messages_0x040c
LANGUE_ENG = 0x0809:Messages_0x0809
)
FacilityNames =
(
FACILITY_NULL = 0
FACILITY_RPC = 1
FACILITY_DISPATCH = 2
FACILITY_STORAGE = 3
FACILITY_ITF = 4
FACILITY_WIN32 = 7
FACILITY_WINDOWS = 8
FACILITY_SSPI = 9
FACILITY_SECURITY = 9
FACILITY_CONTROL = 10
FACILITY_CERT = 11
FACILITY_INTERNET = 12
FACILITY_MEDIASERVER = 13
FACILITY_MSMQ = 14
FACILITY_SETUPAPI = 15
FACILITY_SCARD = 16
FACILITY_COMPLUS = 17
FACILITY_AAF = 18
FACILITY_URT = 19
FACILITY_ACS = 20
FACILITY_DPLAY = 21
FACILITY_UMI = 22
FACILITY_SXS = 23
FACILITY_WINDOWS_CE = 24
FACILITY_HTTP = 25
FACILITY_USERMODE_COMMONLOG = 26
FACILITY_USERMODE_FILTER_MANAGER = 31
FACILITY_BACKGROUNDCOPY = 32
FACILITY_CONFIGURATION = 33
FACILITY_STATE_MANAGEMENT = 34
FACILITY_METADIRECTORY = 35
FACILITY_WINDOWSUPDATE = 36
FACILITY_DIRECTORYSERVICE = 37
FACILITY_GRAPHICS = 38
FACILITY_SHELL = 39
FACILITY_TPM_SERVICES = 40
FACILITY_TPM_SOFTWARE = 41
FACILITY_UI = 42
FACILITY_PLA = 48
FACILITY_FVE = 49
FACILITY_FWP = 50
FACILITY_WINRM = 51
FACILITY_NDIS = 52
FACILITY_USERMODE_HYPERVISOR = 53
FACILITY_CMI = 54
FACILITY_USERMODE_VIRTUALIZATION = 55
FACILITY_USERMODE_VOLMGR = 56
FACILITY_BCD = 57
FACILITY_USERMODE_VHD = 58
FACILITY_SDIAG = 60
FACILITY_WEBSERVICES = 61
FACILITY_WINDOWS_DEFENDER = 80
FACILITY_OPC = 81
FACILITY_XPS = 82
FACILITY_RAS = 83
FACILITY_MBN = 84
)
SeverityNames =
(
Sev_Success = 0x0
Sev_Information = 0x1
Sev_Warning = 0x2
Sev_Error = 0x3
)
;////////////////////////////////////////
;// Events
;//
MessageId = 1000
SymbolicName = EVENT_STARTED_BY_1
Facility = FACILITY_NULL
Severity = Sev_Success
Language = LANGUE_FRA
Succès : L'application '%P01' (%1) a été démarrée par l'utilisateur "%2"
.
Language = LANGUE_ENG
Success : The program '%P01' (%1) has been started by user "%2"
.
MessageId = +0
SymbolicName = EVENT_STARTED_BY_2
Facility = FACILITY_NULL
Severity = Sev_Information
Language = LANGUE_FRA
Information : L'application '%P01' (%2) a été démarrée par l'utilisateur "%1"
.
Language = LANGUE_ENG
Information : The program '%P01' (%2) has been started by user "%1"
.
MessageId = +0
SymbolicName = EVENT_STARTED_BY_3
Facility = FACILITY_NULL
Severity = Sev_Warning
Language = LANGUE_FRA
Warning : L'application '%P01' (%1) a été démarrée par l'utilisateur "%2"
.
Language = LANGUE_ENG
Warning : The program '%P01' (%1) has been started by user "%2"
.
MessageId = +0
SymbolicName = EVENT_STARTED_BY_4
Facility = FACILITY_NULL
Severity = Sev_Error
Language = LANGUE_FRA
Error : L'application '%P01' (%2) a été démarrée par l'utilisateur "%1"
.
Language = LANGUE_ENG
Error : The program '%P01' (%2) has been started by user "%1"
.
MessageId = +1
SymbolicName = EVENT_BACKUP
Facility = FACILITY_NULL
Severity = Sev_Information
Language = LANGUE_FRA
Vous devriez sauvegarder vos données régulièrement !
.
Language = LANGUE_ENG
You should regulary backup your data!
.
;
;#endif //__MESSAGES_EventMessageFile_H__
;
V-D-4. Le fichier ParameterFile.mc▲
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
;#ifndef __MESSAGES_EventParameterFile_H__
;#define __MESSAGES_EventParameterFile_H__
;
;// Ligne de compilation : mc.exe -u "$(InputDir)\$(InputName).mc"
;// -u indique un fichier Unicode
;
MessageIdTypedef = DWORD
Outputbase = 10
LanguageNames =
(
NEUTRAL = 0x0000:Messages_0x0000
)
MessageId = 5001
SymbolicName = PARAMETER_5001
Language = NEUTRAL
'Test %P02'
.
MessageId = 5002
SymbolicName = PARAMETER_5002
Language = NEUTRAL
EventLogWritter.exe
.
;
;#endif //__MESSAGES_EventParameterFile_H__
;
VI. Lecture de messages EventLog▲
Le but de ce programme de démonstration est d'ouvrir un journal d'évènements et d'attendre l'arrivée d'évènements EventLog. L'affichage du contenu des informations du message EventLog est incomplet, ce programme a pour but d'en montrer le principe.
Ce programme ne lit que les évènements générés dans le journal Application du sous-système EventLog.
VI-A. Choix de développement▲
Le programme EventLogReader.exe lit les évènements EventLog du journal Application au fur et à mesure qu'ils sont générés. La génération d'évènements se fera en utilisant le programme EventLogWriter.exe présenté au paragraphe précédent.
Ce programme a été développé sur une machine Windows 7 professional en utilisant le « SDK Windows® Software Development Kit (SDK) for Windows 7 and .NET Framework 3.5 Service Pack 1 ».
Le programme est écrit volontairement en C afin que d'autres langages puissent utiliser les idées développées dans ce code.
Afin de ne pas alourdir le code inutilement, aucun contrôle de bon déroulement n'est effectué. Ces contrôles devront impérativement être mis en place dans un vrai développement.
VI-B. Récupération des fichiers▲
Le programme EventLogReader.exe peut être récupéré ici.
Si une erreur se produit lors du lancement du programme EventLogWriter.exe, il faut installer les redistribuables VS 2005. Ce setup peut être téléchargé ici.
Le projet complet au format Visual Studio 2005 peut être téléchargé ici.
VI-C. Fonctionnement du programme▲
Le programme commence d'abord par calculer le numéro du dernier évènement présent dans le journal avec les fonctions GetOldestEventLogRecord() et GetNumberOfEventLogRecords(). Ensuite, il détermine l'index du dernier évènement (Number of event log + Oldest event log - 1) pour se positionner juste après cet évènement (avec la fonction ReadEventLog(EVENTLOG_SEEK_READ | EVENTLOG_FORWARDS_READ).
Ensuite, il crée un objet évènement avec la fonction CreateEvent(). Cet objet est ensuite donné en paramètre au sous-système EventLog par la fonction NotifyChangeEventLog() afin d'être prévenu lors de l'arrivée d'un nouvel évènement EventLog.
Le programme fait ensuite une attente bloquante sur cet objet évènement et dès qu'il est libéré, le programme lit le journal des évènements avec la fonction ReadEventLog(). Cet appel pouvant retourner 1 ou plusieurs évènements simultanément, il y a une boucle de lecture/extraction des différents évènements EventLog.
Ensuite, chaque évènement EventLog est analysé et affiché par la fonction _analyse().
Remarque : la fonction NotifyChangeEventLog() est utilisée, car elle permet une attente passive. Toutefois, cette fonction ne peut pas être utilisée pour interroger le sous-système EventLog d'une autre machine. Dans ce cas, il faudra passer par une attente active avec temporisation, lecture et analyse s'il y a de nouveaux évènements EventLog.
VI-D. Le fichier EventLogReader.cpp▲
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
237.
238.
239.
240.
241.
242.
243.
244.
245.
246.
247.
248.
249.
250.
251.
252.
253.
254.
255.
256.
257.
258.
259.
260.
261.
262.
263.
264.
265.
266.
267.
268.
269.
270.
271.
272.
273.
274.
275.
276.
277.
278.
279.
280.
281.
282.
283.
284.
285.
286.
287.
288.
289.
290.
291.
292.
293.
294.
295.
296.
297.
298.
299.
300.
301.
302.
303.
304.
305.
306.
307.
308.
309.
310.
311.
312.
313.
314.
315.
316.
317.
318.
319.
320.
321.
322.
323.
324.
325.
326.
327.
328.
329.
330.
331.
332.
333.
334.
335.
336.
337.
338.
339.
340.
341.
342.
343.
344.
345.
346.
347.
348.
349.
350.
351.
352.
353.
354.
355.
356.
357.
358.
359.
360.
361.
362.
363.
364.
365.
366.
367.
368.
369.
370.
371.
372.
373.
374.
375.
376.
377.
378.
379.
380.
381.
382.
383.
384.
385.
386.
387.
388.
389.
390.
391.
392.
393.
394.
395.
396.
397.
398.
399.
400.
401.
402.
403.
404.
405.
406.
407.
// EventLogReader.cpp : définit le point d'entrée pour l'application console.
//
#ifndef _WIN32_WINNT // Autorise l'utilisation des fonctionnalités spécifiques à Windows XP ou version ultérieure.
#define _WIN32_WINNT 0x0501 // Attribuez la valeur appropriée à cet élément pour cibler d'autres versions de Windows.
#endif
#define _CRT_SECURE_NO_DEPRECATE
#define _CRT_NON_CONFORMING_SWPRINTFS
#include <Windows.h>
#include <stdio.h>
#include <locale.h>
#include <Sddl.h>
#define JOURNAL L"Application"
const
wchar_t *
_read_registry
(
const
wchar_t *
Source, const
wchar_t *
Key)
{
wchar_t folder[ MAX_PATH ];
swprintf
(
folder, L"
SYSTEM
\\
CurrentControlSet
\\
Services
\\
EventLog
\\
%s
\\
%s
"
, JOURNAL, Source );
// Create the event source registry key
HKEY hRegKey =
NULL
;
RegOpenKey
(
HKEY_LOCAL_MACHINE, folder, &
hRegKey );
// read the value
DWORD type;
static
BYTE buffer[4096
];
DWORD size =
sizeof
(
buffer);
RegQueryValueEx
(
hRegKey, Key, 0
, &
type, buffer, &
size);
if
(
type ==
REG_SZ)
{
return
(
const
wchar_t *
)buffer;
}
if
(
type ==
REG_EXPAND_SZ)
{
static
wchar_t expanded[4096
];
DWORD Nb =
ExpandEnvironmentStrings
((
const
wchar_t *
)buffer, expanded, sizeof
(
expanded) /
sizeof
(
expanded[0
]));
buffer[Nb] =
0
;
return
(
const
wchar_t *
)expanded;
}
// unknown type
return
L"
???
"
;
}
const
wchar_t *
_load_string
(
const
wchar_t *
Dll, DWORD Id )
{
// load the library
HMODULE lib =
LoadLibrary
(
Dll);
// reset the message
static
wchar_t Message [4096
];
// compute the flag of FormatMessage()
DWORD flags =
FORMAT_MESSAGE_FROM_HMODULE |
FORMAT_MESSAGE_IGNORE_INSERTS;
// load the message
DWORD nb_tchar =
FormatMessage
(
flags, lib, Id, 0
, Message, sizeof
(
Message) /
sizeof
(
Message[0
]), NULL
);
Message[nb_tchar] =
0
;
// unload the library
FreeLibrary
(
lib);
// remove any bad trailing characters (CR, LF)
while
(
nb_tchar >
0
)
{
nb_tchar--
;
if
(
Message[nb_tchar] ==
'
\n
'
)
{
Message[nb_tchar] =
0
;
continue
;
}
if
(
Message[nb_tchar] ==
'
\r
'
)
{
Message[nb_tchar] =
0
;
continue
;
}
break
;
}
return
Message;
}
const
wchar_t *
_get_category_string
(
const
wchar_t *
Source, DWORD Id )
{
// get the DLL to open
const
wchar_t *
dll =
_read_registry
(
Source, L"
CategoryMessageFile
"
);
// copy the message string
const
wchar_t *
msg =
_load_string
(
dll, Id);
return
msg;
}
const
wchar_t *
_get_parameter_string
(
const
wchar_t *
Source, DWORD Id )
{
// get the DLL to open
const
wchar_t *
dll =
_read_registry
(
Source, L"
ParameterMessageFile
"
);
// copy the message string
const
wchar_t *
msg =
_load_string
(
dll, Id);
return
msg;
}
const
wchar_t *
_replace_static
(
const
wchar_t *
Source, const
wchar_t *
buffer, const
wchar_t *
begin, DWORD size, DWORD id)
{
static
wchar_t buf[4096
];
// copy the begin of the string
DWORD len =
(
DWORD)(
begin -
buffer);
memcpy
(
buf, buffer, len *
sizeof
(
wchar_t));
buf[len] =
0
;
// add the new replacement string
wcscat
(
buf, _get_parameter_string
(
Source, id));
// copy the end of the string
wcscat
(
buf, &
begin[size]);
return
buf;
}
const
wchar_t *
_replace_dynamic
(
const
wchar_t *
buffer, const
wchar_t *
begin, DWORD size, const
wchar_t *
Str)
{
static
wchar_t buf[4096
];
// copy the begin of the string
DWORD len =
(
DWORD)(
begin -
buffer);
memcpy
(
buf, buffer, len *
sizeof
(
wchar_t));
buf[len] =
0
;
// add the new replacement string
wcscat
(
buf, Str);
// copy the end of the string
wcscat
(
buf, &
begin[size]);
return
buf;
}
const
wchar_t *
_get_message_string
(
const
wchar_t *
Source, DWORD Id, const
wchar_t *
String[])
{
// get the DLL to open
const
wchar_t *
dll =
_read_registry
(
Source, L"
EventMessageFile
"
);
const
wchar_t *
msg =
_load_string
(
dll, Id);
static
wchar_t buffer[4096
];
wcscpy
(
buffer, msg);
const
wchar_t *
p =
wcschr
(
buffer, '
%
'
);
DWORD id;
const
wchar_t *
begin;
DWORD size;
while
(
p !=
NULL
)
{
begin =
p;
p++
;
size =
1
;
if
(*
p ==
'
%
'
)
{
// this is the ID of a static parameter string
p++
;
size++
;
id =
0
;
while
(
(*
p >=
'
0
'
) &&
(*
p <=
'
9
'
) )
{
id *=
10
;
id +=
*
p -
'
0
'
;
p++
;
size++
;
}
// replace the string
wcscpy
(
buffer, _replace_static
(
Source, buffer, begin, size, id));
p =
wcschr
(
buffer, '
%
'
);
continue
;
}
else
{
// this is the number of a dynamic parameter
id =
0
;
while
(
(*
p >=
'
0
'
) &&
(*
p <=
'
9
'
) )
{
id *=
10
;
id +=
*
p -
'
0
'
;
p++
;
size++
;
}
if
(
id ==
0
) break
;
// replace the string
wcscpy
(
buffer, _replace_dynamic
(
buffer, begin, size, String[id -
1
]));
p =
wcschr
(
buffer, '
%
'
);
continue
;
}
}
return
buffer;
}
const
wchar_t *
_get_sid
(
PSID Sid )
{
if
(
Sid ==
NULL
)
return
L"
Non disponible
"
;
wchar_t *
str;
ConvertSidToStringSid
(
Sid, &
str);
static
wchar_t buf[4096
];
wcscpy
(
buf, str);
LocalFree
(
str);
return
buf;
}
void
_analyse
(
EVENTLOGRECORD *
p_eventlog )
{
wprintf
(
L"
EventLog RecordNumber = %lu
\n
"
, p_eventlog->
RecordNumber );
wprintf
(
L"
Length = %lu
\n
"
, p_eventlog->
Length );
wprintf
(
L"
EventID = 0x%.8x
\n
"
, p_eventlog->
EventID );
wprintf
(
L"
Code(0x%.4x)
"
, p_eventlog->
EventID &
0xffff
);
wprintf
(
L"
Facility(0x%.3x)
"
, (
p_eventlog->
EventID &
0x0fff0000
) >>
16
);
wprintf
(
L"
Reserved(0x%.1x)
"
, (
p_eventlog->
EventID &
0x10000000
) >>
28
);
wprintf
(
L"
Customer(0x%.1x)
"
, (
p_eventlog->
EventID &
0x20000000
) >>
29
);
wprintf
(
L"
Severity(0x%.1x)
"
, (
p_eventlog->
EventID &
0xc0000000
) >>
30
);
wprintf
(
L"
\n
"
);
wprintf
(
L"
EventCategory = %lu
\n
"
, p_eventlog->
EventCategory );
// affichage des types
wprintf
(
L"
EventType = 0x%.4x ==>
"
, p_eventlog->
EventType );
if
(
(
p_eventlog->
EventType &
EVENTLOG_WARNING_TYPE) ==
EVENTLOG_WARNING_TYPE) wprintf
(
L"
Warning
"
);
if
(
(
p_eventlog->
EventType &
EVENTLOG_ERROR_TYPE) ==
EVENTLOG_ERROR_TYPE) wprintf
(
L"
Error
"
);
if
(
(
p_eventlog->
EventType &
EVENTLOG_INFORMATION_TYPE) ==
EVENTLOG_INFORMATION_TYPE) wprintf
(
L"
Information
"
);
if
(
(
p_eventlog->
EventType &
EVENTLOG_AUDIT_FAILURE) ==
EVENTLOG_AUDIT_FAILURE) wprintf
(
L"
Audit failure
"
);
if
(
(
p_eventlog->
EventType &
EVENTLOG_AUDIT_SUCCESS) ==
EVENTLOG_AUDIT_SUCCESS) wprintf
(
L"
Audit success
"
);
wprintf
(
L"
\n
"
);
wprintf
(
L"
Reserved = %lu
\n
"
, p_eventlog->
Reserved );
wprintf
(
L"
TimeGenerated = %lu
\n
"
, p_eventlog->
TimeGenerated );
wprintf
(
L"
TimeWritten = %lu
\n
"
, p_eventlog->
TimeWritten );
wprintf
(
L"
ReservedFlags = %lu
\n
"
, p_eventlog->
ReservedFlags );
wprintf
(
L"
ClosingRecordNumber = %lu
\n
"
, p_eventlog->
ClosingRecordNumber );
wprintf
(
L"
NumStrings = %lu
\n
"
, p_eventlog->
NumStrings );
wprintf
(
L"
StringOffset = %lu
\n
"
, p_eventlog->
StringOffset );
// affichage des strings
const
wchar_t *
String[100
];
DWORD offset =
p_eventlog->
StringOffset;
for
(
DWORD boucle =
0
; boucle !=
p_eventlog->
NumStrings; boucle++
)
{
const
BYTE *
p =
(
BYTE *
)p_eventlog;
p +=
offset;
const
wchar_t *
str =
(
const
wchar_t *
)p;
String[boucle] =
(
const
wchar_t *
)p;
offset +=
(
DWORD)(
wcslen
(
str) +
1
) *
sizeof
(
wchar_t);
}
for
(
DWORD boucle =
0
; boucle !=
p_eventlog->
NumStrings; boucle++
)
{
wprintf
(
L"
String[%lu] = [%s]
\n
"
, boucle, String[boucle] );
}
// extraction du SID
wprintf
(
L"
UserSidLength = %lu
\n
"
, p_eventlog->
UserSidLength );
wprintf
(
L"
UserSidOffset = %lu
\n
"
, p_eventlog->
UserSidOffset );
PSID psid =
NULL
;
if
(
p_eventlog->
UserSidLength !=
0
)
psid =
(
PSID)((
BYTE *
)p_eventlog +
p_eventlog->
UserSidOffset);
// affichage des data
wprintf
(
L"
DataLength = %lu
\n
"
, p_eventlog->
DataLength );
wprintf
(
L"
DataOffset = %lu
\n
"
, p_eventlog->
DataOffset );
const
BYTE *
p =
(
BYTE *
)p_eventlog;
p +=
p_eventlog->
DataOffset;
wprintf
(
L"
"
);
for
(
DWORD boucle =
0
; boucle !=
p_eventlog->
DataLength; boucle++
)
{
wprintf
(
L"
%.2x
"
, *
p );
p++
;
}
wprintf
(
L"
\n
"
);
// get the source name
offset =
sizeof
(
EVENTLOGRECORD );
p =
(
BYTE *
)p_eventlog;
p +=
offset;
const
wchar_t *
SourceName =
(
const
wchar_t *
)p;
wprintf
(
L"
SourceName = [%s]
\n
"
, SourceName );
// get the computer name
offset =
(
DWORD)(
wcslen
(
SourceName) +
1
) *
sizeof
(
wchar_t );
p +=
offset;
const
wchar_t *
ComputerName =
(
const
wchar_t *
)p;
wprintf
(
L"
ComputerName = [%s]
\n
"
, ComputerName );
// get the category string
wprintf
(
L"
Category string = [%s]
\n
"
, _get_category_string
(
SourceName, p_eventlog->
EventCategory ) );
wprintf
(
L"
Message string = [%s]
\n
"
, _get_message_string
(
SourceName, p_eventlog->
EventID, String ) );
wprintf
(
L"
SID = [%s]
\n
"
, _get_sid
(
psid ) );
wprintf
(
L"
\n
"
);
}
int
wmain
(
int
/*argc*/
, wchar_t *
/*argv*/
[])
{
// the console can display french caracters
_wsetlocale
(
LC_ALL, L"
French
"
);
// choose the resource language
WORD PrimaryLanguage =
LANG_ENGLISH;
WORD SubLanguage =
SUBLANG_ENGLISH_UK;
// for french language
// if you prefer english language, comment these 2 lines
PrimaryLanguage =
LANG_FRENCH;
SubLanguage =
SUBLANG_FRENCH;
// set the preferred language for UI
SetThreadUILanguage (
MAKELANGID (
PrimaryLanguage, SubLanguage) );
// ouverture du journal local d'évènements de la localisation "Application"
HANDLE hlog =
OpenEventLog
(
NULL
, JOURNAL );
// get the oldest log number
DWORD OldestLog =
(
DWORD)-
1
;
GetOldestEventLogRecord
(
hlog, &
OldestLog );
// Get the record number of the event log record.
DWORD NbLog =
(
DWORD)-
1
;
GetNumberOfEventLogRecords
(
hlog, &
NbLog);
// the idx where to go
DWORD last_idx =
NbLog +
OldestLog -
1
;
// the buffer where to receive data
BYTE buffer[1024
];
DWORD nb_read;
DWORD nb_needed;
DWORD nb_eventlog =
0
;
// go to the last record
ReadEventLog
(
hlog, // event log handle
EVENTLOG_SEEK_READ |
EVENTLOG_FORWARDS_READ, // reads forward, sequential read
last_idx, // index log to read
buffer, // pointer to buffer
sizeof
(
buffer ), // size of buffer
&
nb_read, // number of bytes read
&
nb_needed ); // number of bytes needed
// creation d'un évènement pour attente bloquante
HANDLE hevent =
CreateEvent
(
NULL
, FALSE, FALSE, L"
Event
"
);
// associe eventlog et event
NotifyChangeEventLog (
hlog, hevent );
for
(
; ; )
{
WaitForSingleObject
(
hevent, INFINITE );
// lecture de l'évènement
while
(
ReadEventLog
(
hlog, // event log handle
EVENTLOG_SEQUENTIAL_READ |
EVENTLOG_FORWARDS_READ, // reads forward, sequential read
0
, // index log to read
buffer, // pointer to buffer
sizeof
(
buffer ), // size of buffer
&
nb_read, // number of bytes read
&
nb_needed ) ==
TRUE) // number of bytes needed
{
// extraction de tous les évènements (il peut y en avoir plusieurs)
DWORD idx =
0
;
while
(
nb_read !=
0
)
{
EVENTLOGRECORD *
p_eventlog =
(
EVENTLOGRECORD *
) &
buffer[ idx ];
DWORD Size =
p_eventlog->
Length;
nb_read -=
Size;
idx +=
Size;
// analyse de l'évènement pointé par p_event
_analyse
(
p_eventlog );
nb_eventlog++
;
}
}
// read only 7 eventlog (test purpose)
if
(
nb_eventlog >=
7
)
break
;
}
// fermeture de l'event log
CloseEventLog
(
hlog );
// destruction de l'event
CloseHandle
(
hevent );
return
0
;
}
VII. Conclusions▲
Cette présentation du sous-système EventLog est maintenant terminée. J'espère que les différents exemples et explications fournis dans ce document inciteront les développeurs d'applications Microsoft à utiliser le sous-système EventLog trop souvent boudé ou ignoré parce que jugé au premier abord trop complexe ou inutile.
Comme dans tout projet, le plus important est avant tout de mettre en place une politique cohérente de messages. Ensuite le code nécessaire à la mécanique est tout de même relativement simple une fois que le fonctionnement est démystifié et qu'un squelette minimal est mis en place.
N'hésitez pas à donner votre avis sur ce document, qu'il vous ait plu ou bien parce qu'il contient des erreurs ou des imprécisions, 19 commentaires, vos retours serviront à faire évoluer ce document.
VII-A. Quelques références▲
Ce chapitre présente les quelques références (en anglais pour la plupart) qui m'ont servi pour comprendre le fonctionnement du sous-système EventLog et pour rédiger ce document.