Compare commits

..
2 Commits
Author SHA1 Message Date
admin 78e8881d38 Add WMI and SQL permission diagnostics 2026-07-22 15:26:51 +02:00
admin 31222ad28f Document BizTalk monitoring permissions 2026-07-22 15:08:47 +02:00
16 changed files with 1432 additions and 87 deletions
+115 -10
View File
@@ -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.
+100
View File
@@ -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 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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.
```
+86
View File
@@ -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`
+5 -3
View File
@@ -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>
+229 -21
View File
@@ -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))
+16 -1
View File
@@ -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
}
}
}
+109 -3
View File
@@ -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; }
}
}
+65 -1
View File
@@ -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));
}
}
}
+12 -1
View File
@@ -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);
}
}
}
+309 -35
View File
@@ -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);
}
}
}
+29 -1
View File
@@ -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
}
}
}