Compare commits
2
Commits
7275546d6c
...
78e8881d38
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78e8881d38 | ||
|
|
31222ad28f |
+115
-10
@@ -91,15 +91,16 @@ biztalk_checkmk_pulse.cmd
|
||||
v
|
||||
BizTalkCheckmkPulse.exe
|
||||
|
|
||||
| liest lokal
|
||||
| liest lokal und prueft im gleichen Sicherheitskontext
|
||||
+-- WMI root\MicrosoftBizTalkServer
|
||||
+-- Windows Application Event Log
|
||||
+-- SQL-Verbindung zu BizTalkMgmtDb/BizTalkMsgBoxDb
|
||||
|
|
||||
v
|
||||
Checkmk Local Check Zeilen nach STDOUT
|
||||
```
|
||||
|
||||
Der `.cmd`-Wrapper liefert auch dann eine gueltige `UNKNOWN`-Zeile, wenn die EXE fehlt. Die EXE selbst faengt Laufzeitfehler ab und schreibt fuer alle stabilen Services `UNKNOWN`, damit unvollstaendige Deployments oder WMI-Probleme in Checkmk sichtbar bleiben.
|
||||
Der `.cmd`-Wrapper liefert auch dann eine gueltige `UNKNOWN`-Zeile mit Massnahme, wenn die EXE fehlt oder bereits der Prozessstart mit einem Exitcode fehlschlaegt. Die EXE selbst faengt Laufzeitfehler ab und schreibt fuer alle stabilen Services `UNKNOWN`, damit unvollstaendige Deployments oder WMI-Probleme in Checkmk sichtbar bleiben.
|
||||
|
||||
## Datenquellen
|
||||
|
||||
@@ -136,11 +137,97 @@ Der Check liest standardmaessig das lokale Application Log fuer die letzten 60 M
|
||||
|
||||
Die Liste ist ueber `EventLogSources` konfigurierbar.
|
||||
|
||||
### SQL-Zugriffsprobe
|
||||
|
||||
Nach erfolgreicher oder teilweise erfolgreicher Plattformabfrage uebernimmt der SQL-Probe die per WMI ermittelten Management- und MessageBox-Ziele. Fuer jedes eindeutige Ziel wird mit `System.Data.SqlClient` eine Verbindung mit integrierter Windows-Authentifizierung geoeffnet und `SELECT 1` ausgefuehrt. Die Verbindung wird unmittelbar danach geschlossen; es werden keine BizTalk-Tabellen gelesen oder veraendert.
|
||||
|
||||
Der Test laeuft unter derselben Identitaet wie der Checkmk Local Check. Damit wird sichtbar, ob `LocalSystem` beziehungsweise das Maschinenkonto des BizTalk-Servers das SQL-Ziel tatsaechlich erreichen und die Datenbank oeffnen kann. Die Probe ist mit `ProbeSqlConnectivity=false` deaktivierbar und verwendet `SqlConnectionTimeoutSeconds` mit dem Default 5 Sekunden je Ziel als Timeout.
|
||||
|
||||
Im produktiven Betrieb sollte der komplette Local Check asynchron mit 300 Sekunden Cache ausgefuehrt werden. So fuehren fehlende SQL-Rechte nicht bei jedem Checkmk-Abruf zu weiteren fehlgeschlagenen Login-Ereignissen. Der Trade-off ist eine Zustandsverzoegerung von maximal fuenf Minuten.
|
||||
|
||||
## Berechtigungsmodell
|
||||
|
||||
### LocalSystem und lokaler WMI-Zugriff
|
||||
|
||||
Der Checkmk Windows Agent und der Agent Controller laufen standardmaessig als `LocalSystem` (`NT AUTHORITY\SYSTEM`). Der Wrapper und die EXE erben diesen Kontext. Das Plugin verbindet sich lokal mit `\\<eigener-server>\root\MicrosoftBizTalkServer`, setzt keine eigenen Anmeldedaten, nutzt kein Remote-WMI und fuehrt keine veraendernden WMI-Methoden aus. Das lokale Windows Application Event Log wird ebenfalls nur gelesen.
|
||||
|
||||
Die lokalen Rechte von `LocalSystem` reichen fuer diese Zugriffe normalerweise aus. Im regulaeren lokalen Betrieb werden deshalb keine zusaetzlichen DCOM-, Firewall- oder pauschalen WMI-Namespace-Freigaben benoetigt.
|
||||
|
||||
### Netzwerkidentitaet zum SQL Server
|
||||
|
||||
BizTalk-WMI-Klassen koennen ihre Daten aus der BizTalk Management- oder MessageBox-Datenbank beziehen. Liegt SQL Server auf einer anderen Maschine, authentifiziert sich `LocalSystem` dort mit dem Active-Directory-Computerkonto des BizTalk-Servers:
|
||||
|
||||
```text
|
||||
DOMAIN\BIZTALKSERVER$
|
||||
```
|
||||
|
||||
Dieses Konto besitzt nicht automatisch BizTalk- oder SQL-Berechtigungen. Daraus kann die Situation entstehen, dass die Verbindung zum lokalen WMI-Namespace erfolgreich ist, einzelne SQL-gestuetzte WMI-Klassen aber `Access denied`, `UnauthorizedAccessException` oder `UNKNOWN` liefern.
|
||||
|
||||
### Pruefung und Freigabe
|
||||
|
||||
Der direkte Programmstart in einer administrativen Shell laeuft unter dem angemeldeten Benutzer und ist deshalb kein ausreichender Berechtigungstest. Verbindlich ist die Ausfuehrung durch den Checkmk Agent Controller als `LocalSystem`:
|
||||
|
||||
```powershell
|
||||
& "C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump |
|
||||
Select-String -Pattern "BizTalk|Access denied|Unauthorized|UNKNOWN" -Context 0,1
|
||||
```
|
||||
|
||||
Bei Zugriffsfehlern gilt folgendes Least-Privilege-Vorgehen:
|
||||
|
||||
1. Computerkonto des BizTalk-Servers ermitteln, normalerweise `DOMAIN\BIZTALKSERVER$`.
|
||||
2. Die bei der BizTalk-Konfiguration verwendete Operator-Gruppe feststellen.
|
||||
3. Das Computerkonto in `BizTalk Server Operators` beziehungsweise die kundenspezifisch benannte Operator-Gruppe aufnehmen.
|
||||
4. Kerberos-Tickets des Systemkontos erneuern oder den Server im Wartungsfenster neu starten.
|
||||
5. Den Agent-Dump wiederholen.
|
||||
6. Nur fuer weiterhin abgelehnte, konkret identifizierte WMI-Klassen mit BizTalk- und SQL-Administration pruefen, ob Administratorrechte erforderlich sind.
|
||||
|
||||
Die Operator-Rolle ist fuer grundlegendes Monitoring und Zustandsabfragen vorgesehen. Direkte manuelle Aenderungen an den Rollen der BizTalk-SQL-Datenbanken sind zu vermeiden; die durch BizTalk konfigurierte Windows-Gruppe ist die vorgesehene Berechtigungsgrenze.
|
||||
|
||||
| Agent-Dump-Ergebnis | Massnahme |
|
||||
| --- | --- |
|
||||
| Plausible Werte fuer alle BizTalk-Services | Keine Berechtigungsaenderung. |
|
||||
| WMI-Namespace nicht erreichbar | BizTalk-WMI-Provider, WMI-Dienst, Namespace und ACL gezielt pruefen. |
|
||||
| Nur SQL-gestuetzte Klassen scheitern | Computerkonto in die BizTalk-Operator-Gruppe aufnehmen. |
|
||||
| Nur Event-Log-Service ist `UNKNOWN` | Lokalen Application-Log-Zugriff pruefen. |
|
||||
| Fehler bleibt mit Operator-Rolle bestehen | Betroffene Klasse und konkrete BizTalk-/SQL-Rollenanforderung untersuchen. |
|
||||
|
||||
### Automatische Diagnose im Programm
|
||||
|
||||
WMI-Verbindungsaufbau und jede erforderliche WMI-Klasse werden separat bewertet. Erwartbare Exceptions werden in folgende Kategorien eingeordnet:
|
||||
|
||||
| Diagnose | Bedeutung | Ausgegebene Massnahme |
|
||||
| --- | --- | --- |
|
||||
| `Wmi/Permission` | Namespace oder BizTalk-WMI-Klasse verweigert den Zugriff. | Namespace-ACL bei Verbindungsfehlern beziehungsweise BizTalk-Operator-Gruppe bei Klassenfehlern pruefen. |
|
||||
| `Wmi/Connectivity` | WMI-/RPC-Ziel nicht erreichbar. | WMI-Dienst, Provider und bei Remote-WMI zusaetzlich DNS/RPC/Firewall pruefen. |
|
||||
| `Wmi/Timeout` | WMI-Abfrage ueberschreitet `QueryTimeoutSeconds`. | WMI-, BizTalk- und SQL-Auslastung untersuchen, bevor der Timeout erhoeht wird. |
|
||||
| `Wmi/Configuration` | Namespace oder Klasse fehlt. | BizTalk-WMI-Provider und BizTalk-Installation pruefen beziehungsweise reparieren. |
|
||||
| `Sql/Permission` | Login oder Datenbankzugriff wird abgelehnt. | Maschinenkonto der BizTalk-Operator-Gruppe zuordnen, Kerberos erneuern, keine direkten DB-Rollen vergeben. |
|
||||
| `Sql/Connectivity` | SQL-Server oder Instanz nicht erreichbar. | Servername, DNS, SQL-Dienst, TCP-Protokoll, Port und Firewall pruefen. |
|
||||
| `Sql/Timeout` | SQL-Verbindung oder Testabfrage laeuft in den Timeout. | Netzwerk und SQL-Auslastung pruefen; Timeout nur begruendet anheben. |
|
||||
| `Sql/Configuration` | WMI-Ziele unvollstaendig oder TLS-, Zertifikats-, SPN-/SSPI-Konfiguration fehlerhaft. | Plattformabfragen beziehungsweise Zertifikatskette, Verschluesselung, SPN und Kerberos gezielt pruefen. |
|
||||
|
||||
Jede Diagnose enthaelt Bereich/Kategorie, betroffene Komponente, eine kurze Ursache, `Massnahme:` und `Technik:` mit Exception-Typ, HRESULT oder SQL-Fehlernummer. Erforderliche Datenquellen besitzen eigene Verfuegbarkeitsflags. Schlaegt beispielsweise `MSBTS_ServiceInstance` fehl, wird `BizTalk Suspended Instances` zwingend `UNKNOWN`; eine leere Ergebnisliste darf nicht als 'keine Suspensions' fehlinterpretiert werden.
|
||||
|
||||
Die Operator-Mitgliedschaft des Computerkontos steht allen auf diesem Server als `LocalSystem` laufenden Diensten fuer Netzwerkzugriffe zur Verfuegung. Falls diese Sicherheitsauswirkung nicht akzeptabel ist, kann ein separater Collector unter einem dedizierten gMSA- oder Dienstkonto mit Operator-Rechten Checkmk-Spooldaten erzeugen. Diese Variante ist noch nicht Bestandteil der aktuellen Implementierung. Der komplette Checkmk-Agent sollte nicht allein fuer dieses Plugin auf eine andere Identitaet umgestellt werden, weil dies alle Agent-Sektionen und Local Checks betrifft.
|
||||
|
||||
Quellen:
|
||||
|
||||
- https://docs.checkmk.com/latest/en/agent_windows.html
|
||||
- https://docs.checkmk.com/latest/en/localchecks.html
|
||||
- https://learn.microsoft.com/en-us/biztalk/core/minimum-security-user-rights
|
||||
- https://learn.microsoft.com/en-us/biztalk/core/access-control-and-data-security
|
||||
- https://learn.microsoft.com/en-us/entra/architecture/service-accounts-computer
|
||||
- https://learn.microsoft.com/en-us/windows/win32/wmisdk/access-to-wmi-namespaces
|
||||
|
||||
## Resilienz
|
||||
|
||||
Das Plugin ist bewusst defensiv gebaut:
|
||||
|
||||
- WMI-Queries haben ein konfigurierbares Timeout.
|
||||
- WMI-Namespace und Pflichtklassen werden getrennt auf Berechtigung, Konfiguration, Erreichbarkeit und Timeout geprueft.
|
||||
- Management- und MessageBox-Datenbank werden mit der echten Agent-Identitaet getestet.
|
||||
- SQL-Fehlernummern werden in Berechtigung, Erreichbarkeit, Timeout oder Providerfehler klassifiziert.
|
||||
- Fehlgeschlagene Pflichtabfragen erzeugen `UNKNOWN` statt irrefuehrender Nullwerte.
|
||||
- Optionale WMI-Klassen erzeugen Diagnosehinweise statt Totalabbruch.
|
||||
- Fehlende Properties werden als leer/unknown behandelt.
|
||||
- Local-Check-Ausgaben verwenden gueltige Checkmk-Zeilen mit genau vier Feldern.
|
||||
@@ -157,6 +244,14 @@ Das Plugin ist bewusst defensiv gebaut:
|
||||
|
||||
Dieser Service ist der Integrationsindikator. Wenn er `UNKNOWN` ist, sind Berechtigungen, BizTalk-Installation oder WMI-Repository zu pruefen.
|
||||
|
||||
### BizTalk SQL Access
|
||||
|
||||
- `OK`: Datenbankziele vollstaendig ermittelt und integrierte Anmeldung an allen Zielen erfolgreich.
|
||||
- `UNKNOWN`: Zielermittlung unvollstaendig, Anmeldung verweigert, SQL nicht erreichbar, Timeout oder Providerfehler.
|
||||
- Bei `ProbeSqlConnectivity=false`: `OK` mit sichtbarem Hinweis, dass die Probe deaktiviert ist.
|
||||
|
||||
Metriken: `biztalk_sql_targets_total`, `biztalk_sql_targets_available` und `biztalk_sql_targets_failed`. Die Ausgabe nennt ausserdem `execution_identity`, `network_identity` und den Zustand jedes getesteten Datenbankziels.
|
||||
|
||||
### BizTalk Suspended Instances
|
||||
|
||||
- `OK`: keine suspendierten Instanzen.
|
||||
@@ -176,6 +271,7 @@ Dieser Service ist der Integrationsindikator. Wenn er `UNKNOWN` ist, sind Berech
|
||||
- `OK`: Artefakte lesbar, keine unbekannten Statuswerte.
|
||||
- `WARN`: nur wenn `AlertOnArtifactRuntimeIssues=true` und deaktivierte/inaktive Artefakte vorhanden sind.
|
||||
- `CRIT`: unbekannte Send-Port- oder Orchestration-Statuswerte.
|
||||
- `UNKNOWN`: mindestens eine erforderliche WMI-Artefaktklasse nicht lesbar.
|
||||
|
||||
Deaktivierte Receive Locations und gestoppte Ports koennen in BizTalk fachlich korrekt sein. Deshalb ist die Alarmierung hier standardmaessig informativ.
|
||||
|
||||
@@ -190,19 +286,20 @@ Deaktivierte Receive Locations und gestoppte Ports koennen in BizTalk fachlich k
|
||||
|
||||
Pro Umgebung sollte der BizTalk-Host in einem eigenen Host-Ordner oder Host-Tag fuer `ACC`, `DEV`, `TST`, `PRD` liegen. Im Dashboard eignen sich:
|
||||
|
||||
- Service State Widgets fuer die fuenf BizTalk-Services
|
||||
- Graphen fuer `biztalk_suspended_total`, `biztalk_host_instances_stopped`, `biztalk_eventlog_errors`
|
||||
- Service State Widgets fuer die sechs stabilen BizTalk-Services
|
||||
- Graphen fuer `biztalk_sql_targets_failed`, `biztalk_suspended_total`, `biztalk_host_instances_stopped`, `biztalk_eventlog_errors`
|
||||
- Hostgruppe/Ordner pro Umgebung
|
||||
- Optional eine View gefiltert auf `Service starts with BizTalk`
|
||||
|
||||
Empfohlene Reihenfolge im Dashboard:
|
||||
|
||||
1. `BizTalk Platform`
|
||||
2. `BizTalk Suspended Instances`
|
||||
3. `BizTalk Host Instances`
|
||||
4. `BizTalk Event Log`
|
||||
5. `BizTalk Runtime Artifacts`
|
||||
6. SQL-Server-Services aus dem Checkmk-MSSQL-Plugin
|
||||
2. `BizTalk SQL Access`
|
||||
3. `BizTalk Suspended Instances`
|
||||
4. `BizTalk Host Instances`
|
||||
5. `BizTalk Event Log`
|
||||
6. `BizTalk Runtime Artifacts`
|
||||
7. SQL-Server-Services aus dem Checkmk-MSSQL-Plugin
|
||||
|
||||
## Rollout-Vorgehen
|
||||
|
||||
@@ -238,6 +335,15 @@ Der Checkmk Windows Agent laeuft normalerweise als LocalSystem. Daher immer auch
|
||||
"C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump
|
||||
```
|
||||
|
||||
Die Diagnose im Service-Text nach `Massnahme:` abarbeiten. Wichtige Kategorien:
|
||||
|
||||
- `Wmi/Permission`: lokale Namespace-ACL oder BizTalk-Operator-Zuordnung pruefen.
|
||||
- `Sql/Permission`: Maschinenkonto `DOMAIN\BIZTALKSERVER$` und BizTalk-Operator-Gruppe pruefen.
|
||||
- `Sql/Connectivity`: SQL-Server-/Instanzname, DNS, SQL-Dienst, TCP und Firewall pruefen.
|
||||
- `Sql/Timeout`: SQL- und Netzwerkauslastung pruefen; Timeout nicht als erste Massnahme erhoehen.
|
||||
|
||||
Der direkte Aufruf des Wrappers kann wegen des angemeldeten Administratorkontos ein anderes Ergebnis liefern als der Agent-Dump. Fuer die Freigabe ist immer der Agent-Dump massgeblich.
|
||||
|
||||
### Keine Services in Discovery
|
||||
|
||||
Pruefen:
|
||||
@@ -262,4 +368,3 @@ Sinnvolle naechste Ausbaustufen:
|
||||
- Optionales serverseitiges Check-Plugin nach Checkmk Check API V2.
|
||||
- Custom Dashboard/View als Checkmk GUI Extension.
|
||||
- Ergaenzung um MessageBox-Spool/Tracking-Daten, falls operativ benoetigt.
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ Checkmk 2.4 kann Windows-Agent-Plugins und Local Checks direkt ausfuehren. Fuer
|
||||
Standardmaessig entstehen diese stabilen Services:
|
||||
|
||||
- `BizTalk Platform`
|
||||
- `BizTalk SQL Access`
|
||||
- `BizTalk Suspended Instances`
|
||||
- `BizTalk Host Instances`
|
||||
- `BizTalk Runtime Artifacts`
|
||||
@@ -112,6 +113,8 @@ Wichtige Werte:
|
||||
| `ServicePrefix` | `BizTalk` | Prefix fuer alle Checkmk-Services. |
|
||||
| `EnvironmentName` | leer | Optional `ACC`, `DEV`, `TST` oder `PRD`. |
|
||||
| `QueryTimeoutSeconds` | `25` | WMI-Timeout pro Query. |
|
||||
| `ProbeSqlConnectivity` | `true` | Prueft die integrierte Windows-Anmeldung an den per WMI ermittelten Management- und MessageBox-Datenbanken. |
|
||||
| `SqlConnectionTimeoutSeconds` | `5` | Timeout je SQL-Ziel fuer Verbindungsaufbau und harmlose Testabfrage. |
|
||||
| `WarnResumableThreshold` | `1` | WARN ab n resumable suspended instances. |
|
||||
| `CritNonResumableThreshold` | `1` | CRIT ab n non-resumable suspended instances. |
|
||||
| `AlertOnArtifactRuntimeIssues` | `false` | Wenn `true`, werden deaktivierte Receive Locations und inaktive Ports/Orchestrations als WARN gewertet. |
|
||||
@@ -157,9 +160,102 @@ Integration ueber Agent Bakery:
|
||||
|
||||
Die Checkmk-Dokumentation nennt fuer Windows Local Checks `%ProgramData%\checkmk\agent\local` und fuer Windows Agent Plugins `%ProgramData%\checkmk\agent\plugins`. Dieses Projekt nutzt bewusst `local`, weil der Zustand direkt vom Host berechnet und sofort als Checkmk-Service geliefert wird.
|
||||
|
||||
## Berechtigungen fuer WMI und BizTalk-Datenbanken
|
||||
|
||||
### Ausfuehrungskontext
|
||||
|
||||
Der Checkmk Windows Agent und der Agent Controller laufen standardmaessig als `LocalSystem` (`NT AUTHORITY\SYSTEM`). Der `.cmd`-Wrapper und `BizTalkCheckmkPulse.exe` erben diesen Sicherheitskontext.
|
||||
|
||||
Das Programm:
|
||||
|
||||
- verbindet sich lokal mit `\\<eigener-server>\root\MicrosoftBizTalkServer`
|
||||
- setzt keine separaten WMI-Anmeldedaten
|
||||
- verwendet kein Remote-WMI
|
||||
- ruft keine veraendernden BizTalk-WMI-Methoden auf
|
||||
- liest zusaetzlich nur das lokale Windows Application Event Log
|
||||
|
||||
Fuer den lokalen WMI-Verbindungsaufbau und das lokale Application Event Log reichen die Rechte von `LocalSystem` normalerweise aus. Es muessen deshalb im Regelfall keine DCOM-, Firewall- oder WMI-Namespace-Freigaben eingerichtet werden.
|
||||
|
||||
### Zugriff auf einen separaten SQL Server
|
||||
|
||||
Einige BizTalk-WMI-Klassen beziehen ihre Daten aus der BizTalk Management- oder MessageBox-Datenbank. Wenn der SQL Server auf einer anderen Maschine laeuft, greift ein unter `LocalSystem` ausgefuehrter Prozess im Netzwerk mit dem Computerkonto des BizTalk-Servers zu:
|
||||
|
||||
```text
|
||||
DOMAIN\BIZTALKSERVER$
|
||||
```
|
||||
|
||||
Die weitreichenden lokalen Rechte von `LocalSystem` ergeben nicht automatisch Berechtigungen auf dem entfernten SQL Server. Deshalb kann die Verbindung zum lokalen WMI-Namespace funktionieren, waehrend einzelne SQL-gestuetzte BizTalk-WMI-Abfragen mit `Access denied`, `UnauthorizedAccessException` oder `UNKNOWN` fehlschlagen.
|
||||
|
||||
### Test im echten Checkmk-Kontext
|
||||
|
||||
Ein manueller Aufruf von `BizTalkCheckmkPulse.exe` oder des Wrappers verwendet das Konto der angemeldeten Person. Ein erfolgreicher manueller Test beweist daher nicht, dass die Ausfuehrung durch Checkmk als `LocalSystem` ebenfalls funktioniert.
|
||||
|
||||
Der verbindliche Test erfolgt ueber den Agent Controller:
|
||||
|
||||
```powershell
|
||||
& "C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump |
|
||||
Select-String -Pattern "BizTalk|Access denied|Unauthorized|UNKNOWN" -Context 0,1
|
||||
```
|
||||
|
||||
Erwartet werden die sechs stabilen `BizTalk ...` Services mit plausiblen Daten. Insbesondere `BizTalk Platform` und `BizTalk SQL Access` duerfen nicht wegen eines WMI-, SQL- oder Berechtigungsfehlers `UNKNOWN` sein.
|
||||
|
||||
Das Programm prueft Berechtigungen aktiv:
|
||||
|
||||
- Der WMI-Namespace-Verbindungsaufbau und jede erforderliche WMI-Klasse werden getrennt bewertet.
|
||||
- Fehler werden als `Permission`, `Connectivity`, `Timeout`, `Configuration` oder `Provider` klassifiziert.
|
||||
- `BizTalk SQL Access` oeffnet mit integrierter Windows-Authentifizierung eine Verbindung zur ermittelten Management- und MessageBox-Datenbank und fuehrt `SELECT 1` aus.
|
||||
- Die Service-Ausgabe nennt Ausfuehrungsidentitaet, erwartete Netzwerkidentitaet, betroffene Komponente, technische Ursache und konkrete Massnahme.
|
||||
- Fehlgeschlagene Pflichtabfragen werden nie als leerer, erfolgreicher Datenbestand gewertet. Der betroffene Service wird `UNKNOWN`.
|
||||
|
||||
`Sql/Configuration` weist je nach Detailtext entweder auf eine unvollstaendige WMI-Zielermittlung oder auf TLS-, Zertifikats-, SPN-/SSPI-Probleme hin. Die Diagnose empfiehlt bewusst nicht, SQL-Verschluesselung pauschal abzuschalten.
|
||||
|
||||
Der SQL-Zugriffstest ersetzt nicht das Checkmk-MSSQL-Plugin auf dem SQL-Server. Er prueft ausschliesslich, ob genau die Identitaet des BizTalk Local Checks die fuer seine Diagnose benoetigten BizTalk-Datenbankziele erreichen kann.
|
||||
|
||||
Da bei fehlenden Rechten jeder Lauf einen abgewiesenen SQL-Login erzeugen kann, wird fuer den produktiven Betrieb eine asynchrone Ausfuehrung mit 300 Sekunden Cache empfohlen. Das begrenzt SQL-Logeintraege und Last, verzoegert einen Zustandswechsel aber um maximal fuenf Minuten. Beispiel in `%ProgramData%\checkmk\agent\check_mk.user.yml`:
|
||||
|
||||
```yaml
|
||||
local:
|
||||
enabled: yes
|
||||
execution:
|
||||
- pattern: $CUSTOM_LOCAL_PATH$\biztalk_checkmk_pulse.cmd
|
||||
async: yes
|
||||
run: yes
|
||||
cache_age: 300
|
||||
```
|
||||
|
||||
### Vorgehen bei Berechtigungsfehlern
|
||||
|
||||
1. Pruefen, unter welchem Computerkonto der BizTalk-Server im Netzwerk auftritt, normalerweise `DOMAIN\BIZTALKSERVER$`.
|
||||
2. Die fuer die BizTalk-Gruppe konfigurierte Windows-Gruppe `BizTalk Server Operators` ermitteln. Der tatsaechliche Gruppenname kann bei der BizTalk-Konfiguration angepasst worden sein.
|
||||
3. Das Computerkonto des BizTalk-Servers in diese Operator-Gruppe aufnehmen.
|
||||
4. Kerberos-Tickets des Systemkontos erneuern oder den BizTalk-Server in einem Wartungsfenster neu starten.
|
||||
5. Den Test mit `cmk-agent-ctl.exe dump` wiederholen.
|
||||
6. Nur wenn konkrete WMI-Klassen weiterhin abgelehnt werden, gemeinsam mit BizTalk- und SQL-Administration pruefen, ob diese Abfrage `BizTalk Server Administrators` benoetigt.
|
||||
|
||||
Die Operator-Rolle ist fuer grundlegende Administration und Monitoring vorgesehen und kann Zustands- und Message-Flow-Informationen lesen, ohne BizTalk-Konfiguration oder Nachrichteninhalte einzusehen. Das Plugin fuehrt ausschliesslich Leseabfragen aus. Direkte manuelle Aenderungen an BizTalk-SQL-Datenbankrollen sollten nicht vorgenommen werden; die von BizTalk konfigurierte Windows-Gruppe ist die vorgesehene Berechtigungsgrenze.
|
||||
|
||||
Entscheidungsmatrix:
|
||||
|
||||
| Ergebnis im Agent-Dump | Massnahme |
|
||||
| --- | --- |
|
||||
| Alle BizTalk-Services liefern plausible Daten | Keine Berechtigungsaenderung erforderlich. |
|
||||
| Verbindung zu `root\MicrosoftBizTalkServer` scheitert | Namespace, BizTalk-WMI-Provider, WMI-Dienst und Namespace-ACL gezielt pruefen. Keine pauschalen WMI-Rechte vergeben. |
|
||||
| `BizTalk SQL Access` meldet `Sql/Permission` | Computerkonto `DOMAIN\BIZTALKSERVER$` in die konfigurierte BizTalk-Operator-Gruppe aufnehmen, Kerberos erneuern und erneut testen. Keine direkten BizTalk-DB-Rollen vergeben. |
|
||||
| `BizTalk SQL Access` meldet `Sql/Connectivity` oder `Sql/Timeout` | Server-/Instanzname, DNS, SQL-Dienst, TCP-Protokoll, Port und Firewall aus Sicht des BizTalk-Servers pruefen. |
|
||||
| Nur SQL-gestuetzte BizTalk-Klassen liefern `Access denied` oder `UNKNOWN` | Computerkonto `DOMAIN\BIZTALKSERVER$` zunaechst in `BizTalk Server Operators` aufnehmen. |
|
||||
| `BizTalk Event Log` ist `UNKNOWN` | Zugriff auf das lokale Application Log pruefen; `LocalSystem` kann es normalerweise lesen. |
|
||||
| Fehler bleibt trotz Operator-Rolle bestehen | Betroffene WMI-Klasse und BizTalk-/SQL-Rollenzuordnung mit den Fachadministratoren untersuchen. |
|
||||
|
||||
### Sicherheitsabwaegung und Alternative
|
||||
|
||||
Wird das Computerkonto in `BizTalk Server Operators` aufgenommen, erhalten alle Dienste, die auf diesem BizTalk-Server als `LocalSystem` laufen, diese Netzwerkberechtigung. Das ist vor dem Rollout mit der Security- und BizTalk-Administration abzustimmen.
|
||||
|
||||
Falls diese Freigabe nicht zulaessig ist, ist die sauberere Alternative ein separater Collector unter einem dedizierten gMSA- oder Dienstkonto mit Operator-Rechten. Dieser Collector kann seine Checkmk-Ausgabe in eine Spool-Datei schreiben. Der Checkmk-Agent liest dann nur die bereits erzeugten Daten ein. Den gesamten Checkmk-Agent-Dienst sollte man nicht allein fuer dieses Plugin von `LocalSystem` auf ein anderes Konto umstellen, weil dadurch alle Agent-Sektionen und Local Checks betroffen sind.
|
||||
|
||||
Empfohlene Alarmierung:
|
||||
|
||||
- `BizTalk Platform`: `UNKNOWN` immer untersuchen, da dann WMI, Rechte oder Deployment betroffen sind.
|
||||
- `BizTalk SQL Access`: `UNKNOWN` als Integrations-, Berechtigungs- oder Verbindungsproblem behandeln und die eingebettete Massnahme abarbeiten.
|
||||
- `BizTalk Suspended Instances`: in `PRD` direkt alarmieren; in Nicht-PRD nach Betriebsbedarf.
|
||||
- `BizTalk Host Instances`: `CRIT` alarmieren, weil gestoppte Host Instances Laufzeitverarbeitung verhindern koennen.
|
||||
- `BizTalk Runtime Artifacts`: zunaechst beobachten; strengere Alarmierung erst aktivieren, wenn deaktivierte Artefakte nicht fachlich gewollt sind.
|
||||
@@ -174,3 +270,7 @@ Empfohlene Alarmierung:
|
||||
- Checkmk MKP-Pakete: https://docs.checkmk.com/latest/en/mkps.html
|
||||
- Microsoft BizTalk WMI `MSBTS_ServiceInstance`: https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-serviceinstance-wmi
|
||||
- Microsoft BizTalk WMI `ServiceStatus`: https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-serviceinstance-servicestatus-property-wmi
|
||||
- Microsoft BizTalk Mindestberechtigungen: https://learn.microsoft.com/en-us/biztalk/core/minimum-security-user-rights
|
||||
- Microsoft BizTalk Access Control: https://learn.microsoft.com/en-us/biztalk/core/access-control-and-data-security
|
||||
- Microsoft LocalSystem und Computerkonten: https://learn.microsoft.com/en-us/entra/architecture/service-accounts-computer
|
||||
- Microsoft WMI Namespace Security: https://learn.microsoft.com/en-us/windows/win32/wmisdk/access-to-wmi-namespaces
|
||||
|
||||
@@ -5,10 +5,13 @@ set "BASE=%~dp0BizTalkCheckmkPulse"
|
||||
set "EXE=%BASE%\BizTalkCheckmkPulse.exe"
|
||||
|
||||
if not exist "%EXE%" (
|
||||
echo 3 "BizTalk Platform" - BizTalkCheckmkPulse.exe not found below %BASE%
|
||||
echo 3 "BizTalk Platform" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen und danach cmk-agent-ctl.exe dump erneut ausfuehren.
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
"%EXE%" %*
|
||||
set "EXITCODE=%ERRORLEVEL%"
|
||||
if not "%EXITCODE%"=="0" (
|
||||
echo 3 "BizTalk Platform" - BizTalkCheckmkPulse.exe konnte nicht erfolgreich gestartet werden, Exitcode=%EXITCODE%. Massnahme: .NET Framework 4.7.2, EXE/Config-Dateien und Windows Application Log pruefen.
|
||||
)
|
||||
exit /b 0
|
||||
|
||||
|
||||
+41
-5
@@ -19,13 +19,50 @@ Status:
|
||||
|
||||
| Status | Bedingung |
|
||||
| --- | --- |
|
||||
| `OK` | WMI-Verbindung erfolgreich. |
|
||||
| `UNKNOWN` | WMI-Verbindung oder Programmstart fehlgeschlagen. |
|
||||
| `OK` | WMI-Verbindung und beide Plattform-Pflichtklassen erfolgreich. |
|
||||
| `UNKNOWN` | Namespace, `MSBTS_GroupSetting`, `MSBTS_MessageBoxSetting` oder Programmstart fehlgeschlagen. |
|
||||
|
||||
Metriken:
|
||||
|
||||
- keine
|
||||
|
||||
## BizTalk SQL Access
|
||||
|
||||
Zweck:
|
||||
|
||||
- Ermittelt Management- und MessageBox-Datenbank ueber BizTalk-WMI.
|
||||
- Oeffnet mit der Windows-Identitaet des Checkmk-Agentprozesses eine integrierte SQL-Verbindung.
|
||||
- Fuehrt die harmlose Abfrage `SELECT 1` aus und schliesst die Verbindung sofort wieder.
|
||||
- Zeigt lokale Ausfuehrungsidentitaet und erwartete Netzwerkidentitaet an.
|
||||
|
||||
Der Service ersetzt nicht das Checkmk-MSSQL-Plugin. Er prueft den fuer das BizTalk-Monitoring relevanten Zugriff des Local Checks.
|
||||
|
||||
Status:
|
||||
|
||||
| Status | Bedingung |
|
||||
| --- | --- |
|
||||
| `OK` | WMI-Zielermittlung vollstaendig und alle ermittelten Datenbanken erreichbar. |
|
||||
| `UNKNOWN` | Zielermittlung unvollstaendig, Anmeldung verweigert, SQL nicht erreichbar, Timeout oder Providerfehler. |
|
||||
| `OK` mit Hinweis | Nur wenn `ProbeSqlConnectivity=false`; der Test ist dann bewusst deaktiviert. |
|
||||
|
||||
Fehlerkategorien:
|
||||
|
||||
| Kategorie | Typische Bedeutung |
|
||||
| --- | --- |
|
||||
| `Sql/Permission` | Login abgelehnt, Datenbank nicht oeffenbar oder fehlende Berechtigung. |
|
||||
| `Sql/Connectivity` | DNS, SQL-Dienst, Instanz, TCP-Port oder Firewall nicht erreichbar. |
|
||||
| `Sql/Timeout` | Verbindungsaufbau oder Testabfrage ueberschreitet das konfigurierte Zeitlimit. |
|
||||
| `Sql/Provider` | Sonstiger von SQL Server oder SqlClient gemeldeter Providerfehler. |
|
||||
| `Sql/Configuration` | WMI-Ziele unvollstaendig oder TLS-, Zertifikats-, SPN-/SSPI-Konfiguration fehlerhaft. |
|
||||
|
||||
Metriken:
|
||||
|
||||
| Metrik | Bedeutung |
|
||||
| --- | --- |
|
||||
| `biztalk_sql_targets_total` | Anzahl eindeutiger Management-/MessageBox-Ziele. |
|
||||
| `biztalk_sql_targets_available` | Erfolgreich getestete SQL-Ziele. |
|
||||
| `biztalk_sql_targets_failed` | Fehlgeschlagene SQL-Ziele. |
|
||||
|
||||
## BizTalk Suspended Instances
|
||||
|
||||
Zweck:
|
||||
@@ -41,7 +78,7 @@ Status:
|
||||
| `OK` | keine suspendierten Instanzen. |
|
||||
| `WARN` | `biztalk_suspended_resumable >= WarnResumableThreshold`. |
|
||||
| `CRIT` | `biztalk_suspended_nonresumable >= CritNonResumableThreshold`. |
|
||||
| `UNKNOWN` | WMI oder Programmfehler. |
|
||||
| `UNKNOWN` | `MSBTS_ServiceInstance` nicht verlaesslich lesbar oder Programmfehler. |
|
||||
|
||||
Metriken:
|
||||
|
||||
@@ -91,7 +128,7 @@ Status:
|
||||
| `OK` | Artefakte lesbar; keine unbekannten Statuswerte. |
|
||||
| `WARN` | Nur bei `AlertOnArtifactRuntimeIssues=true` und inaktiven/deaktivierten Artefakten. |
|
||||
| `CRIT` | Unbekannte Send-Port- oder Orchestration-Statuswerte. |
|
||||
| `UNKNOWN` | Programmfehler. |
|
||||
| `UNKNOWN` | Mindestens eine erforderliche Artefaktklasse nicht lesbar oder Programmfehler. |
|
||||
|
||||
Metriken:
|
||||
|
||||
@@ -141,4 +178,3 @@ BizTalk Suspended <ApplicationName>
|
||||
```
|
||||
|
||||
Das ist fuer Anwendungsteams hilfreich, kann aber die Service-Anzahl erhoehen. Fuer den Basisausbau wird empfohlen, diese Option deaktiviert zu lassen.
|
||||
|
||||
|
||||
+16
-3
@@ -4,13 +4,15 @@
|
||||
|
||||
```text
|
||||
0 "BizTalk Platform" - Self test OK. Checkmk local check output is valid.
|
||||
0 "BizTalk SQL Access" biztalk_sql_targets_total=0;;;0 Self test OK. No SQL connection was opened.
|
||||
0 "BizTalk Suspended Instances" biztalk_suspended_total=0;;;0 Self test OK.
|
||||
```
|
||||
|
||||
## Gesunder BizTalk-Server
|
||||
|
||||
```text
|
||||
0 "BizTalk Platform" - BizTalk WMI reachable, server=BIZTALK01, group=BizTalkMgmtDb, mgmt_db=SQL01\BizTalkMgmtDb, msgbox_db=SQL01\BizTalkMsgBoxDb
|
||||
0 "BizTalk Platform" - BizTalk WMI and platform data reachable, server=BIZTALK01, group=BizTalkMgmtDb, mgmt_db=SQL01\BizTalkMgmtDb, msgbox_db=SQL01\BizTalkMsgBoxDb
|
||||
0 "BizTalk SQL Access" biztalk_sql_targets_total=2;;;0|biztalk_sql_targets_available=2;;;0|biztalk_sql_targets_failed=0;;1;0 SQL access targets=2, available=2, discovery_complete=True, execution_identity=NT AUTHORITY\SYSTEM, network_identity=<DOMAIN>\BIZTALK01$; databases=Management=SQL01\BizTalkMgmtDb:OK, MessageBox=SQL01\BizTalkMsgBoxDb:OK
|
||||
0 "BizTalk Suspended Instances" biztalk_suspended_total=0;;;0|biztalk_suspended_resumable=0;1;;0|biztalk_suspended_nonresumable=0;;1;0 No suspended BizTalk service instances found.
|
||||
0 "BizTalk Host Instances" biztalk_host_instances_total=4;;;0|biztalk_host_instances_started=4;;;0|biztalk_host_instances_stopped=0;;1;0|biztalk_host_instances_pending=0;1;;0|biztalk_host_instances_unknown=0;;1;0 Host instances total=4, started=4, stopped=0, pending=0, unknown=0
|
||||
0 "BizTalk Runtime Artifacts" biztalk_applications=8;;;0|biztalk_receive_locations=24;;;0|biztalk_receive_locations_disabled=0;1;;0|biztalk_send_ports=31;;;0|biztalk_send_ports_started=31;;;0|biztalk_send_ports_inactive=0;1;;0|biztalk_send_ports_unknown=0;;1;0|biztalk_orchestrations=12;;;0|biztalk_orchestrations_started=12;;;0|biztalk_orchestrations_inactive=0;1;;0|biztalk_orchestrations_unknown=0;;1;0 Applications=8; receive_locations total=24, disabled=0; send_ports total=31, started=31, inactive=0, unknown=0; orchestrations total=12, started=12, inactive=0, unknown=0
|
||||
@@ -23,9 +25,20 @@
|
||||
2 "BizTalk Suspended Instances" biztalk_suspended_total=3;;;0|biztalk_suspended_resumable=2;1;;0|biztalk_suspended_nonresumable=1;;1;0 3 suspended BizTalk service instance(s): resumable=2, nonresumable=1; applications=OrderProcessing(R=2, NR=1); examples=Send.Invoice on ProcessingHost
|
||||
```
|
||||
|
||||
## SQL-Berechtigungsfehler
|
||||
|
||||
```text
|
||||
3 "BizTalk SQL Access" biztalk_sql_targets_total=2;;;0|biztalk_sql_targets_available=0;;;0|biztalk_sql_targets_failed=2;;1;0 SQL access targets=2, available=0, discovery_complete=True, execution_identity=NT AUTHORITY\SYSTEM, network_identity=<DOMAIN>\BIZTALK01$; databases=Management=SQL01\BizTalkMgmtDb:FAILED, MessageBox=SQL01\BizTalkMsgBoxDb:FAILED; diagnostics: Sql/Permission [Management SQL01\BizTalkMgmtDb]: SQL Server hat die integrierte Anmeldung abgelehnt. Erwartete Netzwerkidentitaet: <DOMAIN>\BIZTALK01$. Massnahme: <DOMAIN>\BIZTALK01$ in die konfigurierte BizTalk-Operator-Gruppe aufnehmen; keine direkten BizTalk-DB-Rollen vergeben. Danach Kerberos-Tickets erneuern und den Agent-Dump wiederholen. Technik: SqlException Number=18456: Login failed for user
|
||||
```
|
||||
|
||||
## WMI-Berechtigungsfehler
|
||||
|
||||
```text
|
||||
3 "BizTalk Suspended Instances" - Suspendierte Instanzen konnten nicht verlaesslich gelesen werden. Ein leerer Datenbestand wird nicht als OK gewertet.; diagnostics: Wmi/Permission [MSBTS_ServiceInstance]: Zugriff auf MSBTS_ServiceInstance wurde im LocalSystem-Kontext verweigert. Massnahme: Agent-Dump pruefen und Computerkonto <DOMAIN>\BIZTALK01$ zunaechst der konfigurierten BizTalk-Operator-Gruppe zuordnen. Technik: UnauthorizedAccessException HRESULT=0x80070005: Access is denied
|
||||
```
|
||||
|
||||
## Fehlendes Deployment
|
||||
|
||||
```text
|
||||
3 "BizTalk Platform" - BizTalkCheckmkPulse.exe not found below C:\ProgramData\checkmk\agent\local\BizTalkCheckmkPulse
|
||||
3 "BizTalk Platform" - BizTalkCheckmkPulse.exe fehlt unter C:\ProgramData\checkmk\agent\local\BizTalkCheckmkPulse. Massnahme: Deployment-Paket und Dateirechte pruefen und danach cmk-agent-ctl.exe dump erneut ausfuehren.
|
||||
```
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ Empfohlene Service-Behandlung:
|
||||
| Service | Empfehlung |
|
||||
| --- | --- |
|
||||
| `BizTalk Platform` | `UNKNOWN` immer als Integrationsproblem behandeln. |
|
||||
| `BizTalk SQL Access` | `UNKNOWN` anhand der Kategorie `Permission`, `Connectivity`, `Timeout`, `Configuration` oder `Provider` bearbeiten. |
|
||||
| `BizTalk Suspended Instances` | In `PRD` alarmieren; in `ACC`/`TST`/`DEV` nach Teamvereinbarung. |
|
||||
| `BizTalk Host Instances` | `CRIT` alarmieren. |
|
||||
| `BizTalk Runtime Artifacts` | Erst beobachten; strenge Alarmierung nur bei klar definiertem Runtime-Sollzustand. |
|
||||
@@ -67,6 +68,90 @@ Checkmk Managed Services Edition 2.4 enthaelt die kommerziellen Mechanismen fuer
|
||||
|
||||
Hinweis: Dieses Repository enthaelt bewusst noch kein Bakery-Python-Plugin. Die manuelle bzw. dateibasierte Verteilung ist die risikoarme erste Version. Eine Bakery-Erweiterung ist eine sinnvolle Version-2-Ausbaustufe.
|
||||
|
||||
## Berechtigungen fuer WMI und BizTalk-Datenbanken
|
||||
|
||||
### Ausfuehrung als LocalSystem
|
||||
|
||||
Der Checkmk Windows Agent und der Agent Controller laufen standardmaessig unter `LocalSystem` (`NT AUTHORITY\SYSTEM`). Dadurch werden auch `biztalk_checkmk_pulse.cmd` und `BizTalkCheckmkPulse.exe` in diesem Kontext gestartet.
|
||||
|
||||
Das Plugin verbindet sich mit dem lokalen Namespace `\\<eigener-server>\root\MicrosoftBizTalkServer`, setzt keine separaten Anmeldedaten und verwendet kein Remote-WMI. Es fuehrt ausschliesslich WMI-Leseabfragen aus und liest das lokale Windows Application Event Log. Fuer diese lokalen Zugriffe sind normalerweise keine zusaetzlichen DCOM-, Firewall- oder WMI-Namespace-Freigaben erforderlich.
|
||||
|
||||
### Besonderheit bei getrenntem SQL Server
|
||||
|
||||
Mehrere BizTalk-WMI-Klassen lesen Daten aus der BizTalk Management- oder MessageBox-Datenbank. Bei einem getrennten SQL Server verwendet `LocalSystem` fuer diesen Netzwerkzugriff das Active-Directory-Computerkonto des BizTalk-Servers:
|
||||
|
||||
```text
|
||||
DOMAIN\BIZTALKSERVER$
|
||||
```
|
||||
|
||||
Lokale Administratorrechte von `LocalSystem` gelten nicht automatisch auf dem entfernten SQL Server. Daher koennen SQL-gestuetzte WMI-Abfragen fehlschlagen, obwohl der lokale WMI-Namespace grundsaetzlich erreichbar ist.
|
||||
|
||||
### Verbindlicher Funktionstest
|
||||
|
||||
Der direkte Start des Wrappers in einer administrativen Eingabeaufforderung ist nur ein Vorabtest, weil er mit dem angemeldeten Benutzer laeuft. Fuer die Berechtigungspruefung ist die Ausgabe unter dem echten Checkmk-Kontext massgeblich:
|
||||
|
||||
```powershell
|
||||
& "C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump |
|
||||
Select-String -Pattern "BizTalk|Access denied|Unauthorized|UNKNOWN" -Context 0,1
|
||||
```
|
||||
|
||||
Die Ausgabe muss die sechs stabilen `BizTalk ...` Services mit plausiblen Daten enthalten. `Access denied`, `UnauthorizedAccessException`, `Sql/Permission` und ein berechtigungsbedingtes `UNKNOWN` weisen auf eine fehlende Rollenzuordnung hin.
|
||||
|
||||
Die EXE klassifiziert WMI- und SQL-Probleme und schreibt zu jeder Diagnose `Massnahme:` und `Technik:`. Eine fehlgeschlagene erforderliche WMI-Abfrage erzeugt beim betroffenen Service immer `UNKNOWN`; fehlende Daten werden nicht als Nullbestand und damit nicht als `OK` ausgegeben.
|
||||
|
||||
Fuer den produktiven Betrieb wird empfohlen, den Local Check alle 300 Sekunden asynchron auszufuehren. Dadurch erzeugen fehlende SQL-Rechte nicht bei jedem Agent-Abruf neue fehlgeschlagene Logins. In `%ProgramData%\checkmk\agent\check_mk.user.yml`:
|
||||
|
||||
```yaml
|
||||
local:
|
||||
enabled: yes
|
||||
execution:
|
||||
- pattern: $CUSTOM_LOCAL_PATH$\biztalk_checkmk_pulse.cmd
|
||||
async: yes
|
||||
run: yes
|
||||
cache_age: 300
|
||||
```
|
||||
|
||||
Alternativ koennen die Checkmk-Kollegen in der Agent Bakery die Regeln `Set execution mode for plug-ins and local checks` und `Set cache age for plug-ins and local checks` verwenden. Der Cache reduziert Last und SQL-Fehlerlogs; ein Zustandswechsel wird dadurch um maximal die konfigurierte Cache-Zeit verzoegert.
|
||||
|
||||
### Least-Privilege-Vorgehen
|
||||
|
||||
1. Das verwendete Computerkonto feststellen, normalerweise `DOMAIN\BIZTALKSERVER$`.
|
||||
2. Die bei der BizTalk-Konfiguration hinterlegte Operator-Gruppe ermitteln. Sie kann anders als `BizTalk Server Operators` benannt sein.
|
||||
3. Das Computerkonto in diese Operator-Gruppe aufnehmen.
|
||||
4. Kerberos-Tickets des Systemkontos erneuern oder den Server in einem Wartungsfenster neu starten.
|
||||
5. `cmk-agent-ctl.exe dump` erneut ausfuehren.
|
||||
6. Nur bei weiterhin abgelehnten, konkret identifizierten WMI-Klassen mit BizTalk- und SQL-Administration klaeren, ob `BizTalk Server Administrators` erforderlich ist.
|
||||
|
||||
Die BizTalk-Operator-Rolle ist fuer grundlegende Administration und Monitoring vorgesehen. Sie kann Zustands- und Message-Flow-Informationen lesen, darf aber keine Konfiguration oder Nachrichteninhalte einsehen. Das entspricht dem lesenden Funktionsumfang dieses Plugins. Keine direkten SQL-Rollen in den BizTalk-Datenbanken hinzufuegen; die von BizTalk konfigurierte Windows-Gruppe soll die Berechtigungen vermitteln.
|
||||
|
||||
Entscheidungsmatrix:
|
||||
|
||||
| Beobachtung | Bewertung und Massnahme |
|
||||
| --- | --- |
|
||||
| Alle sechs stabilen Services liefern plausible Werte | Keine Aenderung erforderlich. |
|
||||
| `root\MicrosoftBizTalkServer` ist nicht erreichbar | BizTalk-WMI-Provider, WMI-Dienst, Namespace und dessen ACL gezielt pruefen. |
|
||||
| `BizTalk SQL Access` meldet `Sql/Permission` | Maschinenkonto in die konfigurierte BizTalk-Operator-Gruppe aufnehmen, Kerberos erneuern und Agent-Dump wiederholen. |
|
||||
| `BizTalk SQL Access` meldet `Sql/Connectivity` | SQL-Server-/Instanzname, DNS, Dienst, TCP-Port und Firewall pruefen. |
|
||||
| `BizTalk SQL Access` meldet `Sql/Timeout` | SQL-/Netzwerkauslastung untersuchen; Timeout nur nach Ursachenanalyse anpassen. |
|
||||
| Verbindung funktioniert, einzelne SQL-gestuetzte Klassen melden Zugriffsfehler | `DOMAIN\BIZTALKSERVER$` zunaechst in die konfigurierte BizTalk-Operator-Gruppe aufnehmen. |
|
||||
| Nur `BizTalk Event Log` ist `UNKNOWN` | Lokalen Zugriff auf das Application Event Log pruefen. |
|
||||
| Fehler bleibt nach Operator-Zuweisung bestehen | Exakte WMI-Klasse anhand der Service-Ausgabe bestimmen und deren BizTalk-/SQL-Rollenanforderung pruefen. |
|
||||
|
||||
### Sicherheitsauswirkung und gMSA-Alternative
|
||||
|
||||
Die Mitgliedschaft des Computerkontos in der Operator-Gruppe gilt fuer alle Dienste, die auf dem BizTalk-Server als `LocalSystem` laufen und mit dem Computerkonto auf Netzwerkressourcen zugreifen. Diese Auswirkung muss mit Security und BizTalk-Betrieb abgestimmt werden.
|
||||
|
||||
Falls das Computerkonto keine BizTalk-Rechte erhalten darf, kann ein separater Collector unter einem dedizierten gMSA- oder Dienstkonto mit Operator-Rechten die Checkmk-Ausgabe erzeugen und als Spool-Datei bereitstellen. Das waere eine eigene Betriebsvariante und ist in der aktuellen Local-Check-Version noch nicht implementiert. Den gesamten Checkmk-Agent-Dienst nur fuer dieses Plugin auf ein anderes Konto umzustellen ist nicht empfohlen, da die Identitaetsaenderung alle Agent-Sektionen und Local Checks betrifft.
|
||||
|
||||
Quellen:
|
||||
|
||||
- Checkmk Windows Agent: https://docs.checkmk.com/latest/en/agent_windows.html
|
||||
- Checkmk Local Checks: https://docs.checkmk.com/latest/en/localchecks.html
|
||||
- BizTalk Minimum Security User Rights: https://learn.microsoft.com/en-us/biztalk/core/minimum-security-user-rights
|
||||
- BizTalk Access Control and Data Security: https://learn.microsoft.com/en-us/biztalk/core/access-control-and-data-security
|
||||
- Microsoft LocalSystem und Computerkonten: https://learn.microsoft.com/en-us/entra/architecture/service-accounts-computer
|
||||
- Microsoft WMI Namespace Security: https://learn.microsoft.com/en-us/windows/win32/wmisdk/access-to-wmi-namespaces
|
||||
|
||||
## Empfohlene Host-Struktur
|
||||
|
||||
Host-Tags oder Ordner:
|
||||
@@ -87,6 +172,7 @@ Dashboard-Kacheln:
|
||||
|
||||
- Host/Service state fuer BizTalk-Host
|
||||
- Service state fuer `BizTalk Suspended Instances`
|
||||
- Service state fuer `BizTalk SQL Access`
|
||||
- Graph `biztalk_suspended_total`
|
||||
- Graph `biztalk_host_instances_stopped`
|
||||
- Graph `biztalk_eventlog_errors`
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<appSettings>
|
||||
<!-- Empty or "." means local BizTalk machine. -->
|
||||
<!-- Leer oder "." bedeutet die lokale BizTalk-Maschine. -->
|
||||
<add key="Server" value="." />
|
||||
<add key="ServicePrefix" value="BizTalk" />
|
||||
<add key="EnvironmentName" value="" />
|
||||
|
||||
<add key="QueryTimeoutSeconds" value="25" />
|
||||
<!-- Testet integrierte Windows-Anmeldung an den per WMI ermittelten BizTalk-Datenbanken. -->
|
||||
<add key="ProbeSqlConnectivity" value="true" />
|
||||
<add key="SqlConnectionTimeoutSeconds" value="5" />
|
||||
<add key="WarnResumableThreshold" value="1" />
|
||||
<add key="CritNonResumableThreshold" value="1" />
|
||||
<add key="MaxSummaryItems" value="12" />
|
||||
|
||||
<!-- In many BizTalk landscapes stopped artifacts can be intentional. -->
|
||||
<!-- Gestoppte Artefakte koennen in BizTalk-Landschaften fachlich beabsichtigt sein. -->
|
||||
<add key="AlertOnArtifactRuntimeIssues" value="false" />
|
||||
<add key="EmitPerApplicationSuspensionServices" value="false" />
|
||||
|
||||
@@ -25,4 +28,3 @@
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
|
||||
</startup>
|
||||
</configuration>
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Management" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -44,6 +45,7 @@
|
||||
<Compile Include="MonitoringOptions.cs" />
|
||||
<Compile Include="Models.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="SqlConnectivityProbe.cs" />
|
||||
<Compile Include="WmiBizTalkProbe.cs" />
|
||||
<Compile Include="WmiHelpers.cs" />
|
||||
</ItemGroup>
|
||||
@@ -52,4 +54,3 @@
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@ using System.Text;
|
||||
|
||||
namespace BizTalkCheckmkPulse
|
||||
{
|
||||
/// <summary>
|
||||
/// Formatiert Probe-Ergebnisse als robuste Checkmk-Local-Check-Zeilen.
|
||||
/// </summary>
|
||||
internal sealed class CheckmkLocalFormatter
|
||||
{
|
||||
private const int HostStopped = 1;
|
||||
@@ -18,20 +21,30 @@ namespace BizTalkCheckmkPulse
|
||||
private const int HostUnknown = 8;
|
||||
private readonly MonitoringOptions _options;
|
||||
|
||||
/// <summary>
|
||||
/// Initialisiert den Formatter mit Service-Namen und Schwellwerten.
|
||||
/// </summary>
|
||||
/// <param name="options">Monitoring-Konfiguration.</param>
|
||||
public CheckmkLocalFormatter(MonitoringOptions options)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formatiert alle stabilen und optionalen Checkmk-Services.
|
||||
/// </summary>
|
||||
/// <param name="result">Vollstaendiges Ergebnis der Probes.</param>
|
||||
/// <returns>Gueltige Checkmk-Local-Check-Zeilen.</returns>
|
||||
public IEnumerable<string> Format(ProbeResult result)
|
||||
{
|
||||
yield return FormatPlatform(result);
|
||||
yield return FormatSqlAccess(result);
|
||||
yield return FormatSuspendedInstances(result);
|
||||
yield return FormatHostInstances(result);
|
||||
yield return FormatRuntimeArtifacts(result);
|
||||
yield return FormatEventLog(result);
|
||||
|
||||
if (_options.EmitPerApplicationSuspensionServices)
|
||||
if (_options.EmitPerApplicationSuspensionServices && result.Platform.SuspendedInstancesDataAvailable)
|
||||
{
|
||||
foreach (var line in FormatApplicationSuspensions(result))
|
||||
{
|
||||
@@ -40,36 +53,111 @@ namespace BizTalkCheckmkPulse
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Erzeugt minimale Beispielzeilen ohne WMI-, SQL- oder Event-Log-Zugriff.
|
||||
/// </summary>
|
||||
/// <returns>Checkmk-Zeilen zur reinen Format- und Deployment-Pruefung.</returns>
|
||||
public IEnumerable<string> FormatSelfTest()
|
||||
{
|
||||
yield return BuildLine(CheckState.Ok, _options.ServiceName("Platform"), "-", "Self test OK. Checkmk local check output is valid.");
|
||||
yield return BuildLine(CheckState.Ok, _options.ServiceName("SQL Access"), "biztalk_sql_targets_total=0;;;0", "Self test OK. No SQL connection was opened.");
|
||||
yield return BuildLine(CheckState.Ok, _options.ServiceName("Suspended Instances"), "biztalk_suspended_total=0;;;0", "Self test OK.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Erzeugt fuer einen nicht abgefangenen Programmfehler UNKNOWN-Zeilen.
|
||||
/// </summary>
|
||||
/// <param name="message">Bereits aufbereitete technische Fehlermeldung.</param>
|
||||
/// <returns>UNKNOWN-Zeilen fuer alle stabilen Services.</returns>
|
||||
public IEnumerable<string> FormatFatal(string message)
|
||||
{
|
||||
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Platform"), "-", message);
|
||||
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Suspended Instances"), "-", message);
|
||||
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Host Instances"), "-", message);
|
||||
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Runtime Artifacts"), "-", message);
|
||||
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Event Log"), "-", message);
|
||||
var action = message + " Massnahme: Programmdatei, Konfiguration und Windows Application Log pruefen; anschliessend cmk-agent-ctl.exe dump erneut ausfuehren.";
|
||||
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Platform"), "-", action);
|
||||
yield return BuildLine(CheckState.Unknown, _options.ServiceName("SQL Access"), "-", action);
|
||||
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Suspended Instances"), "-", action);
|
||||
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Host Instances"), "-", action);
|
||||
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Runtime Artifacts"), "-", action);
|
||||
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Event Log"), "-", action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formatiert Erreichbarkeit und Basisdaten des BizTalk-WMI-Providers.
|
||||
/// </summary>
|
||||
/// <param name="result">Probe-Ergebnis.</param>
|
||||
/// <returns>Checkmk-Zeile fuer BizTalk Platform.</returns>
|
||||
private string FormatPlatform(ProbeResult result)
|
||||
{
|
||||
var state = result.Platform.WmiConnected ? CheckState.Ok : CheckState.Unknown;
|
||||
var available = result.Platform.WmiConnected && result.Platform.PlatformDataAvailable;
|
||||
var state = available ? CheckState.Ok : CheckState.Unknown;
|
||||
var detail = new StringBuilder();
|
||||
detail.Append(result.Platform.WmiConnected ? "BizTalk WMI reachable" : "BizTalk WMI not reachable");
|
||||
detail.Append(available ? "BizTalk WMI and platform data reachable" : "BizTalk WMI or required platform data not readable");
|
||||
detail.Append(", server=").Append(EmptyAsUnknown(result.Platform.ServerName));
|
||||
AppendOptional(detail, "group", result.Platform.GroupName);
|
||||
AppendOptional(detail, "mgmt_db", JoinDb(result.Platform.ManagementDbServer, result.Platform.ManagementDbName));
|
||||
AppendOptional(detail, "msgbox_db", JoinDb(result.Platform.MessageBoxDbServer, result.Platform.MessageBoxDbName));
|
||||
|
||||
return BuildLine(state, _options.ServiceName("Platform"), "-", AppendDiagnostics(detail.ToString(), result.Diagnostics));
|
||||
var diagnostics = SelectDiagnostics(result, DiagnosticArea.Wmi, "WMI namespace connection", "MSBTS_GroupSetting", "MSBTS_MessageBoxSetting");
|
||||
return BuildLine(state, _options.ServiceName("Platform"), "-", AppendDiagnostics(detail.ToString(), diagnostics));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formatiert die integrierten Anmeldetests an den BizTalk-Datenbanken.
|
||||
/// </summary>
|
||||
/// <param name="result">Probe-Ergebnis mit SQL-Zielen und Identitaeten.</param>
|
||||
/// <returns>Checkmk-Zeile fuer BizTalk SQL Access.</returns>
|
||||
private string FormatSqlAccess(ProbeResult result)
|
||||
{
|
||||
if (!_options.ProbeSqlConnectivity)
|
||||
{
|
||||
return BuildLine(CheckState.Ok, _options.ServiceName("SQL Access"), "-", "SQL connectivity probe disabled by configuration.");
|
||||
}
|
||||
|
||||
var total = result.SqlTargets.Count;
|
||||
var available = result.SqlTargets.Count(x => x.Attempted && x.Available);
|
||||
var discoveryComplete = result.Platform.PlatformDataAvailable;
|
||||
var state = discoveryComplete && total > 0 && available == total ? CheckState.Ok : CheckState.Unknown;
|
||||
var metrics = total == 0
|
||||
? "-"
|
||||
: string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"biztalk_sql_targets_total={0};;;0|biztalk_sql_targets_available={1};;;0|biztalk_sql_targets_failed={2};;1;0",
|
||||
total,
|
||||
available,
|
||||
total - available);
|
||||
|
||||
var detail = new StringBuilder();
|
||||
detail.Append("SQL access targets=").Append(total)
|
||||
.Append(", available=").Append(available)
|
||||
.Append(", discovery_complete=").Append(discoveryComplete)
|
||||
.Append(", execution_identity=").Append(EmptyAsUnknown(result.ExecutionIdentity))
|
||||
.Append(", network_identity=").Append(EmptyAsUnknown(result.NetworkIdentityHint));
|
||||
AppendList(detail, "databases", result.SqlTargets.Select(x =>
|
||||
x.Role + "=" + EmptyAsUnknown(x.Server) + "\\" + EmptyAsUnknown(x.Database) + ":" + (x.Available ? "OK" : "FAILED")));
|
||||
|
||||
return BuildLine(
|
||||
state,
|
||||
_options.ServiceName("SQL Access"),
|
||||
metrics,
|
||||
AppendDiagnostics(detail.ToString(), SelectDiagnostics(result, DiagnosticArea.Sql)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formatiert Anzahl und Kritikalitaet suspendierter Service Instances.
|
||||
/// </summary>
|
||||
/// <param name="result">Probe-Ergebnis.</param>
|
||||
/// <returns>Checkmk-Zeile fuer suspendierte Instanzen.</returns>
|
||||
private string FormatSuspendedInstances(ProbeResult result)
|
||||
{
|
||||
if (!result.Platform.SuspendedInstancesDataAvailable)
|
||||
{
|
||||
var errorDetail = "Suspendierte Instanzen konnten nicht verlaesslich gelesen werden. Ein leerer Datenbestand wird nicht als OK gewertet.";
|
||||
return BuildLine(
|
||||
CheckState.Unknown,
|
||||
_options.ServiceName("Suspended Instances"),
|
||||
"-",
|
||||
AppendDiagnostics(errorDetail, SelectDiagnostics(result, DiagnosticArea.Wmi, "WMI namespace connection", "MSBTS_ServiceInstance")));
|
||||
}
|
||||
|
||||
var total = result.SuspendedInstances.Count;
|
||||
var resumable = result.SuspendedInstances.Count(x => x.Kind == SuspendedKind.Resumable);
|
||||
var nonresumable = result.SuspendedInstances.Count(x => x.Kind == SuspendedKind.NonResumable);
|
||||
@@ -86,12 +174,26 @@ namespace BizTalkCheckmkPulse
|
||||
var detail = total == 0
|
||||
? "No suspended BizTalk service instances found."
|
||||
: BuildSuspensionDetail(result.SuspendedInstances, total, resumable, nonresumable);
|
||||
|
||||
return BuildLine(state, _options.ServiceName("Suspended Instances"), metrics, detail);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formatiert den Laufzeitzustand aller BizTalk Host Instances.
|
||||
/// </summary>
|
||||
/// <param name="result">Probe-Ergebnis.</param>
|
||||
/// <returns>Checkmk-Zeile fuer Host Instances.</returns>
|
||||
private string FormatHostInstances(ProbeResult result)
|
||||
{
|
||||
if (!result.Platform.HostInstancesDataAvailable)
|
||||
{
|
||||
var errorDetail = "BizTalk Host Instances konnten nicht verlaesslich gelesen werden.";
|
||||
return BuildLine(
|
||||
CheckState.Unknown,
|
||||
_options.ServiceName("Host Instances"),
|
||||
"-",
|
||||
AppendDiagnostics(errorDetail, SelectDiagnostics(result, DiagnosticArea.Wmi, "WMI namespace connection", "MSBTS_HostInstance")));
|
||||
}
|
||||
|
||||
var total = result.HostInstances.Count;
|
||||
var started = result.HostInstances.Count(x => x.ServiceState == HostStarted);
|
||||
var stopped = result.HostInstances.Count(x => x.ServiceState == HostStopped || x.ServiceState == HostPaused);
|
||||
@@ -119,12 +221,26 @@ namespace BizTalkCheckmkPulse
|
||||
.Take(_options.MaxSummaryItems)
|
||||
.Select(x => EmptyAsUnknown(x.HostName) + "@" + EmptyAsUnknown(x.RunningServer) + "=" + HostStateName(x.ServiceState));
|
||||
AppendList(detail, "affected", affected);
|
||||
|
||||
return BuildLine(state, _options.ServiceName("Host Instances"), metrics, detail.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formatiert aggregierte Receive-Location-, Send-Port- und Orchestration-Zustaende.
|
||||
/// </summary>
|
||||
/// <param name="result">Probe-Ergebnis.</param>
|
||||
/// <returns>Checkmk-Zeile fuer Runtime Artifacts.</returns>
|
||||
private string FormatRuntimeArtifacts(ProbeResult result)
|
||||
{
|
||||
if (!result.Platform.RuntimeArtifactsDataAvailable)
|
||||
{
|
||||
var errorDetail = "BizTalk Runtime Artifacts konnten nicht vollstaendig gelesen werden. Teilwerte werden nicht als OK gewertet.";
|
||||
return BuildLine(
|
||||
CheckState.Unknown,
|
||||
_options.ServiceName("Runtime Artifacts"),
|
||||
"-",
|
||||
AppendDiagnostics(errorDetail, SelectDiagnostics(result, DiagnosticArea.Wmi, "WMI namespace connection", "MSBTS_ReceiveLocation", "MSBTS_SendPort", "MSBTS_Orchestration")));
|
||||
}
|
||||
|
||||
var receiveLocations = result.Applications.Sum(x => x.ReceiveLocationTotal);
|
||||
var receiveLocationsDisabled = result.Applications.Sum(x => x.ReceiveLocationDisabled);
|
||||
var sendPorts = result.Applications.Sum(x => x.SendPortTotal);
|
||||
@@ -166,10 +282,14 @@ namespace BizTalkCheckmkPulse
|
||||
.Take(_options.MaxSummaryItems)
|
||||
.Select(x => x.ApplicationName + "(rl_disabled=" + x.ReceiveLocationDisabled + ", sp_inactive=" + (x.SendPortStopped + x.SendPortBound) + ", orch_inactive=" + (x.OrchestrationStopped + x.OrchestrationBound + x.OrchestrationUnbound) + ")");
|
||||
AppendList(detail, "notable_apps", affected);
|
||||
|
||||
return BuildLine(state, _options.ServiceName("Runtime Artifacts"), metrics, detail.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formatiert relevante Fehler und Warnungen aus dem Application Event Log.
|
||||
/// </summary>
|
||||
/// <param name="result">Probe-Ergebnis.</param>
|
||||
/// <returns>Checkmk-Zeile fuer das BizTalk Event Log.</returns>
|
||||
private string FormatEventLog(ProbeResult result)
|
||||
{
|
||||
if (!_options.ProbeEventLog)
|
||||
@@ -179,7 +299,11 @@ namespace BizTalkCheckmkPulse
|
||||
|
||||
if (!result.EventLog.Available)
|
||||
{
|
||||
return BuildLine(CheckState.Unknown, _options.ServiceName("Event Log"), "-", "Application event log could not be read: " + result.EventLog.Failure);
|
||||
return BuildLine(
|
||||
CheckState.Unknown,
|
||||
_options.ServiceName("Event Log"),
|
||||
"-",
|
||||
"Application Event Log konnte nicht gelesen werden: " + result.EventLog.Failure + " Massnahme: lokalen Event-Log-Zugriff des Checkmk-Agentkontos und den Windows Event Log Dienst pruefen.");
|
||||
}
|
||||
|
||||
var state = _options.EventLogCritThreshold > 0 && result.EventLog.Errors >= _options.EventLogCritThreshold
|
||||
@@ -200,10 +324,14 @@ namespace BizTalkCheckmkPulse
|
||||
_options.EventLogLookbackMinutes,
|
||||
result.EventLog.Errors,
|
||||
result.EventLog.Warnings);
|
||||
|
||||
return BuildLine(state, _options.ServiceName("Event Log"), metrics, detail);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Erzeugt bei aktivierter Option einen Suspensions-Service je Anwendung.
|
||||
/// </summary>
|
||||
/// <param name="result">Probe-Ergebnis mit suspendierten Instanzen.</param>
|
||||
/// <returns>Checkmk-Zeilen je betroffener BizTalk-Anwendung.</returns>
|
||||
private IEnumerable<string> FormatApplicationSuspensions(ProbeResult result)
|
||||
{
|
||||
foreach (var group in result.SuspendedInstances.GroupBy(x => x.ApplicationName).OrderBy(x => x.Key, StringComparer.OrdinalIgnoreCase))
|
||||
@@ -219,11 +347,16 @@ namespace BizTalkCheckmkPulse
|
||||
_options.WarnResumableThreshold,
|
||||
nonresumable,
|
||||
_options.CritNonResumableThreshold);
|
||||
|
||||
yield return BuildLine(state, _options.ServiceName("Suspended " + group.Key), metrics, "Suspended instances: resumable=" + resumable + ", nonresumable=" + nonresumable);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bestimmt den Checkmk-Zustand aus den beiden Suspensionszaehlern.
|
||||
/// </summary>
|
||||
/// <param name="resumable">Anzahl fortsetzbarer Instanzen.</param>
|
||||
/// <param name="nonresumable">Anzahl nicht fortsetzbarer Instanzen.</param>
|
||||
/// <returns>Zustand gemaess konfigurierten Schwellwerten.</returns>
|
||||
private CheckState DetermineSuspensionState(int resumable, int nonresumable)
|
||||
{
|
||||
if (_options.CritNonResumableThreshold > 0 && nonresumable >= _options.CritNonResumableThreshold)
|
||||
@@ -239,6 +372,14 @@ namespace BizTalkCheckmkPulse
|
||||
return CheckState.Ok;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Erstellt Details mit betroffenen Anwendungen und aktuellen Beispielen.
|
||||
/// </summary>
|
||||
/// <param name="instances">Suspendierte Instanzen.</param>
|
||||
/// <param name="total">Gesamtanzahl.</param>
|
||||
/// <param name="resumable">Anzahl fortsetzbarer Instanzen.</param>
|
||||
/// <param name="nonresumable">Anzahl nicht fortsetzbarer Instanzen.</param>
|
||||
/// <returns>Kompakter Detailtext.</returns>
|
||||
private string BuildSuspensionDetail(IEnumerable<SuspendedInstance> instances, int total, int resumable, int nonresumable)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
@@ -260,6 +401,14 @@ namespace BizTalkCheckmkPulse
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Baut eine syntaktisch gueltige Checkmk-Local-Check-Zeile.
|
||||
/// </summary>
|
||||
/// <param name="state">Numerischer Checkmk-Zustand.</param>
|
||||
/// <param name="serviceName">Stabiler Service-Name.</param>
|
||||
/// <param name="metrics">Performance-Daten oder Bindestrich.</param>
|
||||
/// <param name="detail">Menschenlesbare Service-Ausgabe.</param>
|
||||
/// <returns>Vollstaendige Local-Check-Zeile.</returns>
|
||||
private static string BuildLine(CheckState state, string serviceName, string metrics, string detail)
|
||||
{
|
||||
return ((int)state).ToString(CultureInfo.InvariantCulture)
|
||||
@@ -269,17 +418,44 @@ namespace BizTalkCheckmkPulse
|
||||
+ SanitizeDetail(detail);
|
||||
}
|
||||
|
||||
private static string AppendDiagnostics(string detail, IEnumerable<string> diagnostics)
|
||||
/// <summary>
|
||||
/// Ergaenzt bis zu drei strukturierte Diagnosen am Detailtext.
|
||||
/// </summary>
|
||||
/// <param name="detail">Vorhandener Service-Detailtext.</param>
|
||||
/// <param name="diagnostics">Relevante Diagnosen.</param>
|
||||
/// <returns>Detailtext mit Ursache, Massnahme und technischen Details.</returns>
|
||||
private static string AppendDiagnostics(string detail, IEnumerable<ProbeDiagnostic> diagnostics)
|
||||
{
|
||||
var visible = diagnostics == null ? new string[0] : diagnostics.Take(3).ToArray();
|
||||
if (visible.Length == 0)
|
||||
{
|
||||
return detail;
|
||||
var visible = diagnostics == null
|
||||
? new string[0]
|
||||
: diagnostics.Take(3).Select(x => x.ToDisplayText()).ToArray();
|
||||
return visible.Length == 0
|
||||
? detail
|
||||
: detail + "; diagnostics: " + string.Join("; ", visible);
|
||||
}
|
||||
|
||||
return detail + "; diagnostics: " + string.Join("; ", visible);
|
||||
/// <summary>
|
||||
/// Filtert Diagnosen nach Bereich und optionalen Komponentenfragmenten.
|
||||
/// </summary>
|
||||
/// <param name="result">Probe-Ergebnis mit allen Diagnosen.</param>
|
||||
/// <param name="area">Gesuchter Diagnosebereich.</param>
|
||||
/// <param name="components">Optionale Fragmente betroffener Komponenten.</param>
|
||||
/// <returns>Passende Diagnosen in Entstehungsreihenfolge.</returns>
|
||||
private static IEnumerable<ProbeDiagnostic> SelectDiagnostics(ProbeResult result, DiagnosticArea area, params string[] components)
|
||||
{
|
||||
return result.Diagnostics.Where(x =>
|
||||
x.Area == area
|
||||
&& (components == null
|
||||
|| components.Length == 0
|
||||
|| components.Any(component => (x.Component ?? string.Empty).IndexOf(component, StringComparison.OrdinalIgnoreCase) >= 0)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Haengt eine nicht leere Werteliste an einen Detailtext an.
|
||||
/// </summary>
|
||||
/// <param name="builder">Zu erweiternder Text.</param>
|
||||
/// <param name="label">Bezeichnung der Liste.</param>
|
||||
/// <param name="values">Zu formatierende Werte.</param>
|
||||
private static void AppendList(StringBuilder builder, string label, IEnumerable<string> values)
|
||||
{
|
||||
var list = values.Where(x => !string.IsNullOrWhiteSpace(x)).ToArray();
|
||||
@@ -289,6 +465,12 @@ namespace BizTalkCheckmkPulse
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Haengt einen optionalen benannten Wert an einen Detailtext an.
|
||||
/// </summary>
|
||||
/// <param name="builder">Zu erweiternder Text.</param>
|
||||
/// <param name="label">Bezeichnung des Wertes.</param>
|
||||
/// <param name="value">Optionaler Wert.</param>
|
||||
private static void AppendOptional(StringBuilder builder, string label, string value)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
@@ -297,6 +479,12 @@ namespace BizTalkCheckmkPulse
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verbindet SQL-Server und Datenbank zu einer kompakten Anzeige.
|
||||
/// </summary>
|
||||
/// <param name="server">SQL-Server oder Instanz.</param>
|
||||
/// <param name="database">Datenbankname.</param>
|
||||
/// <returns>Kombination aus Server und Datenbank oder leerer Text.</returns>
|
||||
private static string JoinDb(string server, string database)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(server) && string.IsNullOrWhiteSpace(database))
|
||||
@@ -307,6 +495,11 @@ namespace BizTalkCheckmkPulse
|
||||
return EmptyAsUnknown(server) + "\\" + EmptyAsUnknown(database);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uebersetzt den numerischen BizTalk Host ServiceState in einen Namen.
|
||||
/// </summary>
|
||||
/// <param name="state">Numerischer WMI-Zustand.</param>
|
||||
/// <returns>Menschenlesbarer Zustandsname.</returns>
|
||||
private static string HostStateName(int state)
|
||||
{
|
||||
switch (state)
|
||||
@@ -332,16 +525,31 @@ namespace BizTalkCheckmkPulse
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ersetzt leere Werte durch eine sichtbare Unknown-Markierung.
|
||||
/// </summary>
|
||||
/// <param name="value">Zu normalisierender Text.</param>
|
||||
/// <returns>Getrimmter Wert oder <c>unknown</c>.</returns>
|
||||
private static string EmptyAsUnknown(string value)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(value) ? "unknown" : value.Trim();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entfernt fuer Checkmk problematische Zeichen aus Service-Namen.
|
||||
/// </summary>
|
||||
/// <param name="value">Urspruenglicher Service-Name.</param>
|
||||
/// <returns>Sicherer Service-Name.</returns>
|
||||
private static string SanitizeService(string value)
|
||||
{
|
||||
return SanitizeDetail(value).Replace("\"", "'");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalisiert Detailtext auf eine einzelne Checkmk-kompatible Zeile.
|
||||
/// </summary>
|
||||
/// <param name="value">Urspruenglicher Detailtext.</param>
|
||||
/// <returns>Einzeiliger, nicht leerer Detailtext.</returns>
|
||||
private static string SanitizeDetail(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
|
||||
@@ -4,15 +4,26 @@ using System.Linq;
|
||||
|
||||
namespace BizTalkCheckmkPulse
|
||||
{
|
||||
/// <summary>
|
||||
/// Zaehlt BizTalk-bezogene Fehler und Warnungen im lokalen Application Event Log.
|
||||
/// </summary>
|
||||
internal sealed class EventLogProbe
|
||||
{
|
||||
private readonly MonitoringOptions _options;
|
||||
|
||||
/// <summary>
|
||||
/// Initialisiert den Event-Log-Probe mit Zeitraum, Quellen und Schwellwerten.
|
||||
/// </summary>
|
||||
/// <param name="options">Monitoring-Konfiguration.</param>
|
||||
public EventLogProbe(MonitoringOptions options)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Liest das Application Event Log rueckwaerts bis zum Beginn des Zeitfensters.
|
||||
/// </summary>
|
||||
/// <param name="result">Ergebnisobjekt fuer Zaehler und Fehlerstatus.</param>
|
||||
public void Query(ProbeResult result)
|
||||
{
|
||||
result.EventLog.Since = DateTime.Now.AddMinutes(-_options.EventLogLookbackMinutes);
|
||||
@@ -55,6 +66,11 @@ namespace BizTalkCheckmkPulse
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prueft, ob eine Event-Log-Quelle zu den konfigurierten BizTalk-Quellen gehoert.
|
||||
/// </summary>
|
||||
/// <param name="source">Name der Windows Event-Log-Quelle.</param>
|
||||
/// <returns><c>true</c>, wenn die Quelle ueberwacht werden soll.</returns>
|
||||
private bool IsRelevantSource(string source)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(source))
|
||||
@@ -66,4 +82,3 @@ namespace BizTalkCheckmkPulse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@ using System.Collections.Generic;
|
||||
|
||||
namespace BizTalkCheckmkPulse
|
||||
{
|
||||
/// <summary>
|
||||
/// Definiert die von Checkmk erwarteten numerischen Service-Zustaende.
|
||||
/// </summary>
|
||||
internal enum CheckState
|
||||
{
|
||||
Ok = 0,
|
||||
@@ -11,35 +14,113 @@ namespace BizTalkCheckmkPulse
|
||||
Unknown = 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unterscheidet fortsetzbare und nicht fortsetzbare suspendierte Instanzen.
|
||||
/// </summary>
|
||||
internal enum SuspendedKind
|
||||
{
|
||||
Resumable,
|
||||
NonResumable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kennzeichnet den technischen Bereich, in dem eine Diagnose entstanden ist.
|
||||
/// </summary>
|
||||
internal enum DiagnosticArea
|
||||
{
|
||||
Wmi,
|
||||
Sql,
|
||||
EventLog,
|
||||
General
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Klassifiziert Fehler, damit Bediener eine passende Massnahme erhalten.
|
||||
/// </summary>
|
||||
internal enum DiagnosticCategory
|
||||
{
|
||||
Permission,
|
||||
Connectivity,
|
||||
Timeout,
|
||||
Configuration,
|
||||
Provider,
|
||||
Unexpected
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Strukturierte Diagnose mit Ursache und konkreter Handlungsempfehlung.
|
||||
/// </summary>
|
||||
internal sealed class ProbeDiagnostic
|
||||
{
|
||||
public DiagnosticArea Area { get; set; }
|
||||
public DiagnosticCategory Category { get; set; }
|
||||
public string Component { get; set; }
|
||||
public bool Required { get; set; }
|
||||
public string Summary { get; set; }
|
||||
public string Action { get; set; }
|
||||
public string TechnicalDetails { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Erstellt eine kompakte, fuer Checkmk geeignete Diagnosezeile.
|
||||
/// </summary>
|
||||
/// <returns>Menschenlesbarer Text mit Kategorie, Ursache und Massnahme.</returns>
|
||||
public string ToDisplayText()
|
||||
{
|
||||
var text = Area + "/" + Category + " [" + (Component ?? "unknown") + "]: " + (Summary ?? "Unbekannter Fehler.");
|
||||
if (!string.IsNullOrWhiteSpace(Action))
|
||||
{
|
||||
text += " Massnahme: " + Action;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(TechnicalDetails))
|
||||
{
|
||||
text += " Technik: " + TechnicalDetails;
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sammelt alle Ergebnisse eines Programmlaufs.
|
||||
/// </summary>
|
||||
internal sealed class ProbeResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Initialisiert leere Ergebnislisten und Zustandsobjekte.
|
||||
/// </summary>
|
||||
public ProbeResult()
|
||||
{
|
||||
Diagnostics = new List<string>();
|
||||
Diagnostics = new List<ProbeDiagnostic>();
|
||||
Platform = new PlatformState();
|
||||
HostInstances = new List<HostInstanceState>();
|
||||
SuspendedInstances = new List<SuspendedInstance>();
|
||||
Applications = new List<ApplicationRuntimeState>();
|
||||
SqlTargets = new List<SqlAccessState>();
|
||||
EventLog = new EventLogState();
|
||||
}
|
||||
|
||||
public List<string> Diagnostics { get; private set; }
|
||||
public List<ProbeDiagnostic> Diagnostics { get; private set; }
|
||||
public PlatformState Platform { get; private set; }
|
||||
public List<HostInstanceState> HostInstances { get; private set; }
|
||||
public List<SuspendedInstance> SuspendedInstances { get; private set; }
|
||||
public List<ApplicationRuntimeState> Applications { get; private set; }
|
||||
public List<SqlAccessState> SqlTargets { get; private set; }
|
||||
public string ExecutionIdentity { get; set; }
|
||||
public string NetworkIdentityHint { get; set; }
|
||||
public EventLogState EventLog { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Beschreibt Erreichbarkeit und Teilverfuegbarkeit der BizTalk-WMI-Daten.
|
||||
/// </summary>
|
||||
internal sealed class PlatformState
|
||||
{
|
||||
public bool WmiConnected { get; set; }
|
||||
public bool PlatformDataAvailable { get; set; }
|
||||
public bool HostInstancesDataAvailable { get; set; }
|
||||
public bool SuspendedInstancesDataAvailable { get; set; }
|
||||
public bool RuntimeArtifactsDataAvailable { get; set; }
|
||||
public string ServerName { get; set; }
|
||||
public string GroupName { get; set; }
|
||||
public string ManagementDbServer { get; set; }
|
||||
@@ -48,6 +129,23 @@ namespace BizTalkCheckmkPulse
|
||||
public string MessageBoxDbName { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ergebnis der integrierten Anmeldung an einer BizTalk-Datenbank.
|
||||
/// </summary>
|
||||
internal sealed class SqlAccessState
|
||||
{
|
||||
public string Role { get; set; }
|
||||
public string Server { get; set; }
|
||||
public string Database { get; set; }
|
||||
public bool Attempted { get; set; }
|
||||
public bool Available { get; set; }
|
||||
public DiagnosticCategory? FailureCategory { get; set; }
|
||||
public string Failure { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Laufzeitzustand einer BizTalk Host Instance.
|
||||
/// </summary>
|
||||
internal sealed class HostInstanceState
|
||||
{
|
||||
public string InstanceName { get; set; }
|
||||
@@ -56,6 +154,9 @@ namespace BizTalkCheckmkPulse
|
||||
public int ServiceState { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Relevante Daten einer suspendierten BizTalk Service Instance.
|
||||
/// </summary>
|
||||
internal sealed class SuspendedInstance
|
||||
{
|
||||
public string ApplicationName { get; set; }
|
||||
@@ -68,6 +169,9 @@ namespace BizTalkCheckmkPulse
|
||||
public SuspendedKind Kind { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aggregierte Zustandszaehler einer BizTalk-Anwendung.
|
||||
/// </summary>
|
||||
internal sealed class ApplicationRuntimeState
|
||||
{
|
||||
public string ApplicationName { get; set; }
|
||||
@@ -86,6 +190,9 @@ namespace BizTalkCheckmkPulse
|
||||
public int OrchestrationUnknown { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ergebnis der Auswertung des lokalen Windows Application Event Logs.
|
||||
/// </summary>
|
||||
internal sealed class EventLogState
|
||||
{
|
||||
public bool Available { get; set; }
|
||||
@@ -95,4 +202,3 @@ namespace BizTalkCheckmkPulse
|
||||
public string Failure { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,17 @@ using System.Linq;
|
||||
|
||||
namespace BizTalkCheckmkPulse
|
||||
{
|
||||
/// <summary>
|
||||
/// Enthaelt alle konfigurierbaren Schwellwerte und Probe-Optionen.
|
||||
/// </summary>
|
||||
internal sealed class MonitoringOptions
|
||||
{
|
||||
public string Server { get; set; }
|
||||
public string ServicePrefix { get; set; }
|
||||
public string EnvironmentName { get; set; }
|
||||
public int QueryTimeoutSeconds { get; set; }
|
||||
public bool ProbeSqlConnectivity { get; set; }
|
||||
public int SqlConnectionTimeoutSeconds { get; set; }
|
||||
public int WarnResumableThreshold { get; set; }
|
||||
public int CritNonResumableThreshold { get; set; }
|
||||
public int MaxSummaryItems { get; set; }
|
||||
@@ -24,12 +29,17 @@ namespace BizTalkCheckmkPulse
|
||||
public IReadOnlyList<string> EventLogSources { get; set; }
|
||||
public bool SelfTest { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Setzt konservative Standardwerte fuer BizTalk Server 2020.
|
||||
/// </summary>
|
||||
public MonitoringOptions()
|
||||
{
|
||||
Server = ".";
|
||||
ServicePrefix = "BizTalk";
|
||||
EnvironmentName = string.Empty;
|
||||
QueryTimeoutSeconds = 25;
|
||||
ProbeSqlConnectivity = true;
|
||||
SqlConnectionTimeoutSeconds = 5;
|
||||
WarnResumableThreshold = 1;
|
||||
CritNonResumableThreshold = 1;
|
||||
MaxSummaryItems = 12;
|
||||
@@ -42,6 +52,11 @@ namespace BizTalkCheckmkPulse
|
||||
EventLogSources = new[] { "BizTalk Server", "XLANG/s", "ENTSSO", "BizTalk Server Application", "BizTalk Server EDI" };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Erzeugt den stabilen Checkmk-Service-Namen mit optionalem Umgebungsnamen.
|
||||
/// </summary>
|
||||
/// <param name="suffix">Fachlicher Suffix des Services.</param>
|
||||
/// <returns>Vollstaendiger Service-Name fuer Checkmk.</returns>
|
||||
public string ServiceName(string suffix)
|
||||
{
|
||||
var prefix = string.IsNullOrWhiteSpace(EnvironmentName)
|
||||
@@ -50,6 +65,11 @@ namespace BizTalkCheckmkPulse
|
||||
return prefix.Trim() + " " + suffix;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Laedt Optionen aus App.config und ueberschreibt sie anschliessend mit Argumenten.
|
||||
/// </summary>
|
||||
/// <param name="args">Beim Programmstart uebergebene Kommandozeilenargumente.</param>
|
||||
/// <returns>Validierte Monitoring-Optionen.</returns>
|
||||
public static MonitoringOptions Load(string[] args)
|
||||
{
|
||||
var options = new MonitoringOptions();
|
||||
@@ -59,6 +79,8 @@ namespace BizTalkCheckmkPulse
|
||||
options.ServicePrefix = ReadString(settings, "ServicePrefix", options.ServicePrefix);
|
||||
options.EnvironmentName = ReadString(settings, "EnvironmentName", options.EnvironmentName);
|
||||
options.QueryTimeoutSeconds = ReadInt(settings, "QueryTimeoutSeconds", options.QueryTimeoutSeconds, 5, 120);
|
||||
options.ProbeSqlConnectivity = ReadBool(settings, "ProbeSqlConnectivity", options.ProbeSqlConnectivity);
|
||||
options.SqlConnectionTimeoutSeconds = ReadInt(settings, "SqlConnectionTimeoutSeconds", options.SqlConnectionTimeoutSeconds, 3, 60);
|
||||
options.WarnResumableThreshold = ReadInt(settings, "WarnResumableThreshold", options.WarnResumableThreshold, 0, 1000000);
|
||||
options.CritNonResumableThreshold = ReadInt(settings, "CritNonResumableThreshold", options.CritNonResumableThreshold, 0, 1000000);
|
||||
options.MaxSummaryItems = ReadInt(settings, "MaxSummaryItems", options.MaxSummaryItems, 1, 100);
|
||||
@@ -74,6 +96,11 @@ namespace BizTalkCheckmkPulse
|
||||
return options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wendet unterstuetzte Kommandozeilenargumente auf die Optionen an.
|
||||
/// </summary>
|
||||
/// <param name="options">Zu aktualisierende Optionen.</param>
|
||||
/// <param name="args">Kommandozeilenargumente.</param>
|
||||
private static void ApplyArguments(MonitoringOptions options, string[] args)
|
||||
{
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
@@ -99,15 +126,35 @@ namespace BizTalkCheckmkPulse
|
||||
{
|
||||
options.AlertOnArtifactRuntimeIssues = true;
|
||||
}
|
||||
else if (EqualsAny(arg, "--no-sql-probe", "/no-sql-probe"))
|
||||
{
|
||||
options.ProbeSqlConnectivity = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Liest einen getrimmten Konfigurationswert.
|
||||
/// </summary>
|
||||
/// <param name="settings">AppSettings-Sammlung.</param>
|
||||
/// <param name="key">Name des Konfigurationswertes.</param>
|
||||
/// <param name="fallback">Ersatzwert bei fehlendem Eintrag.</param>
|
||||
/// <returns>Konfigurierter oder ersetzter Textwert.</returns>
|
||||
private static string ReadString(System.Collections.Specialized.NameValueCollection settings, string key, string fallback)
|
||||
{
|
||||
var value = settings[key];
|
||||
return value == null ? fallback : value.Trim();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Liest eine Ganzzahl und begrenzt sie auf den erlaubten Wertebereich.
|
||||
/// </summary>
|
||||
/// <param name="settings">AppSettings-Sammlung.</param>
|
||||
/// <param name="key">Name des Konfigurationswertes.</param>
|
||||
/// <param name="fallback">Ersatzwert bei ungueltiger Eingabe.</param>
|
||||
/// <param name="min">Kleinster erlaubter Wert.</param>
|
||||
/// <param name="max">Groesster erlaubter Wert.</param>
|
||||
/// <returns>Validierter Zahlenwert.</returns>
|
||||
private static int ReadInt(System.Collections.Specialized.NameValueCollection settings, string key, int fallback, int min, int max)
|
||||
{
|
||||
int parsed;
|
||||
@@ -124,12 +171,24 @@ namespace BizTalkCheckmkPulse
|
||||
return parsed > max ? max : parsed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Liest einen booleschen Konfigurationswert.
|
||||
/// </summary>
|
||||
/// <param name="settings">AppSettings-Sammlung.</param>
|
||||
/// <param name="key">Name des Konfigurationswertes.</param>
|
||||
/// <param name="fallback">Ersatzwert bei ungueltiger Eingabe.</param>
|
||||
/// <returns>Konfigurierter oder ersetzter Wahrheitswert.</returns>
|
||||
private static bool ReadBool(System.Collections.Specialized.NameValueCollection settings, string key, bool fallback)
|
||||
{
|
||||
bool parsed;
|
||||
return bool.TryParse(settings[key], out parsed) ? parsed : fallback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Zerlegt die konfigurierten Event-Log-Quellen und entfernt Duplikate.
|
||||
/// </summary>
|
||||
/// <param name="value">Mit Pipe, Semikolon oder Komma getrennte Quellen.</param>
|
||||
/// <returns>Eindeutige Liste relevanter Event-Log-Quellen.</returns>
|
||||
private static IReadOnlyList<string> ReadSources(string value)
|
||||
{
|
||||
return (value ?? string.Empty)
|
||||
@@ -140,10 +199,15 @@ namespace BizTalkCheckmkPulse
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prueft einen Wert ohne Beachtung der Gross-/Kleinschreibung gegen Kandidaten.
|
||||
/// </summary>
|
||||
/// <param name="value">Zu pruefender Wert.</param>
|
||||
/// <param name="candidates">Erlaubte Vergleichswerte.</param>
|
||||
/// <returns><c>true</c>, wenn ein Kandidat passt.</returns>
|
||||
private static bool EqualsAny(string value, params string[] candidates)
|
||||
{
|
||||
return candidates.Any(x => string.Equals(value, x, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,16 @@ using System.Text;
|
||||
|
||||
namespace BizTalkCheckmkPulse
|
||||
{
|
||||
/// <summary>
|
||||
/// Einstiegspunkt des Checkmk Local Checks.
|
||||
/// </summary>
|
||||
internal static class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// Laedt die Konfiguration, fuehrt die Probes aus und schreibt Checkmk-Zeilen nach STDOUT.
|
||||
/// </summary>
|
||||
/// <param name="args">Kommandozeilenargumente wie <c>--self-test</c> oder <c>--environment</c>.</param>
|
||||
/// <returns>Immer 0, damit Diagnosefehler als UNKNOWN-Service statt als kaputte Agent-Sektion erscheinen.</returns>
|
||||
private static int Main(string[] args)
|
||||
{
|
||||
MonitoringOptions options = null;
|
||||
@@ -30,6 +38,10 @@ namespace BizTalkCheckmkPulse
|
||||
var wmiProbe = new WmiBizTalkProbe(options);
|
||||
wmiProbe.Query(result);
|
||||
|
||||
// Der SQL-Test verwendet bewusst dieselbe Windows-Identitaet wie der Checkmk-Agent.
|
||||
var sqlProbe = new SqlConnectivityProbe(options);
|
||||
sqlProbe.Query(result);
|
||||
|
||||
if (options.ProbeEventLog)
|
||||
{
|
||||
var eventLogProbe = new EventLogProbe(options);
|
||||
@@ -57,4 +69,3 @@ namespace BizTalkCheckmkPulse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
using System;
|
||||
using System.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using System.Security.Principal;
|
||||
|
||||
namespace BizTalkCheckmkPulse
|
||||
{
|
||||
/// <summary>
|
||||
/// Prueft mit der aktuellen Windows-Identitaet die Anmeldung an den von WMI
|
||||
/// ermittelten BizTalk Management- und MessageBox-Datenbanken.
|
||||
/// </summary>
|
||||
internal sealed class SqlConnectivityProbe
|
||||
{
|
||||
private readonly MonitoringOptions _options;
|
||||
|
||||
/// <summary>
|
||||
/// Initialisiert die SQL-Diagnose mit den Monitoring-Optionen.
|
||||
/// </summary>
|
||||
/// <param name="options">Konfiguration fuer Aktivierung und Timeout.</param>
|
||||
public SqlConnectivityProbe(MonitoringOptions options)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ermittelt die aktuelle Identitaet und prueft alle bekannten SQL-Ziele.
|
||||
/// </summary>
|
||||
/// <param name="result">Ergebnisobjekt, das um SQL-Zustaende und Diagnosen erweitert wird.</param>
|
||||
public void Query(ProbeResult result)
|
||||
{
|
||||
CaptureIdentity(result);
|
||||
if (!_options.ProbeSqlConnectivity)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AddTarget(result, "Management", result.Platform.ManagementDbServer, result.Platform.ManagementDbName);
|
||||
AddTarget(result, "MessageBox", result.Platform.MessageBoxDbServer, result.Platform.MessageBoxDbName);
|
||||
|
||||
if (!result.Platform.PlatformDataAvailable)
|
||||
{
|
||||
result.Diagnostics.Add(new ProbeDiagnostic
|
||||
{
|
||||
Area = DiagnosticArea.Sql,
|
||||
Category = DiagnosticCategory.Configuration,
|
||||
Component = "BizTalk database discovery",
|
||||
Required = true,
|
||||
Summary = "SQL-Zielermittlung ist unvollstaendig, weil nicht alle erforderlichen BizTalk-Plattformklassen gelesen wurden.",
|
||||
Action = "Zuerst den Service 'BizTalk Platform' sowie MSBTS_GroupSetting und MSBTS_MessageBoxSetting pruefen. Danach den Agent-Dump erneut ausfuehren."
|
||||
});
|
||||
}
|
||||
|
||||
if (result.SqlTargets.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var target in result.SqlTargets)
|
||||
{
|
||||
ProbeTarget(result, target);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Speichert die lokale Prozessidentitaet und einen Hinweis auf die Netzwerkidentitaet.
|
||||
/// </summary>
|
||||
/// <param name="result">Zu aktualisierendes Ergebnisobjekt.</param>
|
||||
private static void CaptureIdentity(ProbeResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var identity = WindowsIdentity.GetCurrent())
|
||||
{
|
||||
result.ExecutionIdentity = identity == null ? "unknown" : identity.Name;
|
||||
var isLocalSystem = identity != null
|
||||
&& identity.User != null
|
||||
&& identity.User.IsWellKnown(WellKnownSidType.LocalSystemSid);
|
||||
result.NetworkIdentityHint = isLocalSystem
|
||||
? "<DOMAIN>\\" + Environment.MachineName + "$"
|
||||
: result.ExecutionIdentity;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.ExecutionIdentity = "unknown";
|
||||
result.NetworkIdentityHint = "unknown";
|
||||
result.Diagnostics.Add(new ProbeDiagnostic
|
||||
{
|
||||
Area = DiagnosticArea.General,
|
||||
Category = DiagnosticCategory.Unexpected,
|
||||
Component = "Windows identity",
|
||||
Required = false,
|
||||
Summary = "Die Windows-Ausfuehrungsidentitaet konnte nicht bestimmt werden.",
|
||||
Action = "Agent-Dienstkonto mit 'sc.exe qc CheckMKService' beziehungsweise in services.msc pruefen.",
|
||||
TechnicalDetails = ex.GetType().Name + ": " + ex.Message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fuegt ein eindeutiges und vollstaendig beschriebenes SQL-Ziel hinzu.
|
||||
/// </summary>
|
||||
/// <param name="result">Ergebnisobjekt mit der SQL-Zielliste.</param>
|
||||
/// <param name="role">Fachliche Rolle der Datenbank.</param>
|
||||
/// <param name="server">SQL-Server oder Instanzname.</param>
|
||||
/// <param name="database">Name der BizTalk-Datenbank.</param>
|
||||
private static void AddTarget(ProbeResult result, string role, string server, string database)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(server) || string.IsNullOrWhiteSpace(database))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var existing = result.SqlTargets.FirstOrDefault(x =>
|
||||
string.Equals(x.Server, server, StringComparison.OrdinalIgnoreCase)
|
||||
&& string.Equals(x.Database, database, StringComparison.OrdinalIgnoreCase));
|
||||
if (existing != null)
|
||||
{
|
||||
existing.Role += "+" + role;
|
||||
return;
|
||||
}
|
||||
|
||||
result.SqlTargets.Add(new SqlAccessState
|
||||
{
|
||||
Role = role,
|
||||
Server = server.Trim(),
|
||||
Database = database.Trim()
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Oeffnet eine integrierte SQL-Verbindung und fuehrt eine harmlose Testabfrage aus.
|
||||
/// </summary>
|
||||
/// <param name="result">Ergebnisobjekt fuer eventuell entstehende Diagnosen.</param>
|
||||
/// <param name="target">Zu pruefendes SQL-Ziel.</param>
|
||||
private void ProbeTarget(ProbeResult result, SqlAccessState target)
|
||||
{
|
||||
target.Attempted = true;
|
||||
try
|
||||
{
|
||||
var builder = new SqlConnectionStringBuilder
|
||||
{
|
||||
DataSource = target.Server,
|
||||
InitialCatalog = target.Database,
|
||||
IntegratedSecurity = true,
|
||||
ConnectTimeout = _options.SqlConnectionTimeoutSeconds,
|
||||
ApplicationName = "BizTalkCheckmkPulse",
|
||||
Pooling = false,
|
||||
PersistSecurityInfo = false
|
||||
};
|
||||
|
||||
using (var connection = new SqlConnection(builder.ConnectionString))
|
||||
using (var command = new SqlCommand("SELECT 1", connection))
|
||||
{
|
||||
command.CommandTimeout = _options.SqlConnectionTimeoutSeconds;
|
||||
connection.Open();
|
||||
command.ExecuteScalar();
|
||||
}
|
||||
|
||||
target.Available = true;
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
var category = ClassifySqlException(ex);
|
||||
target.Available = false;
|
||||
target.FailureCategory = category;
|
||||
target.Failure = "SQL error " + ex.Number + ": " + ex.Message;
|
||||
result.Diagnostics.Add(new ProbeDiagnostic
|
||||
{
|
||||
Area = DiagnosticArea.Sql,
|
||||
Category = category,
|
||||
Component = target.Role + " " + target.Server + "\\" + target.Database,
|
||||
Required = true,
|
||||
Summary = BuildSqlSummary(category, result.NetworkIdentityHint),
|
||||
Action = BuildSqlAction(category, result.NetworkIdentityHint, target),
|
||||
TechnicalDetails = "SqlException Number=" + ex.Number + ", State=" + ex.State + ", Class=" + ex.Class + ": " + ex.Message
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
target.Available = false;
|
||||
target.FailureCategory = DiagnosticCategory.Unexpected;
|
||||
target.Failure = ex.GetType().Name + ": " + ex.Message;
|
||||
result.Diagnostics.Add(new ProbeDiagnostic
|
||||
{
|
||||
Area = DiagnosticArea.Sql,
|
||||
Category = DiagnosticCategory.Unexpected,
|
||||
Component = target.Role + " " + target.Server + "\\" + target.Database,
|
||||
Required = true,
|
||||
Summary = "Unerwarteter Fehler beim SQL-Zugriff.",
|
||||
Action = "Technische Details und Windows-/SQL-Event-Logs pruefen. Danach den Test im Agent-Dump wiederholen.",
|
||||
TechnicalDetails = ex.GetType().Name + ": " + ex.Message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ordnet bekannte SQL-Fehlernummern einer handlungsorientierten Kategorie zu.
|
||||
/// </summary>
|
||||
/// <param name="exception">Von System.Data.SqlClient gelieferter Fehler.</param>
|
||||
/// <returns>Diagnosekategorie fuer Berechtigung, Timeout oder Verbindung.</returns>
|
||||
private static DiagnosticCategory ClassifySqlException(SqlException exception)
|
||||
{
|
||||
var permissionNumbers = new[] { 229, 297, 4060, 916, 18452, 18456 };
|
||||
var connectivityNumbers = new[] { 2, 53, 64, 121, 233, 258, 10060, 10061, 11001 };
|
||||
if (exception.Errors.Cast<SqlError>().Any(x => permissionNumbers.Contains(x.Number)))
|
||||
{
|
||||
return DiagnosticCategory.Permission;
|
||||
}
|
||||
|
||||
var message = exception.Message ?? string.Empty;
|
||||
if (ContainsAny(message, "certificate", "SSL Provider", "TLS", "SSPI context", "target principal name"))
|
||||
{
|
||||
return DiagnosticCategory.Configuration;
|
||||
}
|
||||
|
||||
if (exception.Number == -2)
|
||||
{
|
||||
return DiagnosticCategory.Timeout;
|
||||
}
|
||||
|
||||
if (exception.Errors.Cast<SqlError>().Any(x => connectivityNumbers.Contains(x.Number)))
|
||||
{
|
||||
return DiagnosticCategory.Connectivity;
|
||||
}
|
||||
|
||||
if (ContainsAny(message, "login failed", "cannot open database", "permission", "denied", "not authorized"))
|
||||
{
|
||||
return DiagnosticCategory.Permission;
|
||||
}
|
||||
|
||||
return DiagnosticCategory.Provider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Erstellt eine kurze Fehlerursache fuer die Checkmk-Serviceausgabe.
|
||||
/// </summary>
|
||||
/// <param name="category">Klassifizierte SQL-Fehlerart.</param>
|
||||
/// <param name="networkIdentity">Vermutete Identitaet am entfernten SQL Server.</param>
|
||||
/// <returns>Menschenlesbare Fehlerzusammenfassung.</returns>
|
||||
private static string BuildSqlSummary(DiagnosticCategory category, string networkIdentity)
|
||||
{
|
||||
switch (category)
|
||||
{
|
||||
case DiagnosticCategory.Permission:
|
||||
return "SQL Server hat die integrierte Anmeldung abgelehnt. Erwartete Netzwerkidentitaet: " + networkIdentity + ".";
|
||||
case DiagnosticCategory.Timeout:
|
||||
return "SQL-Verbindungsaufbau oder Testabfrage hat das konfigurierte Zeitlimit ueberschritten.";
|
||||
case DiagnosticCategory.Connectivity:
|
||||
return "SQL Server oder die konfigurierte SQL-Instanz ist aus dem Checkmk-Agent-Kontext nicht erreichbar.";
|
||||
case DiagnosticCategory.Configuration:
|
||||
return "SQL-Verbindung scheitert an TLS-, Zertifikats-, SPN- oder SSPI-Konfiguration.";
|
||||
default:
|
||||
return "SQL Server hat beim Verbindungstest einen Providerfehler geliefert.";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Liefert eine konkrete, zur SQL-Fehlerkategorie passende Handlungsempfehlung.
|
||||
/// </summary>
|
||||
/// <param name="category">Klassifizierte SQL-Fehlerart.</param>
|
||||
/// <param name="networkIdentity">Vermutete Identitaet am entfernten SQL Server.</param>
|
||||
/// <param name="target">Betroffenes SQL-Ziel.</param>
|
||||
/// <returns>Empfohlener Diagnoseschritt.</returns>
|
||||
private static string BuildSqlAction(DiagnosticCategory category, string networkIdentity, SqlAccessState target)
|
||||
{
|
||||
switch (category)
|
||||
{
|
||||
case DiagnosticCategory.Permission:
|
||||
return networkIdentity + " in die konfigurierte BizTalk-Operator-Gruppe aufnehmen; keine direkten BizTalk-DB-Rollen vergeben. Danach Kerberos-Tickets erneuern und den Agent-Dump wiederholen.";
|
||||
case DiagnosticCategory.Timeout:
|
||||
return "Netzwerkpfad, DNS, SQL-Port, Firewall und Auslastung fuer " + target.Server + " pruefen; Timeout nur nach Ursachenanalyse erhoehen.";
|
||||
case DiagnosticCategory.Connectivity:
|
||||
return "Server-/Instanzname " + target.Server + ", DNS, SQL-Dienst, TCP-Protokoll und Firewall aus Sicht des BizTalk-Servers pruefen.";
|
||||
case DiagnosticCategory.Configuration:
|
||||
return "TLS-Zertifikatskette, SQL-Verschluesselung, SPN und Kerberos/SSPI gemeinsam mit SQL- und Windows-Administration pruefen; Verschluesselung nicht pauschal deaktivieren.";
|
||||
default:
|
||||
return "SQL-Fehlernummer mit der SQL-Administration pruefen und den Test gegen " + target.Server + "\\" + target.Database + " im gleichen Dienstkonto-Kontext wiederholen.";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sucht mehrere Teiltexte ohne Beachtung der Gross-/Kleinschreibung.
|
||||
/// </summary>
|
||||
/// <param name="value">Zu untersuchender Text.</param>
|
||||
/// <param name="candidates">Gesuchte Teiltexte.</param>
|
||||
/// <returns><c>true</c>, wenn mindestens ein Teiltext enthalten ist.</returns>
|
||||
private static bool ContainsAny(string value, params string[] candidates)
|
||||
{
|
||||
return candidates.Any(x => value.IndexOf(x, StringComparison.OrdinalIgnoreCase) >= 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,13 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BizTalkCheckmkPulse
|
||||
{
|
||||
/// <summary>
|
||||
/// Liest BizTalk Server 2020 ueber den lokalen Microsoft-WMI-Provider aus.
|
||||
/// </summary>
|
||||
internal sealed class WmiBizTalkProbe
|
||||
{
|
||||
private const string NamespacePath = "root\\MicrosoftBizTalkServer";
|
||||
@@ -18,13 +22,22 @@ namespace BizTalkCheckmkPulse
|
||||
private const int OrchestrationBound = 2;
|
||||
private const int OrchestrationStopped = 3;
|
||||
private const int OrchestrationStarted = 4;
|
||||
private const int EAccessDenied = unchecked((int)0x80070005);
|
||||
private readonly MonitoringOptions _options;
|
||||
|
||||
/// <summary>
|
||||
/// Initialisiert den WMI-Probe mit den konfigurierten Timeouts.
|
||||
/// </summary>
|
||||
/// <param name="options">Monitoring-Konfiguration.</param>
|
||||
public WmiBizTalkProbe(MonitoringOptions options)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verbindet sich mit dem BizTalk-WMI-Namespace und liest alle Datenbereiche.
|
||||
/// </summary>
|
||||
/// <param name="result">Ergebnisobjekt fuer Messwerte, Verfuegbarkeiten und Diagnosen.</param>
|
||||
public void Query(ProbeResult result)
|
||||
{
|
||||
var server = string.IsNullOrWhiteSpace(_options.Server) || _options.Server == "."
|
||||
@@ -32,37 +45,87 @@ namespace BizTalkCheckmkPulse
|
||||
: _options.Server.Trim();
|
||||
|
||||
result.Platform.ServerName = server;
|
||||
|
||||
var scope = new ManagementScope("\\\\" + server + "\\" + NamespacePath);
|
||||
scope.Connect();
|
||||
result.Platform.WmiConnected = true;
|
||||
|
||||
QueryPlatform(scope, result);
|
||||
QueryHostInstances(scope, result);
|
||||
var applicationIndex = BuildApplicationIndex(scope, result);
|
||||
QueryApplicationRuntime(scope, result);
|
||||
QuerySuspendedInstances(scope, result, applicationIndex);
|
||||
if (!TryConnect(scope, result))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
private void QueryPlatform(ManagementScope scope, ProbeResult result)
|
||||
result.Platform.WmiConnected = true;
|
||||
result.Platform.PlatformDataAvailable = QueryPlatform(scope, result);
|
||||
result.Platform.HostInstancesDataAvailable = QueryHostInstances(scope, result);
|
||||
var applicationIndex = BuildApplicationIndex(scope, result);
|
||||
result.Platform.RuntimeArtifactsDataAvailable = QueryApplicationRuntime(scope, result);
|
||||
result.Platform.SuspendedInstancesDataAvailable = QuerySuspendedInstances(scope, result, applicationIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Baut die WMI-Verbindung auf und wandelt Fehler in handlungsorientierte Diagnosen um.
|
||||
/// </summary>
|
||||
/// <param name="scope">Zu verbindender WMI-Scope.</param>
|
||||
/// <param name="result">Ergebnisobjekt fuer eine moegliche Diagnose.</param>
|
||||
/// <returns><c>true</c>, wenn der Namespace verbunden wurde.</returns>
|
||||
private static bool TryConnect(ManagementScope scope, ProbeResult result)
|
||||
{
|
||||
TryQuery(scope, result, "MSBTS_GroupSetting", "SELECT * FROM MSBTS_GroupSetting", item =>
|
||||
try
|
||||
{
|
||||
scope.Connect();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) when (IsExpectedWmiException(ex))
|
||||
{
|
||||
AddWmiDiagnostic(result, "WMI namespace connection", ex, true, true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Liest BizTalk-Gruppe sowie Management- und MessageBox-Datenbankziele.
|
||||
/// </summary>
|
||||
/// <param name="scope">Verbundener BizTalk-WMI-Scope.</param>
|
||||
/// <param name="result">Zu aktualisierendes Ergebnisobjekt.</param>
|
||||
/// <returns><c>true</c>, wenn beide Pflichtklassen gelesen wurden.</returns>
|
||||
private bool QueryPlatform(ManagementScope scope, ProbeResult result)
|
||||
{
|
||||
var groupFound = false;
|
||||
var groupAvailable = TryQuery(scope, result, "MSBTS_GroupSetting", "SELECT * FROM MSBTS_GroupSetting", item =>
|
||||
{
|
||||
groupFound = true;
|
||||
result.Platform.GroupName = FirstNonEmpty(WmiHelpers.GetString(item, "Name"), WmiHelpers.GetString(item, "MgmtDbName"));
|
||||
result.Platform.ManagementDbServer = FirstNonEmpty(WmiHelpers.GetString(item, "MgmtDbServerName"), WmiHelpers.GetString(item, "DBServerName"));
|
||||
result.Platform.ManagementDbName = FirstNonEmpty(WmiHelpers.GetString(item, "MgmtDbName"), WmiHelpers.GetString(item, "DatabaseName"));
|
||||
}, true);
|
||||
|
||||
TryQuery(scope, result, "MSBTS_MessageBoxSetting", "SELECT * FROM MSBTS_MessageBoxSetting", item =>
|
||||
var messageBoxFound = false;
|
||||
var messageBoxAvailable = TryQuery(scope, result, "MSBTS_MessageBoxSetting", "SELECT * FROM MSBTS_MessageBoxSetting", item =>
|
||||
{
|
||||
messageBoxFound = true;
|
||||
if (string.IsNullOrWhiteSpace(result.Platform.MessageBoxDbServer))
|
||||
{
|
||||
result.Platform.MessageBoxDbServer = FirstNonEmpty(WmiHelpers.GetString(item, "DBServerName"), WmiHelpers.GetString(item, "ServerName"));
|
||||
result.Platform.MessageBoxDbName = FirstNonEmpty(WmiHelpers.GetString(item, "DBName"), WmiHelpers.GetString(item, "DatabaseName"));
|
||||
}
|
||||
}, true);
|
||||
|
||||
if (groupAvailable && !groupFound)
|
||||
{
|
||||
AddMissingDataDiagnostic(result, "MSBTS_GroupSetting", "Keine BizTalk-Gruppe gefunden.");
|
||||
}
|
||||
|
||||
if (messageBoxAvailable && !messageBoxFound)
|
||||
{
|
||||
AddMissingDataDiagnostic(result, "MSBTS_MessageBoxSetting", "Keine BizTalk-MessageBox-Datenbank gefunden.");
|
||||
}
|
||||
|
||||
return groupAvailable && groupFound && messageBoxAvailable && messageBoxFound;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Erstellt einen Index, um suspendierte Instanzen BizTalk-Anwendungen zuzuordnen.
|
||||
/// </summary>
|
||||
/// <param name="scope">Verbundener BizTalk-WMI-Scope.</param>
|
||||
/// <param name="result">Ergebnisobjekt fuer optionale Diagnosen.</param>
|
||||
/// <returns>Index aus moeglichen Artefaktschluesseln und Anwendungsnamen.</returns>
|
||||
private Dictionary<string, string> BuildApplicationIndex(ManagementScope scope, ProbeResult result)
|
||||
{
|
||||
var index = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
@@ -73,9 +136,16 @@ namespace BizTalkCheckmkPulse
|
||||
return index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fuegt die Schluessel einer WMI-Artefaktklasse zum Anwendungsindex hinzu.
|
||||
/// </summary>
|
||||
/// <param name="scope">Verbundener BizTalk-WMI-Scope.</param>
|
||||
/// <param name="result">Ergebnisobjekt fuer optionale Diagnosen.</param>
|
||||
/// <param name="index">Zu erweiternder Anwendungsindex.</param>
|
||||
/// <param name="className">Name der auszulesenden WMI-Klasse.</param>
|
||||
private void AddArtifactIndex(ManagementScope scope, ProbeResult result, Dictionary<string, string> index, string className)
|
||||
{
|
||||
TryQuery(scope, result, className, "SELECT * FROM " + className, item =>
|
||||
TryQuery(scope, result, className + " application index", "SELECT * FROM " + className, item =>
|
||||
{
|
||||
var app = GetApplicationName(item);
|
||||
foreach (var key in CandidateKeys(item))
|
||||
@@ -88,9 +158,15 @@ namespace BizTalkCheckmkPulse
|
||||
}, false);
|
||||
}
|
||||
|
||||
private void QueryHostInstances(ManagementScope scope, ProbeResult result)
|
||||
/// <summary>
|
||||
/// Liest alle BizTalk Host Instances und deren Dienstzustand.
|
||||
/// </summary>
|
||||
/// <param name="scope">Verbundener BizTalk-WMI-Scope.</param>
|
||||
/// <param name="result">Zu aktualisierendes Ergebnisobjekt.</param>
|
||||
/// <returns><c>true</c>, wenn die Pflichtabfrage erfolgreich war.</returns>
|
||||
private bool QueryHostInstances(ManagementScope scope, ProbeResult result)
|
||||
{
|
||||
TryQuery(scope, result, "MSBTS_HostInstance", "SELECT * FROM MSBTS_HostInstance", item =>
|
||||
return TryQuery(scope, result, "MSBTS_HostInstance", "SELECT * FROM MSBTS_HostInstance", item =>
|
||||
{
|
||||
result.HostInstances.Add(new HostInstanceState
|
||||
{
|
||||
@@ -102,11 +178,17 @@ namespace BizTalkCheckmkPulse
|
||||
}, true);
|
||||
}
|
||||
|
||||
private void QueryApplicationRuntime(ManagementScope scope, ProbeResult result)
|
||||
/// <summary>
|
||||
/// Aggregiert Receive Locations, Send Ports und Orchestrations pro Anwendung.
|
||||
/// </summary>
|
||||
/// <param name="scope">Verbundener BizTalk-WMI-Scope.</param>
|
||||
/// <param name="result">Zu aktualisierendes Ergebnisobjekt.</param>
|
||||
/// <returns><c>true</c>, wenn alle drei Pflichtabfragen erfolgreich waren.</returns>
|
||||
private bool QueryApplicationRuntime(ManagementScope scope, ProbeResult result)
|
||||
{
|
||||
var apps = new Dictionary<string, ApplicationRuntimeState>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
TryQuery(scope, result, "MSBTS_ReceiveLocation", "SELECT * FROM MSBTS_ReceiveLocation", item =>
|
||||
var receiveLocationsAvailable = TryQuery(scope, result, "MSBTS_ReceiveLocation", "SELECT * FROM MSBTS_ReceiveLocation", item =>
|
||||
{
|
||||
var app = GetApplication(apps, GetApplicationName(item));
|
||||
app.ReceiveLocationTotal++;
|
||||
@@ -116,7 +198,7 @@ namespace BizTalkCheckmkPulse
|
||||
}
|
||||
}, true);
|
||||
|
||||
TryQuery(scope, result, "MSBTS_SendPort", "SELECT * FROM MSBTS_SendPort", item =>
|
||||
var sendPortsAvailable = TryQuery(scope, result, "MSBTS_SendPort", "SELECT * FROM MSBTS_SendPort", item =>
|
||||
{
|
||||
var app = GetApplication(apps, GetApplicationName(item));
|
||||
app.SendPortTotal++;
|
||||
@@ -137,7 +219,7 @@ namespace BizTalkCheckmkPulse
|
||||
}
|
||||
}, true);
|
||||
|
||||
TryQuery(scope, result, "MSBTS_Orchestration", "SELECT * FROM MSBTS_Orchestration", item =>
|
||||
var orchestrationsAvailable = TryQuery(scope, result, "MSBTS_Orchestration", "SELECT * FROM MSBTS_Orchestration", item =>
|
||||
{
|
||||
var app = GetApplication(apps, GetApplicationName(item));
|
||||
app.OrchestrationTotal++;
|
||||
@@ -162,11 +244,19 @@ namespace BizTalkCheckmkPulse
|
||||
}, true);
|
||||
|
||||
result.Applications.AddRange(apps.Values.OrderBy(x => x.ApplicationName, StringComparer.OrdinalIgnoreCase));
|
||||
return receiveLocationsAvailable && sendPortsAvailable && orchestrationsAvailable;
|
||||
}
|
||||
|
||||
private void QuerySuspendedInstances(ManagementScope scope, ProbeResult result, Dictionary<string, string> applicationIndex)
|
||||
/// <summary>
|
||||
/// Liest ausschliesslich fortsetzbar und nicht fortsetzbar suspendierte Instanzen.
|
||||
/// </summary>
|
||||
/// <param name="scope">Verbundener BizTalk-WMI-Scope.</param>
|
||||
/// <param name="result">Zu aktualisierendes Ergebnisobjekt.</param>
|
||||
/// <param name="applicationIndex">Index fuer die Zuordnung zur BizTalk-Anwendung.</param>
|
||||
/// <returns><c>true</c>, wenn die Pflichtabfrage erfolgreich war.</returns>
|
||||
private bool QuerySuspendedInstances(ManagementScope scope, ProbeResult result, Dictionary<string, string> applicationIndex)
|
||||
{
|
||||
TryQuery(scope, result, "MSBTS_ServiceInstance", "SELECT * FROM MSBTS_ServiceInstance WHERE ServiceStatus = 4 OR ServiceStatus = 32", item =>
|
||||
return TryQuery(scope, result, "MSBTS_ServiceInstance", "SELECT * FROM MSBTS_ServiceInstance WHERE ServiceStatus = 4 OR ServiceStatus = 32", item =>
|
||||
{
|
||||
var serviceStatus = WmiHelpers.GetInt32(item, "ServiceStatus", 0);
|
||||
result.SuspendedInstances.Add(new SuspendedInstance
|
||||
@@ -183,26 +273,36 @@ namespace BizTalkCheckmkPulse
|
||||
}, true);
|
||||
}
|
||||
|
||||
private void TryQuery(ManagementScope scope, ProbeResult result, string label, string queryText, Action<ManagementObject> action, bool required)
|
||||
/// <summary>
|
||||
/// Fuehrt eine WQL-Abfrage aus und erfasst erwartbare Providerfehler strukturiert.
|
||||
/// </summary>
|
||||
/// <param name="scope">Verbundener BizTalk-WMI-Scope.</param>
|
||||
/// <param name="result">Ergebnisobjekt fuer Diagnosen.</param>
|
||||
/// <param name="label">Menschenlesbare Bezeichnung der Abfrage.</param>
|
||||
/// <param name="queryText">Auszufuehrende WQL-Abfrage.</param>
|
||||
/// <param name="action">Verarbeitung fuer jedes gefundene WMI-Objekt.</param>
|
||||
/// <param name="required">Kennzeichen, ob die Abfrage fuer einen stabilen Service erforderlich ist.</param>
|
||||
/// <returns><c>true</c>, wenn die Abfrage vollstaendig ausgefuehrt wurde.</returns>
|
||||
private bool TryQuery(ManagementScope scope, ProbeResult result, string label, string queryText, Action<ManagementObject> action, bool required)
|
||||
{
|
||||
try
|
||||
{
|
||||
ForEachObject(scope, queryText, action);
|
||||
return true;
|
||||
}
|
||||
catch (ManagementException ex)
|
||||
catch (Exception ex) when (IsExpectedWmiException(ex))
|
||||
{
|
||||
AddDiagnostic(result, label, ex.Message, required);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
AddDiagnostic(result, label, ex.Message, required);
|
||||
}
|
||||
catch (System.Runtime.InteropServices.COMException ex)
|
||||
{
|
||||
AddDiagnostic(result, label, ex.Message, required);
|
||||
AddWmiDiagnostic(result, label, ex, required, false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumeriert eine WQL-Abfrage mit festem Timeout und gibt jedes Objekt weiter.
|
||||
/// </summary>
|
||||
/// <param name="scope">Verbundener BizTalk-WMI-Scope.</param>
|
||||
/// <param name="queryText">Auszufuehrende WQL-Abfrage.</param>
|
||||
/// <param name="action">Verarbeitung fuer jedes gefundene Objekt.</param>
|
||||
private void ForEachObject(ManagementScope scope, string queryText, Action<ManagementObject> action)
|
||||
{
|
||||
var queryOptions = new EnumerationOptions
|
||||
@@ -225,6 +325,12 @@ namespace BizTalkCheckmkPulse
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Liefert eine vorhandene oder neu angelegte Aggregation fuer eine Anwendung.
|
||||
/// </summary>
|
||||
/// <param name="apps">Aggregation nach Anwendungsname.</param>
|
||||
/// <param name="applicationName">Zu suchender Anwendungsname.</param>
|
||||
/// <returns>Aggregation der angeforderten Anwendung.</returns>
|
||||
private static ApplicationRuntimeState GetApplication(Dictionary<string, ApplicationRuntimeState> apps, string applicationName)
|
||||
{
|
||||
var name = string.IsNullOrWhiteSpace(applicationName) ? UnknownApplication : applicationName.Trim();
|
||||
@@ -238,6 +344,12 @@ namespace BizTalkCheckmkPulse
|
||||
return app;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ermittelt den Anwendungsnamen direkt oder ueber den Artefaktindex.
|
||||
/// </summary>
|
||||
/// <param name="item">Suspendierte WMI-Instanz.</param>
|
||||
/// <param name="applicationIndex">Index bekannter Artefaktschluessel.</param>
|
||||
/// <returns>Ermittelter Anwendungsname oder <c>(unknown)</c>.</returns>
|
||||
private static string ResolveApplication(ManagementBaseObject item, Dictionary<string, string> applicationIndex)
|
||||
{
|
||||
var direct = GetApplicationName(item);
|
||||
@@ -258,6 +370,11 @@ namespace BizTalkCheckmkPulse
|
||||
return UnknownApplication;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Liefert moegliche Schluessel fuer die Zuordnung eines WMI-Objekts.
|
||||
/// </summary>
|
||||
/// <param name="item">Auszuwertendes WMI-Objekt.</param>
|
||||
/// <returns>Vorhandene oder leere Kandidatenwerte.</returns>
|
||||
private static IEnumerable<string> CandidateKeys(ManagementBaseObject item)
|
||||
{
|
||||
yield return WmiHelpers.GetString(item, "ApplicationName");
|
||||
@@ -266,6 +383,11 @@ namespace BizTalkCheckmkPulse
|
||||
yield return WmiHelpers.GetString(item, "HostName");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Liest den Anwendungsnamen aus den je Klasse unterschiedlichen Properties.
|
||||
/// </summary>
|
||||
/// <param name="item">Auszuwertendes WMI-Objekt.</param>
|
||||
/// <returns>Anwendungsname oder <c>(unknown)</c>.</returns>
|
||||
private static string GetApplicationName(ManagementBaseObject item)
|
||||
{
|
||||
return FirstNonEmpty(
|
||||
@@ -274,15 +396,167 @@ namespace BizTalkCheckmkPulse
|
||||
WmiHelpers.GetString(item, "BizTalkApplication")) ?? UnknownApplication;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Liefert den ersten nicht leeren Textwert.
|
||||
/// </summary>
|
||||
/// <param name="values">Zu pruefende Werte in Prioritaetsreihenfolge.</param>
|
||||
/// <returns>Erster nicht leerer Wert oder <c>null</c>.</returns>
|
||||
private static string FirstNonEmpty(params string[] values)
|
||||
{
|
||||
return values == null ? null : values.FirstOrDefault(x => !string.IsNullOrWhiteSpace(x));
|
||||
}
|
||||
|
||||
private static void AddDiagnostic(ProbeResult result, string label, string message, bool required)
|
||||
/// <summary>
|
||||
/// Prueft, ob eine Exception aus dem erwarteten WMI-/COM-Fehlerbereich stammt.
|
||||
/// </summary>
|
||||
/// <param name="exception">Zu klassifizierende Exception.</param>
|
||||
/// <returns><c>true</c> fuer behandelbare WMI-, Rechte- und COM-Fehler.</returns>
|
||||
private static bool IsExpectedWmiException(Exception exception)
|
||||
{
|
||||
result.Diagnostics.Add((required ? "Required" : "Optional") + " WMI query " + label + " failed: " + message);
|
||||
return exception is ManagementException
|
||||
|| exception is UnauthorizedAccessException
|
||||
|| exception is COMException;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Erzeugt aus einer WMI-Exception eine strukturierte Diagnose.
|
||||
/// </summary>
|
||||
/// <param name="result">Ergebnisobjekt fuer die Diagnose.</param>
|
||||
/// <param name="component">Betroffener Namespace oder Klassenname.</param>
|
||||
/// <param name="exception">Ausgeloeste Exception.</param>
|
||||
/// <param name="required">Kennzeichen fuer eine Pflichtabfrage.</param>
|
||||
/// <param name="connectionFailure">Kennzeichen fuer Fehler beim Namespace-Verbindungsaufbau.</param>
|
||||
private static void AddWmiDiagnostic(ProbeResult result, string component, Exception exception, bool required, bool connectionFailure)
|
||||
{
|
||||
var category = ClassifyWmiException(exception);
|
||||
result.Diagnostics.Add(new ProbeDiagnostic
|
||||
{
|
||||
Area = DiagnosticArea.Wmi,
|
||||
Category = category,
|
||||
Component = component,
|
||||
Required = required,
|
||||
Summary = BuildWmiSummary(category, component, connectionFailure),
|
||||
Action = BuildWmiAction(category, connectionFailure),
|
||||
TechnicalDetails = exception.GetType().Name + " HRESULT=0x" + exception.HResult.ToString("X8") + ": " + exception.Message
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Erzeugt eine Konfigurationsdiagnose, wenn eine erfolgreiche Pflichtabfrage keine Instanz liefert.
|
||||
/// </summary>
|
||||
/// <param name="result">Ergebnisobjekt fuer die Diagnose.</param>
|
||||
/// <param name="component">Betroffene WMI-Klasse.</param>
|
||||
/// <param name="summary">Beschreibung der fehlenden BizTalk-Konfiguration.</param>
|
||||
private static void AddMissingDataDiagnostic(ProbeResult result, string component, string summary)
|
||||
{
|
||||
result.Diagnostics.Add(new ProbeDiagnostic
|
||||
{
|
||||
Area = DiagnosticArea.Wmi,
|
||||
Category = DiagnosticCategory.Configuration,
|
||||
Component = component,
|
||||
Required = true,
|
||||
Summary = summary,
|
||||
Action = "BizTalk-Gruppenkonfiguration und WMI-Provider pruefen. Danach den Agent-Dump erneut ausfuehren."
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Klassifiziert WMI-Fehler anhand von Exception-Typ, Status und HRESULT.
|
||||
/// </summary>
|
||||
/// <param name="exception">Zu klassifizierende Exception.</param>
|
||||
/// <returns>Handlungsorientierte Diagnosekategorie.</returns>
|
||||
private static DiagnosticCategory ClassifyWmiException(Exception exception)
|
||||
{
|
||||
var managementException = exception as ManagementException;
|
||||
var comException = exception as COMException;
|
||||
if (exception is UnauthorizedAccessException
|
||||
|| (managementException != null && managementException.ErrorCode == ManagementStatus.AccessDenied)
|
||||
|| (comException != null && comException.ErrorCode == EAccessDenied))
|
||||
{
|
||||
return DiagnosticCategory.Permission;
|
||||
}
|
||||
|
||||
if (managementException != null
|
||||
&& (managementException.ErrorCode == ManagementStatus.InvalidNamespace
|
||||
|| managementException.ErrorCode == ManagementStatus.InvalidClass))
|
||||
{
|
||||
return DiagnosticCategory.Configuration;
|
||||
}
|
||||
|
||||
var message = exception.Message ?? string.Empty;
|
||||
if (ContainsAny(message, "timed out", "timeout", "Zeitlimit"))
|
||||
{
|
||||
return DiagnosticCategory.Timeout;
|
||||
}
|
||||
|
||||
if (ContainsAny(message, "RPC server", "network path", "unavailable", "not available"))
|
||||
{
|
||||
return DiagnosticCategory.Connectivity;
|
||||
}
|
||||
|
||||
return DiagnosticCategory.Provider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Erstellt eine kurze Fehlerursache fuer einen WMI-Fehler.
|
||||
/// </summary>
|
||||
/// <param name="category">Klassifizierte Fehlerart.</param>
|
||||
/// <param name="component">Betroffene Komponente.</param>
|
||||
/// <param name="connectionFailure">Kennzeichen fuer den Namespace-Verbindungsaufbau.</param>
|
||||
/// <returns>Menschenlesbare Fehlerzusammenfassung.</returns>
|
||||
private static string BuildWmiSummary(DiagnosticCategory category, string component, bool connectionFailure)
|
||||
{
|
||||
switch (category)
|
||||
{
|
||||
case DiagnosticCategory.Permission:
|
||||
return "Zugriff auf " + component + " wurde im LocalSystem-Kontext verweigert.";
|
||||
case DiagnosticCategory.Configuration:
|
||||
return connectionFailure
|
||||
? "BizTalk-WMI-Namespace ist nicht vorhanden oder nicht korrekt registriert."
|
||||
: "BizTalk-WMI-Klasse " + component + " ist nicht vorhanden oder nicht korrekt registriert.";
|
||||
case DiagnosticCategory.Timeout:
|
||||
return "WMI-Abfrage fuer " + component + " hat das konfigurierte Zeitlimit ueberschritten.";
|
||||
case DiagnosticCategory.Connectivity:
|
||||
return "WMI-/RPC-Verbindung zu " + component + " konnte nicht hergestellt werden.";
|
||||
default:
|
||||
return "BizTalk-WMI-Provider konnte " + component + " nicht auslesen.";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Liefert eine konkrete Handlungsempfehlung fuer einen WMI-Fehler.
|
||||
/// </summary>
|
||||
/// <param name="category">Klassifizierte Fehlerart.</param>
|
||||
/// <param name="connectionFailure">Kennzeichen fuer den Namespace-Verbindungsaufbau.</param>
|
||||
/// <returns>Empfohlener Diagnoseschritt.</returns>
|
||||
private static string BuildWmiAction(DiagnosticCategory category, bool connectionFailure)
|
||||
{
|
||||
switch (category)
|
||||
{
|
||||
case DiagnosticCategory.Permission:
|
||||
return connectionFailure
|
||||
? "Namespace-ACL fuer root\\MicrosoftBizTalkServer gezielt pruefen. LocalSystem benoetigt lokalen Lesezugriff; keine pauschalen WMI-Rechte vergeben."
|
||||
: "Agent-Dump pruefen und Computerkonto <DOMAIN>\\" + Environment.MachineName + "$ zunaechst der konfigurierten BizTalk-Operator-Gruppe zuordnen.";
|
||||
case DiagnosticCategory.Configuration:
|
||||
return "BizTalk-WMI-Provider/Namespace auf dem BizTalk-Server pruefen und gegebenenfalls mit dem BizTalk-Setup reparieren.";
|
||||
case DiagnosticCategory.Timeout:
|
||||
return "WMI-Dienst, BizTalk-/SQL-Auslastung und Event Logs pruefen; Timeout erst danach erhoehen.";
|
||||
case DiagnosticCategory.Connectivity:
|
||||
return "Bei lokalem Zugriff WMI-Dienst und BizTalk-WMI-Provider pruefen; bei konfiguriertem Remote-WMI zusaetzlich RPC, DNS und Firewall pruefen.";
|
||||
default:
|
||||
return "Technische Details sowie Windows Application Log und BizTalk-Ereignisse pruefen. Betroffene WMI-Klasse separat testen.";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sucht mehrere Teiltexte ohne Beachtung der Gross-/Kleinschreibung.
|
||||
/// </summary>
|
||||
/// <param name="value">Zu untersuchender Text.</param>
|
||||
/// <param name="candidates">Gesuchte Teiltexte.</param>
|
||||
/// <returns><c>true</c>, wenn mindestens ein Teiltext enthalten ist.</returns>
|
||||
private static bool ContainsAny(string value, params string[] candidates)
|
||||
{
|
||||
return candidates.Any(x => value.IndexOf(x, StringComparison.OrdinalIgnoreCase) >= 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,17 @@ using System.Management;
|
||||
|
||||
namespace BizTalkCheckmkPulse
|
||||
{
|
||||
/// <summary>
|
||||
/// Konvertiert optionale WMI-Properties defensiv in .NET-Typen.
|
||||
/// </summary>
|
||||
internal static class WmiHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Liest eine WMI-Property als getrimmten Text.
|
||||
/// </summary>
|
||||
/// <param name="item">WMI-Objekt mit der Property.</param>
|
||||
/// <param name="propertyName">Name der Property.</param>
|
||||
/// <returns>Textwert oder leerer Text bei fehlender Property.</returns>
|
||||
public static string GetString(ManagementBaseObject item, string propertyName)
|
||||
{
|
||||
if (item == null || string.IsNullOrWhiteSpace(propertyName))
|
||||
@@ -24,6 +33,13 @@ namespace BizTalkCheckmkPulse
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Liest eine WMI-Property als Ganzzahl.
|
||||
/// </summary>
|
||||
/// <param name="item">WMI-Objekt mit der Property.</param>
|
||||
/// <param name="propertyName">Name der Property.</param>
|
||||
/// <param name="fallback">Ersatzwert bei fehlender oder ungueltiger Property.</param>
|
||||
/// <returns>Konvertierter oder ersetzter Zahlenwert.</returns>
|
||||
public static int GetInt32(ManagementBaseObject item, string propertyName, int fallback)
|
||||
{
|
||||
var value = GetString(item, propertyName);
|
||||
@@ -31,6 +47,13 @@ namespace BizTalkCheckmkPulse
|
||||
return int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out parsed) ? parsed : fallback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Liest eine WMI-Property als booleschen Wert.
|
||||
/// </summary>
|
||||
/// <param name="item">WMI-Objekt mit der Property.</param>
|
||||
/// <param name="propertyName">Name der Property.</param>
|
||||
/// <param name="fallback">Ersatzwert bei fehlender oder ungueltiger Property.</param>
|
||||
/// <returns>Konvertierter oder ersetzter Wahrheitswert.</returns>
|
||||
public static bool GetBoolean(ManagementBaseObject item, string propertyName, bool fallback)
|
||||
{
|
||||
var value = GetString(item, propertyName);
|
||||
@@ -38,6 +61,12 @@ namespace BizTalkCheckmkPulse
|
||||
return bool.TryParse(value, out parsed) ? parsed : fallback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Konvertiert einen DMTF-Zeitstempel aus einer WMI-Property.
|
||||
/// </summary>
|
||||
/// <param name="item">WMI-Objekt mit der Property.</param>
|
||||
/// <param name="propertyName">Name der Zeitstempel-Property.</param>
|
||||
/// <returns>Zeitpunkt oder <c>null</c> bei fehlendem beziehungsweise ungueltigem Wert.</returns>
|
||||
public static DateTime? GetDmtfDateTime(ManagementBaseObject item, string propertyName)
|
||||
{
|
||||
var value = GetString(item, propertyName);
|
||||
@@ -57,4 +86,3 @@ namespace BizTalkCheckmkPulse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user