1. 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.

2. La journalisation

2.1. 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 2 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.

2.2. 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. Ainsi, 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 t'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.

2.3. 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é.

2.4. 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 est 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.

3. 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é.

Le service EventLog sous Windows XP
Le service EventLog sous Windows XP

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é.

Le service EventLog sous Windows 7
Le service EventLog sous Windows 7

Microsoft propose 2 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.

3.1. Architecture simplifiée

Le schéma suivant montre l'architecture simplifiée du système :

Architecture simplifiée du sous-système EventLog
Architecture simplifiée du sous-système EventLog

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 3 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".

3.2. 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 :

Le programme Event Viewer
Le programme Event Viewer

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.

3.3. 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.

3.3.1. Le type du message

Il existe 6 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. A 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.
Les types de messages EventLog
Les types de messages EventLog

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.

3.3.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.

La source du message EventLog
La source du message EventLog

3.3.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
L'identifiant du message EventLog

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.
Composition de l'identifiant du message EventLog
Composition de l'identifiant du message EventLog

3.3.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.

La catégorie du message EventLog
La catégorie du message EventLog

3.3.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.

Le message EventLog
Le message EventLog

3.3.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.

3.3.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.

3.3.8. La date et l'heure

Le message EventLog contient 2 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).

3.3.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.

3.4. 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.

3.4.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.

3.4.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 :
* Read (0x0001)
* Write (0x0002)
* Clear (0x0004)
Pour être syntaxiquement valide, la valeur CustomSD doit spécifier un propriétaire et un propriétaire du groupe même si ces propriétaires ne sont pas utilisés car ils n'ont pas de sens dans ce contexte. Si CustomSD contient une valeur incorrecte, un évènement est déclenché dans le journal des évènements système lors du démarrage du service de journalisation des évènements et le journal des évènements reçoit un descripteur de sécurité par défaut identique à la valeur d'origine CustomSD pour le journal Application.
* Windows Server 2003 : Les SACLs sont supportées.
* Windows XP/2000 : Les SACLs ne sont pas supportées.
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.
Ce nombre est utilisé par le programme "Event Viewer" pour afficher le nom localisé du journal EventLog. Cette ressource est stockée dans le fichier spécifié par la valeur de l'entrée DisplayNameFile.
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).
Par exemple, la clé de registre PrimaryModule pour le journal Security indique la source Security. Cela signifie que, pour toutes les sources du journal Security qui ne spécifient pas les valeurs nécessaires de configuration (CategoryCount, CategoryMessageFile, EventMessageFile, ParameterMessageFile, TypesSupported ...), ce sont les valeurs stockées dans Security/Security qui seront utilisées.
La clé de registre PrimaryModule permet de spécifier un comportement par défaut pour les sources mal configurées.
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é.
* 0 : Le sous-système EventLog écrase les messages uniquement lorsque le journal EventLog atteint la taille maximale (définie par la valeur de l'entrée MAXSIZE). Cette valeur correspond à l'option Remplacer les évènements si nécessaire dans la boîte de dialogue Propriétés d'un journal affichée par le programme "Event Viewer".
* 0x1-0xFFFFFFFE : Spécifie la durée de vie minimale des messages EventLog avant un éventuel effacement.
* 0xFFFFFFFF : Les messages EventLog ne sont jamais effacés. Lorsque le journal EventLog atteint sa taille maximale (définie par l'entrée MaxSize), il faut alors effacer le fichier manuellement en cliquant sur Effacer tous les évènements. Cette valeur correspond à l'option Ne pas remplacer les évènements dans le programme "Event Viewer". Il est également possible d'activer la fonctionnalité de sécurité qui empêche la machine d'être utilisée si elle ne peut pas ajouter de messages EventLog. Pour activer cette fonctionnalité, il faut définir la valeur de l'entrée CrashOnAuditFail à une valeur différente de 0.
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.
Il semble que la cohérence entre le contenu de cette clé et les différents sous répertoires représentant les sources n'est pas nécessaire.
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.
* 0 : L'accès aux journaux est autorisé pour tous les utilisateurs.
* 1 : L'accès aux journaux est restreint 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.

3.4.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.
Le rôle et l'usage exact de cette clé n'est pas connu. Si vous avez plus d'informations au sujet de cette clé, n'hésitez pas, 19 commentaires, ce document sera mis à jour.
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 :
* EVENTLOG_ERROR_TYPE (0x01)
* EVENTLOG_WARNING_TYPE (0x02)
* EVENTLOG_INFORMATION_TYPE (0x04)
* EVENTLOG_AUDIT_SUCCESS (0x08)
* EVENTLOG_AUDIT_FAILURE (0x10)
Le rôle et l'usage exact de cette clé n'est pas connu car même en mettant une valeur 0, tous les types de messages sont supportés. Si vous avez plus d'informations au sujet de cette clé, n'hésitez pas, 19 commentaires, ce document sera mis à jour.
ProviderGuid REG_SZ ou REG_EXPAND_SZ Cette clé est souvent renseignée par certaine 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 certaine 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 certaine 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 certaine 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.

3.5. Récapitulatif de l'organisation dans la base de registre

Les différentes clés de registres peuvent être vues de la manière suivantes :

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

3.6. 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 autre 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 :

Reconstitution d'un Message EventLog
Reconstitution d'un Message EventLog

3.6.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.

3.6.2. Le format du message

Le texte du message se fait en 2 é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.

3.6.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.

3.6.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().
Le nombre de paramètres dynamiques supportés semble être limité à 256.
%%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.

4. 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.

4.1. 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
BackupEventLog Archive le journal spécifié dans un fichier de sauvegarde.
ClearEventLog Vide le journal spécifié avec un archivage éventuel et préalable dans un fichier de sauvegarde.
CloseEventLog Ferme le descripteur en lecture sur un journal EventLog préalablement obtenu par OpenEventLog() ou OpenBackupEventLog().
DeregisterEventSource Ferme le descripteur en écriture sur un journal EventLog préalablement obtenu par RegisterEventSource().
GetEventLogInformation Récupère les caractéristiques d'un journal en utilisant un descripteur obtenu par OpenEventLog() ou RegisterEventSource().
GetNumberOfEventLogRecords Récupère le nombre d'enregistrements dans le journal d'un journal en utilisant un descripteur obtenu par OpenEventLog() ou OpenBackupEventLog().
GetOldestEventLogRecord Récupère le numéro du plus vieil enregistrement du journal en utilisant un descripteur obtenu par OpenEventLog() ou OpenBackupEventLog().
NotifyChangeEventLog Permet à une application de recevoir une notification quand un nouvel enregistrement EventLog est ajouté dans le journal en utilisant un descripteur obtenu par OpenEventLog().
OpenBackupEventLog Ouvre le journal archivé par BackupEventLog().
OpenEventLog Ouvre en lecture le journal spécifié.
ReadEventLog Lit le nombre d'enregistrements dans le journal en utilisant un descripteur obtenu par OpenEventLog().
RegisterEventSource Ouvre en écriture le journal dont la source est spécifiée.
ReportEvent 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).

4.2. 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).

4.3. Le fichier de messages

4.3.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é.
Commentaire
Sélectionnez

;// 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
Sélectionnez

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
Sélectionnez

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
Sélectionnez

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
Sélectionnez

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 valeur 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
Sélectionnez

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
Sélectionnez

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 :

MessageId
Sélectionnez

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
Sélectionnez

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
Sélectionnez

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
Sélectionnez

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 :

Exemple de fichier de définition complet
CacherSélectionnez
  1.  
  2. ;#ifndef __MESSAGES_EventMessageFile_H__ 
  3. ;#define __MESSAGES_EventMessageFile_H__ 
  4. ; 
  5. ;// Ligne de compilation : mc.exe -u$(InputDir)\$(InputName).mc" 
  6. ;//     -u indique un fichier unicode 
  7. ; 
  8.   
  9. MessageIdTypedef = DWORD 
  10.   
  11. Outputbase = 16 
  12.   
  13. LanguageNames = 
  14.     ( 
  15.         LANGUE_FRA   = 0x040c:Messages_0x040c 
  16.         LANGUE_ENG   = 0x0809:Messages_0x0809 
  17.     ) 
  18.   
  19. FacilityNames = 
  20.    ( 
  21.       FACILITY_NULL                    =  0 
  22.       FACILITY_RPC                     =  1 
  23.       FACILITY_DISPATCH                =  2 
  24.       FACILITY_STORAGE                 =  3 
  25.       FACILITY_ITF                     =  4 
  26.       FACILITY_WIN32                   =  7 
  27.       FACILITY_WINDOWS                 =  8 
  28.       FACILITY_SSPI                    =  9 
  29.       FACILITY_SECURITY                =  9 
  30.       FACILITY_CONTROL                 = 10 
  31.       FACILITY_CERT                    = 11 
  32.       FACILITY_INTERNET                = 12 
  33.       FACILITY_MEDIASERVER             = 13 
  34.       FACILITY_MSMQ                    = 14 
  35.       FACILITY_SETUPAPI                = 15 
  36.       FACILITY_SCARD                   = 16 
  37.       FACILITY_COMPLUS                 = 17 
  38.       FACILITY_AAF                     = 18 
  39.       FACILITY_URT                     = 19 
  40.       FACILITY_ACS                     = 20 
  41.       FACILITY_DPLAY                   = 21 
  42.       FACILITY_UMI                     = 22 
  43.       FACILITY_SXS                     = 23 
  44.       FACILITY_WINDOWS_CE              = 24 
  45.       FACILITY_HTTP                    = 25 
  46.       FACILITY_USERMODE_COMMONLOG      = 26 
  47.       FACILITY_USERMODE_FILTER_MANAGER = 31 
  48.       FACILITY_BACKGROUNDCOPY          = 32 
  49.       FACILITY_CONFIGURATION           = 33 
  50.       FACILITY_STATE_MANAGEMENT        = 34 
  51.       FACILITY_METADIRECTORY           = 35 
  52.       FACILITY_WINDOWSUPDATE           = 36 
  53.       FACILITY_DIRECTORYSERVICE        = 37 
  54.       FACILITY_GRAPHICS                = 38 
  55.       FACILITY_SHELL                   = 39 
  56.       FACILITY_TPM_SERVICES            = 40 
  57.       FACILITY_TPM_SOFTWARE            = 41 
  58.       FACILITY_UI                      = 42 
  59.       FACILITY_PLA                     = 48 
  60.       FACILITY_FVE                     = 49 
  61.       FACILITY_FWP                     = 50 
  62.       FACILITY_WINRM                   = 51 
  63.       FACILITY_NDIS                    = 52 
  64.       FACILITY_USERMODE_HYPERVISOR     = 53 
  65.       FACILITY_CMI                     = 54 
  66.       FACILITY_USERMODE_VIRTUALIZATION = 55 
  67.       FACILITY_USERMODE_VOLMGR         = 56 
  68.       FACILITY_BCD                     = 57 
  69.       FACILITY_USERMODE_VHD            = 58 
  70.       FACILITY_SDIAG                   = 60 
  71.       FACILITY_WEBSERVICES             = 61 
  72.       FACILITY_WINDOWS_DEFENDER        = 80 
  73.       FACILITY_OPC                     = 81 
  74.       FACILITY_XPS                     = 82 
  75.       FACILITY_RAS                     = 83 
  76.       FACILITY_MBN                     = 84 
  77.    ) 
  78.   
  79. SeverityNames = 
  80.    ( 
  81.       Sev_Success      = 0x0 
  82.       Sev_Information  = 0x1 
  83.       Sev_Warning      = 0x2 
  84.       Sev_Error        = 0x3 
  85.    ) 
  86.   
  87.   
  88. ;//////////////////////////////////////// 
  89. ;// Events 
  90. ;// 
  91.   
  92. MessageId       = 1000 
  93. SymbolicName    = EVENT_STARTED_BY_1 
  94. Facility        = FACILITY_MBN 
  95. Severity        = Sev_Success 
  96. Language        = LANGUE_FRA 
  97. Succès : L'application '%P02' (%2) a été démarrée par l'utilisateur%1" 
  98. . 
  99. Language        = LANGUE_ENG 
  100. Success : The program '%P02' (%2) has been started by user%1" 
  101. . 
  102.   
  103. MessageId       = +0 
  104. SymbolicName    = EVENT_STARTED_BY_2 
  105. Facility        = FACILITY_NULL 
  106. Severity        = Sev_Information 
  107. Language        = LANGUE_FRA 
  108. Information : L'application '%P02' (%2) a été démarrée par l'utilisateur%1" 
  109. . 
  110. Language        = LANGUE_ENG 
  111. Information : The program '%P02' (%2) has been started by user%1" 
  112. . 
  113.   
  114. MessageId       = +0 
  115. SymbolicName    = EVENT_STARTED_BY_3 
  116. Facility        = FACILITY_NULL 
  117. Severity        = Sev_Warning 
  118. Language        = LANGUE_FRA 
  119. Warning : L'application '%P02' (%2) a été démarrée par l'utilisateur%1" 
  120. . 
  121. Language        = LANGUE_ENG 
  122. Warning : The program '%P02' (%2) has been started by user%1" 
  123. . 
  124.   
  125. MessageId       = +0 
  126. SymbolicName    = EVENT_STARTED_BY_4 
  127. Facility        = FACILITY_NULL 
  128. Severity        = Sev_Error 
  129. Language        = LANGUE_FRA 
  130. Error : L'application '%P02' (%2) a été démarrée par l'utilisateur%1" 
  131. . 
  132. Language        = LANGUE_ENG 
  133. Error : The program '%P02' (%2) has been started by user%1" 
  134. . 
  135.   
  136. MessageId       = +1 
  137. SymbolicName    = EVENT_BACKUP 
  138. Facility        = FACILITY_NULL 
  139. Severity        = Sev_Information 
  140. Language        = LANGUE_FRA 
  141. Vous devriez sauvegarder vos données régulièrement ! 
  142. . 
  143. Language        = LANGUE_ENG 
  144. You should regulary backup your data! 
  145. . 
  146.   
  147. ; 
  148. ;#endif  //__MESSAGES_EventMessageFile_H__ 
  149. ; 

4.3.2. Construction du fichier de ressources

La chaîne de construction des ressources de messages EventLog est la suivante :

chaîne de construction du fichier de messages
chaîne de construction du fichier de messages

4.4. 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 fichier à distance (par protocole Netbios).

5. 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.

5.1. 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.

5.2. 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.

5.3. 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.

5.4. Les sources du projet

5.4.1. Le fichier EventLogWritter.cpp

Le fichier EventLogWritter.cpp
CacherSélectionnez
  1.  
  2. // EventLogConsole.cpp : définit le point d'entrée pour l'application console. 
  3. // 
  4.   
  5. #ifndef _WIN32_WINNT      // Autorise l'utilisation des fonctionnalités spécifiques à Windows XP ou version ultérieure.                    
  6. #define _WIN32_WINNT 0x0501   // Attribuez la valeur appropriée à cet élément pour cibler d'autres versions de Windows. 
  7. #endif                   
  8.   
  9. #define _CRT_SECURE_NO_DEPRECATE 
  10. #define _CRT_NON_CONFORMING_SWPRINTFS 
  11.   
  12. #include <Windows.h> 
  13. #include <stdio.h> 
  14. #include "MessageFile.h" 
  15. #include "CategorieFile.h" 
  16. #include "ParameterFile.h" 
  17.   
  18. // quelques types bien utiles 
  19. typedef wchar_t MCR_CHAR; 
  20. typedef const wchar_t * MCR_CSTR; 
  21. typedef unsigned char MCR_U1; 
  22. typedef unsigned long MCR_U4; 
  23.   
  24. static void _GetSID(SID * Sid) 
  25. { 
  26.    // Open the access token associated with the calling process. 
  27. HANDLE hToken = NULL; 
  28.    OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken); 
  29.   
  30.    // get the size of the memory buffer needed for the SID 
  31. DWORD dwBufferSize = 0; 
  32.    GetTokenInformation(hToken, TokenUser, NULL, 0, &dwBufferSize); 
  33.   
  34. PTOKEN_USER pTokenUser = (PTOKEN_USER)malloc(dwBufferSize); 
  35.    memset(pTokenUser, 0, dwBufferSize); 
  36.   
  37.    // Retrieve the token information in a TOKEN_USER structure. 
  38.    GetTokenInformation(hToken, TokenUser, pTokenUser, dwBufferSize, &dwBufferSize); 
  39.   
  40.    // copy the SID structure 
  41.    memcpy(Sid, pTokenUser->User.Sid, sizeof(*Sid)); 
  42.   
  43.    // free everything 
  44.    free(pTokenUser); 
  45.    CloseHandle(hToken); 
  46. } 
  47.   
  48. // necessite les privilèges d'administration 
  49. // lancer le programme la première fois en tant qu'admin 
  50. static void _add_event_source(MCR_CSTR pszName, const MCR_U4 dwCategoryCount) 
  51. { 
  52. HKEY      hRegKey = NULL;  
  53. MCR_CHAR szPath[ MAX_PATH ]; 
  54.   
  55.    swprintf( szPath, L"SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\%s", pszName ); 
  56.   
  57.    // Create the event source registry key 
  58.    RegCreateKey( HKEY_LOCAL_MACHINE, szPath, &hRegKey ); 
  59.   
  60.    // Name of the PE module that contains the message resource 
  61.    GetModuleFileName( NULL, szPath, MAX_PATH ); 
  62.   
  63.    // remove the last '\' 
  64.    *wcsrchr(szPath, '\\') = 0; 
  65.   
  66.    // Register EventMessageFile 
  67.    MCR_CHAR szFile[ MAX_PATH ]; 
  68.    swprintf( szFile, L"%s\\%s", szPath, L"EventMessageFile.dll" ); 
  69.    RegSetValueEx(   hRegKey, L"EventMessageFile", 0, REG_EXPAND_SZ, 
  70.                   (LPBYTE)szFile, 
  71.                   (MCR_U4)(wcslen( szFile ) + 1) * sizeof szFile[0] );  
  72.   
  73.    // Register ParameterMessageFile 
  74.    swprintf( szFile, L"%s\\%s", szPath, L"EventParameterFile.dll" ); 
  75.    RegSetValueEx(   hRegKey, L"ParameterMessageFile", 0, REG_EXPAND_SZ, 
  76.                   (LPBYTE)szFile, 
  77.                   (MCR_U4)(wcslen( szFile ) + 1) * sizeof szFile[0] );  
  78.   
  79.   
  80.    // Register CategoryMessageFile 
  81.    swprintf( szFile, L"%s\\%s", szPath, L"EventCategorieFile.dll" ); 
  82.    RegSetValueEx(   hRegKey, L"CategoryMessageFile", 0, REG_EXPAND_SZ, 
  83.                   (LPBYTE)szFile, 
  84.                   (MCR_U4)(wcslen( szFile ) + 1) * sizeof szFile[0] );  
  85.   
  86.    // Register supported event types 
  87.    MCR_U4 dwTypes = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE;  
  88.    RegSetValueEx( hRegKey, L"TypesSupported", 0, REG_DWORD, (LPBYTE) &dwTypes, sizeof dwTypes ); 
  89.   
  90.    // Register category count 
  91.    RegSetValueEx( hRegKey, L"CategoryCount", 0, REG_DWORD, (LPBYTE)&dwCategoryCount, sizeof dwCategoryCount ); 
  92.   
  93.    RegCloseKey( hRegKey ); 
  94. } 
  95.   
  96. static void _log_event(MCR_CSTR pszName) 
  97. { 
  98.    // Open the eventlog 
  99.    HANDLE hEventLog = RegisterEventSource( NULL, pszName ); 
  100.   
  101.    // get the sid of the user 
  102. SID sid; 
  103.    _GetSID(&sid); 
  104.    PSID psid = &sid; 
  105.   
  106.    // make dynamic parameters array 
  107.    MCR_CSTR aInsertions[] = { L"Paramètre 1", L"Paramètre 2" }; 
  108.   
  109.    // buffer parameters 
  110.    MCR_U1 buffer[64]; 
  111.    for(MCR_U1 boucle = 0; boucle != sizeof(buffer); boucle++) 
  112.       buffer[boucle] = ' ' + boucle; 
  113.   
  114.    ReportEvent(hEventLog,                  // Handle to the eventlog 
  115.                EVENTLOG_SUCCESS,             // Type of event 
  116.                CATEGORY_TWO,               // Category (could also be 0) 
  117.                EVENT_STARTED_BY_1,         // Event id 
  118.                psid,                       // User's sid (NULL for none) 
  119.                2,                          // Number of insertion strings 
  120.                sizeof(buffer),             // Number of additional bytes 
  121.                aInsertions,                // Array of insertion strings 
  122.                buffer                      // Pointer to additional bytes 
  123.                ); 
  124.   
  125.    // And another one 
  126.    ReportEvent(hEventLog,                  // Handle to the eventlog 
  127.                EVENTLOG_INFORMATION_TYPE,  // Type of event 
  128.                CATEGORY_TWO,               // Category (could also be 0) 
  129.                EVENT_STARTED_BY_2,         // Event id 
  130.                psid,                       // User's sid (NULL for none) 
  131.                2,                          // Number of insertion strings 
  132.                sizeof(buffer),             // Number of additional bytes 
  133.                aInsertions,                // Array of insertion strings 
  134.                buffer                      // Pointer to additional bytes 
  135.                ); 
  136.   
  137.    ReportEvent(hEventLog,                  // Handle to the eventlog 
  138.                EVENTLOG_WARNING_TYPE,       // Type of event 
  139.                CATEGORY_TWO,               // Category (could also be 0) 
  140.                EVENT_STARTED_BY_3,         // Event id 
  141.                psid,                       // User's sid (NULL for none) 
  142.                2,                          // Number of insertion strings 
  143.                sizeof(buffer),             // Number of additional bytes 
  144.                aInsertions,                // Array of insertion strings 
  145.                buffer                      // Pointer to additional bytes 
  146.                ); 
  147.   
  148.    ReportEvent(hEventLog,                  // Handle to the eventlog 
  149.                EVENTLOG_ERROR_TYPE,          // Type of event 
  150.                CATEGORY_TWO,               // Category (could also be 0) 
  151.                EVENT_STARTED_BY_4,         // Event id 
  152.                psid,                       // User's sid (NULL for none) 
  153.                2,                          // Number of insertion strings 
  154.                sizeof(buffer),             // Number of additional bytes 
  155.                aInsertions,                // Array of insertion strings 
  156.                buffer                      // Pointer to additional bytes 
  157.                ); 
  158.   
  159.    ReportEvent(hEventLog,                  // Handle to the eventlog 
  160.                EVENTLOG_AUDIT_SUCCESS,       // Type of event 
  161.                CATEGORY_TWO,               // Category (could also be 0) 
  162.                EVENT_STARTED_BY_1,         // Event id 
  163.                psid,                       // User's sid (NULL for none) 
  164.                2,                          // Number of insertion strings 
  165.                sizeof(buffer),             // Number of additional bytes 
  166.                aInsertions,                // Array of insertion strings 
  167.                buffer                      // Pointer to additional bytes 
  168.                ); 
  169.   
  170.    ReportEvent(hEventLog,                  // Handle to the eventlog 
  171.                EVENTLOG_AUDIT_FAILURE,       // Type of event 
  172.                CATEGORY_ONE,               // Category (could also be 0) 
  173.                EVENT_STARTED_BY_2,         // Event id 
  174.                psid,                       // User's sid (NULL for none) 
  175.                2,                          // Number of insertion strings 
  176.                sizeof(buffer),             // Number of additional bytes 
  177.                aInsertions,                // Array of insertion strings 
  178.                buffer                      // Pointer to additional bytes 
  179.                ); 
  180.   
  181.    ReportEvent(hEventLog,                  // Handle to the eventlog 
  182.                EVENTLOG_SUCCESS,       // Type of event 
  183.                CATEGORY_ONE,               // Category (could also be 0) 
  184.                EVENT_BACKUP,                // Event id 
  185.                NULL,                       // User's sid (NULL for none) 
  186.                2,                          // Number of insertion strings 
  187.                0,                          // Number of additional bytes 
  188.                aInsertions,                // Array of insertion strings 
  189.                NULL                        // Pointer to additional bytes 
  190.                ); 
  191.   
  192.    // Close eventlog 
  193.    DeregisterEventSource( hEventLog ); 
  194. } 
  195.   
  196. int wmain(int /*argc*/, wchar_t * /*argv*/ []) 
  197. { 
  198.    // register the source EventLog 
  199.    // should be done during the setup procedure by the setup 
  200.    // but in this sample, will be done every times 
  201.    _add_event_source(L"TestApp", LAST_CATEGORY); 
  202.   
  203.    _log_event(L"TestApp"); 
  204.   
  205.    return 0; 
  206. } 

5.4.2. Le fichier CategoryFile.mc

Le fichier CategoryFile.mc
CacherSélectionnez
  1.  
  2. ;#ifndef __MESSAGES_EventCategorieFile_H__ 
  3. ;#define __MESSAGES_EventCategorieFile_H__ 
  4. ; 
  5. ;// Ligne de compilation : mc.exe -u "$(InputDir)\$(InputName).mc" 
  6. ;//     -u indique un fichier unicode 
  7. ; 
  8.   
  9. MessageIdTypedef = DWORD 
  10.   
  11. Outputbase = 10 
  12.   
  13. LanguageNames = 
  14.     ( 
  15.         LANGUE_FRA   = 0x040c:Messages_0x040c 
  16.         LANGUE_ENG   = 0x0809:Messages_0x0809 
  17.     ) 
  18.   
  19. ;//////////////////////////////////////// 
  20. ;// Eventlog categories 
  21. ;// 
  22. ;// Categories always have to be the first entries in a message file! 
  23. ;// 
  24.   
  25. MessageId       = +1 
  26. SymbolicName    = CATEGORY_ONE 
  27. Language        = LANGUE_FRA 
  28. Première catégorie d'événements 
  29. . 
  30. Language        = LANGUE_ENG 
  31. First category of events 
  32. . 
  33.   
  34. MessageId       = +1 
  35. SymbolicName    = CATEGORY_TWO 
  36. Language        = LANGUE_FRA 
  37. Seconde catégorie d'événements 
  38. . 
  39. Language        = LANGUE_ENG 
  40. Second category of events 
  41. . 
  42.   
  43. MessageId       = +1 
  44. SymbolicName    = LAST_CATEGORY 
  45. Language        = LANGUE_FRA 
  46. Repère pour la dernière catégorie d'événements 
  47. . 
  48. Language        = LANGUE_ENG 
  49. Tag for the last category of events 
  50. . 
  51.   
  52. ; 
  53. ;#endif  //__MESSAGES_EventCategorieFile_H__ 
  54. ; 

5.4.3. Le fichier MessageFile.mc

Le fichier MessageFile.mc
CacherSélectionnez
  1.  
  2. ;#ifndef __MESSAGES_EventMessageFile_H__ 
  3. ;#define __MESSAGES_EventMessageFile_H__ 
  4. ; 
  5. ;// Ligne de compilation : mc.exe -u "$(InputDir)\$(InputName).mc" 
  6. ;//     -u indique un fichier unicode 
  7. ; 
  8.   
  9. MessageIdTypedef = DWORD 
  10.   
  11. Outputbase = 16 
  12.   
  13. LanguageNames = 
  14.     ( 
  15.         LANGUE_FRA   = 0x040c:Messages_0x040c 
  16.         LANGUE_ENG   = 0x0809:Messages_0x0809 
  17.     ) 
  18.   
  19. FacilityNames = 
  20.    ( 
  21.       FACILITY_NULL                    =  0 
  22.       FACILITY_RPC                     =  1 
  23.       FACILITY_DISPATCH                =  2 
  24.       FACILITY_STORAGE                 =  3 
  25.       FACILITY_ITF                     =  4 
  26.       FACILITY_WIN32                   =  7 
  27.       FACILITY_WINDOWS                 =  8 
  28.       FACILITY_SSPI                    =  9 
  29.       FACILITY_SECURITY                =  9 
  30.       FACILITY_CONTROL                 = 10 
  31.       FACILITY_CERT                    = 11 
  32.       FACILITY_INTERNET                = 12 
  33.       FACILITY_MEDIASERVER             = 13 
  34.       FACILITY_MSMQ                    = 14 
  35.       FACILITY_SETUPAPI                = 15 
  36.       FACILITY_SCARD                   = 16 
  37.       FACILITY_COMPLUS                 = 17 
  38.       FACILITY_AAF                     = 18 
  39.       FACILITY_URT                     = 19 
  40.       FACILITY_ACS                     = 20 
  41.       FACILITY_DPLAY                   = 21 
  42.       FACILITY_UMI                     = 22 
  43.       FACILITY_SXS                     = 23 
  44.       FACILITY_WINDOWS_CE              = 24 
  45.       FACILITY_HTTP                    = 25 
  46.       FACILITY_USERMODE_COMMONLOG      = 26 
  47.       FACILITY_USERMODE_FILTER_MANAGER = 31 
  48.       FACILITY_BACKGROUNDCOPY          = 32 
  49.       FACILITY_CONFIGURATION           = 33 
  50.       FACILITY_STATE_MANAGEMENT        = 34 
  51.       FACILITY_METADIRECTORY           = 35 
  52.       FACILITY_WINDOWSUPDATE           = 36 
  53.       FACILITY_DIRECTORYSERVICE        = 37 
  54.       FACILITY_GRAPHICS                = 38 
  55.       FACILITY_SHELL                   = 39 
  56.       FACILITY_TPM_SERVICES            = 40 
  57.       FACILITY_TPM_SOFTWARE            = 41 
  58.       FACILITY_UI                      = 42 
  59.       FACILITY_PLA                     = 48 
  60.       FACILITY_FVE                     = 49 
  61.       FACILITY_FWP                     = 50 
  62.       FACILITY_WINRM                   = 51 
  63.       FACILITY_NDIS                    = 52 
  64.       FACILITY_USERMODE_HYPERVISOR     = 53 
  65.       FACILITY_CMI                     = 54 
  66.       FACILITY_USERMODE_VIRTUALIZATION = 55 
  67.       FACILITY_USERMODE_VOLMGR         = 56 
  68.       FACILITY_BCD                     = 57 
  69.       FACILITY_USERMODE_VHD            = 58 
  70.       FACILITY_SDIAG                   = 60 
  71.       FACILITY_WEBSERVICES             = 61 
  72.       FACILITY_WINDOWS_DEFENDER        = 80 
  73.       FACILITY_OPC                     = 81 
  74.       FACILITY_XPS                     = 82 
  75.       FACILITY_RAS                     = 83 
  76.       FACILITY_MBN                     = 84 
  77.    ) 
  78.   
  79. SeverityNames = 
  80.    ( 
  81.       Sev_Success      = 0x0 
  82.       Sev_Information  = 0x1 
  83.       Sev_Warning      = 0x2 
  84.       Sev_Error        = 0x3 
  85.    ) 
  86.   
  87.   
  88. ;//////////////////////////////////////// 
  89. ;// Events 
  90. ;// 
  91.   
  92. MessageId     = 1000 
  93. SymbolicName  = EVENT_STARTED_BY_1 
  94. Facility      = FACILITY_NULL 
  95. Severity      = Sev_Success 
  96. Language      = LANGUE_FRA 
  97. Succès : L'application '%P01' (%1) a été démarrée par l'utilisateur "%2" 
  98. . 
  99. Language      = LANGUE_ENG 
  100. Success : The program '%P01' (%1) has been started by user "%2" 
  101. . 
  102.   
  103. MessageId     = +0 
  104. SymbolicName  = EVENT_STARTED_BY_2 
  105. Facility      = FACILITY_NULL 
  106. Severity      = Sev_Information 
  107. Language      = LANGUE_FRA 
  108. Information : L'application '%P01' (%2) a été démarrée par l'utilisateur "%1" 
  109. . 
  110. Language      = LANGUE_ENG 
  111. Information : The program '%P01' (%2) has been started by user "%1" 
  112. . 
  113.   
  114. MessageId     = +0 
  115. SymbolicName  = EVENT_STARTED_BY_3 
  116. Facility      = FACILITY_NULL 
  117. Severity      = Sev_Warning 
  118. Language      = LANGUE_FRA 
  119. Warning : L'application '%P01' (%1) a été démarrée par l'utilisateur "%2" 
  120. . 
  121. Language      = LANGUE_ENG 
  122. Warning : The program '%P01' (%1) has been started by user "%2" 
  123. . 
  124.   
  125. MessageId     = +0 
  126. SymbolicName  = EVENT_STARTED_BY_4 
  127. Facility      = FACILITY_NULL 
  128. Severity      = Sev_Error 
  129. Language      = LANGUE_FRA 
  130. Error : L'application '%P01' (%2) a été démarrée par l'utilisateur "%1" 
  131. . 
  132. Language      = LANGUE_ENG 
  133. Error : The program '%P01' (%2) has been started by user "%1" 
  134. . 
  135.   
  136. MessageId     = +1 
  137. SymbolicName  = EVENT_BACKUP 
  138. Facility      = FACILITY_NULL 
  139. Severity      = Sev_Information 
  140. Language      = LANGUE_FRA 
  141. Vous devriez sauvegarder vos données régulièrement ! 
  142. . 
  143. Language      = LANGUE_ENG 
  144. You should regulary backup your data! 
  145. . 
  146.   
  147. ; 
  148. ;#endif  //__MESSAGES_EventMessageFile_H__ 
  149. ; 

5.4.4. Le fichier ParameterFile.mc

Le fichier ParameterFile.mc
CacherSélectionnez
  1.  
  2. ;#ifndef __MESSAGES_EventParameterFile_H__ 
  3. ;#define __MESSAGES_EventParameterFile_H__ 
  4. ; 
  5. ;// Ligne de compilation : mc.exe -u "$(InputDir)\$(InputName).mc" 
  6. ;//     -u indique un fichier unicode 
  7. ; 
  8.   
  9. MessageIdTypedef = DWORD 
  10.   
  11. Outputbase = 10 
  12.   
  13. LanguageNames = 
  14.     ( 
  15.         NEUTRAL = 0x0000:Messages_0x0000 
  16.     ) 
  17.   
  18. MessageId     = 5001 
  19. SymbolicName  = PARAMETER_5001 
  20. Language      = NEUTRAL 
  21. 'Test %P02' 
  22. . 
  23.   
  24. MessageId     = 5002 
  25. SymbolicName  = PARAMETER_5002 
  26. Language      = NEUTRAL 
  27. EventLogWritter.exe 
  28. . 
  29.   
  30. ; 
  31. ;#endif  //__MESSAGES_EventParameterFile_H__ 
  32. ; 

6. 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.

6.1. 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.

6.2. 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.

6.3. 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éé 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.

6.4. Le fichier EventLogReader.cpp

Le fichier EventLogReader.cpp
CacherSélectionnez
  1.  
  2. // EventLogReader.cpp : définit le point d'entrée pour l'application console. 
  3. // 
  4.   
  5. #ifndef _WIN32_WINNT      // Autorise l'utilisation des fonctionnalités spécifiques à Windows XP ou version ultérieure.                    
  6. #define _WIN32_WINNT 0x0501   // Attribuez la valeur appropriée à cet élément pour cibler d'autres versions de Windows. 
  7. #endif 
  8.   
  9. #define _CRT_SECURE_NO_DEPRECATE 
  10. #define _CRT_NON_CONFORMING_SWPRINTFS 
  11.   
  12. #include <Windows.h> 
  13. #include <stdio.h> 
  14. #include <locale.h> 
  15. #include <Sddl.h> 
  16.   
  17. #define JOURNAL L"Application" 
  18.   
  19. const wchar_t * _read_registry(const wchar_t * Source, const wchar_t * Key) 
  20. { 
  21. wchar_t folder[ MAX_PATH ]; 
  22.    swprintf( folder, L"SYSTEM\\CurrentControlSet\\Services\\EventLog\\%s\\%s", JOURNAL, Source ); 
  23.   
  24.    // Create the event source registry key 
  25. HKEY      hRegKey = NULL;  
  26.    RegOpenKey( HKEY_LOCAL_MACHINE, folder, &hRegKey ); 
  27.   
  28.    // read the value 
  29. DWORD type; 
  30. static BYTE buffer[4096]; 
  31. DWORD size = sizeof(buffer); 
  32.    RegQueryValueEx(hRegKey, Key, 0,   &type, buffer,   &size); 
  33.    if(type == REG_SZ) 
  34.    { 
  35.       return (const wchar_t *)buffer; 
  36.    } 
  37.   
  38.    if(type == REG_EXPAND_SZ) 
  39.    { 
  40.       static wchar_t expanded[4096]; 
  41.       DWORD Nb = ExpandEnvironmentStrings((const wchar_t *)buffer, expanded, sizeof(expanded) / sizeof(expanded[0])); 
  42.       buffer[Nb] = 0; 
  43.       return (const wchar_t *)expanded; 
  44.    } 
  45.   
  46.    // unknown type 
  47.    return L"???"; 
  48. } 
  49.   
  50. const wchar_t * _load_string( const wchar_t * Dll, DWORD Id ) 
  51. { 
  52.    // load the library 
  53.    HMODULE lib = LoadLibrary(Dll); 
  54.   
  55.    // reset the message 
  56. static wchar_t Message [4096]; 
  57.   
  58.    // compute the flag of FormatMessage() 
  59.    DWORD flags = FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS; 
  60.   
  61.    // load the message 
  62.    DWORD nb_tchar = FormatMessage(flags, lib, Id, 0, Message, sizeof(Message) / sizeof(Message[0]), NULL); 
  63.    Message[nb_tchar] = 0; 
  64.   
  65.    // unload the library 
  66.    FreeLibrary(lib); 
  67.   
  68.    // remove any bad trailing characters (CR, LF) 
  69.    while(nb_tchar > 0) 
  70.    { 
  71.       nb_tchar--; 
  72.       if(Message[nb_tchar] == '\n') 
  73.       { 
  74.          Message[nb_tchar] = 0; 
  75.          continue; 
  76.       } 
  77.   
  78.       if(Message[nb_tchar] == '\r') 
  79.       { 
  80.          Message[nb_tchar] = 0; 
  81.          continue; 
  82.       } 
  83.   
  84.       break; 
  85.    } 
  86.   
  87.    return Message; 
  88. } 
  89.   
  90. const wchar_t * _get_category_string( const wchar_t * Source, DWORD Id ) 
  91. { 
  92.    // get the DLL to open 
  93.    const wchar_t * dll = _read_registry( Source, L"CategoryMessageFile" ); 
  94.   
  95.    // copy the message string 
  96.    const wchar_t * msg = _load_string(dll, Id); 
  97.   
  98.    return msg; 
  99. } 
  100.   
  101. const wchar_t * _get_parameter_string( const wchar_t * Source, DWORD Id ) 
  102. { 
  103.    // get the DLL to open 
  104.    const wchar_t * dll = _read_registry( Source, L"ParameterMessageFile" ); 
  105.   
  106.    // copy the message string 
  107.    const wchar_t * msg = _load_string(dll, Id); 
  108.   
  109.    return msg; 
  110. } 
  111.   
  112. const wchar_t * _replace_static(const wchar_t * Source, const wchar_t * buffer, const wchar_t * begin, DWORD size, DWORD id) 
  113. { 
  114. static wchar_t buf[4096]; 
  115.   
  116.    // copy the begin of the string 
  117.    DWORD len = (DWORD)(begin - buffer); 
  118.    memcpy(buf, buffer, len * sizeof(wchar_t)); 
  119.    buf[len] = 0; 
  120.   
  121.    // add the new replacement string 
  122.    wcscat(buf, _get_parameter_string(Source, id)); 
  123.   
  124.    // copy the end of the string 
  125.    wcscat(buf, &begin[size]); 
  126.   
  127.    return buf; 
  128. } 
  129.   
  130. const wchar_t * _replace_dynamic(const wchar_t * buffer, const wchar_t * begin, DWORD size, const wchar_t * Str) 
  131. { 
  132. static wchar_t buf[4096]; 
  133.   
  134.    // copy the begin of the string 
  135.    DWORD len = (DWORD)(begin - buffer); 
  136.    memcpy(buf, buffer, len * sizeof(wchar_t)); 
  137.    buf[len] = 0; 
  138.   
  139.    // add the new replacement string 
  140.    wcscat(buf, Str); 
  141.   
  142.    // copy the end of the string 
  143.    wcscat(buf, &begin[size]); 
  144.   
  145.    return buf; 
  146. } 
  147.   
  148. const wchar_t * _get_message_string( const wchar_t * Source, DWORD Id, const wchar_t * String[]) 
  149. { 
  150.    // get the DLL to open 
  151.    const wchar_t * dll = _read_registry( Source, L"EventMessageFile" ); 
  152.   
  153.    const wchar_t * msg = _load_string(dll, Id); 
  154.    static wchar_t buffer[4096]; 
  155.    wcscpy(buffer, msg); 
  156.   
  157.    const wchar_t * p = wcschr(buffer, '%'); 
  158.    DWORD id; 
  159.    const wchar_t * begin; 
  160.    DWORD size; 
  161.    while(p != NULL) 
  162.    { 
  163.       begin = p; 
  164.       p++; 
  165.       size = 1; 
  166.       if(*p == '%') 
  167.       { 
  168.          // this is the ID of a static parameter string 
  169.          p++; 
  170.          size++; 
  171.          id = 0; 
  172.          while( (*p >= '0') && (*p <= '9') ) 
  173.          { 
  174.             id *= 10; 
  175.             id += *p - '0'; 
  176.             p++; 
  177.             size++; 
  178.          } 
  179.   
  180.          // replace the string 
  181.          wcscpy(buffer, _replace_static(Source, buffer, begin, size, id)); 
  182.          p = wcschr(buffer, '%'); 
  183.          continue; 
  184.       } 
  185.       else 
  186.       { 
  187.          // this is the number of a dynamic parameter 
  188.          id = 0; 
  189.          while( (*p >= '0') && (*p <= '9') ) 
  190.          { 
  191.             id *= 10; 
  192.             id += *p - '0'; 
  193.             p++; 
  194.             size++; 
  195.          } 
  196.          if(id == 0) break; 
  197.   
  198.          // replace the string 
  199.          wcscpy(buffer, _replace_dynamic(buffer, begin, size, String[id - 1])); 
  200.          p = wcschr(buffer, '%'); 
  201.          continue; 
  202.       } 
  203.    } 
  204.   
  205.    return buffer; 
  206. } 
  207.   
  208. const wchar_t * _get_sid( PSID Sid ) 
  209. { 
  210.    if(Sid == NULL) 
  211.       return L"Non disponible"; 
  212.   
  213. wchar_t *str; 
  214.    ConvertSidToStringSid( Sid, &str); 
  215.   
  216. static wchar_t buf[4096]; 
  217.    wcscpy(buf, str); 
  218.    LocalFree(str); 
  219.    return buf; 
  220. } 
  221.   
  222. void _analyse( EVENTLOGRECORD * p_eventlog ) 
  223. { 
  224.    wprintf(L"EventLog RecordNumber = %lu\n", p_eventlog->RecordNumber ); 
  225.   
  226.    wprintf(L"   Length = %lu\n", p_eventlog->Length ); 
  227.   
  228.    wprintf(L"   EventID = 0x%.8x\n", p_eventlog->EventID ); 
  229.    wprintf(L"      Code(0x%.4x) ", p_eventlog->EventID & 0xffff); 
  230.    wprintf(L"Facility(0x%.3x) ", (p_eventlog->EventID & 0x0fff0000) >> 16); 
  231.    wprintf(L"Reserved(0x%.1x) ", (p_eventlog->EventID & 0x10000000) >> 28); 
  232.    wprintf(L"Customer(0x%.1x) ", (p_eventlog->EventID & 0x20000000) >> 29); 
  233.    wprintf(L"Severity(0x%.1x) ", (p_eventlog->EventID & 0xc0000000) >> 30); 
  234.    wprintf(L"\n"); 
  235.   
  236.    wprintf(L"   EventCategory = %lu\n", p_eventlog->EventCategory ); 
  237.   
  238.    // affichage des types 
  239.    wprintf(L"   EventType = 0x%.4x ==> ", p_eventlog->EventType ); 
  240.    if( (p_eventlog->EventType & EVENTLOG_WARNING_TYPE) == EVENTLOG_WARNING_TYPE) wprintf(L"Warning "); 
  241.    if( (p_eventlog->EventType & EVENTLOG_ERROR_TYPE) == EVENTLOG_ERROR_TYPE) wprintf(L"Error "); 
  242.    if( (p_eventlog->EventType & EVENTLOG_INFORMATION_TYPE) == EVENTLOG_INFORMATION_TYPE) wprintf(L"Information "); 
  243.    if( (p_eventlog->EventType & EVENTLOG_AUDIT_FAILURE) == EVENTLOG_AUDIT_FAILURE) wprintf(L"Audit failure "); 
  244.    if( (p_eventlog->EventType & EVENTLOG_AUDIT_SUCCESS) == EVENTLOG_AUDIT_SUCCESS) wprintf(L"Audit success "); 
  245.    wprintf(L"\n"); 
  246.   
  247.    wprintf(L"   Reserved = %lu\n", p_eventlog->Reserved ); 
  248.    wprintf(L"   TimeGenerated = %lu\n", p_eventlog->TimeGenerated ); 
  249.    wprintf(L"   TimeWritten   = %lu\n", p_eventlog->TimeWritten ); 
  250.    wprintf(L"   ReservedFlags = %lu\n", p_eventlog->ReservedFlags ); 
  251.    wprintf(L"   ClosingRecordNumber = %lu\n", p_eventlog->ClosingRecordNumber ); 
  252.   
  253.    wprintf(L"   NumStrings = %lu\n", p_eventlog->NumStrings ); 
  254.    wprintf(L"   StringOffset = %lu\n", p_eventlog->StringOffset ); 
  255.   
  256.    // affichage des strings 
  257.    const wchar_t * String[100]; 
  258.    DWORD offset = p_eventlog->StringOffset; 
  259.    for(DWORD boucle = 0; boucle != p_eventlog->NumStrings; boucle++) 
  260.    { 
  261.       const BYTE *p = (BYTE *)p_eventlog; 
  262.       p += offset; 
  263.       const wchar_t * str = (const wchar_t *)p; 
  264.       String[boucle] = (const wchar_t *)p; 
  265.       offset += (DWORD)(wcslen(str) + 1) * sizeof(wchar_t); 
  266.    } 
  267.    for(DWORD boucle = 0; boucle != p_eventlog->NumStrings; boucle++) 
  268.    { 
  269.       wprintf(L"      String[%lu] = [%s]\n", boucle, String[boucle] ); 
  270.    } 
  271.   
  272.    // extraction du SID 
  273.    wprintf(L"   UserSidLength = %lu\n", p_eventlog->UserSidLength ); 
  274.    wprintf(L"   UserSidOffset = %lu\n", p_eventlog->UserSidOffset ); 
  275.    PSID psid = NULL; 
  276.    if(p_eventlog->UserSidLength != 0) 
  277.       psid = (PSID)((BYTE *)p_eventlog + p_eventlog->UserSidOffset); 
  278.   
  279.    // affichage des data 
  280.    wprintf(L"   DataLength = %lu\n", p_eventlog->DataLength ); 
  281.    wprintf(L"   DataOffset = %lu\n", p_eventlog->DataOffset ); 
  282.   
  283.    const BYTE * p = (BYTE *)p_eventlog; 
  284.    p += p_eventlog->DataOffset; 
  285.    wprintf(L"      "); 
  286.    for(DWORD boucle = 0; boucle != p_eventlog->DataLength; boucle++) 
  287.    { 
  288.       wprintf(L"%.2x ", *p ); 
  289.       p++; 
  290.    } 
  291.    wprintf(L"\n"); 
  292.   
  293.    // get the source name 
  294.    offset = sizeof ( EVENTLOGRECORD ); 
  295.    p = (BYTE *)p_eventlog; 
  296.    p += offset; 
  297.    const wchar_t * SourceName = (const wchar_t *)p; 
  298.    wprintf(L"   SourceName = [%s]\n", SourceName ); 
  299.   
  300.    // get the computer name 
  301.    offset = (DWORD)(wcslen(SourceName) + 1) * sizeof ( wchar_t ); 
  302.    p += offset; 
  303.    const wchar_t * ComputerName = (const wchar_t *)p; 
  304.    wprintf(L"   ComputerName = [%s]\n", ComputerName ); 
  305.   
  306.    // get the category string 
  307.    wprintf(L"   Category string = [%s]\n", _get_category_string( SourceName, p_eventlog->EventCategory ) ); 
  308.    wprintf(L"   Message string = [%s]\n", _get_message_string( SourceName, p_eventlog->EventID, String ) ); 
  309.    wprintf(L"   SID = [%s]\n", _get_sid( psid ) ); 
  310.   
  311.    wprintf(L"\n"); 
  312. } 
  313.   
  314. int wmain(int /*argc*/, wchar_t * /*argv*/ []) 
  315. { 
  316.    // the console can display french caracters 
  317.    _wsetlocale( LC_ALL, L"French" ); 
  318.   
  319.    // choose the resource language 
  320.    WORD PrimaryLanguage = LANG_ENGLISH; 
  321.    WORD SubLanguage = SUBLANG_ENGLISH_UK; 
  322.   
  323.    // for french language 
  324.    // if you prefer english language, comment these 2 lines 
  325.    PrimaryLanguage = LANG_FRENCH; 
  326.    SubLanguage = SUBLANG_FRENCH; 
  327.   
  328.    // set the preferred language for UI 
  329.    SetThreadUILanguage ( MAKELANGID (PrimaryLanguage, SubLanguage) ); 
  330.   
  331.    // ouverture du journal local d'événements de la localisation "Application" 
  332.    HANDLE hlog = OpenEventLog( NULL, JOURNAL ); 
  333.   
  334.    // get the oldest log number 
  335.    DWORD OldestLog = (DWORD)-1; 
  336.    GetOldestEventLogRecord( hlog, &OldestLog ); 
  337.   
  338.    // Get the record number of the event log record. 
  339.    DWORD NbLog = (DWORD)-1; 
  340.    GetNumberOfEventLogRecords( hlog, &NbLog); 
  341.   
  342.    // the idx where to go 
  343.    DWORD last_idx = NbLog + OldestLog - 1; 
  344.   
  345.    // the buffer where to receive data 
  346.    BYTE buffer[1024]; 
  347.    DWORD nb_read; 
  348.    DWORD nb_needed; 
  349.    DWORD nb_eventlog = 0; 
  350.   
  351.    // go to the last record 
  352.    ReadEventLog(   hlog,                                             // event log handle 
  353.                   EVENTLOG_SEEK_READ | EVENTLOG_FORWARDS_READ,      // reads forward, sequential read 
  354.                   last_idx,                                       // index log to read 
  355.                   buffer,                                          // pointer to buffer 
  356.                   sizeof( buffer ),                                 // size of buffer 
  357.                   &nb_read,                                       // number of bytes read 
  358.                   &nb_needed );                                    // number of bytes needed 
  359.   
  360.    // creation d'un evenement pour attente bloquante 
  361.    HANDLE hevent = CreateEvent(NULL, FALSE, FALSE, L"Event" ); 
  362.   
  363.    // associe eventlog et event 
  364.    NotifyChangeEventLog ( hlog, hevent ); 
  365.   
  366.    for( ; ; ) 
  367.    { 
  368.       WaitForSingleObject( hevent, INFINITE ); 
  369.   
  370.       // lecture de l'evenement 
  371.       while(ReadEventLog(   hlog,                                                // event log handle 
  372.                            EVENTLOG_SEQUENTIAL_READ | EVENTLOG_FORWARDS_READ,   // reads forward, sequential read 
  373.                            0,                                                   // index log to read 
  374.                            buffer,                                             // pointer to buffer 
  375.                            sizeof( buffer ),                                    // size of buffer 
  376.                            &nb_read,                                          // number of bytes read 
  377.                            &nb_needed ) == TRUE)                              // number of bytes needed 
  378.       { 
  379.          // extraction de tous les evenements (il peut y en avoir plusieurs) 
  380.          DWORD idx = 0; 
  381.          while(nb_read != 0) 
  382.          { 
  383.             EVENTLOGRECORD *p_eventlog = (EVENTLOGRECORD *) &buffer[ idx ]; 
  384.   
  385.             DWORD Size = p_eventlog->Length; 
  386.             nb_read -= Size; 
  387.             idx += Size; 
  388.   
  389.             // analyse de l'evenement pointé par p_event 
  390.             _analyse( p_eventlog ); 
  391.   
  392.             nb_eventlog++; 
  393.          } 
  394.       } 
  395.   
  396.       // read only 7 eventlog (test purpose) 
  397.       if(nb_eventlog >= 7) 
  398.          break; 
  399.    } 
  400.   
  401.    // fermeture de l'event log 
  402.    CloseEventLog( hlog ); 
  403.   
  404.    // destruction de l'event 
  405.    CloseHandle( hevent ); 
  406.   
  407.    return 0; 
  408. } 

7. 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.

7.1. 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.

7.2. Remerciements

Je tiens à remercier 3DArchi et renaud26 pour leurs conseils avisés lors de la relecture de ce tutoriel.