Benachrichtigungen erhalten wenn der Symantec Endpoint Protection Manager (SEPM) keine Virendefinitionen aktualisiert

Seit Jahren benutzen wir die Symantec Endpoint Protection in meiner Firma. Aktuell die Version 14.0.1.
Eigentlich macht das Ding was es soll. Aber… Es gibt einen Fall in dem im Werkzeug Koffer von Symantec kein Tool vorhanden ist das Problem zu lösen.

Was passiert?

Eigentlich möchte man ja von einem Antivirensystem nichts hören und sehen. Es soll funktionieren und das war es.
Ganz besonders in einer Firma in der 5, 10, 20 und mehr Clients vorhanden sind.
Der SEPM (Symantec Endpoint Protection Manager), benachrichtigt much brav wenn auf Stations nach mehreren Tagen noch alte Virendefinition sind, oder auch eine bestimme Anzahl von PCs erreicht wurden, die alte Virendefinitionen haben. Oft sind das bei uns Maschinen, die unterwegs sind, oder lange nicht eingeschaltet wurden.

Aber es gibt einen Fall in dem der SEPM vollkommen versagt: Wenn nämlich der SEPM selber keine neuen Virendefinitionen erhält. Warum auch immer!

Ich hatte in den letzten Jahren mehrfach den Fall, in denen der SEPM keine neuen Virendefinitionen von Symantec geladen hat. Die Gründe waren vielseitig. Mal hatte der SEPM kein Internet aufgrund eines Konfigurationsfehlers, mal startete der SEPM gar nicht nach einem Windows Update.
Aber in den meisten Fällen war der SEPM einfach nicht fähig, die neuen Signaturen zu laden obwohl er anzeigte, dass welche zum Download bereit stehen.

Der letzte Fall ist besonders nervig. Ich habe zwei Support-Cases zu dem Thema schon offen gehabt, aber die redlich bemühten Supporter haben dennoch nichts herausbekommen.
Nach jeweiligem Neustart des Dienstes oder des Servers, lief es fast immer wieder. Also hatte sich scheinbar nur irgendwas intern „verklemmt“!

Dieser Fall ist aber gefährlich. Man bekommt von der ganzen Sache nichts mit, bis eine bestimmte Anzahl von PCs nach ein paar Tagen eben alte Virendefinitionen haben. In der Einstellung bei uns sind das 10% der Maschinen nach 4 Tagen. Man kann das zwar herunter drücken, aber diese Warnungen nerven meistens nur ohne triftigen Grund.
Und man kann in diesem Fall nicht mal testweise einfach den SEPM neu starten.
Eigentlich will ich keine Meldung und das System soll erstmal selbst versuchen erkannte Problem zu lösen.
Vor allem habe ich keine Lust irgendjemanden zu beauftragen, der pro Tag einmal diese blöde Konsole startet und nachschaut was los ist. Über alles andere bekomme ich ja auch Emails.

Ich finde solch eine lange Latenz, in der es nicht bemerkt wird, dass die AV-Signaturen alt sind, einfach gefährlich.
Aber Bordmittel darüber zu warnen gibt es nicht!
Zudem trat dieser Fall ca alle 6-9 Wochen immer einmal wieder auf.
Und das nervt.

Also habe ich mich auf die Suche gemacht und habe für den SQL Server in dem unsere Daten gehalten werden zwei kleine Jobs geschrieben die nachfolgend beschreiben werden.
Diese Jobs laufen nun einige Monate und haben bereits mehrfach erfolgreich dieses Problem „selbsttätig“ behoben…

Job 1: Symantec Virus Signature Check

Dieser Job läuft jede Stunde einmal.
Der Code macht einfach folgendes.

  • Sollte es in den letzten 32 Stunden (siehe Wert für @delta) eine Änderungen in den Signaturen gegeben haben ist alles OK
  • Gab es kein Update der Signaturen dann werden zwei Schritte eingeleitet.
  • Es wird über den internen SQL Mail Dienst eine Warnung versendet an den Admin.
  • Danach wird ein weiterer Job gestartet mit dem Namen Symantec SEPM Restart.

Die 32 Stunden, sind ein Wert der sich aus Erfahrungswerten gebildet hat. In 98% aller Fälle werden Signaturen innerhalb von 24h aktualisiert. Aber es gibt eben ein paar Ausnahmen

Taucht die Email mehr als zweimal auf, muss ich wohl irgendwie aktiv werden und mal manuell kontrollieren.

DECLARE @delta INT
-- number of hours
SET @delta = 32 
DECLARE @d DATETIME 
DECLARE @t VARCHAR(MAX)
IF NOT EXISTS(SELECT * FROM PATTERN WHERE INSERTDATETIME>DATEADD(hh,-@delta,GETDATE()) AND PATTERN_TYPE='VIRUS_DEFS')
BEGIN
      SET @d = (SELECT TOP 1 INSERTDATETIME FROM PATTERN WHERE PATTERN_TYPE='VIRUS_DEFS' ORDER BY INSERTDATETIME DESC)
      SET @t = 'Hallo Admin!

Die letzten Antivirus-Signaturen wurden am ' + CONVERT(VARCHAR, @d, 120)+' aktualisiert!
Es wird versucht den SEPM Dienst neu zu starten!

Liebe Grüße Ihr
SQLServerAgent'
      EXEC msdb.dbo.sp_send_dbmail @profile_name='Administrator',
				   @recipients='administrator@mydomain.de',
				   @subject='Symantec Virus Definitionen sind nicht aktuell',
				   @body=@t
      PRINT 'Virus Signaturen sind veraltet! Letztes Update: ' + CONVERT(VARCHAR, @d, 120)
      EXEC msdb.dbo.sp_start_job @job_name='Symantec Restart'
      PRINT 'Restart SEPM server!!!'
    END
  ELSE
    BEGIN 
      SET @d = (SELECT TOP 1 INSERTDATETIME FROM PATTERN WHERE PATTERN_TYPE='VIRUS_DEFS' ORDER BY INSERTDATETIME DESC)
      PRINT 'Virus Signaturen sind OK! Letztes Update: ' + CONVERT(VARCHAR, @d, 120)
    END

Job 2: Symnatec Restart

Dieser Job wird nur durch den Job 1 gestartet und er ist äußerst trivial.
Führt einfach nur 2 Befehle aus, die den SEPM stoppen und anschließend neu starten.

NET STOP SEMSRV
NET START SEMSRV

PS: Traurig war, dass man durch den Support auch keine Hilfe bekam, nachdem ich solch einen Lösungsansatz vorschlug. Man wollte mir keine Infos über die Strukturen der Tabellen geben. Letzten Endes waren die Suchmaschinen so nett alle nötigen Informationen zu liefern, denn ich war nicht der einzige mit diesem Problem.

Visual Studio 2013 Update 2 RC ist nun verfügbar

Mit dem neuen SQL 2014 der gestern veröffentlicht wurde kamen auch ein RC für Visual Studio 2013 Update 2. Dabei ist das Update 1 erst ende Januar heraus gekommen.
Das Update 2 für den TFS 2013 ist bereits veröffentlicht worden.
Siehe:  http://blogs.msdn.com/b/bharry/archive/2014/04/02/tfs-2013-2-update-2-released.aspx

Besonders gespannt bin ich auf die Linker Enchantements, die mit Update 2 kommen.
http://blogs.msdn.com/b/vcblog/archive/2014/03/25/linker-enhancements-in-visual-studio-2013-update-2-ctp2.aspx

Die Geschwindigkeit mit der aktuell Updates und Releases veröffentlicht werden ist geradezu verstörend. Man kommt kaum dazu sich auf eine Entwicklungsumgebung festzulegen und da ist schon das nächste Update, dass mit neuen Features Funktionen und Fixes reizt. 🙁

PS: Die Veröffentlichung von MS-SQL 2014 erinnert mich schmerzhaft, dass es nur noch 5 Jahre Support für OLE-DB gibt. Das ist noch einen traurigen Smiley wert 🙁

Probleme nach Upgrade auf Microsoft SQL Server 2008 R2

Ein Kollege von mir hat ein Upgrade von seinen Microsoft SQL Server 2008 auf einen Microsoft SQL Server 2008 R2 durchgeführt.

Soweit ist alles gut abgelaufen, der SQL Server läuft. Einzig die Reporting Services ließen sich nicht mehr starten. Die Reporting Services liefen allerdings zuvor ohne Probleme.

Eine Reparaturinstallation sowie Entfernen und erneutes Hinzufügen des Dienstes wurden ohne jedweden Erfolg durchgeführt.

Nach dem fehlgeschlagenem Start des Dienst fanden sich nur die drei folgenden Einträge im Event-Log:

Protokollname: Application
Quelle:        SQL Server Reporting Services (MSSQLSERVER)
Datum:         26.05.2011 14:56:09
Ereignis-ID:   0
Aufgabenkategorie:Keine
Ebene:         Fehler
Schlüsselwörter:Klassisch
Benutzer:      Nicht zutreffend
Computer:      xyz
Beschreibung:
Der Dienst kann nicht gestartet werden. System.Exception: Default appdomain failed to initialize.
bei Microsoft.ReportingServices.Library.ServiceAppDomainController.Start()
bei Microsoft.ReportingServices.Library.ReportService.OnStart(String[] args)
bei System.ServiceProcess.ServiceBase.ServiceQueuedMainCallback(Object state)
Protokollname: Application
Quelle:        Report Server Windows Service (MSSQLSERVER)
Datum:         26.05.2011 14:56:09
Ereignis-ID:   140
Aufgabenkategorie:Starten/Herunterfahren
Ebene:         Fehler
Schlüsselwörter:Klassisch
Benutzer:      Nicht zutreffend
Computer:      xyz
Beschreibung:
Fehler beim Initialisieren der DefaultDomain-Anwendungsdomäne. Fehler: Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt..

Protokollname: Application
Quelle:        Report Server Windows Service (MSSQLSERVER)
Datum:         26.05.2011 14:56:09
Ereignis-ID:   113
Aufgabenkategorie:Protokollierung
Ebene:         Fehler
Schlüsselwörter:Klassisch
Benutzer:      Nicht zutreffend
Computer:      xyz
Beschreibung:
Der Berichtsserver kann den Leistungsindikator 'Report Requests' nicht erstellen.

Der letzte Eintrag mit der Meldung „Der Berichtsserver kann den Leistungsindikator ‚Report Requests‘ nicht erstellen.“, in Verbindung mit der Meldung „System.Exception: Default appdomain failed to initialize.“ . Hat mich letzten Endes auf die Spur gebracht.

Die Lösung ist hier zu finden: http://support.microsoft.com/kb/956155/en-us

Offensichtlich wurde bei der Deinstallation des 2008er SQL-Servers mehr entfernt als gut war und der hier vorgeschlagene Weg einer Reparaturinstallation war leider nicht erfolgreich.
Ich habe die 4 Schlüssel die fehlten einfach manuell eingefügt und der Dienst konnte danach sofort wieder gestartet werden.

Nette Falle im SQL-Server: Der Kompatibilitätsgrad

Nach einem der letzten Updates unserer Software meldete uns ein Kunde einen SQL Fehler, der bei einer bestimmten Operation auftrat. Er setzt den MS-SQL Server 2008 ein.

OK, meine Testumgebung hat drei Server von SQL 2000, über 2005 bis 2008 R2. Keine der Testumgebungen brachte bei der entsprechenden gleichen Operation einen Fehler 😕 Gut oder besser schlecht… Der Kunde bekommt nun eine Fehlermeldung und auch wenn Kunden meistens ja nicht recht haben wenn sie Fehler melden 😀 schaute ich mir dennoch alle SQL Befehle etwas genauer an, die meine Software da auslöste.
In dem entsprechenden Teil meiner wurde nach Benutzerangaben ein relativ komplexer Query durch einen Abfragegenerator zusammengebaut. Darunter fand sich auch der folgende Subquery, als Teil der gesamten Abfrage:

SELECT a.[Id] FROM [tblXYZ] AS a
  WHERE
    (((a..[IdParent] IS NULL
       AND a..[Id] NOT IN
         (SELECT [IdXYZ]
            FROM [tblSomething]
              WHERE [IdParent] IS NOT NULL))))

Unschwer zu sehen werden hier mit dem Alias a zusammen irgendwie zwei Punkte verwendet. Bleibt die Frage warum in meiner Umgebung nun kein Fehler passiert und beim Kunden ein nun Syntax Fehler ausgelöst wird.

Nach einigem Suchen fand ich die Ursache im Kompatibilitätsgrad, den man im Managementstudio unter Datenbank -> Datenbankname -> Eigenschaften -> Optionen je Datenbank separat einstellen kann. Dort sind folgende Einstellungen möglich.

SQL Server 2000 (80)
SQL Server 2005 (90)
SQL Server 2010 (100)

In meiner Testumgebung verwende ich eine Datenbank, die seit den ersten Anfängen unserer Software immer weiter als Testumgebung mit vielen Testdaten dient. Sie wurde erstmals auf einem SQL Server 2000 angelegt. Dann auf einen 2005er und schließlich auf einen SQL Server 2008 R2 umgezogen. Netterweise – oder besser dummerweise – hat sich der SQL Server bei jeder Umstellung die ehemalige Kompatibilität gemerkt. Und man staunt nicht schlecht: Auf einem SQL Server 2000 ist es kein Fehler zwischen Alias und Spaltennamen zwei Punkte zu schreiben. Bei einem SQL Server 2005 oder später ist das sehr wohl ein Syntaxfehler.
Der Fehler lag also doch bei uns – was ja wirklich selten vorkommt 😀 – und wurde trotz genauer Tests nicht entdeckt.

Man merke sich: SQL Server Syntax ist trotz gleicher SQL Server Version eben doch lange nicht das selbe.
Wer also Software auf einem SQL Server testet sollte tunlichst darauf achten welchen Kompatibilitätsgrad er benutzt ❗

Alle SQL Server enumerieren mit den OLE-DB Enumeratoren

Wie bekommt man eigentlich einfach eine Liste aller verfügbaren SQL-Server im Netz?

In der MSDN findet sich schnell ein Artikel How to enumerate available instances of SQL Server by using the SQLDMO components. Allerdings ist dieser Artikel wenig nützlich, denn SQL-DMO findet man nur noch selten auf einem Rechner.

Dabei ist es doch relativ einfach, denn OLE-DB sieht hierfür Enumeratoren vor. Aber auch die sind nicht sonderlich gut dokumentiert. In der SQL-2000 Server Doku findet sich noch ein Eintrag für den SQLOLEDB Enumerator. Für den neuen nativen OLE-DB Clienst für den SQL-Server finde ich nichts mehr dazu.

ATL stellt direkt Klassen zur Verfügung, die die Nutzung von Enumeratoren zu einem Kinderspiel machen.

Anbei ein Codeschnippsel der alle bekannten MS-SQL Server enumeriert. Ich beginne dabei mit dem neuesten Client (2008) und gehe die Schleife weiter bis zum ältesten Server Client (2000).
Wird ein Enumerator gefunden, und dieser lieferte Ergebnisse, dann wird die Schleife abgebrochen. Denn alle Enumeratoren liefern im Allgemeinen das gleiche Ergebnis.

Code:

//////////////////////////////////////////////////////////////////////////
// Main function to enumerate all servers with the appropriate
// known enumerators.

typedef std::set  TSET_CString;

void EnumSQLServer(TSET_CString &setSQLServer)
{
  // We may need the local server name. 
  // We replace the token (local) with the current computer name.
  CString strCompLocal;
  DWORD dwLen = MAX_COMPUTERNAME_LENGTH+1;
  ::GetComputerName(CStrBuf(strCompLocal,dwLen),&dwLen);

  // Loop over all enumerators we know
  static const PCWSTR aEnumerator[] =
  {
    L"SQLNCLI10 Enumerator",    // SQL 2008
    L"SQLNCLI Enumerator",      // SQL 2005
    L"SQLOLEDB Enumerator"      // SQL 2000
  };

  // Try all enumerators
  for (int i=0; i < _countof(aEnumerator); ++i)
  {
    // Check if we have an enumerator
    bool bFoundAny = false;
    HRESULT hr;
    CLSID clsid;
    hr = CLSIDFromProgID(aEnumerator[i],&clsid);
    if (SUCCEEDED(hr))
    {
      // Open enumerator and loop over all entries
      CEnumerator enumrator;
      hr = enumrator.Open(&clsid);
      if (SUCCEEDED(hr))
      {
        while ((hr=enumrator.MoveNext())==S_OK)
        {
          CString strServerName(enumrator.m_szName);

          // Skip empty server names 
          // (older enumerators return sometimes an empty name)
          if (strServerName.IsEmpty())
            continue;

          // Some enumerators return (local) for a local main
          // SQL server instance
          if (strServerName.CompareNoCase(_T("(local)"))==0)
          {
            ATLTRACE(__FUNCTION__ " found local computer\n");
            strServerName = strCompLocal;
          }

          // get uppercase server name
          strServerName.MakeUpper();

          // Insert in list and avoid duplicates with this, if
          // developer decides not to break the loop after the first
          // enumerator.
          if (setSQLServer.insert(strServerName).second)
            ATLTRACE(__FUNCTION__ " found server %s\n",
                  CT2A(strServerName.GetString()));
          bFoundAny = true;
        }
      }

      // After we have found data in one enumerator. There is no need
      // to do this again.
      // But a developer might decide to do this for every enumerator
      if (bFoundAny)
        break;
    }
  }
}

Ein lauffähiges Projekt kann man hier herunterladen: EnumSQLServer.zip.

MS-SQL: Universelles Datumsformat

Datumsangaben in SQL Queries sind immer wieder eine Freude. Im Gegensatz zu allen anderen Datentypen hat man es hier auch noch mit einer sprachspezifischen Einstellung (US English, Deutsch etc.) zu tun.
Also was macht der Entwickler, er schreibt SQL Code wie diesen, der die Funktion CONVERT oder CAST verwendet:

CREATE VIEW dbo.International_Dates AS
SELECT PurchaseOrderID, TotalDue
FROM AdventureWorks.Purchasing.PurchaseOrderHeader
WHERE OrderDate < CONVERT(DATETIME,'2002.05.01',102);

Man kann eben nicht sicher sein wie der Datumswert interpretiert wird. Das folgende Statement würde also nur mit einer englischen Locale funktionieren:

CREATE VIEW dbo.USA_Dates AS
SELECT PurchaseOrderID, TotalDue
FROM AdventureWorks.Purchasing.PurchaseOrderHeader
WHERE OrderDate < 'May 1, 2002';

Dabei gibt es ein Datmsformat im MS-SQL Server, dass immer gleich interpretiert wird, das so genannte Unseparated String Format . Netterweise geht das sogar für DateTime Werte wenn man diesen im Format yyyyMMdd hh:mm:ss.ttt angibt.
Dies entspricht den CAST/CONVERT Formaten 112 und 114!

Man kann den obigen Query also komplett locale-unabhängig so schreiben.

CREATE VIEW dbo.Uniform_Date AS
SELECT PurchaseOrderID, TotalDue
FROM AdventureWorks.Purchasing.PurchaseOrderHeader
WHERE OrderDate < '20020501';

Man kann sich CAST und CONVERT also oft genug sparen wenn man Queries aufbaut, da Datum-Strings im Format yyyyMMdd hh:mm:ss.ttt, eben sprach/localeunabhängig sind.
Ich habe eine spezielle Funktion die genau dieses MS-SQL-Format benutzt um ein COleDateTime in das entsprechende Stringformat für einen SQL Query zu bringen, wenn ich mal keine Parameter in einem Query benutzen will oder kann.

Nachtrag: es ist ohne Probleme möglich die Uhrzeit auch abgekürzt zu übergeben, wie z.B. auch in der Form: yyyyMMdd hh:mm, oder yyyyMMdd hh:mm:ss