Harden BizTalk WMI monitoring

This commit is contained in:
2026-07-29 09:01:39 +02:00
parent 78e8881d38
commit 8d2934e343
17 changed files with 427 additions and 88 deletions
+8 -3
View File
@@ -1,9 +1,11 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
# Visual Studio Version 16
VisualStudioVersion = 16.0.31624.102
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizTalkCheckmkPulse", "src\BizTalkCheckmkPulse\BizTalkCheckmkPulse.csproj", "{A4D4D050-9EA7-4A71-B510-7D9D699B9F38}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizTalkCheckmkPulse.Tests", "tests\BizTalkCheckmkPulse.Tests\BizTalkCheckmkPulse.Tests.csproj", "{E219C45A-CC95-4F45-9B6F-8244CBAEE35A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -14,9 +16,12 @@ Global
{A4D4D050-9EA7-4A71-B510-7D9D699B9F38}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A4D4D050-9EA7-4A71-B510-7D9D699B9F38}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A4D4D050-9EA7-4A71-B510-7D9D699B9F38}.Release|Any CPU.Build.0 = Release|Any CPU
{E219C45A-CC95-4F45-9B6F-8244CBAEE35A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E219C45A-CC95-4F45-9B6F-8244CBAEE35A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E219C45A-CC95-4F45-9B6F-8244CBAEE35A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E219C45A-CC95-4F45-9B6F-8244CBAEE35A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
+16 -9
View File
@@ -100,7 +100,7 @@ BizTalkCheckmkPulse.exe
Checkmk Local Check Zeilen nach STDOUT
```
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.
Der `.cmd`-Wrapper liefert auch dann fuer alle sechs stabilen Services gueltige `UNKNOWN`-Zeilen mit Massnahme, wenn die EXE fehlt oder bereits der Prozessstart mit einem Exitcode fehlschlaegt. Die EXE selbst faengt Laufzeitfehler ab und schreibt ebenfalls fuer alle stabilen Services `UNKNOWN`, damit unvollstaendige Deployments oder WMI-Probleme in Checkmk sichtbar bleiben.
## Datenquellen
@@ -116,14 +116,15 @@ Genutzte Klassen:
| Klasse | Zweck |
| --- | --- |
| `MSBTS_GroupSetting` | BizTalk-Gruppe und Management-DB-Hinweise. |
| `MSBTS_MessageBoxSetting` | MessageBox-DB-Hinweise. |
| `MSBTS_HostInstance` | Host-Instance-Zustand. |
| `MSBTS_GroupSetting` | BizTalk-Gruppe, Management-DB und Master-MessageBox (`SubscriptionDB*`). |
| `MSBTS_HostInstance` | Host-Instance-Zustand; Ergebnis wird clientseitig auf den ueberwachten Server begrenzt. |
| `MSBTS_ServiceInstance` | Suspended service instances. |
| `MSBTS_ReceiveLocation` | Receive-Location-Zustand. |
| `MSBTS_SendPort` | Send-Port-Zustand. |
| `MSBTS_Orchestration` | Orchestration-Zustand. |
| `MSBTS_ReceivePort` | Application-Mapping fuer Details. |
| `MSBTS_ReceivePort` | Optionales Best-Effort-Application-Mapping, nur wenn per-Application-Services aktiviert sind. |
Die Plattformabfrage verwendet absichtlich keine vermeintliche Klasse `MSBTS_MessageBoxSetting`: Sie ist nicht Bestandteil des dokumentierten BizTalk-WMI-Schemas. Auch die vorhandene Klasse `MSBTS_MsgBoxSetting` ist fuer die Zielermittlung nicht erforderlich. `MSBTS_GroupSetting` liefert mit `SubscriptionDBServerName` und `SubscriptionDBName` bereits das Ziel der Master-MessageBox. Dadurch entfallen eine Providerabfrage und eine unnoetige Berechtigungs-/Schemaschnittstelle.
### Windows Application Event Log
@@ -139,7 +140,7 @@ 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.
Nach erfolgreicher oder teilweise erfolgreicher Plattformabfrage uebernimmt der SQL-Probe die per `MSBTS_GroupSetting` ermittelten Management- und Master-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.
@@ -200,13 +201,14 @@ WMI-Verbindungsaufbau und jede erforderliche WMI-Klasse werden separat bewertet.
| `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. |
| `Wmi/Configuration` | BizTalk-WMI-Namespace fehlt oder Plattformdaten sind unvollstaendig. | Provider, Namespace und BizTalk-Konfiguration pruefen. |
| `Wmi/Schema` | `InvalidClass` oder `InvalidQuery`; Klasse beziehungsweise WQL passt nicht zum installierten Provider. | Klasse/Properties gegen das BizTalk-WMI-Schema pruefen; keine Rechteerhoehung vornehmen. |
| `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.
Jede Diagnose enthaelt Bereich/Kategorie, betroffene Komponente, eine kurze Ursache, `Massnahme:` und `Technik:` mit Exception-Typ, HRESULT oder SQL-Fehlernummer. Bei WMI-Abfragefehlern werden zusaetzlich WQL und Laufzeit bis zum Fehler ausgegeben. 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.
@@ -223,9 +225,13 @@ Quellen:
Das Plugin ist bewusst defensiv gebaut:
- Der dependency-freie Regressionstest laeuft mit dem gleichen .NET-Framework-/MSBuild-Baseline wie die Anwendung.
- 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.
- Management- und Master-MessageBox-Datenbank werden aus einer dokumentierten WMI-Klasse ermittelt und mit der echten Agent-Identitaet getestet.
- `InvalidClass`/`InvalidQuery` werden als Schemafehler statt als Berechtigungsproblem ausgewiesen.
- Host Instances werden ohne namensabhaengigen WQL-Filter clientseitig auf den ueberwachten Server begrenzt.
- Bereits fuer Runtime-Zustaende gelesene Artefakte werden fuer das Best-Effort-Application-Mapping wiederverwendet; doppelte WMI-Abfragen entfallen.
- SQL-Fehlernummern werden in Berechtigung, Erreichbarkeit, Timeout oder Providerfehler klassifiziert.
- Fehlgeschlagene Pflichtabfragen erzeugen `UNKNOWN` statt irrefuehrender Nullwerte.
- Optionale WMI-Klassen erzeugen Diagnosehinweise statt Totalabbruch.
@@ -338,6 +344,7 @@ Der Checkmk Windows Agent laeuft normalerweise als LocalSystem. Daher immer auch
Die Diagnose im Service-Text nach `Massnahme:` abarbeiten. Wichtige Kategorien:
- `Wmi/Permission`: lokale Namespace-ACL oder BizTalk-Operator-Zuordnung pruefen.
- `Wmi/Schema`: Klasse/WQL gegen das installierte BizTalk-Schema pruefen; `InvalidClass` ist nicht durch zusaetzliche Rechte loesbar.
- `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.
+19 -4
View File
@@ -4,6 +4,8 @@
Der Ansatz ist fuer Umgebungen wie `ACC`, `DEV`, `TST` und `PRD` gedacht, in denen pro Umgebung ein BizTalk Server 2020 und ein SQL Server betrieben werden. SQL Server bleibt beim mitgelieferten Checkmk-MSSQL-Plugin; dieses Projekt ergaenzt die BizTalk-spezifische Sicht.
Die Plattform- und SQL-Zielermittlung verwendet ausschliesslich die dokumentierte WMI-Klasse `MSBTS_GroupSetting`. Management-Datenbank und Master-MessageBox werden ueber `MgmtDbServerName`/`MgmtDbName` sowie `SubscriptionDBServerName`/`SubscriptionDBName` gelesen. Eine Klasse `MSBTS_MessageBoxSetting` gehoert nicht zum BizTalk-WMI-Schema und wird bewusst nicht abgefragt.
## Warum Local Check statt serverseitigem Check-Plugin?
Checkmk 2.4 kann Windows-Agent-Plugins und Local Checks direkt ausfuehren. Fuer diese Umgebung ist ein C#/.NET-Framework-Programm mit `.cmd`-Wrapper die robusteste Variante:
@@ -34,7 +36,7 @@ Beispielausgaben stehen in [docs/ExampleOutput.md](docs/ExampleOutput.md).
Voraussetzungen auf einem Windows-Build-Host:
- Visual Studio 2022 Build Tools oder Visual Studio
- Visual Studio 2019/2022 Build Tools oder Visual Studio
- MSBuild im `PATH`
- .NET Framework 4.7.2 Developer Pack
@@ -44,6 +46,12 @@ Build:
scripts\build-release.cmd
```
Build plus dependency-free regression tests:
```cmd
scripts\test-release.cmd
```
Deployment-Paket erstellen:
```cmd
@@ -66,6 +74,8 @@ Validierung nach dem Build:
artifacts\BizTalkCheckmkPulse-deploy\biztalk_checkmk_pulse.cmd --self-test
```
Der Self-Test fuehrt keine WMI-, SQL- oder Event-Log-Abfrage aus und muss genau sechs `OK`-Zeilen liefern. Der Test-Runner prueft zusaetzlich UNKNOWN-Fallbacks, dynamische Application-Services, FQDN-/Kurznamensvergleich ohne WQL-Namensfilter und den dokumentierten `MSBTS_GroupSetting`-Queryvertrag. Die zusaetzliche lokale Verifikation mit Mono/MSBuild 16 prueft Build und Ausgabeformat, ersetzt aber nicht den Windows-/BizTalk-Test.
## Installation auf dem BizTalk-Server
Kopiere den Inhalt von `artifacts\BizTalkCheckmkPulse-deploy` nach:
@@ -113,7 +123,7 @@ 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. |
| `ProbeSqlConnectivity` | `true` | Prueft die integrierte Windows-Anmeldung an der per WMI ermittelten Management- und Master-MessageBox-Datenbank. |
| `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. |
@@ -202,13 +212,15 @@ Erwartet werden die sechs stabilen `BizTalk ...` Services mit plausiblen Daten.
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.
- Fehler werden als `Permission`, `Connectivity`, `Timeout`, `Configuration`, `Schema` oder `Provider` klassifiziert.
- `BizTalk SQL Access` oeffnet mit integrierter Windows-Authentifizierung eine Verbindung zur ermittelten Management- und Master-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.
`Wmi/Schema` kennzeichnet `InvalidClass` oder `InvalidQuery`. Das ist kein Berechtigungsfehler: Klassen- und Property-Namen muessen gegen das installierte BizTalk-WMI-Schema geprueft werden; eine Rechteerhoehung kann eine nicht vorhandene Klasse nicht erzeugen. Die technischen Details enthalten die ausgefuehrte WQL-Abfrage und deren Laufzeit bis zum Fehler.
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`:
@@ -240,6 +252,7 @@ Entscheidungsmatrix:
| --- | --- |
| 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. |
| Service meldet `Wmi/Schema` beziehungsweise `InvalidClass`/`InvalidQuery` | WMI-Klasse und Properties gegen das BizTalk-Schema pruefen. Keine Berechtigungen erweitern. |
| `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. |
@@ -270,6 +283,8 @@ 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 WMI `MSBTS_GroupSetting`: https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-groupsetting-wmi
- Microsoft BizTalk WMI Core Server Classes: https://learn.microsoft.com/en-us/biztalk/core/technical-reference/core-server-classes
- 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
@@ -6,6 +6,11 @@ set "EXE=%BASE%\BizTalkCheckmkPulse.exe"
if not exist "%EXE%" (
echo 3 "BizTalk Platform" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen und danach cmk-agent-ctl.exe dump erneut ausfuehren.
echo 3 "BizTalk SQL Access" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen.
echo 3 "BizTalk Suspended Instances" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen.
echo 3 "BizTalk Host Instances" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen.
echo 3 "BizTalk Runtime Artifacts" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen.
echo 3 "BizTalk Event Log" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen.
exit /b 0
)
@@ -13,5 +18,10 @@ if not exist "%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.
echo 3 "BizTalk SQL Access" - BizTalkCheckmkPulse.exe endete mit Exitcode=%EXITCODE%. Massnahme: .NET Framework 4.7.2, EXE/Config-Dateien und Windows Application Log pruefen.
echo 3 "BizTalk Suspended Instances" - BizTalkCheckmkPulse.exe endete mit Exitcode=%EXITCODE%. Massnahme: Programmstart und Windows Application Log pruefen.
echo 3 "BizTalk Host Instances" - BizTalkCheckmkPulse.exe endete mit Exitcode=%EXITCODE%. Massnahme: Programmstart und Windows Application Log pruefen.
echo 3 "BizTalk Runtime Artifacts" - BizTalkCheckmkPulse.exe endete mit Exitcode=%EXITCODE%. Massnahme: Programmstart und Windows Application Log pruefen.
echo 3 "BizTalk Event Log" - BizTalkCheckmkPulse.exe endete mit Exitcode=%EXITCODE%. Massnahme: Programmstart und Windows Application Log pruefen.
)
exit /b 0
+8 -7
View File
@@ -13,14 +13,14 @@ Die Ausgabe ist so gebaut, dass sie auch mit Checkmk 2.4.0p4 kompatibel bleibt:
Zweck:
- Prueft, ob der BizTalk-WMI-Namespace erreichbar ist.
- Zeigt Server, Gruppe, Management-DB und MessageBox-DB, soweit WMI diese Werte liefert.
- Zeigt Server, Gruppe, Management-DB und Master-MessageBox-DB aus `MSBTS_GroupSetting`.
Status:
| Status | Bedingung |
| --- | --- |
| `OK` | WMI-Verbindung und beide Plattform-Pflichtklassen erfolgreich. |
| `UNKNOWN` | Namespace, `MSBTS_GroupSetting`, `MSBTS_MessageBoxSetting` oder Programmstart fehlgeschlagen. |
| `OK` | WMI-Verbindung erfolgreich und `MSBTS_GroupSetting` liefert beide vollstaendigen Datenbankziele. |
| `UNKNOWN` | Namespace, `MSBTS_GroupSetting`, ein erforderliches Datenbankziel oder der Programmstart fehlgeschlagen. |
Metriken:
@@ -30,7 +30,7 @@ Metriken:
Zweck:
- Ermittelt Management- und MessageBox-Datenbank ueber BizTalk-WMI.
- Ermittelt Management- und Master-MessageBox-Datenbank ueber `MSBTS_GroupSetting`.
- 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.
@@ -93,6 +93,7 @@ Metriken:
Zweck:
- Ueberwacht BizTalk Host Instances auf dem BizTalk-Server.
- Filtert die gruppenweite WMI-Auflistung clientseitig auf den konfigurierten BizTalk-Server; Namen mit Sonderzeichen gelangen nicht in WQL.
- Nicht gestartete Host Instances sind normalerweise betriebsrelevant.
Status:
@@ -134,7 +135,7 @@ Metriken:
| Metrik | Bedeutung |
| --- | --- |
| `biztalk_applications` | Anzahl erkannter BizTalk-Anwendungen. |
| `biztalk_applications` | Anzahl der per WMI erkannten Anwendungs-Buckets; `(unknown)` ist moeglich, da die dokumentierten Artefaktklassen keine verlaessliche Application-Property garantieren. |
| `biztalk_receive_locations` | Gesamtzahl Receive Locations. |
| `biztalk_receive_locations_disabled` | Deaktivierte Receive Locations. |
| `biztalk_send_ports` | Gesamtzahl Send Ports. |
@@ -171,10 +172,10 @@ Metriken:
## Optionale per-Application-Services
Wenn `EmitPerApplicationSuspensionServices=true` gesetzt ist, werden zusaetzliche Services fuer Anwendungen erzeugt, die suspendierte Instanzen haben:
Wenn `EmitPerApplicationSuspensionServices=true` gesetzt ist, werden zusaetzliche Services fuer Anwendungen erzeugt, die suspendierte Instanzen haben und deren Anwendung per WMI tatsaechlich zugeordnet werden konnte:
```text
BizTalk Suspended <ApplicationName>
```
Das ist fuer Anwendungsteams hilfreich, kann aber die Service-Anzahl erhoehen. Fuer den Basisausbau wird empfohlen, diese Option deaktiviert zu lassen.
Nicht zuordenbare Instanzen bleiben im stabilen Aggregat-Service enthalten; ein irrefuehrender per-Application-Service namens `(unknown)` wird nicht erzeugt. Die Option kann die Service-Anzahl erhoehen und bleibt fuer den Basisausbau deaktiviert.
+11 -2
View File
@@ -6,13 +6,16 @@
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.
0 "BizTalk Host Instances" biztalk_host_instances_total=0;;;0 Self test OK. No WMI query was executed.
0 "BizTalk Runtime Artifacts" biztalk_applications=0;;;0 Self test OK. No WMI query was executed.
0 "BizTalk Event Log" biztalk_eventlog_errors=0;;;0 Self test OK. No event log was read.
```
## Gesunder BizTalk-Server
```text
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 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, MasterMessageBox=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
@@ -28,7 +31,7 @@
## 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
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, MasterMessageBox=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
@@ -37,6 +40,12 @@
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
```
## WMI-Schemafehler
```text
3 "BizTalk Runtime Artifacts" - BizTalk Runtime Artifacts konnten nicht vollstaendig gelesen werden. Teilwerte werden nicht als OK gewertet.; diagnostics: Wmi/Schema [MSBTS_Orchestration]: WMI-Klasse oder WQL fuer MSBTS_Orchestration wird vom installierten BizTalk-Provider nicht unterstuetzt. Massnahme: Klassen- und Property-Namen gegen das installierte BizTalk-WMI-Schema und die Microsoft-Dokumentation pruefen. Rechteerhoehung behebt InvalidClass/InvalidQuery nicht. Technik: ManagementException HRESULT=0x80041010: Invalid class WQL={SELECT * FROM MSBTS_Orchestration} DurationMs=18
```
## Fehlendes Deployment
```text
+4 -1
View File
@@ -97,7 +97,7 @@ Der direkte Start des Wrappers in einer administrativen Eingabeaufforderung ist
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.
Die EXE klassifiziert WMI- und SQL-Probleme und schreibt zu jeder Diagnose `Massnahme:` und `Technik:`. WMI-Schemafehler (`InvalidClass`/`InvalidQuery`) werden separat als `Wmi/Schema` ausgewiesen und duerfen nicht durch Rechteerweiterungen behandelt werden. 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`:
@@ -130,6 +130,7 @@ Entscheidungsmatrix:
| --- | --- |
| 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. |
| `Wmi/Schema` beziehungsweise `InvalidClass`/`InvalidQuery` | Klassen- und Property-Namen gegen das installierte BizTalk-WMI-Schema pruefen; keine Berechtigungen erweitern. |
| `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. |
@@ -149,6 +150,8 @@ Quellen:
- 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
- BizTalk `MSBTS_GroupSetting`: https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-groupsetting-wmi
- BizTalk WMI Core Server Classes: https://learn.microsoft.com/en-us/biztalk/core/technical-reference/core-server-classes
- 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
+1 -2
View File
@@ -7,6 +7,5 @@ if errorlevel 1 (
exit /b 1
)
msbuild BizTalkCheckmkPulse.sln /p:Configuration=Release /p:Platform="Any CPU" /m /v:minimal
msbuild BizTalkCheckmkPulse.sln /p:Configuration=Release /p:Platform="Any CPU" /m:1 /v:minimal
exit /b %ERRORLEVEL%
+8
View File
@@ -0,0 +1,8 @@
@echo off
setlocal
call "%~dp0build-release.cmd"
if errorlevel 1 exit /b 1
"%~dp0..\tests\BizTalkCheckmkPulse.Tests\bin\Release\BizTalkCheckmkPulse.Tests.exe"
exit /b %ERRORLEVEL%
@@ -40,6 +40,7 @@
<Reference Include="System.Management" />
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="CheckmkLocalFormatter.cs" />
<Compile Include="EventLogProbe.cs" />
<Compile Include="MonitoringOptions.cs" />
@@ -62,6 +62,9 @@ namespace BizTalkCheckmkPulse
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.");
yield return BuildLine(CheckState.Ok, _options.ServiceName("Host Instances"), "biztalk_host_instances_total=0;;;0", "Self test OK. No WMI query was executed.");
yield return BuildLine(CheckState.Ok, _options.ServiceName("Runtime Artifacts"), "biztalk_applications=0;;;0", "Self test OK. No WMI query was executed.");
yield return BuildLine(CheckState.Ok, _options.ServiceName("Event Log"), "biztalk_eventlog_errors=0;;;0", "Self test OK. No event log was read.");
}
/// <summary>
@@ -96,7 +99,7 @@ namespace BizTalkCheckmkPulse
AppendOptional(detail, "mgmt_db", JoinDb(result.Platform.ManagementDbServer, result.Platform.ManagementDbName));
AppendOptional(detail, "msgbox_db", JoinDb(result.Platform.MessageBoxDbServer, result.Platform.MessageBoxDbName));
var diagnostics = SelectDiagnostics(result, DiagnosticArea.Wmi, "WMI namespace connection", "MSBTS_GroupSetting", "MSBTS_MessageBoxSetting");
var diagnostics = SelectDiagnostics(result, DiagnosticArea.Wmi, "WMI namespace connection", "MSBTS_GroupSetting");
return BuildLine(state, _options.ServiceName("Platform"), "-", AppendDiagnostics(detail.ToString(), diagnostics));
}
@@ -334,7 +337,10 @@ namespace BizTalkCheckmkPulse
/// <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))
foreach (var group in result.SuspendedInstances
.Where(x => !string.Equals(x.ApplicationName, "(unknown)", StringComparison.OrdinalIgnoreCase))
.GroupBy(x => x.ApplicationName)
.OrderBy(x => x.Key, StringComparer.OrdinalIgnoreCase))
{
var resumable = group.Count(x => x.Kind == SuspendedKind.Resumable);
var nonresumable = group.Count(x => x.Kind == SuspendedKind.NonResumable);
+1
View File
@@ -43,6 +43,7 @@ namespace BizTalkCheckmkPulse
Connectivity,
Timeout,
Configuration,
Schema,
Provider,
Unexpected
}
@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("BizTalkCheckmkPulse.Tests")]
@@ -35,7 +35,7 @@ namespace BizTalkCheckmkPulse
}
AddTarget(result, "Management", result.Platform.ManagementDbServer, result.Platform.ManagementDbName);
AddTarget(result, "MessageBox", result.Platform.MessageBoxDbServer, result.Platform.MessageBoxDbName);
AddTarget(result, "MasterMessageBox", result.Platform.MessageBoxDbServer, result.Platform.MessageBoxDbName);
if (!result.Platform.PlatformDataAvailable)
{
@@ -46,7 +46,7 @@ namespace BizTalkCheckmkPulse
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."
Action = "Zuerst den Service 'BizTalk Platform' und MSBTS_GroupSetting pruefen. Danach den Agent-Dump erneut ausfuehren."
});
}
+159 -56
View File
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Management;
using System.Runtime.InteropServices;
@@ -23,6 +24,8 @@ namespace BizTalkCheckmkPulse
private const int OrchestrationStopped = 3;
private const int OrchestrationStarted = 4;
private const int EAccessDenied = unchecked((int)0x80070005);
internal const string GroupSettingQuery =
"SELECT Name, MgmtDbServerName, MgmtDbName, SubscriptionDBServerName, SubscriptionDBName FROM MSBTS_GroupSetting";
private readonly MonitoringOptions _options;
/// <summary>
@@ -53,9 +56,14 @@ namespace BizTalkCheckmkPulse
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.HostInstancesDataAvailable = QueryHostInstances(scope, result, server);
var applicationIndex = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
result.Platform.RuntimeArtifactsDataAvailable = QueryApplicationRuntime(scope, result, applicationIndex);
if (_options.EmitPerApplicationSuspensionServices)
{
AddArtifactIndex(scope, result, applicationIndex, "MSBTS_ReceivePort");
}
result.Platform.SuspendedInstancesDataAvailable = QuerySuspendedInstances(scope, result, applicationIndex);
}
@@ -74,7 +82,7 @@ namespace BizTalkCheckmkPulse
}
catch (Exception ex) when (IsExpectedWmiException(ex))
{
AddWmiDiagnostic(result, "WMI namespace connection", ex, true, true);
AddWmiDiagnostic(result, "WMI namespace connection", ex, true, true, null, null);
return false;
}
}
@@ -88,23 +96,19 @@ namespace BizTalkCheckmkPulse
private bool QueryPlatform(ManagementScope scope, ProbeResult result)
{
var groupFound = false;
var groupAvailable = TryQuery(scope, result, "MSBTS_GroupSetting", "SELECT * FROM MSBTS_GroupSetting", item =>
var groupAvailable = TryQuery(scope, result, "MSBTS_GroupSetting", GroupSettingQuery, item =>
{
if (groupFound)
{
return;
}
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);
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"));
}
result.Platform.ManagementDbServer = WmiHelpers.GetString(item, "MgmtDbServerName");
result.Platform.ManagementDbName = WmiHelpers.GetString(item, "MgmtDbName");
result.Platform.MessageBoxDbServer = WmiHelpers.GetString(item, "SubscriptionDBServerName");
result.Platform.MessageBoxDbName = WmiHelpers.GetString(item, "SubscriptionDBName");
}, true);
if (groupAvailable && !groupFound)
@@ -112,32 +116,33 @@ namespace BizTalkCheckmkPulse
AddMissingDataDiagnostic(result, "MSBTS_GroupSetting", "Keine BizTalk-Gruppe gefunden.");
}
if (messageBoxAvailable && !messageBoxFound)
var managementTargetComplete = HasDatabaseTarget(
result.Platform.ManagementDbServer,
result.Platform.ManagementDbName);
if (groupAvailable && groupFound && !managementTargetComplete)
{
AddMissingDataDiagnostic(result, "MSBTS_MessageBoxSetting", "Keine BizTalk-MessageBox-Datenbank gefunden.");
AddMissingDataDiagnostic(
result,
"MSBTS_GroupSetting.ManagementDatabase",
"BizTalk Management-Datenbankziel ist in MSBTS_GroupSetting unvollstaendig.");
}
return groupAvailable && groupFound && messageBoxAvailable && messageBoxFound;
var messageBoxTargetComplete = HasDatabaseTarget(
result.Platform.MessageBoxDbServer,
result.Platform.MessageBoxDbName);
if (groupAvailable && groupFound && !messageBoxTargetComplete)
{
AddMissingDataDiagnostic(
result,
"MSBTS_GroupSetting.SubscriptionDatabase",
"Master-MessageBox-Datenbankziel ist in MSBTS_GroupSetting unvollstaendig.");
}
return groupAvailable && groupFound && managementTargetComplete && messageBoxTargetComplete;
}
/// <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);
AddArtifactIndex(scope, result, index, "MSBTS_SendPort");
AddArtifactIndex(scope, result, index, "MSBTS_ReceivePort");
AddArtifactIndex(scope, result, index, "MSBTS_ReceiveLocation");
AddArtifactIndex(scope, result, index, "MSBTS_Orchestration");
return index;
}
/// <summary>
/// Fuegt die Schluessel einer WMI-Artefaktklasse zum Anwendungsindex hinzu.
/// Fuegt die Schluessel einer optionalen WMI-Artefaktklasse zum Anwendungsindex hinzu.
/// </summary>
/// <param name="scope">Verbundener BizTalk-WMI-Scope.</param>
/// <param name="result">Ergebnisobjekt fuer optionale Diagnosen.</param>
@@ -147,14 +152,7 @@ namespace BizTalkCheckmkPulse
{
TryQuery(scope, result, className + " application index", "SELECT * FROM " + className, item =>
{
var app = GetApplicationName(item);
foreach (var key in CandidateKeys(item))
{
if (!string.IsNullOrWhiteSpace(key) && !index.ContainsKey(key))
{
index.Add(key, app);
}
}
AddArtifactIndexEntry(index, item);
}, false);
}
@@ -163,16 +161,28 @@ namespace BizTalkCheckmkPulse
/// </summary>
/// <param name="scope">Verbundener BizTalk-WMI-Scope.</param>
/// <param name="result">Zu aktualisierendes Ergebnisobjekt.</param>
/// <param name="server">BizTalk-Server, dessen Host Instances bewertet werden.</param>
/// <returns><c>true</c>, wenn die Pflichtabfrage erfolgreich war.</returns>
private bool QueryHostInstances(ManagementScope scope, ProbeResult result)
private bool QueryHostInstances(ManagementScope scope, ProbeResult result, string server)
{
return TryQuery(scope, result, "MSBTS_HostInstance", "SELECT * FROM MSBTS_HostInstance", item =>
return TryQuery(
scope,
result,
"MSBTS_HostInstance",
"SELECT Name, HostName, RunningServer, ServiceState FROM MSBTS_HostInstance",
item =>
{
var runningServer = WmiHelpers.GetString(item, "RunningServer");
if (!string.IsNullOrWhiteSpace(runningServer) && !IsSameServer(runningServer, server))
{
return;
}
result.HostInstances.Add(new HostInstanceState
{
InstanceName = FirstNonEmpty(WmiHelpers.GetString(item, "InstanceName"), WmiHelpers.GetString(item, "Name")),
HostName = WmiHelpers.GetString(item, "HostName"),
RunningServer = WmiHelpers.GetString(item, "RunningServer"),
RunningServer = runningServer,
ServiceState = WmiHelpers.GetInt32(item, "ServiceState", 0)
});
}, true);
@@ -183,13 +193,18 @@ namespace BizTalkCheckmkPulse
/// </summary>
/// <param name="scope">Verbundener BizTalk-WMI-Scope.</param>
/// <param name="result">Zu aktualisierendes Ergebnisobjekt.</param>
/// <param name="applicationIndex">Best-Effort-Index fuer Suspensionsdetails.</param>
/// <returns><c>true</c>, wenn alle drei Pflichtabfragen erfolgreich waren.</returns>
private bool QueryApplicationRuntime(ManagementScope scope, ProbeResult result)
private bool QueryApplicationRuntime(
ManagementScope scope,
ProbeResult result,
Dictionary<string, string> applicationIndex)
{
var apps = new Dictionary<string, ApplicationRuntimeState>(StringComparer.OrdinalIgnoreCase);
var receiveLocationsAvailable = TryQuery(scope, result, "MSBTS_ReceiveLocation", "SELECT * FROM MSBTS_ReceiveLocation", item =>
{
AddArtifactIndexEntry(applicationIndex, item);
var app = GetApplication(apps, GetApplicationName(item));
app.ReceiveLocationTotal++;
if (WmiHelpers.GetBoolean(item, "IsDisabled", false))
@@ -200,6 +215,7 @@ namespace BizTalkCheckmkPulse
var sendPortsAvailable = TryQuery(scope, result, "MSBTS_SendPort", "SELECT * FROM MSBTS_SendPort", item =>
{
AddArtifactIndexEntry(applicationIndex, item);
var app = GetApplication(apps, GetApplicationName(item));
app.SendPortTotal++;
switch (WmiHelpers.GetInt32(item, "Status", 0))
@@ -221,6 +237,7 @@ namespace BizTalkCheckmkPulse
var orchestrationsAvailable = TryQuery(scope, result, "MSBTS_Orchestration", "SELECT * FROM MSBTS_Orchestration", item =>
{
AddArtifactIndexEntry(applicationIndex, item);
var app = GetApplication(apps, GetApplicationName(item));
app.OrchestrationTotal++;
switch (WmiHelpers.GetInt32(item, "OrchestrationStatus", 0))
@@ -285,6 +302,7 @@ namespace BizTalkCheckmkPulse
/// <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)
{
var stopwatch = Stopwatch.StartNew();
try
{
ForEachObject(scope, queryText, action);
@@ -292,7 +310,7 @@ namespace BizTalkCheckmkPulse
}
catch (Exception ex) when (IsExpectedWmiException(ex))
{
AddWmiDiagnostic(result, label, ex, required, false);
AddWmiDiagnostic(result, label, ex, required, false, queryText, stopwatch.Elapsed);
return false;
}
}
@@ -344,6 +362,28 @@ namespace BizTalkCheckmkPulse
return app;
}
/// <summary>
/// Nimmt nur tatsaechlich vorhandene Anwendungszuordnungen in den Best-Effort-Index auf.
/// </summary>
/// <param name="index">Zu erweiternder Index.</param>
/// <param name="item">BizTalk-Artefakt aus WMI.</param>
private static void AddArtifactIndexEntry(Dictionary<string, string> index, ManagementBaseObject item)
{
var app = GetApplicationName(item);
if (string.Equals(app, UnknownApplication, StringComparison.OrdinalIgnoreCase))
{
return;
}
foreach (var key in CandidateKeys(item))
{
if (!string.IsNullOrWhiteSpace(key) && !index.ContainsKey(key))
{
index.Add(key, app);
}
}
}
/// <summary>
/// Ermittelt den Anwendungsnamen direkt oder ueber den Artefaktindex.
/// </summary>
@@ -406,6 +446,32 @@ namespace BizTalkCheckmkPulse
return values == null ? null : values.FirstOrDefault(x => !string.IsNullOrWhiteSpace(x));
}
/// <summary>
/// Prueft, ob Server und Datenbank eines SQL-Ziels vorhanden sind.
/// </summary>
private static bool HasDatabaseTarget(string server, string database)
{
return !string.IsNullOrWhiteSpace(server) && !string.IsNullOrWhiteSpace(database);
}
/// <summary>
/// Vergleicht kurzen Namen und FQDN eines BizTalk-Servers ohne WQL-Namensfilter.
/// </summary>
internal static bool IsSameServer(string left, string right)
{
return string.Equals(
NormalizeServerName(left),
NormalizeServerName(right),
StringComparison.OrdinalIgnoreCase);
}
private static string NormalizeServerName(string value)
{
var normalized = (value ?? string.Empty).Trim().TrimStart('\\');
var dotIndex = normalized.IndexOf('.');
return dotIndex > 0 ? normalized.Substring(0, dotIndex) : normalized;
}
/// <summary>
/// Prueft, ob eine Exception aus dem erwarteten WMI-/COM-Fehlerbereich stammt.
/// </summary>
@@ -426,9 +492,29 @@ namespace BizTalkCheckmkPulse
/// <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)
private static void AddWmiDiagnostic(
ProbeResult result,
string component,
Exception exception,
bool required,
bool connectionFailure,
string queryText,
TimeSpan? duration)
{
var category = ClassifyWmiException(exception);
var technicalDetails = exception.GetType().Name
+ " HRESULT=0x" + exception.HResult.ToString("X8")
+ ": " + exception.Message;
if (!string.IsNullOrWhiteSpace(queryText))
{
technicalDetails += " WQL={" + queryText + "}";
}
if (duration.HasValue)
{
technicalDetails += " DurationMs=" + Math.Round(duration.Value.TotalMilliseconds);
}
result.Diagnostics.Add(new ProbeDiagnostic
{
Area = DiagnosticArea.Wmi,
@@ -437,7 +523,7 @@ namespace BizTalkCheckmkPulse
Required = required,
Summary = BuildWmiSummary(category, component, connectionFailure),
Action = BuildWmiAction(category, connectionFailure),
TechnicalDetails = exception.GetType().Name + " HRESULT=0x" + exception.HResult.ToString("X8") + ": " + exception.Message
TechnicalDetails = technicalDetails
});
}
@@ -477,12 +563,25 @@ namespace BizTalkCheckmkPulse
}
if (managementException != null
&& (managementException.ErrorCode == ManagementStatus.InvalidNamespace
|| managementException.ErrorCode == ManagementStatus.InvalidClass))
&& managementException.ErrorCode == ManagementStatus.InvalidNamespace)
{
return DiagnosticCategory.Configuration;
}
if (managementException != null
&& (managementException.ErrorCode == ManagementStatus.InvalidClass
|| managementException.ErrorCode == ManagementStatus.InvalidProperty
|| managementException.ErrorCode == ManagementStatus.InvalidQuery
|| managementException.ErrorCode == ManagementStatus.UnparsableQuery))
{
return DiagnosticCategory.Schema;
}
if (managementException != null && managementException.ErrorCode == ManagementStatus.Timedout)
{
return DiagnosticCategory.Timeout;
}
var message = exception.Message ?? string.Empty;
if (ContainsAny(message, "timed out", "timeout", "Zeitlimit"))
{
@@ -513,7 +612,9 @@ namespace BizTalkCheckmkPulse
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.";
: "BizTalk-WMI-Konfiguration fuer " + component + " ist unvollstaendig.";
case DiagnosticCategory.Schema:
return "WMI-Klasse oder WQL fuer " + component + " wird vom installierten BizTalk-Provider nicht unterstuetzt.";
case DiagnosticCategory.Timeout:
return "WMI-Abfrage fuer " + component + " hat das konfigurierte Zeitlimit ueberschritten.";
case DiagnosticCategory.Connectivity:
@@ -539,6 +640,8 @@ namespace BizTalkCheckmkPulse
: "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.Schema:
return "Klassen- und Property-Namen gegen das installierte BizTalk-WMI-Schema und die Microsoft-Dokumentation pruefen. Rechteerhoehung behebt InvalidClass/InvalidQuery nicht.";
case DiagnosticCategory.Timeout:
return "WMI-Dienst, BizTalk-/SQL-Auslastung und Event Logs pruefen; Timeout erst danach erhoehen.";
case DiagnosticCategory.Connectivity:
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" Condition="false" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{E219C45A-CC95-4F45-9B6F-8244CBAEE35A}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>BizTalkCheckmkPulse.Tests</RootNamespace>
<AssemblyName>BizTalkCheckmkPulse.Tests</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\BizTalkCheckmkPulse\BizTalkCheckmkPulse.csproj">
<Project>{A4D4D050-9EA7-4A71-B510-7D9D699B9F38}</Project>
<Name>BizTalkCheckmkPulse</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
+124
View File
@@ -0,0 +1,124 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace BizTalkCheckmkPulse.Tests
{
internal static class Program
{
private static readonly List<string> Failures = new List<string>();
private static int Main()
{
Run("SelfTestEmitsAllStableServices", SelfTestEmitsAllStableServices);
Run("UnavailableSourcesAreUnknown", UnavailableSourcesAreUnknown);
Run("UnknownApplicationDoesNotCreateService", UnknownApplicationDoesNotCreateService);
Run("KnownApplicationCreatesService", KnownApplicationCreatesService);
Run("ServerNamesAreComparedWithoutWql", ServerNamesAreComparedWithoutWql);
Run("GroupSettingQueryUsesDocumentedSchema", GroupSettingQueryUsesDocumentedSchema);
if (Failures.Count == 0)
{
Console.WriteLine("PASS: 6 tests");
return 0;
}
foreach (var failure in Failures)
{
Console.Error.WriteLine("FAIL: " + failure);
}
return 1;
}
private static void SelfTestEmitsAllStableServices()
{
var lines = new CheckmkLocalFormatter(new MonitoringOptions()).FormatSelfTest().ToArray();
AssertEqual(6, lines.Length, "self-test line count");
AssertEqual(6, lines.Distinct(StringComparer.Ordinal).Count(), "unique self-test lines");
Assert(lines.All(x => x.StartsWith("0 \"BizTalk ", StringComparison.Ordinal)), "every self-test line must be OK");
Assert(lines.Any(x => x.Contains("\"BizTalk Event Log\"")), "Event Log service missing");
}
private static void UnavailableSourcesAreUnknown()
{
var lines = new CheckmkLocalFormatter(new MonitoringOptions()).Format(new ProbeResult()).Take(6).ToArray();
AssertEqual(6, lines.Length, "stable service count");
Assert(lines.All(x => x.StartsWith("3 \"BizTalk ", StringComparison.Ordinal)), "unavailable sources must be UNKNOWN");
}
private static void UnknownApplicationDoesNotCreateService()
{
var options = new MonitoringOptions { EmitPerApplicationSuspensionServices = true };
var result = CreateSuspensionResult("(unknown)");
var lines = new CheckmkLocalFormatter(options).Format(result).ToArray();
AssertEqual(6, lines.Length, "unknown application must not create a dynamic service");
}
private static void KnownApplicationCreatesService()
{
var options = new MonitoringOptions { EmitPerApplicationSuspensionServices = true };
var result = CreateSuspensionResult("Orders");
var lines = new CheckmkLocalFormatter(options).Format(result).ToArray();
AssertEqual(7, lines.Length, "known application should create one dynamic service");
Assert(lines.Any(x => x.Contains("\"BizTalk Suspended Orders\"")), "known application service missing");
}
private static void ServerNamesAreComparedWithoutWql()
{
Assert(WmiBizTalkProbe.IsSameServer("BIZTALK01.corp.example", "biztalk01"), "FQDN and short name must match");
Assert(WmiBizTalkProbe.IsSameServer(@"\\BIZ-TALK+01", "biz-talk+01.example"), "special characters must be compared client-side");
Assert(!WmiBizTalkProbe.IsSameServer("BIZTALK01", "BIZTALK02"), "different servers must not match");
}
private static void GroupSettingQueryUsesDocumentedSchema()
{
var query = WmiBizTalkProbe.GroupSettingQuery;
Assert(query.Contains("FROM MSBTS_GroupSetting"), "MSBTS_GroupSetting query missing");
Assert(query.Contains("SubscriptionDBServerName"), "master MessageBox server property missing");
Assert(query.Contains("SubscriptionDBName"), "master MessageBox database property missing");
Assert(query.IndexOf("MessageBoxSetting", StringComparison.OrdinalIgnoreCase) < 0, "unsupported MessageBoxSetting class present");
}
private static ProbeResult CreateSuspensionResult(string applicationName)
{
var result = new ProbeResult();
result.Platform.SuspendedInstancesDataAvailable = true;
result.SuspendedInstances.Add(new SuspendedInstance
{
ApplicationName = applicationName,
ServiceName = "TestService",
Kind = SuspendedKind.Resumable
});
return result;
}
private static void Run(string name, Action test)
{
try
{
test();
}
catch (Exception ex)
{
Failures.Add(name + ": " + ex.Message);
}
}
private static void Assert(bool condition, string message)
{
if (!condition)
{
throw new InvalidOperationException(message);
}
}
private static void AssertEqual(int expected, int actual, string label)
{
if (expected != actual)
{
throw new InvalidOperationException(label + ": expected " + expected + ", actual " + actual);
}
}
}
}