Открываем .evt файл с помощью класса EventLog, рефлексии и dllivoke
Зачем это нужно
Однажы у меня возникла производственная необходимость прочитать в C# .evt файл. С удивлением я обнаружил, что класс EventLog не содержит методов/конструкторов, позволяющих инициализировать его из файла, что меня немного удивило, т.к. я знал над каким API этот клас обёрнут, и знал что у этого API есть функция открытия лога из файла. Тогда я решил продолжить поиски и нашёл другой класс — EventLogReader, который позволял открывать лог из файла, но являлся обёрткой над другим API, более новым. Добавленым в Vista+ системы. Что делать дальше, было не совсем понятно, т.к. решение нужно было и под более младшие системы типа win XP.
Возможные варианты
Проанализировав ситуацию, я понял что у меня не такой уж и большой выбор вариантов. Это либо писать свою собёртку над старым API, аналогичную классу EventLog, т.е. изобретать собственный велосипед (чего очень не хотелось), либо как-то модернизировать существующий велосипед.
Как это сделать
Благодаря наличию разнообразных утилит декомпиляции .net сборок (я пользовался dotPeek) я смог подглядеть реализацию класса EventLog. Как я и предполагал, этот класс (точнее группа классов), является обёрктой над старым API работы с логами событий ОС.
Класс SafeEventLogReadHandle используется в классе EventLog в методе OpenForRead.
И инициализирует закрытый член readHandle. Метод OpenForRead, в свою очередь вызывается в семи разных методах, но везде с одинаковой проверкой. IsOpenForRead, это вычисляемое свойство только для чтения. Возникло желание попытаться через рефлексию инициализировать readHandle нужным значением (полученным из API функции OpenBackupEventLog). К сожалению, эта функция возвращает голый хэндл, а у класса SafeEventLogReadHandle нет конструктора, позволяющего инициализацию голым хендлом и сам класс sealed, так что отнаследоваться и расширить его не получится. И вообще единственный конструктор класса помечен как internal, так что получать его экземпляр придётся так же через рефлексию. Если мы взглянем на определение класса, то увидим, что он наследует SafeHandleZeroOrMinusOneIsInvalid, который в свою очередь наследует SafeHandle. Класс SafeHandle содержит закрытое поле handle. Значит, нам нужно через рефлексию получать экземпляр класса и, через рефлексию же, устанавливать значение поля handle значением полученным от функции OpenBackupEventLog, затем, проинициализированной таким образом переменной типа SafeEventLogReadHandle через рефлексию инициализировать поле readHandle. Приведённый пример прекрасно открывает .evt/.evtx файлы по имени переданному через командную строку. Во время разработки использовался .Net Framework v3.5, что критично только для использованния LINQ в примере.
Недостатки
Из существенных недостатков данного способа стоит отметить тот факт, что работа такого решния не гарантируется и есть вероятность, что в будущем внутренняя реализация EventLog изменится. Достаточно изменения имени закрытого поля readHandle для того, чтобы пришлось заново писать код. Хотя фактически вероятность такого исхода крайне мала.
Приемущества
Приемущества такого подхода очевидны: нет необходимости заново писать уже написаный когда-то код работы с API чтения логов событий ОС. Так же приемуществом перед классом EventLogReader является работа данного решения на всех ОС, на которых можно поставить .NetFramework.