IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

La journalisation dans le monde Microsoft, découvrez l'API EventLog

Image non disponible

Ce document a pour but de présenter l'API Microsoft EventLog de journalisation dans l'environnement Microsoft Windows. Il présente aussi deux exemples écrits en C, un programme de génération de messages EventLog et un programme de récupération des messages EventLog.

Votre avis et vos suggestions sur ce tutoriel m'intéressent ! Alors après votre lecture, n'hésitez pas : 19 commentaires

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Le but de ce document est de présenter la journalisation, les concepts utilisés ainsi que l'API de programmation de la journalisation dans le monde Microsoft.

Ce document est organisé en plusieurs chapitres.

  • Le chapitre 2 présente le rôle de la journalisation de manière générale.
  • Le chapitre 3 présente les spécificités introduites et les concepts manipulés par le sous-système EventLog Microsoft dans la fonctionnalité de journalisation.
  • Le chapitre 4 présente l'API Microsoft permettant de manipuler la journalisation aussi bien en mode écriture/génération qu'en mode lecture/analyse.
  • Le chapitre 5 présente un exemple complet de code permettant de générer des messages EventLog.
  • Le chapitre 6 présente un exemple complet de code permettant de récupérer et analyser les messages EventLog.

II. La journalisation

II-A. Quelques définitions

La journalisation est l'action d'enregistrer dans un journal tout ou partie des évènements qui se produisent dans un système informatique pendant son fonctionnement.

Il est possible de définir deux types de journalisation :

  • la journalisation système : ce type de journalisation désigne l'enregistrement chronologique des évènements survenant au niveau des composants du système. Le niveau de cette journalisation peut être paramétré, afin de filtrer les différents évènements selon leur catégorie de gravité ;
  • la journalisation applicative : ce type de journalisation désigne l'enregistrement chronologique des opérations de la logique métier pendant le fonctionnement de l'application. Un journal applicatif est lui-même une exigence du métier. Il est donc défini comme une fonctionnalité faisant partie de la logique applicative. Par conséquent, il ne devrait pas être arrêté pendant le fonctionnement de l'application.

II-B. Pourquoi journaliser ?

Le rôle et les buts de la journalisation sont multiples et souvent liés ou imposés par des contraintes de sécurité.

  • Légal : Dans ce cadre, le but de la journalisation est de fournir un élément de preuve juridique. Par exemple, les fournisseurs d'accès à Internet doivent conserver et archiver les journaux concernant les connexions de leurs utilisateurs afin de pouvoir répondre à une requête judiciaire.
  • Statistique : La journalisation permet aussi de fournir des informations statistiques concernant l'usage. Un administrateur de serveur WWW par exemple peut légitimement se demander « Quelle est la page WWW de mon site qui est la plus visitée ? », ou encore, « D'où proviennent majoritairement les requêtes ? »
  • Analyse : Enfin, la journalisation permet d'analyser un problème et de trouver les enchaînements qui ont provoqué le problème. « Que s'est-il passé sur le réseau à 11H27 juste avant que le serveur DNS ne s'arrête ? », ou encore, « Quelle requête HTTP a bien pu générer un plantage de mon serveur WWW ? »

Comme on peut le voir, les buts de la journalisation sont multiples. Suivant les buts recherchés, le profil des personnes qui liront les journaux sera différent.

II-C. Contraintes liées à la journalisation

Certaines contraintes doivent (ou devraient) s'appliquer à la journalisation.

  • Intégrité : L'intégrité de la journalisation ne doit pas pouvoir être remise en cause. Ce qui est archivé dans les journaux doit être ce que l'application ou le système a journalisé. Il ne devrait pas être possible de modifier un élément de la journalisation a posteriori.
  • Complétude : L'archivage de la journalisation doit impérativement comporter tous les évènements reçus. Si pour une raison quelconque (surcharge de la machine par exemple), un évènement venait à être perdu, il y aurait incomplétude de la journalisation. Un évènement perdu ou absent peut entrainer une mauvaise analyse d'un problème ou encore la non-prise en compte de la journalisation en tant que preuve juridique valide. Comme il n'est pas toujours possible d'avoir une complétude forte, il faut au moins que le système mis en place puisse détecter la perte ou l'altération d'un ou plusieurs évènements, on parlera dans ce cas-là de complétude faible.
  • Cohérence : Afin de faciliter une éventuelle analyse, il est important que les évènements journalisés soient cohérents. Cela veut dire par exemple que la date système de la machine doit être synchronisée afin que la séquence des différents évènements conduisant au problème puisse être ordonnée.
  • Utilité : Un évènement de journalisation doit contenir de l'information utile et riche. En effet, Erreur mémoire lors de l'allocation d'un buffer de 112 octets n'est pas très utile pour comprendre un éventuel problème s'il n'est pas couplé avec un message de plus haut niveau du genre : Erreur système lors du traitement de la connexion de l'utilisateur 'Administrateur'.
  • Confidentialité : Les informations contenues dans les évènements présentent un certain caractère de confidentialité. Il importe donc que les moyens utilisés pour transporter et archiver ces évènements garantissent cette confidentialité.

II-D. Les différentes méthodes de journalisation

Il existe plusieurs méthodes permettant de transporter et d'archiver les différents évènements de journalisation. Le choix et l'usage de ces méthodes sont fonction de l'environnement et de l'application.

  • Fichier plat : C'est probablement la plus simple des méthodes. Les évènements sont stockés dans un fichier en général au format texte. La gestion de ce fichier (droits du fichier, format d'écriture, durée de rétention …) est entièrement laissée à l'application ou même au système d'exploitation.
  • Syslog : C'est la méthode de journalisation historique utilisée dans le monde Unix. Cette méthode comprend un logiciel de collecte et d'archivage (c'est le démon syslogd par exemple), un protocole réseau (défini par le RFC 3164) et une API de programmation permettant de s'interfacer avec le protocole Syslog (voir openlog(), syslog() et closelog()). Plus d'informations sur le protocole Syslog ici.
  • Base de données : Les évènements de journalisation sont stockés dans une base de données.
  • Le sous-système EventLog : Ce système de journalisation est utilisé dans le monde Microsoft.

Ce document a pour but de présenter la journalisation dans le monde Microsoft et le sous-système EventLog.

III. Le sous-système EventLog

La journalisation dans le monde Microsoft est réalisée par le sous-système EventLog. Ce sous-système est implémenté par le service Windows EventLog.

Jusqu'à Windows XP, il n'était pas possible d'arrêter ce sous-système. Toutefois, il est possible de modifier le Type de démarrage du service en mettant « Désactivé » et de redémarrer la machine. Le service ne sera alors pas lancé.

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 deux API (Application Programming Interface) pour s'interfacer avec le sous-système EventLog.

  • L'API Event Logging. Cette API est disponible depuis Windows 3.1.
  • L'API Windows Event Log. Cette API est disponible depuis Windows Vista et Windows Server 2008. Cette API doit, à terme, remplacer l'API Event Logging.

Une difficulté introduite par ces deux API est que leurs noms sont très proches et qu'il est très facile de se tromper.

Dans la suite de ce document, nous ne parlerons que de l'API Event Logging. La nouvelle API Windows Event Log introduite à partir de Windows Vista fera l'objet d'une autre présentation ou d'une modification de ce document.

III-A. Architecture simplifiée

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

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 trois journaux dans lesquels les messages EventLog sont enregistrés. Ces journaux sont :

  • Application : il contient les messages EventLog générés par les applications et les services. Par exemple, un journal pourrait enregistrer un message d'erreur. C'est au développeur de l'application de décider les évènements à journaliser. L'emplacement par défaut du fichier d'archivage est %SystemRoot%\System32\Config\AppEvent.evt ;
  • Sécurité : il contient les messages EventLog tels que les échecs et les réussites des connexions aussi bien que les évènements relatifs à l'usage des ressources comme l'ouverture, la création ou la destruction de fichiers ou autres objets. Un administrateur peut activer l'audit pour enregistrer ces évènements. L'emplacement par défaut du fichier d'archivage est %SystemRoot%\System32\Config\SecEvent.evt ;
  • Système : il contient les messages EventLog générés par les composants du système, comme un problème lors du chargement d'un driver au démarrage. Les évènements journalisés par les composants du système sont prédéterminés. L'emplacement par défaut du fichier d'archivage %SystemRoot%\System32\Config\SysEvent.evt.

D'autres journaux peuvent être créés et utilisés si nécessaire (depuis Windows 2000). Par exemple, depuis Internet Explorer version 7, il existe le journal « Internet Explorer ». Un autre exemple : Windows 2003 Server définit les journaux nommés « Directory Service », « DNS Server » et « File Replication Service ».

III-B. Le programme « Event Viewer »

Le programme utilisé pour visualiser les messages EventLog est le programme « Event Viewer ». Ce programme est lancé par la commande eventvwr.exe et se trouve normalement dans le répertoire C:\Windows\system32.

Le programme « Event Viewer » permet de visualiser les différents journaux ainsi que les différents messages archivés dans ces journaux :

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.

III-C. Un message EventLog

Un message EventLog contient les informations suivantes.

  • Le type du message.
  • La source du message.
  • L'identifiant du message.
  • La catégorie du message.
  • Le message lui-même.
  • Des informations dynamiques facultatives.
  • Un buffer de données facultatif.
  • La date et l'heure.
  • D'autres informations.

III-C-1. Le type du message

Il existe six types de messages EventLog. Le programme « Event Viewer » utilise ces types pour déterminer l'icône à utiliser pour afficher la liste des messages EventLog.

  • Success : Il s'agit d'un message EventLog qui indique le succès d'une opération.
  • Error : Il s'agit d'un message EventLog qui indique un problème significatif comme la perte de données ou de fonctionnalités. Par exemple, si un service ne parvient pas à démarrer, un message de type Error est généré.
  • Warning : Il s'agit d'un message EventLog pas forcément significatif, mais qui pourrait indiquer un possible futur problème. Par exemple, quand l'espace disque devient faible, un message de type Warning est généré. En règle générale, si une application peut gérer ce type d'évènements sans perdre de fonctionnalités ou de données, l'évènement peut être classifié comme un EventLog de type Warning.
  • Information : Il s'agit d'un message EventLog décrivant la réussite d'une opération pour une application, un service ou un driver. Par exemple, quand un driver réseau se charge avec succès, il est approprié de journaliser un message de type Information. À noter qu'il est généralement inapproprié pour une application utilisateur de générer un tel message à chaque lancement.
  • Audit Success : Il s'agit d'un message EventLog qui enregistre le succès d'un accès à un objet dont la sécurité est auditée. Par exemple, la connexion avec succès d'un utilisateur sur le système est journalisée comme un évènement de type Audit Success.
  • Audit Failure : Il s'agit d'un message EventLog qui enregistre l'échec d'une tentative d'accès à un objet dont la sécurité est auditée. Par exemple, si un utilisateur tente un accès à un disque réseau et échoue, cette tentative est journalisée comme un évènement de type Audit Failure.
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.

III-C-2. La source du message

La source du message EventLog est le nom du programme ou du service qui a généré cet évènement. C'est souvent le nom de l'application ou le nom d'un composant de cette application si celle-ci est vraiment importante. Une application ou un service peuvent utiliser leur propre nom. Cela peut être MSSQLSERVER ou Tcpip par exemple.

La source du message EventLog
La source du message EventLog

III-C-3. L'identifiant du message

L'identifiant du message EventLog est un nombre sur 32 bits qui identifie le message EventLog. Le programme « Event Viewer » affiche cet identifiant.

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

III-C-4. La catégorie du message

La catégorie du message EventLog permet d'organiser et d'ajouter de l'information au message EventLog. Cette catégorie est une chaîne de caractères et chaque source de messages EventLog peut définir ses propres catégories. La signification de cette catégorie dépend de la source du message. Le programme « Event Viewer » montre la catégorie du message EventLog.

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

III-C-5. Le message EventLog

La signification et le contenu du message EventLog dépend de la source du message EventLog. Le programme « Event Viewer » montre le texte du message EventLog.

Le message EventLog
Le message EventLog

III-C-6. Les informations dynamiques

Le programme utilisateur peut fournir, lors de la génération du message EventLog, des paramètres dynamiques sous forme d'un tableau de chaînes de caractères. Ces chaînes de caractères sont ensuite insérées dans le message EventLog lors de l'affichage de celui-ci. Le contenu et l'ordre de ces paramètres dynamiques sont à la libre appréciation du développeur de l'application. Ce contenu doit toutefois être en cohérence avec le message à afficher.

Ces informations dynamiques sont optionnelles.

III-C-7. Le buffer de données

Le programme utilisateur peut fournir, lors de la génération du message EventLog, un buffer de données applicatives. Le contenu de ce buffer sera ensuite inséré sous une forme hexa décimale dans le message EventLog lors de l'affichage de celui-ci. Le contenu de ce buffer est à la libre appréciation du développeur de l'application. Ce contenu peut fournir une aide au diagnostic par exemple.

Ce buffer de données est optionnel.

III-C-8. La date et l'heure

Le message EventLog contient deux informations de date et d'heure.

  • La date de génération : C'est la date à laquelle l'évènement a été transmis au sous-système EventLog. Cette date est au format UTC (Universal Coordinated Time).
  • La date d'écriture : C'est la date à laquelle l'évènement a été reçu et traité par le sous-système EventLog et enregistré dans le journal des messages EventLog. Cette date est au format UTC (Universal Coordinated Time).

III-C-9. Les autres informations

D'autres informations moins importantes peuvent aussi être récupérées dans un message EventLog. Ainsi, il est possible de récupérer.

  • L'utilisateur : C'est le nom de l'utilisateur qui a généré le message EventLog. Cette information peut ne pas être disponible si elle n'a pas de sens dans le contexte courant d'exécution.
  • Le nom de la machine : C'est le nom de la machine sur laquelle le message EventLog a été généré.
  • Le numéro d'enregistrement : Cette valeur est le numéro de l'enregistrement dans le journal des messages EventLog. Il peut être utilisé pour lire ou rechercher un enregistrement spécifique.
  • La taille de l'enregistrement : C'est la taille de l'enregistrement dans le journal des messages EventLog. Ce n'est pas forcément la longueur du message lui-même.

III-D. La base de registre

Comme souvent pour les systèmes Microsoft, toutes les informations de configuration du sous-système EventLog se situent dans la base de registre.

III-D-1. Clé de registre racine

La clé de registre racine de tout ce qui concerne le sous-système EventLog est HKLM\SYSTEM\CurrentControlSet\Services\EventLog.

III-D-2. Les différents journaux

Le sous-système EventLog introduit la notion de journal d'enregistrement. La documentation MSDN utilise le terme anglais localisation. Dans un but de clarté, j'utiliserai dans ce document le terme français de journal ou journal d'enregistrement même si la traduction n'est pas parfaite, je trouve que ce terme a plus de sens.

Un journal d'enregistrement est un fichier géré par le sous-système EventLog dans lequel sont stockés les différents messages EventLog générés par les sources de messages qui appartiennent à ce journal.

Les noms des différents journaux gérés par le sous-système EventLog figurent dans la base de registre sous la racine du sous-système EventLog, par exemple :

  • HKLM\SYSTEM\CurrentControlSet\Services\EventLog\Application\ ;
  • HKLM\SYSTEM\CurrentControlSet\Services\EventLog\Security\ ;

Chacun des journaux EventLog définis dans la base de registre contient les informations de configuration suivantes :

Clé de registre

Type

Description

CustomSD

REG_SZ

Cette valeur permet de définir l'accès au journal des évènements. La syntaxe utilisée SDDL (Security Descriptor Definition Language). Les droits suivants peuvent être accordés :
* 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.

III-D-3. Les différentes sources

Toutes les sources EventLog pour un journal particulier sont listées dans la base de registre sous la clé HKLM\SYSTEM\CurrentControlSet\Services\EventLog\<journal>\<source>, par exemple :

  • HKLM\SYSTEM\CurrentControlSet\Services\EventLog\Application\.NET Runtime\ ;
  • HKLM\SYSTEM\CurrentControlSet\Services\EventLog\Security\SC Manager\ ;
  • HKLM\SYSTEM\CurrentControlSet\Services\EventLog\System\acpi\ ;

Chacune des sources EventLog définie dans la base de registre contient les informations de configuration suivantes :

Clé de registre

Type

Description

CategoryCount

REG_DWORD

Nombre de catégories utilisées par cette source de messages EventLog.
Le rôle et l'usage exacts de cette clé ne sont pas connus. 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é ne sont pas connus, 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 certaines sources et contient comme valeur un Guid. Le rôle exact de cette clé est inconnu. Si vous avez plus d'informations au sujet de cette clé, n'hésitez pas, 19 commentaires, ce document sera mis à jour.

publisherGuid

REG_EXPAND_SZ

Cette clé est parfois renseignée par certaines sources. Le rôle exact de cette clé est inconnu. Si vous avez plus d'informations au sujet de cette clé, n'hésitez pas, 19 commentaires, ce document sera mis à jour.

LoggingLevel

REG_DWORD

Cette clé est parfois renseignée par certaines sources. Le rôle exact de cette clé est inconnu. Si vous avez plus d'informations au sujet de cette clé, n'hésitez pas, 19 commentaires, ce document sera mis à jour.

Version

REG_DWORD

Cette clé est parfois renseignée par certaines sources. Le rôle exact de cette clé est inconnu. Si vous avez plus d'informations au sujet de cette clé, n'hésitez pas, 19 commentaires, ce document sera mis à jour.

III-E. Récapitulatif de l'organisation dans la base de registre

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

Répertoires dans la base de registre

Clés dans la base de registre

HKLM\SYSTEM\CurrentControlSet\Services\EventLog

     
 

Application

 

CustomSD, DisplayNameFile, DisplayNameID, File, MaxSize, PrimaryModule, Retention, Sources, AutoBackupLogFiles, RestrictGuestAccess, Isolation

   

.NET Runtime

CategoryCount, CategoryMessageFile, EventMessageFile, ParameterMessageFile, TypesSupported

   

.NET Runtime Optimization Service

CategoryCount, CategoryMessageFile, EventMessageFile, ParameterMessageFile, TypesSupported

   

<autre source>

CategoryCount, CategoryMessageFile, EventMessageFile, ParameterMessageFile, TypesSupported

 

Security

 

CustomSD, DisplayNameFile, DisplayNameID, File, MaxSize, PrimaryModule, Retention, Sources, AutoBackupLogFiles, RestrictGuestAccess, Isolation

   

DSA

CategoryCount, CategoryMessageFile, EventMessageFile, ParameterMessageFile, TypesSupported

   

LSA

CategoryCount, CategoryMessageFile, EventMessageFile, ParameterMessageFile, TypesSupported

   

<autre source>

CategoryCount, CategoryMessageFile, EventMessageFile, ParameterMessageFile, TypesSupported

 

System

 

CustomSD, DisplayNameFile, DisplayNameID, File, MaxSize, PrimaryModule, Retention, Sources, AutoBackupLogFiles, RestrictGuestAccess, Isolation

   

ACPI

CategoryCount, CategoryMessageFile, EventMessageFile, ParameterMessageFile, TypesSupported

   

adp94xx

CategoryCount, CategoryMessageFile, EventMessageFile, ParameterMessageFile, TypesSupported

   

<autre source>

CategoryCount, CategoryMessageFile, EventMessageFile, ParameterMessageFile, TypesSupported

 

<autre journal>

 

CustomSD, DisplayNameFile, DisplayNameID, File, MaxSize, PrimaryModule, Retention, Sources, AutoBackupLogFiles, RestrictGuestAccess, Isolation

   

<autre source>

CategoryCount, CategoryMessageFile, EventMessageFile, ParameterMessageFile, TypesSupported

III-F. Reconstitution du texte d'un message EventLog

La reconstitution du message textuel (par le programme « Event Viewer » par exemple) suit un processus rigoureux, mais assez « tortueux ». Cette reconstitution est partiellement décrite dans la documentation du MSDN. Parfois, certains points de ce processus ont été déduits à partir du fonctionnement du programme « Event Viewer ». Si vous trouvez des informations plus complètes ou plus formelles, n'hésitez pas, 19 commentaires, ce document sera mis à jour.

Lorsqu'une application génère un message EventLog, elle fournit entre autres les informations suivantes.

  • L'identifiant de la catégorie du message.
  • L'identifiant du format de message.
  • Un tableau optionnel de paramètres dynamiques.
  • Un buffer optionnel de données complémentaires.

Le schéma suivant décrit le cheminement pour recréer le texte localisé complet du message EventLog :

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

III-F-1. Le nom de la catégorie

C'est l'élément le plus facile à récupérer et la méthode est la suivante.

  • Il suffit de lire la clé de registre CategoryMessageFile de la source de données concernée. Cette clé indique le nom du fichier qui contient la liste des textes localisés des différents noms de catégories.
  • Ensuite, on récupère le texte de la ressource à l'aide de la fonction FormatMessage(). La ressource à utiliser est choisie par la fonction en utilisant le langage du programme ou du système. L'ordre de sélection du langage est le suivant :

    • Le Langage neutral.
    • Le langage spécifié pour le thread courant.
    • Le langage spécifié pour l'utilisateur.
    • Le langage spécifié pour le système.
    • Le langage US English.

III-F-2. Le format du message

Le texte du message se fait en deux étapes :

  • la première étape consiste à récupérer le format du message ;
  • la seconde étape consiste à remplacer les codes d'insertion par leurs valeurs.

III-F-3. La récupération du format du message

La récupération du format du message suit le même procédé que celui de la récupération du nom de la catégorie :

  • Il suffit de lire la clé de registre EventMessageFile de la source de données concernée. Cette clé indique le nom du fichier qui contient la liste des textes localisés des différents formats de messages.
  • Ensuite, on récupère le texte de la ressource à l'aide de la fonction FormatMessage(). La ressource à utiliser est choisie par la fonction en utilisant le langage du programme ou du système. L'ordre de sélection du langage est le suivant :

    • Le Langage neutral.
    • Le langage spécifié pour le thread courant.
    • Le langage spécifié pour l'utilisateur.
    • Le langage spécifié pour le système.
    • Le langage US English.

III-F-4. Le remplacement des codes d'insertion

Le format du message peut contenir des codes d'insertion. Cette dernière étape consiste à remplacer ces codes d'insertion par leurs vraies valeurs. Les différents codes d'insertion sont les suivants :

Code d'insertion

Signification

%n

Insertion du paramètre dynamique n-1 fourni par le programme lors de l'appel à la fonction ReportEvent().
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.

IV. L'API de programmation Event Logging

Rappel : Microsoft propose 2 API pour s'interfacer avec le sous-système EventLog :

  • L'API Event Logging. Cette API est disponible depuis Windows 3.1.
  • L'API Windows Event Log. Cette API est disponible depuis Windows Vista et Windows Server 2008. Cette API doit, à terme, remplacer l'API Event Logging.

Dans ce document, nous n'abordons que l'API Event Logging.

IV-A. L'API Event Logging

Les fonctions de l'API Event Logging disponibles pour accéder au sous-système EventLog sont les suivantes :

Fonction

Description

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

IV-B. La fonction ReportEvent()

La fonction ReportEvent() attend entre autres les paramètres suivants :

  • Le type : Il s'agit du code de gravité du message. Il n'y a aucun lien entre le message et son code de gravité. Ce sont le développeur et le projet, qui en fonction du contexte, déterminent le code de gravité. Les valeurs autorisées sont :

    • EVENTLOG_SUCCESS (0x00)
    • EVENTLOG_AUDIT_FAILURE (0x10)
    • EVENTLOG_AUDIT_SUCCESS (0x08)
    • EVENTLOG_ERROR_TYPE (0x01)
    • EVENTLOG_INFORMATION_TYPE (0x04)
    • EVENTLOG_WARNING_TYPE (0x02)
  • La catégorie : Il s'agit d'un identifiant (sous forme de nombre) qui permet de qualifier le message en l'accompagnant d'un nom de sous-système par exemple. Ce nombre est l'identifiant d'une ressource chaîne de caractères qui est affichée par le programme « Event Viewer ». Il n'y a aucun lien entre le message et la catégorie, c'est-à-dire qu'un message peut être envoyé une première fois avec une catégorie et une seconde fois avec une autre catégorie sans que cela ne pose un problème. Cette catégorie de messages doit être unique pour une source donnée de messages EventLog. Les identifiants des catégories de messages doivent être séquentiels et commencer à 1. Ce sont le développeur et le projet, qui en fonction du contexte, déterminent la catégorie à utiliser.
  • Le message : Il s'agit d'un identifiant (sous forme de nombre) d'une ressource chaîne de caractères qui représente le format d'affichage du message d'erreur. Cet identifiant de message doit être unique pour une source de messages Eventlog donnée. Ce sont le développeur et le projet, qui en fonction du contexte, déterminent le message à utiliser. Cet identifiant de message est constitué de 4 parties :

    • La racine de l'identifiant, c'est un nombre sur 16 bits.
    • La facilité du message, c'est un nombre sur 12 bits. Les facilités 0 à 255 sont normalement réservées par Microsoft et définies dans les fichiers include du SDK Microsoft, les facilités 256 à 4095 sont à la libre disposition des développeurs.
    • Le bit customer. Ce bit indique s'il s'agit d'un message système ou utilisateur.
    • La sévérité du message, c'est un nombre sur 2 bits qui indique la gravité du message. Les valeurs autorisées sont Success (0), Informational (1), Warning (2) et Error (3).
  • L'identifiant de sécurité : Il s'agit des informations permettant de connaitre le nom de l'utilisateur qui a généré le message. Cet identifiant peut être absent si cela n'a pas de sens dans le contexte courant (dans le cas d'un service par exemple).
  • Les paramètres applicatifs : Il s'agit d'un tableau optionnel de chaînes de caractères fourni par l'application permettant de compléter le message en fonction du contexte. Ces paramètres applicatifs sont déterminés par le développeur et le projet en fonction du contexte du message. Ces paramètres applicatifs doivent être mis en cohérence par rapport à l'information qu'attend le format du message (il est incohérent de passer un nom d'utilisateur si le message doit afficher le chemin de fichier de configuration par exemple).
  • Un buffer de données : Il s'agit d'un tableau optionnel d'octets permettant d'ajouter de l'information au message (dump d'une structure par exemple).

IV-C. Le fichier de messages

IV-C-1. Format du fichier

Les chaînes de caractères des catégories, les formats de messages et les paramètres statiques sont des ressources situées dans un ou plusieurs exécutables ou DLL. Ces fichiers sont identifiés dans la base de registre pour une source de messages EventLog donnée.

Ces fichiers de messages sont compilés et transformés en ressources par le programme mc.exe fourni avec le SDK. Ces ressources sont des chaînes de caractères localisées ou non d'un type spécial. Elles ne peuvent pas être générées, éditées ou modifiées par l'éditeur de ressources Visual Studio. Il est obligatoire de passer par un fichier texte source Unicode ou non (par habitude, l'extension de ce fichier est .mc) et le compilateur mc.exe.

La syntaxe du header d'un fichier est la suivante :

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

IV-C-2. Construction du fichier de ressources

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

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

IV-D. Les limitations de EventLog

Le sous-système EventLog présente tout de même quelques limitations :

  • Il n'est pas prévu nativement de pouvoir transférer les messages EventLog vers un concentrateur de messages de logs. Les seules possibilités sont la sauvegarde des différents journaux dans un fichier de sauvegarde ou alors la lecture des messages générés à l'aide de la fonction ReadEventLog() qui permet de se connecter à une machine distante par protocole Netbios. Cette fonctionnalité nécessite toutefois que la machine qui récupère les messages ait un compte sur la machine distante (machines dans un même domaine ou alors login/password sur la machine distante).
  • Les messages ne peuvent être générés que sur la machine locale, car les ressources messages sont situées dans des DLL (ou exe) sur la machine qui génère le message. Une machine distante qui récupèrerait le message (par ReadEventLog() ou alors par une copie du journal des évènements) devrait avoir les mêmes DLL (ou exe) en local ou alors devrait pouvoir lire les fichiers à distance (par protocole Netbios).

V. Génération de messages EventLog

Le but du programme de test EventLogWriter est de générer plusieurs évènements EventLog. La visualisation de l'évènement généré se fera avec le programme « Event Viewer ».

Attention, le fonctionnement du programme « Event Viewer » fait qu'il bloque le fichier dans lequel sont contenues les ressources texte des messages EventLog. Ce fonctionnement fait que la recompilation du programme peut poser des problèmes, car le fichier exécutable étant bloqué, l'environnement de développement ne peut finir de construire l'exécutable. Parfois même il n'y a pas de message d'erreur. Il est donc impératif lors d'une phase de recompilation d'arrêter toutes les instances du programme « Event Viewer » sous peine de prise de tête.

V-A. Choix de développement

Le choix qui a été fait pour ce projet à des fins purement didactiques est de réaliser une DLL pour chacune des familles de messages (catégories, messages et paramètres). Il est probable que dans un vrai projet, au moins les catégories et les messages seraient regroupés dans une seule DLL (avec peut-être aussi les paramètres).

Le programme utilise 3 DLL qui contiennent chacune les messages :

  • EventCategorieFile.dll : cette DLL contient les ressources sous forme de chaîne de caractères des noms des différentes catégories en français et en anglais. Cette DLL définit 3 catégories dont les identifiants commencent à 1 et finissent à 3.
  • EventMessageFile.dll : cette DLL contient les ressources sous forme de chaîne de caractères des formats d'affichage des différents messages en français et en anglais. Cette DLL définit 5 messages. Il est absolument impératif que les identifiants des messages commencent après le dernier identifiant de catégories. Si cette dernière règle n'est pas respectée, l'affichage des messages dans le programme « Event Viewer » sera incohérent.
  • EventParameterFile.dll : cette DLL contient les ressources sous forme de chaîne de caractères des paramètres fixes des différents messages en langue neutre. Cette DLL définit un paramètre fixe dont l'identifiant est arbitrairement fixé à 5002. Il n'y a aucune contrainte sur le choix des identifiants des messages des paramètres.

Ces 3 DLL sont référencées dans la base de registre respectivement dans les clés de registre CategoryMessageFile, EventMessageFile et ParameterMessageFile.

Ce programme a été développé sur une machine Windows 7 professional en utilisant le « SDK Windows® Software Development Kit (SDK) for Windows 7 and .NET Framework 3.5 Service Pack 1 ».

Le programme est écrit volontairement en C afin que d'autres langages puissent utiliser les idées développées dans ce code.

Afin de ne pas alourdir le code inutilement, aucun contrôle de bon déroulement n'est effectué. Ces contrôles devront impérativement être mis en place dans un vrai développement.

V-B. Récupération des fichiers

Les différents binaires constituants ce projet sont :

  • Le programme EventLogWriter.exe peut être récupéré ici.
  • La DLL EventCategorieFile.dll peut être récupérée ici.
  • La DLL EventMessageFile.dll peut être récupérée ici.
  • La DLL EventParameterFile.dll peut être récupérée ici.

Ces 4 fichiers doivent être mis impérativement dans le même répertoire (et pas dans la racine d'un disque).

Si une erreur se produit lors du lancement du programme EventLogWriter.exe, il faut installer les redistribuables VS 2005. Ce setup peut être téléchargé ici.

Le projet complet au format Visual Studio 2005 peut être téléchargé ici.

V-C. Fonctionnement du programme

Le programme doit être appelé une première fois avec les privilèges d'administrateur. Ces privilèges sont nécessaires pour créer les différentes clés dans la base de registre.

Les clés qui vont être créées sont :

Clé

Type

Valeur

HKLM\SYSTEM\CurrentControlSet\services\eventlog\Application\TestApp\

Folder

 

HKLM\SYSTEM\CurrentControlSet\services\eventlog\Application\TestApp\CategoryCount

REG_DWORD

3

HKLM\SYSTEM\CurrentControlSet\services\eventlog\Application\TestApp\CategoryMessageFile

REG_EXPAND_SZ

<répertoire d'installation>\EventCategorieFile.dll

HKLM\SYSTEM\CurrentControlSet\services\eventlog\Application\TestApp\EventMessageFile

REG_EXPAND_SZ

<répertoire d'installation>\EventMessageFile.dll

HKLM\SYSTEM\CurrentControlSet\services\eventlog\Application\TestApp\ParameterMessageFile

REG_EXPAND_SZ

<répertoire d'installation>\EventParameterFile.dll

HKLM\SYSTEM\CurrentControlSet\services\eventlog\Application\TestApp\TypesSupported

REG_DWORD

7

Le fonctionnement du programme est le suivant :

  • Il commence par appeler la fonction _add_event_source() qui enregistre le programme TestApp comme source de messages EventLog dans le journal Application. C'est pour cela que les privilèges d'administrateur sont nécessaires la première fois.
  • Ensuite le programme appelle la fonction _log_event(). Cette fonction va ouvrir une connexion avec le sous-système EventLog et ensuite envoyer 7 messages EventLog avec des caractéristiques différentes.
  • La fonction _GetSid() est utilisée pour récupérer le SID (Security Identifier) de l'utilisateur actuellement connecté et le transmettre dans le message EventLog.

V-D. Les sources du projet

V-D-1. Le fichier EventLogWritter.cpp

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

V-D-2. Le fichier CategoryFile.mc

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

V-D-3. Le fichier MessageFile.mc

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

V-D-4. Le fichier ParameterFile.mc

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

VI. Lecture de messages EventLog

Le but de ce programme de démonstration est d'ouvrir un journal d'évènements et d'attendre l'arrivée d'évènements EventLog. L'affichage du contenu des informations du message EventLog est incomplet, ce programme a pour but d'en montrer le principe.

Ce programme ne lit que les évènements générés dans le journal Application du sous-système EventLog.

VI-A. Choix de développement

Le programme EventLogReader.exe lit les évènements EventLog du journal Application au fur et à mesure qu'ils sont générés. La génération d'évènements se fera en utilisant le programme EventLogWriter.exe présenté au paragraphe précédent.

Ce programme a été développé sur une machine Windows 7 professional en utilisant le « SDK Windows® Software Development Kit (SDK) for Windows 7 and .NET Framework 3.5 Service Pack 1 ».

Le programme est écrit volontairement en C afin que d'autres langages puissent utiliser les idées développées dans ce code.

Afin de ne pas alourdir le code inutilement, aucun contrôle de bon déroulement n'est effectué. Ces contrôles devront impérativement être mis en place dans un vrai développement.

VI-B. Récupération des fichiers

Le programme EventLogReader.exe peut être récupéré ici.

Si une erreur se produit lors du lancement du programme EventLogWriter.exe, il faut installer les redistribuables VS 2005. Ce setup peut être téléchargé ici.

Le projet complet au format Visual Studio 2005 peut être téléchargé ici.

VI-C. Fonctionnement du programme

Le programme commence d'abord par calculer le numéro du dernier évènement présent dans le journal avec les fonctions GetOldestEventLogRecord() et GetNumberOfEventLogRecords(). Ensuite, il détermine l'index du dernier évènement (Number of event log + Oldest event log - 1) pour se positionner juste après cet évènement (avec la fonction ReadEventLog(EVENTLOG_SEEK_READ | EVENTLOG_FORWARDS_READ).

Ensuite, il crée un objet évènement avec la fonction CreateEvent(). Cet objet est ensuite donné en paramètre au sous-système EventLog par la fonction NotifyChangeEventLog() afin d'être prévenu lors de l'arrivée d'un nouvel évènement EventLog.

Le programme fait ensuite une attente bloquante sur cet objet évènement et dès qu'il est libéré, le programme lit le journal des évènements avec la fonction ReadEventLog(). Cet appel pouvant retourner 1 ou plusieurs évènements simultanément, il y a une boucle de lecture/extraction des différents évènements EventLog.

Ensuite, chaque évènement EventLog est analysé et affiché par la fonction _analyse().

Remarque : la fonction NotifyChangeEventLog() est utilisée, car elle permet une attente passive. Toutefois, cette fonction ne peut pas être utilisée pour interroger le sous-système EventLog d'une autre machine. Dans ce cas, il faudra passer par une attente active avec temporisation, lecture et analyse s'il y a de nouveaux évènements EventLog.

VI-D. Le fichier EventLogReader.cpp

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

VII. Conclusions

Cette présentation du sous-système EventLog est maintenant terminée. J'espère que les différents exemples et explications fournis dans ce document inciteront les développeurs d'applications Microsoft à utiliser le sous-système EventLog trop souvent boudé ou ignoré parce que jugé au premier abord trop complexe ou inutile.

Comme dans tout projet, le plus important est avant tout de mettre en place une politique cohérente de messages. Ensuite le code nécessaire à la mécanique est tout de même relativement simple une fois que le fonctionnement est démystifié et qu'un squelette minimal est mis en place.

N'hésitez pas à donner votre avis sur ce document, qu'il vous ait plu ou bien parce qu'il contient des erreurs ou des imprécisions, 19 commentaires, vos retours serviront à faire évoluer ce document.

VII-A. Quelques références

Ce chapitre présente les quelques références (en anglais pour la plupart) qui m'ont servi pour comprendre le fonctionnement du sous-système EventLog et pour rédiger ce document.

VII-B. Remerciements

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

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2008-2010 ram-0000. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.