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

ram-0000

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 : Commentez ♪

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

- Auteur : ram-0000

- Parution : 23 janvier 2010

- Licence : 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 et 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.

Sommaire

- I. Introduction

- II. La journalisation

- II-A. Quelques définitions

- II-B. Pourquoi journaliser ?

- II-C. Contraintes liées à la journalisation

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

- III. Le sous-système EventLog

- III-A. Architecture simplifiée

- III-B. Le programme « Event Viewer »

- III-C. Un message EventLog

- III-C-1. Le type du message

- III-C-2. La source du message

- III-C-3. L'identifiant du message

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

- III-C-5. Le message EventLog

- III-C-6. Les informations dynamiques

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

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

- III-C-9. Les autres informations

- III-D. La base de registre

- III-D-1. Clé de registre racine

- III-D-2. Les différents journaux

- III-D-3. Les différentes sources

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

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

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

- III-F-2. Le format du message

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

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

- IV. L'API de programmation Event Logging

- IV-A. L'API Event Logging

- IV-B. La fonction ReportEvent()

- IV-C. Le fichier de messages

- IV-C-1. Format du fichier

- IV-C-2. Construction du fichier de ressources

- IV-D. Les limitations de EventLog

- V. Génération de messages EventLog

- V-A. Choix de développement

- V-B. Récupération des fichiers

- V-C. Fonctionnement du programme

- V-D. Les sources du projet

- V-D-1. Le fichier EventLogWritter.cpp

- V-D-2. Le fichier CategoryFile.mc

- V-D-3. Le fichier MessageFile.mc

- V-D-4. Le fichier ParameterFile.mc

- VI. Lecture de messages EventLog

- VI-A. Choix de développement

- VI-B. Récupération des fichiers

- VI-C. Fonctionnement du programme

- VI-D. Le fichier EventLogReader.cpp

- VII. Conclusions

- VII-A. Quelques références

- VII-B. Remerciements

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

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

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

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

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

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

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

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

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

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, Commentez , 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, Commentez , 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, Commentez , 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, Commentez , 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, Commentez , 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, Commentez , 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, Commentez , 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

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

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

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

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

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

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

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

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

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

Severity      = Sev_Success
Severity      = Sev_Information

- Facility : Il s'agit de la fonctionnalité du message (12 bits intermédiaires). Ce mot clé est optionnel. S'il est absent, c'est la facilité du message précédent (ou 0) qui est utilisée pour ce message. Bien que la documentation indique que la facilité par défaut soit Application (0xfff), il semble en fait que la valeur par défaut soit 0.

Facility

Facility      = 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

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

1. ;#ifndef __MESSAGES_EventMessageFile_H__

2. ;#define __MESSAGES_EventMessageFile_H__

3. ;

4. ;// Ligne de compilation : mc.exe -u$(InputDir)\$(InputName).mc"

5. ;//     -u indique un fichier Unicode

6. ;

8. MessageIdTypedef = DWORD

10. Outputbase = 16

12. LanguageNames =

13. (

14. LANGUE_FRA   = 0x040c:Messages_0x040c

15. LANGUE_ENG   = 0x0809:Messages_0x0809

16. )

18. FacilityNames =

19. (

20. FACILITY_NULL                    =  0

21. FACILITY_RPC                     =  1

22. FACILITY_DISPATCH                =  2

23. FACILITY_STORAGE                 =  3

24. FACILITY_ITF                     =  4

25. FACILITY_WIN32                   =  7

26. FACILITY_WINDOWS                 =  8

27. FACILITY_SSPI                    =  9

28. FACILITY_SECURITY                =  9

29. FACILITY_CONTROL                 = 10

30. FACILITY_CERT                    = 11

31. FACILITY_INTERNET                = 12

32. FACILITY_MEDIASERVER             = 13

33. FACILITY_MSMQ                    = 14

34. FACILITY_SETUPAPI                = 15

35. FACILITY_SCARD                   = 16

36. FACILITY_COMPLUS                 = 17

37. FACILITY_AAF                     = 18

38. FACILITY_URT                     = 19

39. FACILITY_ACS                     = 20

40. FACILITY_DPLAY                   = 21

41. FACILITY_UMI                     = 22

42. FACILITY_SXS                     = 23

43. FACILITY_WINDOWS_CE              = 24

44. FACILITY_HTTP                    = 25

45. FACILITY_USERMODE_COMMONLOG      = 26

46. FACILITY_USERMODE_FILTER_MANAGER = 31

47. FACILITY_BACKGROUNDCOPY          = 32

48. FACILITY_CONFIGURATION           = 33

49. FACILITY_STATE_MANAGEMENT        = 34

50. FACILITY_METADIRECTORY           = 35

51. FACILITY_WINDOWSUPDATE           = 36

52. FACILITY_DIRECTORYSERVICE        = 37

53. FACILITY_GRAPHICS                = 38

54. FACILITY_SHELL                   = 39

55. FACILITY_TPM_SERVICES            = 40

56. FACILITY_TPM_SOFTWARE            = 41

57. FACILITY_UI                      = 42

58. FACILITY_PLA                     = 48

59. FACILITY_FVE                     = 49

60. FACILITY_FWP                     = 50

61. FACILITY_WINRM                   = 51

62. FACILITY_NDIS                    = 52

63. FACILITY_USERMODE_HYPERVISOR     = 53

64. FACILITY_CMI                     = 54

65. FACILITY_USERMODE_VIRTUALIZATION = 55

66. FACILITY_USERMODE_VOLMGR         = 56

67. FACILITY_BCD                     = 57

68. FACILITY_USERMODE_VHD            = 58

69. FACILITY_SDIAG                   = 60

70. FACILITY_WEBSERVICES             = 61

71. FACILITY_WINDOWS_DEFENDER        = 80

72. FACILITY_OPC                     = 81

73. FACILITY_XPS                     = 82

74. FACILITY_RAS                     = 83

75. FACILITY_MBN                     = 84

76. )

78. SeverityNames =

79. (

80. Sev_Success      = 0x0

81. Sev_Information  = 0x1

82. Sev_Warning      = 0x2

83. Sev_Error        = 0x3

84. )

87. ;////////////////////////////////////////

88. ;// Events

89. ;//

91. MessageId       = 1000

92. SymbolicName    = EVENT_STARTED_BY_1

93. Facility        = FACILITY_MBN

94. Severity        = Sev_Success

95. Language        = LANGUE_FRA

96. Succès : L'application '%P02' (%2) a été démarrée par l'utilisateur%1"

97. .

98. Language        = LANGUE_ENG

99. Success : The program '%P02' (%2) has been started by user%1"

100. .

102. MessageId       = +0

103. SymbolicName    = EVENT_STARTED_BY_2

104. Facility        = FACILITY_NULL

105. Severity        = Sev_Information

106. Language        = LANGUE_FRA

107. Information : L'application '%P02' (%2) a été démarrée par l'utilisateur%1"

108. .

109. Language        = LANGUE_ENG

110. Information : The program '%P02' (%2) has been started by user%1"

111. .

113. MessageId       = +0

114. SymbolicName    = EVENT_STARTED_BY_3

115. Facility        = FACILITY_NULL

116. Severity        = Sev_Warning

117. Language        = LANGUE_FRA

118. Warning : L'application '%P02' (%2) a été démarrée par l'utilisateur%1"

119. .

120. Language        = LANGUE_ENG

121. Warning : The program '%P02' (%2) has been started by user%1"

122. .

124. MessageId       = +0

125. SymbolicName    = EVENT_STARTED_BY_4

126. Facility        = FACILITY_NULL

127. Severity        = Sev_Error

128. Language        = LANGUE_FRA

129. Error : L'application '%P02' (%2) a été démarrée par l'utilisateur%1"

130. .

131. Language        = LANGUE_ENG

132. Error : The program '%P02' (%2) has been started by user%1"

133. .

135. MessageId       = +1

136. SymbolicName    = EVENT_BACKUP

137. Facility        = FACILITY_NULL

138. Severity        = Sev_Information

139. Language        = LANGUE_FRA

140. Vous devriez sauvegarder vos données régulièrement !

141. .

142. Language        = LANGUE_ENG

143. You should regulary backup your data!

144. .

146. ;

147. ;#endif  //__MESSAGES_EventMessageFile_H__

148. ;

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

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

1. // EventLogConsole.cpp : définit le point d'entrée pour l'application console.

2. //

4. #ifndef _WIN32_WINNT      // Autorise l'utilisation des fonctionnalités spécifiques à Windows XP ou version ultérieure.

5. #define _WIN32_WINNT 0x0501   // Attribuez la valeur appropriée à cet élément pour cibler d'autres versions de Windows.

6. #endif

8. #define _CRT_SECURE_NO_DEPRECATE

9. #define _CRT_NON_CONFORMING_SWPRINTFS

11. #include <Windows.h>

12. #include <stdio.h>

13. #include "MessageFile.h"

14. #include "CategorieFile.h"

15. #include "ParameterFile.h"

17. // quelques types bien utiles

18. typedef wchar_t MCR_CHAR;

19. typedef const wchar_t * MCR_CSTR;

20. typedef unsigned char MCR_U1;

21. typedef unsigned long MCR_U4;

23. static void _GetSID(SID * Sid)

24. {

25. // Open the access token associated with the calling process.

26. HANDLE hToken = NULL;

27. OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken);

29. // get the size of the memory buffer needed for the SID

30. DWORD dwBufferSize = 0;

31. GetTokenInformation(hToken, TokenUser, NULL, 0, &dwBufferSize);

33. PTOKEN_USER pTokenUser = (PTOKEN_USER)malloc(dwBufferSize);

34. memset(pTokenUser, 0, dwBufferSize);

36. // Retrieve the token information in a TOKEN_USER structure.

37. GetTokenInformation(hToken, TokenUser, pTokenUser, dwBufferSize, &dwBufferSize);

39. // copy the SID structure

40. memcpy(Sid, pTokenUser->User.Sid, sizeof(*Sid));

42. // free everything

43. free(pTokenUser);

44. CloseHandle(hToken);

45. }

47. // nécessite les privilèges d'administration

48. // lancer le programme la première fois en tant qu'admin

49. static void _add_event_source(MCR_CSTR pszName, const MCR_U4 dwCategoryCount)

50. {

51. HKEY      hRegKey = NULL;

52. MCR_CHAR szPath[ MAX_PATH ];

54. swprintf( szPath, L"SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\%s", pszName );

56. // Create the event source registry key

57. RegCreateKey( HKEY_LOCAL_MACHINE, szPath, &hRegKey );

59. // Name of the PE module that contains the message resource

60. GetModuleFileName( NULL, szPath, MAX_PATH );

62. // remove the last '\'

63. *wcsrchr(szPath, '\\') = 0;

65. // Register EventMessageFile

66. MCR_CHAR szFile[ MAX_PATH ];

67. swprintf( szFile, L"%s\\%s", szPath, L"EventMessageFile.dll" );

68. RegSetValueEx(   hRegKey, L"EventMessageFile", 0, REG_EXPAND_SZ,

69. (LPBYTE)szFile,

70. (MCR_U4)(wcslen( szFile ) + 1) * sizeof szFile[0] );

72. // Register ParameterMessageFile

73. swprintf( szFile, L"%s\\%s", szPath, L"EventParameterFile.dll" );

74. RegSetValueEx(   hRegKey, L"ParameterMessageFile", 0, REG_EXPAND_SZ,

75. (LPBYTE)szFile,

76. (MCR_U4)(wcslen( szFile ) + 1) * sizeof szFile[0] );

79. // Register CategoryMessageFile

80. swprintf( szFile, L"%s\\%s", szPath, L"EventCategorieFile.dll" );

81. RegSetValueEx(   hRegKey, L"CategoryMessageFile", 0, REG_EXPAND_SZ,

82. (LPBYTE)szFile,

83. (MCR_U4)(wcslen( szFile ) + 1) * sizeof szFile[0] );

85. // Register supported event types

86. MCR_U4 dwTypes = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE;

87. RegSetValueEx( hRegKey, L"TypesSupported", 0, REG_DWORD, (LPBYTE) &dwTypes, sizeof dwTypes );

89. // Register category count

90. RegSetValueEx( hRegKey, L"CategoryCount", 0, REG_DWORD, (LPBYTE)&dwCategoryCount, sizeof dwCategoryCount );

92. RegCloseKey( hRegKey );

93. }

95. static void _log_event(MCR_CSTR pszName)

96. {

97. // Open the eventlog

98. HANDLE hEventLog = RegisterEventSource( NULL, pszName );

100. // get the sid of the user

101. SID sid;

102. _GetSID(&sid);

103. PSID psid = &sid;

105. // make dynamic parameters array

106. MCR_CSTR aInsertions[] = { L"Paramètre 1", L"Paramètre 2" };

108. // buffer parameters

109. MCR_U1 buffer[64];

110. for(MCR_U1 boucle = 0; boucle != sizeof(buffer); boucle++)

111. buffer[boucle] = ' ' + boucle;

113. ReportEvent(hEventLog,                  // Handle to the eventlog

114. EVENTLOG_SUCCESS,             // Type of event

115. CATEGORY_TWO,               // Category (could also be 0)

116. EVENT_STARTED_BY_1,         // Event id

117. psid,                       // User's sid (NULL for none)

118. 2,                          // Number of insertion strings

119. sizeof(buffer),             // Number of additional bytes

120. aInsertions,                // Array of insertion strings

121. buffer                      // Pointer to additional bytes

122. );

124. // And another one

125. ReportEvent(hEventLog,                  // Handle to the eventlog

126. EVENTLOG_INFORMATION_TYPE,  // Type of event

127. CATEGORY_TWO,               // Category (could also be 0)

128. EVENT_STARTED_BY_2,         // Event id

129. psid,                       // User's sid (NULL for none)

130. 2,                          // Number of insertion strings

131. sizeof(buffer),             // Number of additional bytes

132. aInsertions,                // Array of insertion strings

133. buffer                      // Pointer to additional bytes

134. );

136. ReportEvent(hEventLog,                  // Handle to the eventlog

137. EVENTLOG_WARNING_TYPE,       // Type of event

138. CATEGORY_TWO,               // Category (could also be 0)

139. EVENT_STARTED_BY_3,         // Event id

140. psid,                       // User's sid (NULL for none)

141. 2,                          // Number of insertion strings

142. sizeof(buffer),             // Number of additional bytes

143. aInsertions,                // Array of insertion strings

144. buffer                      // Pointer to additional bytes

145. );

147. ReportEvent(hEventLog,                  // Handle to the eventlog

148. EVENTLOG_ERROR_TYPE,          // Type of event

149. CATEGORY_TWO,               // Category (could also be 0)

150. EVENT_STARTED_BY_4,         // Event id

151. psid,                       // User's sid (NULL for none)

152. 2,                          // Number of insertion strings

153. sizeof(buffer),             // Number of additional bytes

154. aInsertions,                // Array of insertion strings

155. buffer                      // Pointer to additional bytes

156. );

158. ReportEvent(hEventLog,                  // Handle to the eventlog

159. EVENTLOG_AUDIT_SUCCESS,       // Type of event

160. CATEGORY_TWO,               // Category (could also be 0)

161. EVENT_STARTED_BY_1,         // Event id

162. psid,                       // User's sid (NULL for none)

163. 2,                          // Number of insertion strings

164. sizeof(buffer),             // Number of additional bytes

165. aInsertions,                // Array of insertion strings

166. buffer                      // Pointer to additional bytes

167. );

169. ReportEvent(hEventLog,                  // Handle to the eventlog

170. EVENTLOG_AUDIT_FAILURE,       // Type of event

171. CATEGORY_ONE,               // Category (could also be 0)

172. EVENT_STARTED_BY_2,         // Event id

173. psid,                       // User's sid (NULL for none)

174. 2,                          // Number of insertion strings

175. sizeof(buffer),             // Number of additional bytes

176. aInsertions,                // Array of insertion strings

177. buffer                      // Pointer to additional bytes

178. );

180. ReportEvent(hEventLog,                  // Handle to the eventlog

181. EVENTLOG_SUCCESS,       // Type of event

182. CATEGORY_ONE,               // Category (could also be 0)

183. EVENT_BACKUP,                // Event id

184. NULL,                       // User's sid (NULL for none)

185. 2,                          // Number of insertion strings

186. 0,                          // Number of additional bytes

187. aInsertions,                // Array of insertion strings

188. NULL                        // Pointer to additional bytes

189. );

191. // Close eventlog

192. DeregisterEventSource( hEventLog );

193. }

195. int wmain(int /*argc*/, wchar_t * /*argv*/ [])

196. {

197. // register the source EventLog

198. // should be done during the setup procedure by the setup

199. // but in this sample, will be done every times

200. _add_event_source(L"TestApp", LAST_CATEGORY);

202. _log_event(L"TestApp");

204. return 0;

205. }

V-D-2 - Le fichier CategoryFile.mc

Le fichier CategoryFile.mc

1. ;#ifndef __MESSAGES_EventCategorieFile_H__

2. ;#define __MESSAGES_EventCategorieFile_H__

3. ;

4. ;// Ligne de compilation : mc.exe -u "$(InputDir)\$(InputName).mc"

5. ;//     -u indique un fichier Unicode

6. ;

8. MessageIdTypedef = DWORD

10. Outputbase = 10

12. LanguageNames =

13. (

14. LANGUE_FRA   = 0x040c:Messages_0x040c

15. LANGUE_ENG   = 0x0809:Messages_0x0809

16. )

18. ;////////////////////////////////////////

19. ;// Eventlog categories

20. ;//

21. ;// Categories always have to be the first entries in a message file!

22. ;//

24. MessageId       = +1

25. SymbolicName    = CATEGORY_ONE

26. Language        = LANGUE_FRA

27. Première catégorie d'évènements

28. .

29. Language        = LANGUE_ENG

30. First category of events

31. .

33. MessageId       = +1

34. SymbolicName    = CATEGORY_TWO

35. Language        = LANGUE_FRA

36. Seconde catégorie d'évènements

37. .

38. Language        = LANGUE_ENG

39. Second category of events

40. .

42. MessageId       = +1

43. SymbolicName    = LAST_CATEGORY

44. Language        = LANGUE_FRA

45. Repère pour la dernière catégorie d'évènements

46. .

47. Language        = LANGUE_ENG

48. Tag for the last category of events

49. .

51. ;

52. ;#endif  //__MESSAGES_EventCategorieFile_H__

53. ;

V-D-3 - Le fichier MessageFile.mc

Le fichier MessageFile.mc

1. ;#ifndef __MESSAGES_EventMessageFile_H__

2. ;#define __MESSAGES_EventMessageFile_H__

3. ;

4. ;// Ligne de compilation : mc.exe -u "$(InputDir)\$(InputName).mc"

5. ;//     -u indique un fichier Unicode

6. ;

8. MessageIdTypedef = DWORD

10. Outputbase = 16

12. LanguageNames =

13. (

14. LANGUE_FRA   = 0x040c:Messages_0x040c

15. LANGUE_ENG   = 0x0809:Messages_0x0809

16. )

18. FacilityNames =

19. (

20. FACILITY_NULL                    =  0

21. FACILITY_RPC                     =  1

22. FACILITY_DISPATCH                =  2

23. FACILITY_STORAGE                 =  3

24. FACILITY_ITF                     =  4

25. FACILITY_WIN32                   =  7

26. FACILITY_WINDOWS                 =  8

27. FACILITY_SSPI                    =  9

28. FACILITY_SECURITY                =  9

29. FACILITY_CONTROL                 = 10

30. FACILITY_CERT                    = 11

31. FACILITY_INTERNET                = 12

32. FACILITY_MEDIASERVER             = 13

33. FACILITY_MSMQ                    = 14

34. FACILITY_SETUPAPI                = 15

35. FACILITY_SCARD                   = 16

36. FACILITY_COMPLUS                 = 17

37. FACILITY_AAF                     = 18

38. FACILITY_URT                     = 19

39. FACILITY_ACS                     = 20

40. FACILITY_DPLAY                   = 21

41. FACILITY_UMI                     = 22

42. FACILITY_SXS                     = 23

43. FACILITY_WINDOWS_CE              = 24

44. FACILITY_HTTP                    = 25

45. FACILITY_USERMODE_COMMONLOG      = 26

46. FACILITY_USERMODE_FILTER_MANAGER = 31

47. FACILITY_BACKGROUNDCOPY          = 32

48. FACILITY_CONFIGURATION           = 33

49. FACILITY_STATE_MANAGEMENT        = 34

50. FACILITY_METADIRECTORY           = 35

51. FACILITY_WINDOWSUPDATE           = 36

52. FACILITY_DIRECTORYSERVICE        = 37

53. FACILITY_GRAPHICS                = 38

54. FACILITY_SHELL                   = 39

55. FACILITY_TPM_SERVICES            = 40

56. FACILITY_TPM_SOFTWARE            = 41

57. FACILITY_UI                      = 42

58. FACILITY_PLA                     = 48

59. FACILITY_FVE                     = 49

60. FACILITY_FWP                     = 50

61. FACILITY_WINRM                   = 51

62. FACILITY_NDIS                    = 52

63. FACILITY_USERMODE_HYPERVISOR     = 53

64. FACILITY_CMI                     = 54

65. FACILITY_USERMODE_VIRTUALIZATION = 55

66. FACILITY_USERMODE_VOLMGR         = 56

67. FACILITY_BCD                     = 57

68. FACILITY_USERMODE_VHD            = 58

69. FACILITY_SDIAG                   = 60

70. FACILITY_WEBSERVICES             = 61

71. FACILITY_WINDOWS_DEFENDER        = 80

72. FACILITY_OPC                     = 81

73. FACILITY_XPS                     = 82

74. FACILITY_RAS                     = 83

75. FACILITY_MBN                     = 84

76. )

78. SeverityNames =

79. (

80. Sev_Success      = 0x0

81. Sev_Information  = 0x1

82. Sev_Warning      = 0x2

83. Sev_Error        = 0x3

84. )

87. ;////////////////////////////////////////

88. ;// Events

89. ;//

91. MessageId     = 1000

92. SymbolicName  = EVENT_STARTED_BY_1

93. Facility      = FACILITY_NULL

94. Severity      = Sev_Success

95. Language      = LANGUE_FRA

96. Succès : L'application '%P01' (%1) a été démarrée par l'utilisateur "%2"

97. .

98. Language      = LANGUE_ENG

99. Success : The program '%P01' (%1) has been started by user "%2"

100. .

102. MessageId     = +0

103. SymbolicName  = EVENT_STARTED_BY_2

104. Facility      = FACILITY_NULL

105. Severity      = Sev_Information

106. Language      = LANGUE_FRA

107. Information : L'application '%P01' (%2) a été démarrée par l'utilisateur "%1"

108. .

109. Language      = LANGUE_ENG

110. Information : The program '%P01' (%2) has been started by user "%1"

111. .

113. MessageId     = +0

114. SymbolicName  = EVENT_STARTED_BY_3

115. Facility      = FACILITY_NULL

116. Severity      = Sev_Warning

117. Language      = LANGUE_FRA

118. Warning : L'application '%P01' (%1) a été démarrée par l'utilisateur "%2"

119. .

120. Language      = LANGUE_ENG

121. Warning : The program '%P01' (%1) has been started by user "%2"

122. .

124. MessageId     = +0

125. SymbolicName  = EVENT_STARTED_BY_4

126. Facility      = FACILITY_NULL

127. Severity      = Sev_Error

128. Language      = LANGUE_FRA

129. Error : L'application '%P01' (%2) a été démarrée par l'utilisateur "%1"

130. .

131. Language      = LANGUE_ENG

132. Error : The program '%P01' (%2) has been started by user "%1"

133. .

135. MessageId     = +1

136. SymbolicName  = EVENT_BACKUP

137. Facility      = FACILITY_NULL

138. Severity      = Sev_Information

139. Language      = LANGUE_FRA

140. Vous devriez sauvegarder vos données régulièrement !

141. .

142. Language      = LANGUE_ENG

143. You should regulary backup your data!

144. .

146. ;

147. ;#endif  //__MESSAGES_EventMessageFile_H__

148. ;

V-D-4 - Le fichier ParameterFile.mc

Le fichier ParameterFile.mc

1. ;#ifndef __MESSAGES_EventParameterFile_H__

2. ;#define __MESSAGES_EventParameterFile_H__

3. ;

4. ;// Ligne de compilation : mc.exe -u "$(InputDir)\$(InputName).mc"

5. ;//     -u indique un fichier Unicode

6. ;

8. MessageIdTypedef = DWORD

10. Outputbase = 10

12. LanguageNames =

13. (

14. NEUTRAL = 0x0000:Messages_0x0000

15. )

17. MessageId     = 5001

18. SymbolicName  = PARAMETER_5001

19. Language      = NEUTRAL

20. 'Test %P02'

21. .

23. MessageId     = 5002

24. SymbolicName  = PARAMETER_5002

25. Language      = NEUTRAL

26. EventLogWritter.exe

27. .

29. ;

30. ;#endif  //__MESSAGES_EventParameterFile_H__

31. ;

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

1. // EventLogReader.cpp : définit le point d'entrée pour l'application console.

2. //

4. #ifndef _WIN32_WINNT      // Autorise l'utilisation des fonctionnalités spécifiques à Windows XP ou version ultérieure.

5. #define _WIN32_WINNT 0x0501   // Attribuez la valeur appropriée à cet élément pour cibler d'autres versions de Windows.

6. #endif

8. #define _CRT_SECURE_NO_DEPRECATE

9. #define _CRT_NON_CONFORMING_SWPRINTFS

11. #include <Windows.h>

12. #include <stdio.h>

13. #include <locale.h>

14. #include <Sddl.h>

16. #define JOURNAL L"Application"

18. const wchar_t * _read_registry(const wchar_t * Source, const wchar_t * Key)

19. {

20. wchar_t folder[ MAX_PATH ];

21. swprintf( folder, L"SYSTEM\\CurrentControlSet\\Services\\EventLog\\%s\\%s", JOURNAL, Source );

23. // Create the event source registry key

24. HKEY      hRegKey = NULL;

25. RegOpenKey( HKEY_LOCAL_MACHINE, folder, &hRegKey );

27. // read the value

28. DWORD type;

29. static BYTE buffer[4096];

30. DWORD size = sizeof(buffer);

31. RegQueryValueEx(hRegKey, Key, 0,   &type, buffer,   &size);

32. if(type == REG_SZ)

33. {

34. return (const wchar_t *)buffer;

35. }

37. if(type == REG_EXPAND_SZ)

38. {

39. static wchar_t expanded[4096];

40. DWORD Nb = ExpandEnvironmentStrings((const wchar_t *)buffer, expanded, sizeof(expanded) / sizeof(expanded[0]));

41. buffer[Nb] = 0;

42. return (const wchar_t *)expanded;

43. }

45. // unknown type

46. return L"???";

47. }

49. const wchar_t * _load_string( const wchar_t * Dll, DWORD Id )

50. {

51. // load the library

52. HMODULE lib = LoadLibrary(Dll);

54. // reset the message

55. static wchar_t Message [4096];

57. // compute the flag of FormatMessage()

58. DWORD flags = FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS;

60. // load the message

61. DWORD nb_tchar = FormatMessage(flags, lib, Id, 0, Message, sizeof(Message) / sizeof(Message[0]), NULL);

62. Message[nb_tchar] = 0;

64. // unload the library

65. FreeLibrary(lib);

67. // remove any bad trailing characters (CR, LF)

68. while(nb_tchar > 0)

69. {

70. nb_tchar--;

71. if(Message[nb_tchar] == '\n')

72. {

73. Message[nb_tchar] = 0;

74. continue;

75. }

77. if(Message[nb_tchar] == '\r')

78. {

79. Message[nb_tchar] = 0;

80. continue;

81. }

83. break;

84. }

86. return Message;

87. }

89. const wchar_t * _get_category_string( const wchar_t * Source, DWORD Id )

90. {

91. // get the DLL to open

92. const wchar_t * dll = _read_registry( Source, L"CategoryMessageFile" );

94. // copy the message string

95. const wchar_t * msg = _load_string(dll, Id);

97. return msg;

98. }

100. const wchar_t * _get_parameter_string( const wchar_t * Source, DWORD Id )

101. {

102. // get the DLL to open

103. const wchar_t * dll = _read_registry( Source, L"ParameterMessageFile" );

105. // copy the message string

106. const wchar_t * msg = _load_string(dll, Id);

108. return msg;

109. }

111. const wchar_t * _replace_static(const wchar_t * Source, const wchar_t * buffer, const wchar_t * begin, DWORD size, DWORD id)

112. {

113. static wchar_t buf[4096];

115. // copy the begin of the string

116. DWORD len = (DWORD)(begin - buffer);

117. memcpy(buf, buffer, len * sizeof(wchar_t));

118. buf[len] = 0;

120. // add the new replacement string

121. wcscat(buf, _get_parameter_string(Source, id));

123. // copy the end of the string

124. wcscat(buf, &begin[size]);

126. return buf;

127. }

129. const wchar_t * _replace_dynamic(const wchar_t * buffer, const wchar_t * begin, DWORD size, const wchar_t * Str)

130. {

131. static wchar_t buf[4096];

133. // copy the begin of the string

134. DWORD len = (DWORD)(begin - buffer);

135. memcpy(buf, buffer, len * sizeof(wchar_t));

136. buf[len] = 0;

138. // add the new replacement string

139. wcscat(buf, Str);

141. // copy the end of the string

142. wcscat(buf, &begin[size]);

144. return buf;

145. }

147. const wchar_t * _get_message_string( const wchar_t * Source, DWORD Id, const wchar_t * String[])

148. {

149. // get the DLL to open

150. const wchar_t * dll = _read_registry( Source, L"EventMessageFile" );

152. const wchar_t * msg = _load_string(dll, Id);

153. static wchar_t buffer[4096];

154. wcscpy(buffer, msg);

156. const wchar_t * p = wcschr(buffer, '%');

157. DWORD id;

158. const wchar_t * begin;

159. DWORD size;

160. while(p != NULL)

161. {

162. begin = p;

163. p++;

164. size = 1;

165. if(*p == '%')

166. {

167. // this is the ID of a static parameter string

168. p++;

169. size++;

170. id = 0;

171. while( (*p >= '0') && (*p <= '9') )

172. {

173. id *= 10;

174. id += *p - '0';

175. p++;

176. size++;

177. }

179. // replace the string

180. wcscpy(buffer, _replace_static(Source, buffer, begin, size, id));

181. p = wcschr(buffer, '%');

182. continue;

183. }

184. else

185. {

186. // this is the number of a dynamic parameter

187. id = 0;

188. while( (*p >= '0') && (*p <= '9') )

189. {

190. id *= 10;

191. id += *p - '0';

192. p++;

193. size++;

194. }

195. if(id == 0) break;

197. // replace the string

198. wcscpy(buffer, _replace_dynamic(buffer, begin, size, String[id - 1]));

199. p = wcschr(buffer, '%');

200. continue;

201. }

202. }

204. return buffer;

205. }

207. const wchar_t * _get_sid( PSID Sid )

208. {

209. if(Sid == NULL)

210. return L"Non disponible";

212. wchar_t *str;

213. ConvertSidToStringSid( Sid, &str);

215. static wchar_t buf[4096];

216. wcscpy(buf, str);

217. LocalFree(str);

218. return buf;

219. }

221. void _analyse( EVENTLOGRECORD * p_eventlog )

222. {

223. wprintf(L"EventLog RecordNumber = %lu\n", p_eventlog->RecordNumber );

225. wprintf(L"   Length = %lu\n", p_eventlog->Length );

227. wprintf(L"   EventID = 0x%.8x\n", p_eventlog->EventID );

228. wprintf(L"      Code(0x%.4x) ", p_eventlog->EventID & 0xffff);

229. wprintf(L"Facility(0x%.3x) ", (p_eventlog->EventID & 0x0fff0000) >> 16);

230. wprintf(L"Reserved(0x%.1x) ", (p_eventlog->EventID & 0x10000000) >> 28);

231. wprintf(L"Customer(0x%.1x) ", (p_eventlog->EventID & 0x20000000) >> 29);

232. wprintf(L"Severity(0x%.1x) ", (p_eventlog->EventID & 0xc0000000) >> 30);

233. wprintf(L"\n");

235. wprintf(L"   EventCategory = %lu\n", p_eventlog->EventCategory );

237. // affichage des types

238. wprintf(L"   EventType = 0x%.4x ==> ", p_eventlog->EventType );

239. if( (p_eventlog->EventType & EVENTLOG_WARNING_TYPE) == EVENTLOG_WARNING_TYPE) wprintf(L"Warning ");

240. if( (p_eventlog->EventType & EVENTLOG_ERROR_TYPE) == EVENTLOG_ERROR_TYPE) wprintf(L"Error ");

241. if( (p_eventlog->EventType & EVENTLOG_INFORMATION_TYPE) == EVENTLOG_INFORMATION_TYPE) wprintf(L"Information ");

242. if( (p_eventlog->EventType & EVENTLOG_AUDIT_FAILURE) == EVENTLOG_AUDIT_FAILURE) wprintf(L"Audit failure ");

243. if( (p_eventlog->EventType & EVENTLOG_AUDIT_SUCCESS) == EVENTLOG_AUDIT_SUCCESS) wprintf(L"Audit success ");

244. wprintf(L"\n");

246. wprintf(L"   Reserved = %lu\n", p_eventlog->Reserved );

247. wprintf(L"   TimeGenerated = %lu\n", p_eventlog->TimeGenerated );

248. wprintf(L"   TimeWritten   = %lu\n", p_eventlog->TimeWritten );

249. wprintf(L"   ReservedFlags = %lu\n", p_eventlog->ReservedFlags );

250. wprintf(L"   ClosingRecordNumber = %lu\n", p_eventlog->ClosingRecordNumber );

252. wprintf(L"   NumStrings = %lu\n", p_eventlog->NumStrings );

253. wprintf(L"   StringOffset = %lu\n", p_eventlog->StringOffset );

255. // affichage des strings

256. const wchar_t * String[100];

257. DWORD offset = p_eventlog->StringOffset;

258. for(DWORD boucle = 0; boucle != p_eventlog->NumStrings; boucle++)

259. {

260. const BYTE *p = (BYTE *)p_eventlog;

261. p += offset;

262. const wchar_t * str = (const wchar_t *)p;

263. String[boucle] = (const wchar_t *)p;

264. offset += (DWORD)(wcslen(str) + 1) * sizeof(wchar_t);

265. }

266. for(DWORD boucle = 0; boucle != p_eventlog->NumStrings; boucle++)

267. {

268. wprintf(L"      String[%lu] = [%s]\n", boucle, String[boucle] );

269. }

271. // extraction du SID

272. wprintf(L"   UserSidLength = %lu\n", p_eventlog->UserSidLength );

273. wprintf(L"   UserSidOffset = %lu\n", p_eventlog->UserSidOffset );

274. PSID psid = NULL;

275. if(p_eventlog->UserSidLength != 0)

276. psid = (PSID)((BYTE *)p_eventlog + p_eventlog->UserSidOffset);

278. // affichage des data

279. wprintf(L"   DataLength = %lu\n", p_eventlog->DataLength );

280. wprintf(L"   DataOffset = %lu\n", p_eventlog->DataOffset );

282. const BYTE * p = (BYTE *)p_eventlog;

283. p += p_eventlog->DataOffset;

284. wprintf(L"      ");

285. for(DWORD boucle = 0; boucle != p_eventlog->DataLength; boucle++)

286. {

287. wprintf(L"%.2x ", *p );

288. p++;

289. }

290. wprintf(L"\n");

292. // get the source name

293. offset = sizeof ( EVENTLOGRECORD );

294. p = (BYTE *)p_eventlog;

295. p += offset;

296. const wchar_t * SourceName = (const wchar_t *)p;

297. wprintf(L"   SourceName = [%s]\n", SourceName );

299. // get the computer name

300. offset = (DWORD)(wcslen(SourceName) + 1) * sizeof ( wchar_t );

301. p += offset;

302. const wchar_t * ComputerName = (const wchar_t *)p;

303. wprintf(L"   ComputerName = [%s]\n", ComputerName );

305. // get the category string

306. wprintf(L"   Category string = [%s]\n", _get_category_string( SourceName, p_eventlog->EventCategory ) );

307. wprintf(L"   Message string = [%s]\n", _get_message_string( SourceName, p_eventlog->EventID, String ) );

308. wprintf(L"   SID = [%s]\n", _get_sid( psid ) );

310. wprintf(L"\n");

311. }

313. int wmain(int /*argc*/, wchar_t * /*argv*/ [])

314. {

315. // the console can display french caracters

316. _wsetlocale( LC_ALL, L"French" );

318. // choose the resource language

319. WORD PrimaryLanguage = LANG_ENGLISH;

320. WORD SubLanguage = SUBLANG_ENGLISH_UK;

322. // for french language

323. // if you prefer english language, comment these 2 lines

324. PrimaryLanguage = LANG_FRENCH;

325. SubLanguage = SUBLANG_FRENCH;

327. // set the preferred language for UI

328. SetThreadUILanguage ( MAKELANGID (PrimaryLanguage, SubLanguage) );

330. // ouverture du journal local d'évènements de la localisation "Application"

331. HANDLE hlog = OpenEventLog( NULL, JOURNAL );

333. // get the oldest log number

334. DWORD OldestLog = (DWORD)-1;

335. GetOldestEventLogRecord( hlog, &OldestLog );

337. // Get the record number of the event log record.

338. DWORD NbLog = (DWORD)-1;

339. GetNumberOfEventLogRecords( hlog, &NbLog);

341. // the idx where to go

342. DWORD last_idx = NbLog + OldestLog - 1;

344. // the buffer where to receive data

345. BYTE buffer[1024];

346. DWORD nb_read;

347. DWORD nb_needed;

348. DWORD nb_eventlog = 0;

350. // go to the last record

351. ReadEventLog(   hlog,                                             // event log handle

352. EVENTLOG_SEEK_READ | EVENTLOG_FORWARDS_READ,      // reads forward, sequential read

353. last_idx,                                       // index log to read

354. buffer,                                          // pointer to buffer

355. sizeof( buffer ),                                 // size of buffer

356. &nb_read,                                       // number of bytes read

357. &nb_needed );                                    // number of bytes needed

359. // creation d'un évènement pour attente bloquante

360. HANDLE hevent = CreateEvent(NULL, FALSE, FALSE, L"Event" );

362. // associe eventlog et event

363. NotifyChangeEventLog ( hlog, hevent );

365. for( ; ; )

366. {

367. WaitForSingleObject( hevent, INFINITE );

369. // lecture de l'évènement

370. while(ReadEventLog(   hlog,                                                // event log handle

371. EVENTLOG_SEQUENTIAL_READ | EVENTLOG_FORWARDS_READ,   // reads forward, sequential read

372. 0,                                                   // index log to read

373. buffer,                                             // pointer to buffer

374. sizeof( buffer ),                                    // size of buffer

375. &nb_read,                                          // number of bytes read

376. &nb_needed ) == TRUE)                              // number of bytes needed

377. {

378. // extraction de tous les évènements (il peut y en avoir plusieurs)

379. DWORD idx = 0;

380. while(nb_read != 0)

381. {

382. EVENTLOGRECORD *p_eventlog = (EVENTLOGRECORD *) &buffer[ idx ];

384. DWORD Size = p_eventlog->Length;

385. nb_read -= Size;

386. idx += Size;

388. // analyse de l'évènement pointé par p_event

389. _analyse( p_eventlog );

391. nb_eventlog++;

392. }

393. }

395. // read only 7 eventlog (test purpose)

396. if(nb_eventlog >= 7)

397. break;

398. }

400. // fermeture de l'event log

401. CloseEventLog( hlog );

403. // destruction de l'event

404. CloseHandle( hevent );

406. return 0;

407. }

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

- Message Compiler (MC.exe)

- Message Text Files

- Language Identifier Constants and Strings

- Client Processing of Event Descriptions and Other Localizable Strings

- FormatMessage Function

- Event Viewer

- L'API Event Logging

- L'API Windows Event Log

- Security Descriptor Definition Language

VII-B - Remerciements

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