WMI Abfrage für Ereignisprotokoll nicht zuverlässig?
Donnerstag, 25. Februar 2010 09:34
Ich habe die letzten 2 Tage an einem Phänomen gekämpft, das hat mich wirklich an meinen Fähigkeiten als Entwickler zweifeln lassen.

Folgende Situation:
  • Ein Testfall erzeugt durch Systemzeitumstellungen definierte Einträge in verschiedene Ereignisprotokolle.
  • Nach jedem Schreiben gibt es im Testframework eine Sicherheitsüberprüfung, ob der Eintrag vorhanden ist.
  • Nachdem alle Einträge geschrieben wurden, wird meine Komponente getestet, dessen Aufgabe es ist Ereignisse von verchiedenen Rechnern und Ereignisprotokollen zu holen.
  • Aufgrund der hohen Datenmengen werden die Ereignisse zeitscheibenweise per WMI abgeholt (siehe auch: Blog Rehl - Quota violation)
Fehlerbeschreibung:
Unter Umständen kam es vor, dass für den ersten konfigurierten Rechner und das erste Ereignisprotokoll in der ersten Zeitscheibe keine Ergebnisse vorlagen (trotz richtig konzipiertem Testfall), ich schätze das Auftreten auf ca. 30%.

Details:
Meine Vorgehensweise ist so codetechnisch umgesetzt worden:
                    ManagementObjectSearcher searcher = null;
                    ManagementScope scope = null;
                    SelectQuery query = null;

                    // Vorbereitungen und WMI Query Objekt Initialisierungen
                    searcher = new ManagementObjectSearcher();
                    scope = new ManagementScope(string.Format("\\\\{0}", computerName));
                    scope.Path = new ManagementPath("root\\CIMV2");
                    scope.Path.Server = computerName;
                    scope.Connect();
                    searcher.Scope = scope;
                    query = new SelectQuery();

                    // Datum WMI gerecht umwandeln
                    strIntervalStartTime = ManagementDateTimeConverter.ToDmtfDateTime(intervalStartTime);
                    strIntervalEndTime = ManagementDateTimeConverter.ToDmtfDateTime(intervalEndTime);

                    // Hiermit werden die Eventlogs ausgelesen
                    query.QueryString = string.Format("SELECT * FROM Win32_NTLogEvent WHERE (LogFile = '{0}' And TimeGenerated > '{1}' And TimeGenerated < '{2}')", eventLog, strIntervalStartTime, strIntervalEndTime);

                    // Die Abfrage dem Suchobjekt zuweisen
                    searcher.Query = query;

                    // Ergebnisse ermitteln
                    retVal = searcher.Get();


Analyse:
Zunächst dachte ich, dass es etwas mit den Zeitscheiben zu tun hatte, die Trace-Ausgaben waren aber eindeutig, dass alles richtig laufen sollte. Ein nachträglich eingebautes Retry (10 Stück an der Zahl), das durchgeführt wurde, wenn keine Ergebnisse ermittelt wurden brachte kein Ergebnis.

Also haben wir einen Alternativweg gesucht um Ereignisse zu ermitteln und stießen im .NET Framework auf System.Diagnostic.EventLog. Die EventLog Klasse verwendet intern die Windows API anstatt WMI. Erste Tests brachten hier ein besseres Ergebnis. Es wurden immer alle Ereignisse zurückgeliefert. Problem: Die EventLog Klasse kann keine Filterung durchführen. Es holt immer alle Ereignisse aus einem Protokoll (zumindest die Headerinformationen hierzu). Ein Lasttest brachte auch die Erkenntnis, dass das bei großen Ereignisprotokollen nicht zu empfehlen ist.
                EventLog log = new EventLog(eventLog, computerName);
                foreach (EventLogEntry entry in log.Entries)
                {
                    if (entry.TimeGenerated > startTime && entry.TimeGenerated < endTime)
                    {
                        retVal = false;
                        break;
                    }
                }


Ergebnis:
Also machten wir einen abschließenden Test: Bringt die WMI Abfrage keine Ergebnisse, dann ermitteln wir mittels der EventLog Klasse die Anzahl der Ereignisse, die in der Zeitscheibe liegen. Nur dann wenn WMI kein Ergebnis liefert, und die Windows API auch nicht, dann überspringen wir die Zeitscheibe, ansonsten wird die Verarbeitung aufgrund des Datenverlusts abgebrochen.

Fazit:
Ich würde aufgrund unserer Tests, WMI als nicht zuverlässig einstufen, wenn es um das Holen von Ereignissen geht!
Übrigens ein Count(*) ist grundsätzlich für WMI gültig, aber für die Ereignisse nicht verfügbar.
von Timo Rehl | mit no comments
Abgelegt unter: , ,
Visual Studio Source Control Explorer (TFS) Encoding Detection
Dienstag, 2. Februar 2010 15:31
Heute bin ich auf ein interessantes Feature in Visual Studio im Source Control Explorer gestossen. Über Rechts-Klick auf eine Datei (die vorab per Get auf die Platte geholt werden muss) -> Properties kommt man unter anderem auf die Möglichkeit "Set Encoding...", das man problemlos anklicken kann.

Das Ganze sieht dann so aus:

Mittels "Detect" kann man Visual Studio erkennen lassen in welchem Format aktuell die Datei abgespeichert ist. Man kann hier auch das Encoding direkt setzen, aber bei Verlusten (z.b. von UTF-8 zu Windows-1252) möchte ich nicht wissen was Visual Studio dann verschluckt.

Zum Erkennen von Encodings aber durchaus brauchbar, ganz besonders wenn es über UTF-8 und Ansi hinausgeht.
von Timo Rehl | mit no comments
Abgelegt unter: ,
Winterchaos Odenwald
Montag, 1. Februar 2010 09:25
Vorab: Das hat nun nichts mit Entwicklung zu tun, aber ich fand das so beeindrucken, dass ich das mal hier festhalten will. Wink

Es ist schon traumhaft einen schönen Winter zu haben. Dieser Winter ist besonders extrem, und wenn man sich das mal auf dem Bild anschaut...


... dann hat so ein Winter aber auch seine Nachteile ;-)

Müllabfuhr kam nicht durch, Busse sind teilweise nicht mehr gefahren, Einkaufen war ein Abenteuer. Aber im Schnee wandern war toll Big Smile
Ich hoffe, dass Ihr besser durch den Winter kommt.
Anhand Framework Version WPF oder WinForms Anwendung starten
Freitag, 29. Januar 2010 03:43

Ich habe auf http://dotnet-forum.de einen Artikel mit oben genanntem Thema verfasst. Den kompletten Artikel findet ihr hier: Artikel.

Kurzzusammenfassung:

Einleitung und Zielgruppe:
(Schwierigkeit: mittel)
Das Thema WPF oder WinForms? ist aktuell in jedem Projekt, das .NET Oberfläche bietet eine interessante Frage. Für die Projekte die es sich leisten können beides zu implementieren und anhand der installierten .NET Version dann zu entscheiden, ob WPF verwendet wird oder nicht, für die könnte dieser Artikel von Interesse sein.

Weiterhin zeigt der Artikel wie WPF oder WinForms Anwendungen programmatisch zu starten sind. Die Auswahl, welche Anwendung geladen werden soll wird mittels dem Ansatz von Microkernel und dem dynamischen Laden von Assemblies realisiert. Schaut Euch einfach den Artikel mal an ;-) ...

Euer Timo

von Timo Rehl | mit no comments
Snippet: Binärvergleich zweier Dateien
Freitag, 22. Januar 2010 10:31
Ich habe für unsere Testabteilung ein kleines Snippet geschrieben, das 2 Dateien binär miteinander vergleicht. Das Ganze ist als eine einzelne statische Methode konzipiert, die lediglich ein "True" oder ein "False" als Vergleichsergebnis zurückliefert.

Das Snippet und den Quellcode findet Ihr unter:
Binärvergleich zweier Dateien

Vielleicht könnt Ihr so etwas auch mal gebrauchen, viel Spaß damit.
EventLog remote einsammeln per WMI verursacht quota violation Fehler
Mittwoch, 20. Januar 2010 19:28
Wir bekommen sehr häufig den Fehler "quota violation" wenn wir über remote WMI versuchen EventLog Einträge zu sammeln. Hierfür gibt es auch einen Microsoft Support Beitrag, der allerdings für uns keine Lösung brachte. Selbst der dort vorhandene Hotfix brachte keine Verbesserung.

Wir hatten sogar einen Microsoft Call aufgemacht, bekommen aber nur den Workaround vorgeschlagen: Limitieren Sie die Datenmenge indem sie per WHERE Klausel die Datenmenge einschränken. Jetzt gehen wir doch schon so vor, dass wir EventLogs nur tagesweise per WMI anfordern, doch die Datenmenge ist immer noch zu groß.

Leider ist es auch nicht möglich vorher abzufragen wie viele Ereigniseinträge denn die Abfrage holen würde, ein Count(*) ist für diese Abfrage nicht zulässig. Eine andere Möglichkeit die Datenmenge vorab zu ermitteln ist mir nicht bekannt.

Jetzt bauen wir den Sammler so um, dass er in einer Schleife die Ereignisse in konfigurierbaren Zeitintervallen aufteilt und abfragt. Wir hoffen, dass wir das Intervall so klein wählen, dass die Datenmenge nicht überschritten wird.

TIPP: Also kann ich allen, die per WMI Ereignisprotokolle auslesen nur raten:
Baut schon von vornherein solch eine Möglichkeit ein, dass ihr die Ereignisse mit Zeitintervallen einteilen lassen könnt und holt Euch die Daten Häppchenweise.
BizTalk: Behandlung von SOAP Fault Nachrichten in custom pipeline Komponenten
Dienstag, 19. Januar 2010 16:18
Heute habe ich für eine unserer BizTalk Send Pipeline Komponente ein Handling für SOAP Fault Nachrichten vorgesehen. Alle Recherchen, die ich im Internet gefunden habe beziehen sich auf WebServices und WCF Adapter, die solch eine Meldung "von außen" bekommen. Mein unten aufgeführter Helfer kann höchstwahrscheinlich nicht mit SOAP Faults umgehen, die nicht von BizTalk erzeugt wurden.

Mein Helfer behandelt ein anderes Szenario. Ich wollte die SOAP Fault Nachrichten behandeln, die von BizTalk erzeugt worden sind (in aller Regel Routing Failures oder missing correlation token oder disposed service handler). Die von BizTalk erzeugten SOAP Fault Nachrichten verfügen über Promoted Properties im Kontext, auf die man bequem zurückgreifen kann.

Ich habe dann für die Behandlung dieser SOAP Fault Nachrichten einen kleinen Helfer geschrieben:
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;

// benötigte Referenzen (aus dem BizTalk SDK):
//     Microsoft.BizTalk.GlobalPropertySchemas.dll
//    Microsoft.BizTalk.Pipeline.dll
//    Microsoft.XLANGs.BaseTypes
using Microsoft.BizTalk.Message.Interop;
using Microsoft.XLANGs.BaseTypes;

namespace MyProject.PipelineComponentHelper
{
    /// <summary>
    /// Helferklasse, die Methoden und Eigenschaften anbietet um mit BizTalk SOAP Fault
    /// Nachrichten umgehen zu können.
    /// </summary>
    public class SoapFaultMessage
    {
        #region Constants

        /// <summary>
        /// Definiert den Typ einer SOAP Fault Nachricht.
        /// </summary>
        private const string c_ACKTYPEFAULT = "NACK";

        /// <summary>
        /// XPath Ausdruck, der auf den SOAP Fault Basisknoten zeigt.
        /// </summary>
        private const string c_XPATHSOAPFAULTBASE = "*[local-name='Body']/*[local-name='Fault']";

        #endregion

        #region Variable definitions

        /// <summary>
        /// Enthält die Referenz auf die AckType Property aus dem BizTalk SDK.
        /// </summary>
        private static readonly PropertyBase m_AckTypeMessageProperty = new BTS.AckType();

        /// <summary>
        /// Enthält die Referenz auf die AckDescription Property aus dem BizTalk SDK.
        /// </summary>
        private static readonly PropertyBase m_AckDescriptionMessageProperty = new BTS.AckDescription();

        /// <summary>
        /// Enthält die Referenz auf die AckFailureCode Property aus dem BizTalk SDK.
        /// </summary>
        private static readonly PropertyBase m_AckFailureCodeMessageProperty = new BTS.AckFailureCode();

        /// <summary>
        /// Enthält die BizTalk Nachricht.
        /// </summary>
        private IBaseMessage m_BizTalkMessage = null;

        /// <summary>
        /// Enthält den FailureCode der SOAP Fault Nachricht.
        /// </summary>
        private string m_FailureCode = string.Empty;

        /// <summary>
        /// Enthält die Fehlerbeschreibung der SOAP Fault Nachricht.
        /// </summary>
        private string m_FailureDescription = string.Empty;

        /// <summary>
        /// Enthält die SOAP Fault Nachricht als Xml Dokument.
        /// </summary>
        private XmlDocument m_MessageBody = null;

        #endregion

        #region Constructor

        /// <summary>
        /// Standard Konstruktor, der eine IBase Nachricht entgegennimmt.
        /// </summary>
        public SoapFaultMessage(IBaseMessage soapFaultMessage)
        {
            // Eine Exception werfen, wenn es sich um keine SOAP Fault Nachricht handelt.
            if (!IsSoapFaultMessage(soapFaultMessage))
            {
                throw new ArgumentException("Es wurde versucht ein SOAPFaultMessage Objekt aus einer Nachricht zu erzeugen, die keine SOAP Fault Nachricht ist.");
            }
            
            // Datenübernahme
            m_BizTalkMessage = soapFaultMessage;
        }

        #endregion

        #region Public properties

        /// <summary>
        /// Liefert den SOAP Fault Fehlercode.
        /// </summary>
        public string FailureCode
        {
            get { return m_FailureCode; }
        }

        /// <summary>
        /// Liefert die SOAP Fault Fehlerbeschreibung.
        /// </summary>
        public string FailureDescription
        {
            get { return m_FailureDescription; }
        }

        /// <summary>
        /// Liefert die komplette Nachricht als Xml Dokument
        /// </summary>
        /// <remarks>Die Eigenschaft liest den Nachrichten Inhalt aus dem BizTalk Nachrichten
        /// Body Stream. Sollte dieser nicht in der Lage sein durchsuchbar zu sein (CanSeek == false),
        /// so ist der Stream nach dem Zugriff auf die Eigenschaft vollständig gelesen.</remarks>
        public XmlNode SoapFaultBaseNode
        {
            get
            {
                return GetSoapFaultBaseNode();
            }
        }

        #endregion

        #region Public static methods

        /// <summary>
        /// Diese Methode überprüft, ob es sich bei der BizTalk Nachricht um eine SOAP Fault
        /// Nachricht handelt. Als Kriterium dient die Kontexteigenschaft "AckType", die
        /// vorhanden und "NACK" sein muss.
        /// </summary>
        /// <param name="msgToCheck">Die Nachricht, die es zu überprüfen gilt.</param>
        /// <returns>True, falls es sich um eine SOAP Fault Nachricht handelt, ansonsten False.</returns>
        public static bool IsSoapFaultMessage(IBaseMessage msgToCheck)
        {
            object ackType = null;
            bool retVal = false;
            string tmpString = string.Empty;

            // Wenn kein Nachrichtenobjekt übergeben wurde, dann gleich False zurückgeben
            if (msgToCheck != null)
            {
                // Kontexteigenschaft "AckType" auslesen
                ackType = msgToCheck.Context.Read(m_AckTypeMessageProperty.Name.Name, m_AckTypeMessageProperty.Name.Namespace);

                // Überprüfen, ob Kontexteigenschaften gefunden wurde, und ob es sich um eine SOAP Fault Nachricht handelt
                if (ackType != null) 
                {
                    tmpString = (string)ackType;
                    retVal = tmpString.Equals(c_ACKTYPEFAULT);
                }
            }

            return retVal;
        }

        #endregion

        #region Private methods

        /// <summary>
        /// Extrahiert alle Informationen aus der SOAP Fault Nachricht.
        /// </summary>
        private void ExtractSoapInfoFromMessage()
        {
            object tmpObject = null;

            // Fehlercode auslesen
            tmpObject = m_BizTalkMessage.Context.Read(m_AckFailureCodeMessageProperty.Name.Name, m_AckFailureCodeMessageProperty.Name.Namespace);
            if (tmpObject != null)
            {
                m_FailureCode = (string)tmpObject;
            }

            // Fehlerbeschreibung auslesen
            tmpObject = m_BizTalkMessage.Context.Read(m_AckDescriptionMessageProperty.Name.Name, m_AckDescriptionMessageProperty.Name.Namespace);
            if (tmpObject != null)
            {
                m_FailureDescription = (string)tmpObject;
            }
        }

        /// <summary>
        /// Methode, die den Body der BizTalk Message in ein Xml Dokument einliest.
        /// </summary>
        private void ExtractMessageToXmlDocument()
        {
            // Falls noch nie erfolgreich die Nachricht eingelesen wurde, dann
            // jetzt einlesen, ansonsten ist hier nichts zu tun
            if (m_MessageBody == null)
            {
                try
                {
                    Stream bodyStream = m_BizTalkMessage.BodyPart.GetOriginalDataStream();
                    m_MessageBody = new XmlDocument();
                    m_MessageBody.Load(bodyStream);

                    if (bodyStream.CanSeek)
                    {
                        bodyStream.Position = 0;
                    }
                }
                catch
                {
                    // Fehler sollten ignoriert werden, weil sie abhängig von der
                    // Struktur der BizTalk Nachricht sind, und weil das Einlesen
                    // in das Xml Dokument für die Verarbeitung irrelevant ist.
                    m_MessageBody = null;
                }
            }
        }

        /// <summary>
        /// Liefert den SOAP Fault Basisknoten als Xml Knoten.
        /// </summary>
        /// <returns>SOAP Fault Basisknoten als Xml Knoten.</returns>
        private XmlNode GetSoapFaultBaseNode()
        {
            XmlNode retVal = null;

            // Nachricht als Xml Dokument einlesen
            ExtractMessageToXmlDocument();

            // Falls erfolgreich, dann den SOAP Fault Base Knoten finden
            if (m_MessageBody != null)
            {
                retVal = m_MessageBody.SelectSingleNode(c_XPATHSOAPFAULTBASE);
            }

            return retVal;
        }

        #endregion
    }
}


Statische Methode IsSoapFaultMessage:
Die Methode hilft einem zu überprüfen, ob es sich um eine von BizTalk erzeugte SOAP Fault Nachricht handelt. Als Kriterium für den Rückgabewert wird die Kontexteigenschaft AckType herangezogen, und auf den Wert "NACK" (Fehler) überprüft. Sollte die Kontexteigenschaft mit dem Wert übereinstimmen, so liefert die Methode True zurück, andernfalls False.

Konstruktor mit Parameter IBaseMessage:
Der Konstruktor nimmt die BizTalk Nachricht via Interface IBaseMessage entgegen und überprüft, ob es sich um eine SOAP Fault Nachricht handelt. Sollte dem nicht so sein, so schmeißt der Konstruktor eine Exception.
-> Das Schmeißen von Exception in Konstruktoren ist unter Umständen schlechter Stil. In meinem Anwendungsfall war es aber die einzige Möglichkeit die Verwendung der Klasse eindeutig zu machen.
Wenn es sich um eine SOAP Fault Nchricht handelt, so wird sich die Referenz darauf gemerkt.

Properties:
Die Properties für diese Klasse sollten laut Codekommentar recht eindeutig sein.


Verwendung:
Bevor man eine Instanz dieses Helfers erzeugt sollte man mittels der statischen Methode IsSoapFaultMessage überhaupt überprüfen, ob es sich um eine von BizTalk erzeugte SOAP Fault Nachricht handelt. Als Kriterium wird die Kontexteigenschaft AckType herangezogen, und auf den Wert "NACK" (Fehler) überprüft.

Beispiel einer Verwendung:
// Überprüfen, ob ein BizTalk SoapFault vorliegt
if (SoapFaultMessage.IsSoapFaultMessage(inMsg))
{
    SoapFaultMessage soapMessage = new SoapFaultMessage(inMsg);
    throw new Exception(string.Format("BizTalk Fehler: {0} mit Fehlertext: {1}", soapMessage.FailureCode, soapMessage.FailureDescription));
}


Fazit:
Mit Hilfe dieser Klasse habe ich es geschafft SOAP Fault Nachrichten gekapselt auszuwerten und durch einfachen Zugriff auf die Daten transparent zu halten.

Verbesserungspotenzial:
Es gibt bei einer SOAP Fault Nachricht noch weitere Daten, die über Properties zugänglich gemacht werden können. Hier würde ich mit XPathes im Helfer arbeiten.
XSD generierte Klasse mit minOccurs="0" und Default Werten
Montag, 18. Januar 2010 10:25
Mir ist bei einem Fehler aufgefallen, dass XSD.exe zusammen mit dem XmlSerializer so seine Tücken hat.
Ich weiß, dass XSD.exe nicht das gelbe vom Ei ist, weshalb ich hier nicht auf Alternativen eingehe.

Problembeschreibung:
Eine Klasse, die mittels XSD.exe aus einem Schema heraus generiert werden soll. Das Schema sieht z.B. so aus:
<?xml version="1.0" encoding="utf-16"?>
<xs:schema xmlns="http://tempuri.org/TestObject.xsd" elementFormDefault="qualified" targetNamespace="http://tempuri.org/TestObject.xsd" id="TestObject" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="TestObject">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="TestStringField" type="xs:string" />
        <xs:element default="-1" minOccurs="0" name="TestIntegerField" type="xs:integer" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

Wichtig ist, dass TestIntegerField "minOccurs" und einen "default" besitzt!

Die Klasse, die mittels XSD.exe generiert wird sieht dann so aus:
using System.Xml.Serialization;

[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.42")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="http://tempuri.org/TestObject.xsd")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="http://tempuri.org/TestObject.xsd", IsNullable=false)]
public partial class TestObject {
    
    private string testStringFieldField;
    
    private string testIntegerFieldField;
    
    public TestObject() {
        this.testIntegerFieldField = "-1";
    }
    
    /// <remarks/>
    public string TestStringField {
        get {
            return this.testStringFieldField;
        }
        set {
            this.testStringFieldField = value;
        }
    }
    
    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(DataType="integer")]
    [System.ComponentModel.DefaultValueAttribute("-1")]
    public string TestIntegerField {
        get {
            return this.testIntegerFieldField;
        }
        set {
            this.testIntegerFieldField = value;
        }
    }
}


Problem ist, wenn man die Default Werte unangetastet lässt, dass beim Serialisieren mittels dem XmlSerializer diese Xml Knoten nicht mitserialisert werden.

Lösung:
Es gibt hierfür auch bei Microsoft einen Support Beitrag, wo erklärt wird, dass das "By Design" ist.
Es bleibt einem nur übrig, alle Attribute
[System.ComponentModel.DefaultValueAttribute("-1")]
im generierten Code nachträglich zu entfernen.

Keine glückliche Lösung, aber es ist immerhin eine.
Mehr Beiträge Nächste Seite »