Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
826d87fef7 | ||
|
|
d15bb6539b | ||
|
|
e3d7f6a780 | ||
|
|
0001d7b861 | ||
|
|
8d2934e343 |
@@ -1,9 +1,11 @@
|
|||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 17
|
# Visual Studio Version 16
|
||||||
VisualStudioVersion = 17.0.31903.59
|
VisualStudioVersion = 16.0.31624.102
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizTalkCheckmkPulse", "src\BizTalkCheckmkPulse\BizTalkCheckmkPulse.csproj", "{A4D4D050-9EA7-4A71-B510-7D9D699B9F38}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizTalkCheckmkPulse", "src\BizTalkCheckmkPulse\BizTalkCheckmkPulse.csproj", "{A4D4D050-9EA7-4A71-B510-7D9D699B9F38}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizTalkCheckmkPulse.Tests", "tests\BizTalkCheckmkPulse.Tests\BizTalkCheckmkPulse.Tests.csproj", "{E219C45A-CC95-4F45-9B6F-8244CBAEE35A}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{A4D4D050-9EA7-4A71-B510-7D9D699B9F38}.Release|Any CPU.Build.0 = 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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
||||||
|
|||||||
+550
-323
@@ -1,110 +1,180 @@
|
|||||||
# Dokumentation: BizTalk Checkmk Pulse
|
# Technische Dokumentation: BizTalk Checkmk Pulse
|
||||||
|
|
||||||
## Zielbild
|
## 1. Ziel und Randbedingungen
|
||||||
|
|
||||||
Ziel ist ein wartbares Monitoring fuer BizTalk Server 2020 in den Umgebungen `ACC`, `DEV`, `TST` und `PRD`. Jede Umgebung besitzt einen BizTalk Server 2020 und einen SQL Server. SQL Server wird mit dem Checkmk-eigenen MSSQL-Plugin ueberwacht; fuer BizTalk liefert dieses Projekt die fehlende fachliche und technische Laufzeitsicht.
|
Die Anwendung ueberwacht BizTalk Server 2020 auf Windows Server 2019 mit
|
||||||
|
Checkmk 2.4. Pro Umgebung (`ACC`, `DEV`, `TST`, `PRD`) wird lokal auf dem
|
||||||
|
BizTalk-Server gesammelt. Das Checkmk-MSSQL-Plugin bleibt fuer die allgemeine
|
||||||
|
SQL-Ueberwachung zustaendig; BizTalk Checkmk Pulse liefert die
|
||||||
|
BizTalk-spezifische Laufzeit- und Berechtigungssicht.
|
||||||
|
|
||||||
Das Monitoring soll:
|
Verbindliche Randbedingungen:
|
||||||
|
|
||||||
- auf jeder BizTalk-Maschine lokal laufen
|
- Der Checkmk Windows Agent bleibt `LocalSystem`.
|
||||||
- ohne PowerShell-Abhaengigkeit funktionieren
|
- `LocalSystem` erhaelt keine BizTalk-/SQL-Gruppenmitgliedschaft.
|
||||||
- keine BizTalk-DLLs im Build erzwingen
|
- Ein normales dediziertes AD-Servicekonto sammelt minuetlich.
|
||||||
- Checkmk-2.4-kompatible Services und Metriken erzeugen
|
- Der Agentpfad fuehrt niemals WMI- oder SQL-Abfragen aus.
|
||||||
- service-discovery-freundlich und dashboard-tauglich sein
|
- Fehler muessen als gueltige Checkmk-`UNKNOWN`-Services sichtbar werden.
|
||||||
- bei Fehlern gueltige `UNKNOWN`-Services statt kaputter Agent-Ausgaben liefern
|
- Schreiben und Lesen duerfen nie einen halben Snapshot exponieren.
|
||||||
|
- Laufzeitcode benoetigt keine PowerShell und keine BizTalk-DLL-Referenz.
|
||||||
|
- Installation, ACL und Scheduled Task muessen reproduzierbar sein.
|
||||||
|
|
||||||
## Technische Bewertung
|
## 2. Ausloeser der Architekturanderung
|
||||||
|
|
||||||
### Option A: Checkmk Local Check mit C#/.NET Framework
|
Der ACC-Agent-Dump vom 29.07.2026 bewies:
|
||||||
|
|
||||||
Bewertung: empfohlen und umgesetzt.
|
|
||||||
|
|
||||||
Vorteile:
|
|
||||||
|
|
||||||
- Checkmk 2.4 unterstuetzt Local Checks direkt.
|
|
||||||
- Windows Server mit BizTalk 2020 bringt .NET Framework in der Regel passend mit.
|
|
||||||
- `System.Management` kann BizTalk-WMI lesen.
|
|
||||||
- Keine PowerShell Execution Policy, keine Script-Signing-Frage.
|
|
||||||
- Kein serverseitiges Checkmk-Python-Plugin notwendig.
|
|
||||||
- Rollout ist eine einfache Dateiablage unter `%ProgramData%\checkmk\agent\local`.
|
|
||||||
|
|
||||||
Nachteile:
|
|
||||||
|
|
||||||
- Schwellwerte sind in der `.exe.config`, nicht als Checkmk-Regelsatz in WATO.
|
|
||||||
- Eigene Graphing-Definitionen sind nicht enthalten; Checkmk zeigt Local-Check-Metriken trotzdem als Performance-Daten und Graphen.
|
|
||||||
|
|
||||||
### Option B: Agent Plugin plus serverseitiges Checkmk-Plugin
|
|
||||||
|
|
||||||
Bewertung: technisch elegant, aber fuer den ersten produktiven Schritt schwerer.
|
|
||||||
|
|
||||||
Vorteile:
|
|
||||||
|
|
||||||
- Checkmk-Regeln, Discovery und Metrikdefinitionen koennen sauber zentral modelliert werden.
|
|
||||||
- Bessere langfristige Erweiterbarkeit als MKP.
|
|
||||||
|
|
||||||
Nachteile:
|
|
||||||
|
|
||||||
- Checkmk-Check-API-Versionen muessen enger gepflegt werden.
|
|
||||||
- Server-seitige Installation in jeder Site erforderlich.
|
|
||||||
- Mehr Aufwand fuer Managed-Services-Betrieb und Updates.
|
|
||||||
|
|
||||||
Empfehlung: als Version 2 dieses Projekts denkbar, wenn die Local-Check-Variante stabil in PRD laeuft und zentrale Regelsaetze wirklich benoetigt werden.
|
|
||||||
|
|
||||||
### Option C: PowerShell Local Check
|
|
||||||
|
|
||||||
Bewertung: nicht empfohlen fuer diese Umgebung.
|
|
||||||
|
|
||||||
Vorteile:
|
|
||||||
|
|
||||||
- Schnell zu schreiben.
|
|
||||||
- WMI/CIM-Zugriff ist komfortabel.
|
|
||||||
|
|
||||||
Nachteile:
|
|
||||||
|
|
||||||
- PowerShell ist in vielen Serverumgebungen eingeschraenkt oder signaturpflichtig.
|
|
||||||
- Ausfuehrungsverhalten im Checkmk-Agent-Kontext ist haeufiger fehleranfaellig.
|
|
||||||
|
|
||||||
### Option D: BizTalk ExplorerOM/OperationsOM
|
|
||||||
|
|
||||||
Bewertung: fachlich stark, deployseitig unnoetig schwer.
|
|
||||||
|
|
||||||
Vorteile:
|
|
||||||
|
|
||||||
- Hoehere BizTalk-Abstraktion als rohe WMI-Klassen.
|
|
||||||
|
|
||||||
Nachteile:
|
|
||||||
|
|
||||||
- BizTalk-DLL-Versionen muessen beim Build und teilweise zur Laufzeit passen.
|
|
||||||
- Build-Agenten brauchen BizTalk-Komponenten oder SDK-Dateien.
|
|
||||||
- Fuer die benoetigten Zustandsdaten reicht WMI aus.
|
|
||||||
|
|
||||||
## Architektur
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Checkmk Windows Agent
|
Lokaler Prozess: NT AUTHORITY\SYSTEM
|
||||||
|
|
Netzwerkprincipal: BEW\AV23AGPWBIO1$
|
||||||
| startet lokale Checks aus %ProgramData%\checkmk\agent\local
|
Providerfehler: COMException 0x80131904
|
||||||
v
|
Innerer Fehler: Login failed for user 'BEW\AV23AGPWBIO1$'
|
||||||
biztalk_checkmk_pulse.cmd
|
|
||||||
|
|
|
||||||
| startet
|
|
||||||
v
|
|
||||||
BizTalkCheckmkPulse.exe
|
|
||||||
|
|
|
||||||
| 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 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.
|
Lokales `root\MicrosoftBizTalkServer` war erreichbar. Die
|
||||||
|
datenbankgestuetzten BizTalk-WMI-Klassen scheiterten erst beim Zugriff des
|
||||||
|
Providers auf den getrennten SQL Server. DCOM-, Firewall- oder pauschale
|
||||||
|
WMI-ACL-Aenderungen beheben diesen Befund nicht.
|
||||||
|
|
||||||
## Datenquellen
|
Eine Berechtigung des Maschinenkontos haette die Netzwerkrolle allen
|
||||||
|
`LocalSystem`-Diensten des BizTalk-Servers vermittelt. Die jetzt implementierte
|
||||||
|
Producer/Consumer-Trennung beschraenkt sie stattdessen auf ein dediziertes
|
||||||
|
Konto.
|
||||||
|
|
||||||
### BizTalk WMI Namespace
|
## 3. Architektur
|
||||||
|
|
||||||
|
```text
|
||||||
|
Privilegierte Zone
|
||||||
|
┌──────────────────────────────────────────────────────────────┐
|
||||||
|
│ Task Scheduler: "BizTalk Checkmk Pulse Provider" │
|
||||||
|
│ Konto: DOMAIN\svc_biztalk_cmk (RunLevel Limited) │
|
||||||
|
│ Intervall: 1 Minute, IgnoreNew, Laufzeitlimit 5 Minuten │
|
||||||
|
│ │
|
||||||
|
│ BizTalkCheckmkPulse.exe --collect │
|
||||||
|
│ ├─ WMI root\MicrosoftBizTalkServer │
|
||||||
|
│ ├─ integrierter SQL-Test an MgmtDb/MsgBoxDb │
|
||||||
|
│ ├─ lokales Windows Application Event Log │
|
||||||
|
│ ├─ Checkmk-Formatierung │
|
||||||
|
│ └─ atomare Snapshot-Publikation + Provider-Log │
|
||||||
|
└──────────────────────────────┬───────────────────────────────┘
|
||||||
|
│ Datei/ACL
|
||||||
|
v
|
||||||
|
%ProgramData%\BizTalkCheckmkPulse\data\biztalk-checkmk-pulse.snapshot
|
||||||
|
│
|
||||||
|
v
|
||||||
|
┌──────────────────────────────────────────────────────────────┐
|
||||||
|
│ Unprivilegierter Transportpfad │
|
||||||
|
│ Checkmk-Agent (LocalSystem) │
|
||||||
|
│ └─ local\biztalk_checkmk_pulse.cmd │
|
||||||
|
│ └─ BizTalkCheckmkPulse.exe --consume │
|
||||||
|
│ ├─ Format/Alter/Maschine/SHA-256 validieren │
|
||||||
|
│ └─ Payload unveraendert nach STDOUT │
|
||||||
|
└──────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
Eine gemeinsame EXE implementiert beide Modi. Das reduziert
|
||||||
|
Versionsabweichungen. Die zentrale Config unter `%ProgramFiles%` gilt fuer
|
||||||
|
Provider und Consumer.
|
||||||
|
|
||||||
|
### 3.1 Provider-Modus
|
||||||
|
|
||||||
|
Aufruf:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
"%ProgramFiles%\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe" --collect
|
||||||
|
```
|
||||||
|
|
||||||
|
Ablauf:
|
||||||
|
|
||||||
|
1. Config laden und absolute Runtimepfade validieren.
|
||||||
|
2. Tageslogs gemaess `LogRetentionDays` bereinigen.
|
||||||
|
3. exklusives Handle auf `<SnapshotPath>.provider.lock` halten.
|
||||||
|
4. WMI-, SQL- und Event-Log-Probes ausfuehren.
|
||||||
|
5. acht stabile und optionale dynamische Checkmk-Zeilen formatieren.
|
||||||
|
6. Snapshot in einer eindeutigen Temporaerdatei desselben Verzeichnisses
|
||||||
|
schreiben.
|
||||||
|
7. `Flush(true)` ausfuehren und Temporaerdatei atomar publizieren.
|
||||||
|
8. Laufzeit, Zeilen- und Diagnosezahl loggen.
|
||||||
|
|
||||||
|
Exitcodes:
|
||||||
|
|
||||||
|
| Code | Bedeutung |
|
||||||
|
| --- | --- |
|
||||||
|
| `0` | Sammlung und Snapshot-Publikation erfolgreich. |
|
||||||
|
| `1` | unerwarteter Providerfehler; aktueller `UNKNOWN`-Snapshot wird versucht. |
|
||||||
|
| `2` | Parallelstart oder Snapshot-I/O-Fehler. |
|
||||||
|
|
||||||
|
Fachliche WMI-/SQL-Fehler werden strukturiert in den Snapshot geschrieben und
|
||||||
|
muessen nicht zwingend einen Prozessfehler verursachen. So bleibt die genaue
|
||||||
|
Fehlerkategorie in Checkmk sichtbar.
|
||||||
|
|
||||||
|
### 3.2 Consumer-Modus
|
||||||
|
|
||||||
|
Aufruf:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
"%ProgramFiles%\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe" --consume
|
||||||
|
```
|
||||||
|
|
||||||
|
Ohne Modusargument ist `--consume` der sichere Default. Der Checkmk-Wrapper
|
||||||
|
setzt es trotzdem explizit.
|
||||||
|
|
||||||
|
Der Consumer:
|
||||||
|
|
||||||
|
- oeffnet nur den Snapshot,
|
||||||
|
- begrenzt die Dateigroesse vor dem Einlesen,
|
||||||
|
- validiert striktes UTF-8,
|
||||||
|
- validiert Formatversion und Header,
|
||||||
|
- fordert die lokale Maschinenkennung,
|
||||||
|
- berechnet SHA-256 ueber den exakten Payload,
|
||||||
|
- prueft Zeilenanzahl und Checkmk-Zeilenform,
|
||||||
|
- verwirft Zeitstempel mehr als fuenf Minuten in der Zukunft,
|
||||||
|
- verwirft Snapshots nach `SnapshotMaxAgeSeconds`,
|
||||||
|
- gibt bei Erfolg ausschliesslich den validierten Payload aus.
|
||||||
|
|
||||||
|
Er gibt immer Exitcode `0` zurueck, damit ein Fehler die komplette
|
||||||
|
Checkmk-Agentsektion nicht zerstoert. Jede Ablehnung erzeugt acht
|
||||||
|
`UNKNOWN`-Zeilen und einen Eintrag im Consumer-Log.
|
||||||
|
|
||||||
|
## 4. Snapshot-Vertrag
|
||||||
|
|
||||||
|
Version 2:
|
||||||
|
|
||||||
|
```text
|
||||||
|
BIZTALK_CHECKMK_PULSE_SNAPSHOT_V2
|
||||||
|
generatedUtc=2026-07-30T12:34:56.1234567Z
|
||||||
|
machineBase64=QVYyM0FHUFdCSU8x
|
||||||
|
identityBase64=QkVXXHN2Y19iaXp0YWxrX2NtayQ=
|
||||||
|
payloadLines=8
|
||||||
|
payloadSha256=<64 hex characters>
|
||||||
|
|
||||||
|
0 "BizTalk Platform" - ...
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Maschine und Identitaet sind Base64-kodiert, um Zeilenumbrueche und
|
||||||
|
Trennzeichen im Header auszuschliessen. Base64 ist hier keine Verschluesselung.
|
||||||
|
Die SHA-256-Pruefung schuetzt vor unbemerkter Teil-/Fehlschreibung, nicht vor
|
||||||
|
einem lokalen Administrator.
|
||||||
|
|
||||||
|
### 4.1 Atomare Publikation
|
||||||
|
|
||||||
|
Die Temporaerdatei liegt zwingend im Snapshotverzeichnis. Dadurch findet der
|
||||||
|
Ersatz auf demselben Volume statt. Existiert bereits ein Snapshot, wird
|
||||||
|
`File.Replace` verwendet; beim ersten Lauf `File.Move`. Bis zum erfolgreichen
|
||||||
|
Ersatz bleibt der letzte vollstaendige Snapshot erhalten. Seine Alterspruefung
|
||||||
|
verhindert unbegrenztes Weiterreichen alter Daten.
|
||||||
|
|
||||||
|
### 4.2 Keine direkte Checkmk-Spooldatei
|
||||||
|
|
||||||
|
Checkmk kann zwar Dateien aus seinem Spoolverzeichnis direkt anhaengen, prueft
|
||||||
|
deren Inhalt aber laut eigener Dokumentation nicht. Ein fehlerhafter Spool kann
|
||||||
|
nachfolgende Agentsektionen beeinflussen. Deshalb liegt der Provider-Snapshot
|
||||||
|
in einem eigenen Verzeichnis und wird erst durch den validierenden Consumer in
|
||||||
|
die Local-Check-Ausgabe uebernommen.
|
||||||
|
|
||||||
|
## 5. Datenquellen
|
||||||
|
|
||||||
|
### 5.1 BizTalk WMI
|
||||||
|
|
||||||
Namespace:
|
Namespace:
|
||||||
|
|
||||||
@@ -112,259 +182,416 @@ Namespace:
|
|||||||
root\MicrosoftBizTalkServer
|
root\MicrosoftBizTalkServer
|
||||||
```
|
```
|
||||||
|
|
||||||
Genutzte Klassen:
|
Pflichtklassen:
|
||||||
|
|
||||||
| Klasse | Zweck |
|
| Klasse | Zweck |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `MSBTS_GroupSetting` | BizTalk-Gruppe und Management-DB-Hinweise. |
|
| `MSBTS_GroupSetting` | Gruppe, Read-Only-/Operator-Gruppe, Management- und Master-MessageBox-Ziel. |
|
||||||
| `MSBTS_MessageBoxSetting` | MessageBox-DB-Hinweise. |
|
| `MSBTS_HostInstance` | lokale Host-Instance-Zustaende. |
|
||||||
| `MSBTS_HostInstance` | Host-Instance-Zustand. |
|
| `MSBTS_ServiceInstance` | suspendierte Instanzen. |
|
||||||
| `MSBTS_ServiceInstance` | Suspended service instances. |
|
| `MSBTS_ReceiveLocation` | Receive-Location-Zustaende. |
|
||||||
| `MSBTS_ReceiveLocation` | Receive-Location-Zustand. |
|
| `MSBTS_SendPort` | Send-Port-Zustaende. |
|
||||||
| `MSBTS_SendPort` | Send-Port-Zustand. |
|
| `MSBTS_Orchestration` | Orchestration-Zustaende. |
|
||||||
| `MSBTS_Orchestration` | Orchestration-Zustand. |
|
|
||||||
| `MSBTS_ReceivePort` | Application-Mapping fuer Details. |
|
|
||||||
|
|
||||||
### Windows Application Event Log
|
`MSBTS_GroupSetting` wird mit den dokumentierten Properties
|
||||||
|
`MgmtDbServerName`, `MgmtDbName`, `SubscriptionDBServerName`,
|
||||||
|
`SubscriptionDBName`, `BizTalkReadOnlyUserGroup` und
|
||||||
|
`BizTalkOperatorGroup` gelesen. Eine nicht dokumentierte
|
||||||
|
`MSBTS_MessageBoxSetting`-Klasse wird nicht abgefragt.
|
||||||
|
|
||||||
Der Check liest standardmaessig das lokale Application Log fuer die letzten 60 Minuten und filtert auf Quellen wie:
|
Hostnamen werden clientseitig verglichen. Sonderzeichen und FQDN-/Kurzname
|
||||||
|
gelangen nicht in dynamisch erzeugte WQL-Filter.
|
||||||
|
|
||||||
- `BizTalk Server`
|
`MSBTS_ServiceInstance` wird fuer `ServiceStatus=4` (resumable),
|
||||||
- `XLANG/s`
|
`ServiceStatus=32` (non-resumable) und `ServiceClass=64` abgefragt.
|
||||||
- `ENTSSO`
|
`ServiceClass=64` kennzeichnet Routing Failure Reports. Sie werden als eigene
|
||||||
- `BizTalk Server Application`
|
Metrik ausgewiesen und zugleich in `suspended_nonresumable` mitgezaehlt.
|
||||||
- `BizTalk Server EDI`
|
|
||||||
|
|
||||||
Die Liste ist ueber `EventLogSources` konfigurierbar.
|
### 5.2 Kompaktes Checkmk-Servicebild
|
||||||
|
|
||||||
### SQL-Zugriffsprobe
|
Die alte Sammelzeile `BizTalk Runtime Artifacts` wurde durch drei kurze
|
||||||
|
Services ersetzt. Insgesamt entstehen acht stabile Services:
|
||||||
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.
|
|
||||||
- Fatal Errors erzeugen `UNKNOWN` fuer alle stabilen Services.
|
|
||||||
- Event-Log-Auswertung ist isoliert; ein Fehler dort bricht WMI-Monitoring nicht ab.
|
|
||||||
- Service-Namen sind stabil, damit Service Discovery nicht bei jedem Lauf neue Services erzeugt.
|
|
||||||
|
|
||||||
## Statusmodell
|
|
||||||
|
|
||||||
### BizTalk Platform
|
|
||||||
|
|
||||||
- `OK`: BizTalk-WMI erreichbar.
|
|
||||||
- `UNKNOWN`: WMI nicht erreichbar oder kompletter Programmfehler.
|
|
||||||
|
|
||||||
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.
|
|
||||||
- `WARN`: mindestens `WarnResumableThreshold` resumable suspended instances.
|
|
||||||
- `CRIT`: mindestens `CritNonResumableThreshold` non-resumable suspended instances.
|
|
||||||
- `UNKNOWN`: Datenquelle nicht lesbar.
|
|
||||||
|
|
||||||
### BizTalk Host Instances
|
|
||||||
|
|
||||||
- `OK`: alle Host-Instanzen sind started.
|
|
||||||
- `WARN`: mindestens eine Host-Instanz ist pending.
|
|
||||||
- `CRIT`: mindestens eine Host-Instanz ist stopped oder unknown.
|
|
||||||
- `UNKNOWN`: keine Host-Instanzen gefunden oder Datenquelle nicht lesbar.
|
|
||||||
|
|
||||||
### BizTalk Runtime Artifacts
|
|
||||||
|
|
||||||
- `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.
|
|
||||||
|
|
||||||
### BizTalk Event Log
|
|
||||||
|
|
||||||
- `OK`: keine relevanten Fehler/Warnungen ueber Schwellwert.
|
|
||||||
- `WARN`: Fehler oder Warnungen ab `EventLogWarnThreshold`.
|
|
||||||
- `CRIT`: Fehler ab `EventLogCritThreshold`.
|
|
||||||
- `UNKNOWN`: Event Log nicht lesbar.
|
|
||||||
|
|
||||||
## Dashboard-Empfehlung
|
|
||||||
|
|
||||||
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 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`
|
1. `BizTalk Platform`
|
||||||
2. `BizTalk SQL Access`
|
2. `BizTalk SQL Access`
|
||||||
3. `BizTalk Suspended Instances`
|
3. `BizTalk Suspended Instances`
|
||||||
4. `BizTalk Host Instances`
|
4. `BizTalk Host Instances`
|
||||||
5. `BizTalk Event Log`
|
5. `BizTalk Receive Locations`
|
||||||
6. `BizTalk Runtime Artifacts`
|
6. `BizTalk Send Ports`
|
||||||
7. SQL-Server-Services aus dem Checkmk-MSSQL-Plugin
|
7. `BizTalk Orchestrations`
|
||||||
|
8. `BizTalk Event Log`
|
||||||
|
|
||||||
## Rollout-Vorgehen
|
Receive Locations und Send Ports alarmieren nur fuer unerwartete
|
||||||
|
Aus-Zustaende. Fachlich bewusst deaktivierte Namen werden exakt, ohne
|
||||||
|
Wildcards, in `ExpectedDisabledReceiveLocations` beziehungsweise
|
||||||
|
`ExpectedInactiveSendPorts` hinterlegt. Ein Eintrag ist entweder nur der Name
|
||||||
|
oder `Anwendung\Name`.
|
||||||
|
|
||||||
1. Build-Paket erzeugen.
|
Pro Summary werden maximal `MaxSummaryItems` Treffer angezeigt. Weitere
|
||||||
2. In `DEV` auf dem BizTalk-Server installieren.
|
Artefakte werden als `(+n more)` zusammengefasst. `MaxDetailCharacters`
|
||||||
3. `--self-test` und normalen Lauf ausfuehren.
|
begrenzt jede Detailausgabe, damit Checkmk-Ansichten kompakt bleiben, waehrend
|
||||||
4. Agent-Dump pruefen.
|
alle numerischen Metriken vollstaendig erhalten bleiben.
|
||||||
5. Checkmk Discovery durchfuehren.
|
|
||||||
6. Eine Woche Messwerte und false positives beobachten.
|
|
||||||
7. Nach `TST` und `ACC` uebernehmen.
|
|
||||||
8. In `PRD` mit `AlertOnArtifactRuntimeIssues=false` starten.
|
|
||||||
9. Nach Betriebsfreigabe Schwellwerte feinjustieren.
|
|
||||||
|
|
||||||
## Troubleshooting
|
### 5.3 SQL-Zugriffsprobe
|
||||||
|
|
||||||
### Service bleibt UNKNOWN
|
Der Provider oeffnet fuer Management- und Master-MessageBox-Datenbank eine
|
||||||
|
kurze `System.Data.SqlClient`-Verbindung mit integrierter
|
||||||
|
Windows-Authentifizierung, fuehrt `SELECT 1` aus und schliesst sie. Keine
|
||||||
|
BizTalk-Tabelle wird direkt gelesen oder veraendert.
|
||||||
|
|
||||||
|
Die Ausgabe `execution_identity=` zeigt deshalb das Provider-Konto, nicht mehr
|
||||||
|
`NT AUTHORITY\SYSTEM`. Der Check beweist, dass genau das Scheduled-Task-Konto
|
||||||
|
die Ziele erreichen kann.
|
||||||
|
|
||||||
|
### 5.4 Event Log
|
||||||
|
|
||||||
|
Der Provider liest das lokale Application Log im konfigurierten Zeitfenster
|
||||||
|
und filtert Quellen wie `BizTalk Server`, `XLANG/s`, `ENTSSO`,
|
||||||
|
`BizTalk Server Application` und `BizTalk Server EDI`.
|
||||||
|
|
||||||
|
## 6. Berechtigungsmodell
|
||||||
|
|
||||||
|
### 6.1 Bevorzugte BizTalk-Rolle
|
||||||
|
|
||||||
|
BizTalk Server 2020 fuehrte die `BizTalk Server Read Only Users`-Gruppe ein.
|
||||||
|
Sie darf Artefakte, Servicezustand, Message Flow und Trackinginformationen
|
||||||
|
lesen, aber keine administrativen Operationen ausfuehren. SQL-seitig wird sie
|
||||||
|
ueber `BTS_READONLY_USERS` vermittelt.
|
||||||
|
|
||||||
|
Soll:
|
||||||
|
|
||||||
|
```text
|
||||||
|
DOMAIN\svc_biztalk_cmk$
|
||||||
|
-> konfigurierte BizTalk Server Read Only Users-Domaingruppe
|
||||||
|
-> vorhandener Windows-Gruppenlogin am SQL Server
|
||||||
|
-> BTS_READONLY_USERS in den von BizTalk konfigurierten Datenbanken
|
||||||
|
```
|
||||||
|
|
||||||
|
Die genaue Gruppe ist aus
|
||||||
|
`MSBTS_GroupSetting.BizTalkReadOnlyUserGroup` beziehungsweise der
|
||||||
|
BizTalk-Administration zu lesen. Nicht vom Standardnamen ausgehen.
|
||||||
|
|
||||||
|
Die Operator-Gruppe (`BTS_OPERATORS`) wird erst geprueft, wenn eine konkret
|
||||||
|
benannte erforderliche WMI-Klasse trotz nachgewiesener Read-Only-Konfiguration
|
||||||
|
und neuem Anmeldetoken abgewiesen wird. Die Erweiterung muss mit Security,
|
||||||
|
BizTalk- und SQL-Administration begruendet werden.
|
||||||
|
|
||||||
|
Unzulaessig:
|
||||||
|
|
||||||
|
- Checkmk-Dienstkonto aendern,
|
||||||
|
- Maschinenkonto pauschal berechtigen,
|
||||||
|
- individueller SQL-Login fuer das Provider-Konto,
|
||||||
|
- direkte ad-hoc DB-Rollen,
|
||||||
|
- `db_owner` oder `sysadmin`,
|
||||||
|
- WMI-/DCOM-Vollzugriff als Reaktion auf einen SQL-Loginfehler.
|
||||||
|
|
||||||
|
### 6.2 Dateisystem-ACL
|
||||||
|
|
||||||
|
Der Installer entfernt die Vererbung an den Runtimeverzeichnissen und setzt
|
||||||
|
explizite Eintraege mit sprachneutralen Well-known-SIDs:
|
||||||
|
|
||||||
|
| Ziel | Collector | SYSTEM | lokale Administratoren |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| Runtime-Root | RX | RX | F |
|
||||||
|
| `data` | Modify | RX | F |
|
||||||
|
| `logs` | Modify | Modify | F |
|
||||||
|
|
||||||
|
`LocalSystem` kann deshalb den Snapshot lesen, aber im Datenverzeichnis nicht
|
||||||
|
schreiben. Schreibrecht im Logverzeichnis wird benoetigt, um abgelehnte
|
||||||
|
Snapshots aus dem Consumer-Kontext zu protokollieren.
|
||||||
|
|
||||||
|
## 7. Scheduled Task
|
||||||
|
|
||||||
|
Name:
|
||||||
|
|
||||||
|
```text
|
||||||
|
BizTalk Checkmk Pulse Provider
|
||||||
|
```
|
||||||
|
|
||||||
|
Sollkonfiguration:
|
||||||
|
|
||||||
|
| Einstellung | Wert |
|
||||||
|
| --- | --- |
|
||||||
|
| Trigger | einmalig, Wiederholung jede Minute ohne Enddatum |
|
||||||
|
| Programm | `%ProgramFiles%\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe` |
|
||||||
|
| Argument | `--collect` |
|
||||||
|
| Arbeitsverzeichnis | `%ProgramFiles%\BizTalkCheckmkPulse` |
|
||||||
|
| Benutzer | normales dediziertes AD-Servicekonto |
|
||||||
|
| Run level | `Limited`; keine lokale Administratorrolle erforderlich |
|
||||||
|
| Mehrfachinstanzen | `IgnoreNew` |
|
||||||
|
| Laufzeitlimit | 5 Minuten |
|
||||||
|
| StartWhenAvailable | aktiv |
|
||||||
|
| Restart | zweimal im Minutenabstand |
|
||||||
|
|
||||||
|
Der Task wird mit gespeichertem Dienstkontokennwort ausgefuehrt, also
|
||||||
|
unabhaengig von einer interaktiven Anmeldung. Bei Kennwortwechsel oder
|
||||||
|
-ablauf muss das Task-Kennwort aktualisiert werden. Ein gMSA ist weiterhin
|
||||||
|
optional unterstuetzt, aber nicht die produktive Standardannahme.
|
||||||
|
|
||||||
|
## 8. Installation
|
||||||
|
|
||||||
|
### 8.1 Build
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
scripts\test-release.cmd
|
||||||
|
scripts\package-release.cmd
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 Normales Servicekonto
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Install-BizTalkCheckmkPulse.ps1 `
|
||||||
|
-CollectorAccount 'BEW\svc_biztalk_cmk' `
|
||||||
|
-EnvironmentName ACC
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Installer muss als lokaler Administrator laufen. Er vergibt keine
|
||||||
|
AD-/BizTalk-/SQL-Rechte; diese bleiben getrennte administrative Freigaben.
|
||||||
|
|
||||||
|
Der installierte Task selbst laeuft mit `RunLevel Limited`. Der Installer fragt
|
||||||
|
das Kennwort mit `Get-Credential` ab; es wird nicht in Config oder Log
|
||||||
|
geschrieben. Das Konto benoetigt `Log on as a batch job`.
|
||||||
|
|
||||||
|
### 8.3 Optionales gMSA
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Install-BizTalkCheckmkPulse.ps1 `
|
||||||
|
-CollectorAccount 'BEW\svc_biztalk_cmk$' `
|
||||||
|
-Gmsa `
|
||||||
|
-EnvironmentName ACC
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.4 Checkmk
|
||||||
|
|
||||||
|
Der installierte Wrapper liegt unter:
|
||||||
|
|
||||||
|
```text
|
||||||
|
%ProgramData%\checkmk\agent\local\biztalk_checkmk_pulse.cmd
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Agent darf den Consumer synchron ausfuehren. Er liest nur maximal 1 MiB
|
||||||
|
lokale Daten; ein Checkmk-Async-Cache ist daher nicht notwendig. Falls alte
|
||||||
|
`check_mk.user.yml`-Regeln fuer den bisherigen 300-Sekunden-Cache existieren,
|
||||||
|
sollten sie entfernt werden, damit Checkmk den minuetlich aktualisierten
|
||||||
|
Snapshot nicht zusaetzlich verzoegert.
|
||||||
|
|
||||||
|
Nach dem Agent-Dump:
|
||||||
|
|
||||||
|
1. Service Discovery fuer den BizTalk-Host ausfuehren.
|
||||||
|
2. acht stabile Services aufnehmen; den alten Service
|
||||||
|
`BizTalk Runtime Artifacts` nach erfolgreicher Discovery entfernen.
|
||||||
|
3. Changes aktivieren.
|
||||||
|
4. Views/Benachrichtigungen nach Umgebung konfigurieren.
|
||||||
|
|
||||||
|
## 9. Logging
|
||||||
|
|
||||||
|
Pfad:
|
||||||
|
|
||||||
|
```text
|
||||||
|
%ProgramData%\BizTalkCheckmkPulse\logs\
|
||||||
|
biztalk-checkmk-pulse-YYYYMMDD.log
|
||||||
|
```
|
||||||
|
|
||||||
|
Jede Zeile enthaelt UTC-Zeit, Level, Komponente, PID, Windows-Identitaet und
|
||||||
|
einzeilige Nachricht. Provider-Erfolge werden pro Lauf geloggt; der Consumer
|
||||||
|
loggt nur abgelehnte Snapshots. Exceptions werden mit Typ, Nachricht,
|
||||||
|
HRESULT-/Providerdetails und Stacktrace einzeilig gespeichert.
|
||||||
|
|
||||||
|
Der erfolgreiche Abschluss nennt Suspensions, Routing Failure Reports,
|
||||||
|
Receive-Location-/Send-Port-Zahlen und Laufzeit. Jede strukturierte
|
||||||
|
WMI-/SQL-/Event-Log-Diagnose wird zusaetzlich als eigene `WARN`-Zeile
|
||||||
|
protokolliert und bleibt damit auch ausserhalb der gekuerzten Checkmk-Summary
|
||||||
|
vollstaendig nachvollziehbar.
|
||||||
|
|
||||||
|
Logging ist best effort: Ein blockiertes Log darf Checkmk-Ausgabe oder
|
||||||
|
Snapshot-Publikation nicht zerstoeren. Der Provider entfernt beim Start Dateien
|
||||||
|
aelter als `LogRetentionDays`.
|
||||||
|
|
||||||
|
## 10. Diagnose und Runbook
|
||||||
|
|
||||||
|
### 10.1 Grundpruefung
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Get-ScheduledTaskInfo -TaskName 'BizTalk Checkmk Pulse Provider'
|
||||||
|
Get-Item "$env:ProgramData\BizTalkCheckmkPulse\data\biztalk-checkmk-pulse.snapshot"
|
||||||
|
Get-Content "$env:ProgramData\BizTalkCheckmkPulse\logs\*.log" -Tail 100
|
||||||
|
& "$env:ProgramFiles\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe" --consume
|
||||||
|
& "C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.2 Kein Snapshot
|
||||||
|
|
||||||
Pruefen:
|
Pruefen:
|
||||||
|
|
||||||
```cmd
|
1. Task existiert und ist aktiviert.
|
||||||
"%ProgramData%\checkmk\agent\local\biztalk_checkmk_pulse.cmd"
|
2. `LastTaskResult` und Provider-Log.
|
||||||
|
3. Dienstkonto kann sich als Batch anmelden.
|
||||||
|
4. gespeichertes Task-Kennwort ist nach Rotation/Ablauf noch gueltig.
|
||||||
|
5. Provider besitzt Modify auf `data` und `logs`.
|
||||||
|
6. EXE/Config sind ausfuehrbar.
|
||||||
|
|
||||||
|
### 10.3 Staler Snapshot
|
||||||
|
|
||||||
|
Der Consumer nennt Alter und Grenzwert. Ursachen:
|
||||||
|
|
||||||
|
- Task deaktiviert oder Konto/Kennwort ungueltig,
|
||||||
|
- vorheriger Lauf haengt,
|
||||||
|
- WMI-/SQL-Timeouts,
|
||||||
|
- Task Scheduler lief waehrend Wartung nicht,
|
||||||
|
- Snapshot-Publikation wird durch AV/EDR oder ACL blockiert.
|
||||||
|
|
||||||
|
Nicht einfach `SnapshotMaxAgeSeconds` stark erhoehen. Zuerst den minuetlichen
|
||||||
|
Lauf wiederherstellen. Bei geplanten Wartungen darf Checkmk bewusst `UNKNOWN`
|
||||||
|
anzeigen.
|
||||||
|
|
||||||
|
### 10.4 SHA-256-/Formatfehler
|
||||||
|
|
||||||
|
Snapshot nicht reparieren oder direkt an Checkmk kopieren. Task neu starten,
|
||||||
|
Datentraeger und AV/EDR pruefen. Wiederholt sich der Fehler, Temporaerdateien,
|
||||||
|
NTFS-Ereignisse und Schreibrechte untersuchen.
|
||||||
|
|
||||||
|
### 10.5 BizTalk-/SQL-Permission
|
||||||
|
|
||||||
|
1. Log zeigt `execution_identity` des Providers.
|
||||||
|
2. `BizTalkReadOnlyUserGroup` gegen BizTalk-Konfiguration abgleichen.
|
||||||
|
3. AD-Gruppenmitgliedschaft des Provider-Kontos pruefen.
|
||||||
|
4. neuen Logontoken durch neuen Tasklauf sicherstellen.
|
||||||
|
5. SQL-seitig Gruppenlogin und `BTS_READONLY_USERS` read-only pruefen.
|
||||||
|
6. keine Einzelrechte erteilen.
|
||||||
|
7. Nur wenn eine spezifische Klasse weiter abgewiesen wird, Read-Only-Umfang
|
||||||
|
mit Microsoft-Dokumentation und BizTalk-Betrieb abgleichen; Operator-Rolle
|
||||||
|
ist eine bewusste Eskalation.
|
||||||
|
|
||||||
|
### 10.6 WMI-Schema
|
||||||
|
|
||||||
|
`InvalidClass` oder `InvalidQuery` wird als `Wmi/Schema` klassifiziert.
|
||||||
|
Berechtigungserhoehung kann eine nicht vorhandene Klasse nicht erzeugen.
|
||||||
|
Installierte BizTalk-Version, WQL und Properties pruefen.
|
||||||
|
|
||||||
|
## 11. Konfiguration
|
||||||
|
|
||||||
|
Alle AppSettings befinden sich in:
|
||||||
|
|
||||||
|
```text
|
||||||
|
%ProgramFiles%\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe.config
|
||||||
```
|
```
|
||||||
|
|
||||||
Wenn WMI nicht erreichbar ist:
|
Runtime:
|
||||||
|
|
||||||
|
| Key | Default | Bereich |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `SnapshotPath` | `%ProgramData%\BizTalkCheckmkPulse\data\...` | absolut |
|
||||||
|
| `SnapshotMaxAgeSeconds` | `180` | 60 bis 86400 |
|
||||||
|
| `SnapshotMaxBytes` | `1048576` | 4096 bis 16777216 |
|
||||||
|
| `LogDirectory` | `%ProgramData%\BizTalkCheckmkPulse\logs` | absolut |
|
||||||
|
| `LogRetentionDays` | `30` | 1 bis 365 |
|
||||||
|
|
||||||
|
BizTalk-Probes:
|
||||||
|
|
||||||
|
| Key | Default |
|
||||||
|
| --- | --- |
|
||||||
|
| `Server` | `.` |
|
||||||
|
| `QueryTimeoutSeconds` | `25` |
|
||||||
|
| `ProbeSqlConnectivity` | `true` |
|
||||||
|
| `SqlConnectionTimeoutSeconds` | `5` |
|
||||||
|
| `ProbeEventLog` | `true` |
|
||||||
|
| `EventLogLookbackMinutes` | `60` |
|
||||||
|
|
||||||
|
Alarmierung:
|
||||||
|
|
||||||
|
| Key | Default |
|
||||||
|
| --- | --- |
|
||||||
|
| `WarnResumableThreshold` | `1` |
|
||||||
|
| `CritNonResumableThreshold` | `1` |
|
||||||
|
| `CritRoutingFailureThreshold` | `1` |
|
||||||
|
| `AlertOnArtifactRuntimeIssues` | `true` |
|
||||||
|
| `ExpectedDisabledReceiveLocations` | leer; exakte Pipe-Liste |
|
||||||
|
| `ExpectedInactiveSendPorts` | leer; exakte Pipe-Liste |
|
||||||
|
| `AlertOnInactiveOrchestrations` | `false` |
|
||||||
|
| `MaxSummaryItems` | `5` |
|
||||||
|
| `MaxDetailCharacters` | `1600` |
|
||||||
|
| `EmitPerApplicationSuspensionServices` | `false` |
|
||||||
|
| `EventLogWarnThreshold` | `1` |
|
||||||
|
| `EventLogCritThreshold` | `10` |
|
||||||
|
|
||||||
|
## 12. Tests und Abnahmekriterien
|
||||||
|
|
||||||
|
Automatisiert:
|
||||||
|
|
||||||
|
- Release-Build .NET Framework 4.7.2,
|
||||||
|
- exakt acht Self-Test-Services,
|
||||||
|
- unbekannte Quellen werden `UNKNOWN`,
|
||||||
|
- dynamische Anwendungsservices nur bei bekannter Anwendung,
|
||||||
|
- WMI-Queryvertrag nutzt dokumentierte Properties,
|
||||||
|
- eingebetteter SQL-Loginfehler wird `Permission`,
|
||||||
|
- abgewiesener Principal wird extrahiert,
|
||||||
|
- Routing Failure Reports werden separat und als non-resumable gezaehlt,
|
||||||
|
- Receive-/Send-Allowlisten trennen bewusste von unerwarteten Aus-Zustaenden,
|
||||||
|
- ein fehlender `IsDisabled`-Wert wird `UNKNOWN` statt still als enabled,
|
||||||
|
- betroffene Namen und Gesamtlaenge der Summary bleiben begrenzt,
|
||||||
|
- Snapshot-Roundtrip und Ersatz,
|
||||||
|
- SHA-256-Manipulation wird verworfen,
|
||||||
|
- Stale-Snapshot wird verworfen,
|
||||||
|
- Consumer-Fallback enthaelt acht `UNKNOWN`-Services.
|
||||||
|
|
||||||
|
Windows-/ACC-Abnahme:
|
||||||
|
|
||||||
|
1. `LastTaskResult=0`.
|
||||||
|
2. Snapshot wird mindestens dreimal minuetlich aktualisiert.
|
||||||
|
3. Provider-Log nennt das dedizierte Konto.
|
||||||
|
4. `BizTalk Platform` zeigt `read_only_group=`.
|
||||||
|
5. `BizTalk SQL Access`: `targets=2`, `available=2`.
|
||||||
|
6. keine Permission-`UNKNOWN`s.
|
||||||
|
7. Suspensionsmetriken mit der BizTalk Group Hub Page plausibilisieren,
|
||||||
|
einschliesslich Routing Failure Reports.
|
||||||
|
8. Eine bewusst deaktivierte Receive Location und einen inaktiven Send Port
|
||||||
|
ueber die exakten Allowlisten als expected bestaetigen.
|
||||||
|
9. Einen Testnamen aus der Allowlist entfernen und den erwarteten CRIT mit
|
||||||
|
kurzem `affected=`-Detail pruefen.
|
||||||
|
10. ACL-Test: `LocalSystem` kann Snapshot lesen, nicht schreiben.
|
||||||
|
11. Task deaktivieren: nach 180 Sekunden acht stale-`UNKNOWN`s.
|
||||||
|
12. Task wieder aktivieren: naechster Snapshot stellt Echtzustand her.
|
||||||
|
13. Agent-Dump und Checkmk Service Discovery erfolgreich.
|
||||||
|
|
||||||
|
Erst nach ACC-Abnahme erfolgt der gestufte Rollout nach DEV/TST/PRD.
|
||||||
|
|
||||||
|
## 13. Deinstallation und Rollback
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Uninstall-BizTalkCheckmkPulse.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
`-KeepRuntimeData` erhaelt Logs und Snapshot. Das Skript entfernt Task,
|
||||||
|
Wrapper und Programmdateien, aber keine AD-Gruppenmitgliedschaft. Die
|
||||||
|
Berechtigung wird separat nach Change-Verfahren entzogen.
|
||||||
|
|
||||||
|
Rollback auf die alte direkte `LocalSystem`-Architektur ist nicht empfohlen.
|
||||||
|
Falls kurzfristig notwendig, muss Security die Maschinenkonto-Berechtigung
|
||||||
|
explizit freigeben; der Checkmk-Agentdienst selbst darf nicht auf das
|
||||||
|
Collector-Konto umgestellt werden.
|
||||||
|
|
||||||
|
## 14. Transport per certutil
|
||||||
|
|
||||||
|
Die Source-Uebergabe besteht aus ZIP plus Base64-TXT:
|
||||||
|
|
||||||
```cmd
|
```cmd
|
||||||
wmic /namespace:\\root\MicrosoftBizTalkServer path MSBTS_HostInstance get HostName,RunningServer,ServiceState
|
certutil -decode <datei>.zip.b64.txt <datei>.zip
|
||||||
|
certutil -hashfile <datei>.zip SHA256
|
||||||
|
tar -xf <datei>.zip
|
||||||
```
|
```
|
||||||
|
|
||||||
Der Checkmk Windows Agent laeuft normalerweise als LocalSystem. Daher immer auch den Agent-Dump verwenden:
|
ZIP und Base64 werden erst nach dem Git-Commit aus genau diesem Commit erzeugt.
|
||||||
|
|
||||||
```cmd
|
## 15. Referenzen
|
||||||
"C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump
|
|
||||||
```
|
|
||||||
|
|
||||||
Die Diagnose im Service-Text nach `Massnahme:` abarbeiten. Wichtige Kategorien:
|
- https://learn.microsoft.com/en-us/biztalk/core/managing-biztalk-server-security
|
||||||
|
- https://learn.microsoft.com/en-us/biztalk/core/windows-groups-and-user-accounts-in-biztalk-server
|
||||||
- `Wmi/Permission`: lokale Namespace-ACL oder BizTalk-Operator-Zuordnung pruefen.
|
- https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-groupsetting-biztalkreadonlyusergroup-property-wmi
|
||||||
- `Sql/Permission`: Maschinenkonto `DOMAIN\BIZTALKSERVER$` und BizTalk-Operator-Gruppe pruefen.
|
- https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-groupsetting-wmi
|
||||||
- `Sql/Connectivity`: SQL-Server-/Instanzname, DNS, SQL-Dienst, TCP und Firewall pruefen.
|
- https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-serviceinstance-serviceclass-property-wmi
|
||||||
- `Sql/Timeout`: SQL- und Netzwerkauslastung pruefen; Timeout nicht als erste Massnahme erhoehen.
|
- https://learn.microsoft.com/en-us/biztalk/core/types-of-message-failures
|
||||||
|
- https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-receivelocation-isdisabled-property-wmi
|
||||||
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.
|
- https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-sendport-status-property-wmi
|
||||||
|
- https://docs.checkmk.com/latest/en/agent_windows.html
|
||||||
### Keine Services in Discovery
|
- https://docs.checkmk.com/latest/en/localchecks.html
|
||||||
|
- https://docs.checkmk.com/latest/en/spool_directory.html
|
||||||
Pruefen:
|
|
||||||
|
|
||||||
- Liegt `biztalk_checkmk_pulse.cmd` direkt unter `%ProgramData%\checkmk\agent\local`?
|
|
||||||
- Gibt der Wrapper direkt eine Zeile im Format `0 "Service" metric=value Details` aus?
|
|
||||||
- Wurde der Checkmk-Agent nach Policy-/Bakery-Aenderungen neu ausgerollt?
|
|
||||||
|
|
||||||
### Runtime Artifacts zeigt deaktivierte Artefakte
|
|
||||||
|
|
||||||
Das ist standardmaessig `OK`, damit gewollt deaktivierte BizTalk-Artefakte nicht alarmieren. Fuer strengere PRD-Standards:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<add key="AlertOnArtifactRuntimeIssues" value="true" />
|
|
||||||
```
|
|
||||||
|
|
||||||
## Weiterentwicklung
|
|
||||||
|
|
||||||
Sinnvolle naechste Ausbaustufen:
|
|
||||||
|
|
||||||
- MKP mit Agent-Bakery-Regel fuer zentrale Konfiguration.
|
|
||||||
- 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.
|
|
||||||
|
|||||||
@@ -1,276 +1,393 @@
|
|||||||
# BizTalk Checkmk Pulse
|
# BizTalk Checkmk Pulse
|
||||||
|
|
||||||
`BizTalk Checkmk Pulse` ist ein lokaler Checkmk-Check fuer Microsoft BizTalk Server 2020 auf Windows. Er liest BizTalk-Betriebsdaten ueber WMI aus `root\MicrosoftBizTalkServer`, erzeugt Checkmk-Local-Check-Ausgaben und kann dadurch ohne serverseitigen Python-Check in Checkmk 2.4 integriert werden.
|
`BizTalk Checkmk Pulse` ist ein robuster Checkmk-2.4-Local-Check fuer Microsoft
|
||||||
|
BizTalk Server 2020 auf Windows Server 2019. Die Anwendung trennt den
|
||||||
|
berechtigten BizTalk-Datenzugriff vollstaendig vom Checkmk-Agenten:
|
||||||
|
|
||||||
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.
|
```text
|
||||||
|
Scheduled Task (normales dediziertes Servicekonto)
|
||||||
|
|
|
||||||
|
| jede Minute: BizTalkCheckmkPulse.exe --collect
|
||||||
|
v
|
||||||
|
lokales BizTalk-WMI + BizTalk-SQL + Application Event Log
|
||||||
|
|
|
||||||
|
| atomarer, versionierter, SHA-256-geschuetzter Snapshot
|
||||||
|
v
|
||||||
|
%ProgramData%\BizTalkCheckmkPulse\data\biztalk-checkmk-pulse.snapshot
|
||||||
|
^
|
||||||
|
| nur lesen: BizTalkCheckmkPulse.exe --consume
|
||||||
|
|
|
||||||
|
Checkmk Windows Agent (LocalSystem)
|
||||||
|
|
|
||||||
|
v
|
||||||
|
acht kompakte Checkmk Local Checks
|
||||||
|
```
|
||||||
|
|
||||||
## Warum Local Check statt serverseitigem Check-Plugin?
|
Damit bekommt `LocalSystem` keine BizTalk- oder SQL-Berechtigung. Nur das
|
||||||
|
dedizierte Provider-Konto wird in die konfigurierte BizTalk Server Read Only
|
||||||
|
Users-Gruppe aufgenommen. Der Checkmk-Consumer liest keine BizTalk-Datenbank,
|
||||||
|
verwendet kein WMI und nimmt keine Identitaetswechsel vor.
|
||||||
|
|
||||||
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:
|
## Warum die Architektur geaendert wurde
|
||||||
|
|
||||||
- keine PowerShell-Ausfuehrung erforderlich
|
Der ACC-Test vom 29.07.2026 zeigte:
|
||||||
- keine BizTalk-ExplorerOM/OperationsOM-DLL als Build-Abhaengigkeit
|
|
||||||
- keine Python-Check-API-Abhaengigkeit auf der Checkmk-Site
|
|
||||||
- einfache Verteilung auf die BizTalk-Maschinen per Dateiablage, Softwareverteilung oder Agent Bakery
|
|
||||||
- automatische Service Discovery in Checkmk
|
|
||||||
|
|
||||||
## Erzeugte Checkmk-Services
|
```text
|
||||||
|
Login failed for user 'BEW\AV23AGPWBIO1$'
|
||||||
|
```
|
||||||
|
|
||||||
Standardmaessig entstehen diese stabilen Services:
|
Der Checkmk-Agent lief korrekt als `NT AUTHORITY\SYSTEM` und erreichte lokales
|
||||||
|
BizTalk-WMI. Datenbankgestuetzte WMI-Abfragen wurden jedoch am SQL Server als
|
||||||
|
Maschinenkonto `BEW\AV23AGPWBIO1$` abgewiesen. Eine Mitgliedschaft des
|
||||||
|
Computerkontos wuerde die BizTalk-Netzwerkberechtigung allen als `LocalSystem`
|
||||||
|
laufenden Diensten des Servers geben.
|
||||||
|
|
||||||
|
Die neue Trennung reduziert diesen Berechtigungsumfang. Fuer BizTalk Server
|
||||||
|
2020 ist die konfigurierte `BizTalk Server Read Only Users`-Gruppe mit der
|
||||||
|
SQL-Datenbankrolle `BTS_READONLY_USERS` die bevorzugte Grenze. Die
|
||||||
|
`BizTalk Server Operators`-Gruppe ist nur ein dokumentierter Fallback, wenn
|
||||||
|
eine konkret benoetigte WMI-Klasse trotz bestaetigter Read-Only-Zuordnung
|
||||||
|
abgewiesen wird. Direkte SQL-Logins, manuelle Datenbankrollen und `sysadmin`
|
||||||
|
sind keine Loesung.
|
||||||
|
|
||||||
|
## Robustheit
|
||||||
|
|
||||||
|
Der Datenaustausch ist bewusst defensiv:
|
||||||
|
|
||||||
|
- Der Provider schreibt zuerst eine eindeutige Temporaerdatei im Zielordner,
|
||||||
|
leert Betriebssystempuffer und ersetzt danach den Snapshot atomar.
|
||||||
|
- Der Snapshot enthaelt Formatversion, UTC-Zeit, Quellmaschine,
|
||||||
|
Provider-Identitaet, Zeilenanzahl und SHA-256 des Payloads.
|
||||||
|
- Der Consumer akzeptiert nur denselben Rechner, gueltiges UTF-8, intakte
|
||||||
|
Checkmk-Zeilen, korrekte SHA-256-Pruefsumme und ein maximales Alter von
|
||||||
|
standardmaessig 180 Sekunden.
|
||||||
|
- Fehlende, veraltete, abgeschnittene, manipulierte oder unlesbare Dateien
|
||||||
|
ergeben acht gueltige `UNKNOWN`-Services statt einer kaputten Agent-Ausgabe.
|
||||||
|
- Ein exklusives Lock und die Task-Einstellung `IgnoreNew` verhindern
|
||||||
|
ueberlappende Providerlaeufe.
|
||||||
|
- Ein unerwarteter Providerfehler erzeugt nach Moeglichkeit einen aktuellen
|
||||||
|
`UNKNOWN`-Snapshot und einen ungleich null lautenden Task-Exitcode.
|
||||||
|
- Provider und Consumer protokollieren in taegliche Dateien; die Aufbewahrung
|
||||||
|
ist standardmaessig 30 Tage.
|
||||||
|
- Snapshotgroesse, WMI-/SQL-Timeouts, Log-Retention und Stale-Grenze sind
|
||||||
|
begrenzt und konfigurierbar.
|
||||||
|
|
||||||
|
## Erzeugte Services
|
||||||
|
|
||||||
|
Standardmaessig entstehen:
|
||||||
|
|
||||||
- `BizTalk Platform`
|
- `BizTalk Platform`
|
||||||
- `BizTalk SQL Access`
|
- `BizTalk SQL Access`
|
||||||
- `BizTalk Suspended Instances`
|
- `BizTalk Suspended Instances`
|
||||||
- `BizTalk Host Instances`
|
- `BizTalk Host Instances`
|
||||||
- `BizTalk Runtime Artifacts`
|
- `BizTalk Receive Locations`
|
||||||
|
- `BizTalk Send Ports`
|
||||||
|
- `BizTalk Orchestrations`
|
||||||
- `BizTalk Event Log`
|
- `BizTalk Event Log`
|
||||||
|
|
||||||
Wenn `EnvironmentName=ACC`, `DEV`, `TST` oder `PRD` gesetzt wird, wird der Name vor den Suffix gesetzt, z.B. `BizTalk PRD Suspended Instances`. Das ist praktisch, wenn die Umgebung bereits im Hostnamen oder Ordner abgebildet ist, aber nicht zwingend noetig.
|
Die drei Artefaktbereiche sind absichtlich getrennte Services. Dadurch sind
|
||||||
|
Zustand und Graphen direkt erkennbar, ohne eine lange Sammelzeile zu lesen:
|
||||||
|
|
||||||
Details zu Statuslogik und Metriken stehen in [docs/CheckmkServices.md](docs/CheckmkServices.md).
|
- Suspensions: total, resumable, non-resumable und Routing Failure Reports
|
||||||
Beispielausgaben stehen in [docs/ExampleOutput.md](docs/ExampleOutput.md).
|
- Receive Locations: total, enabled, unerwartet/bewusst disabled und unbekannt
|
||||||
|
- Send Ports: total, started, stopped, bound, unbekannt sowie bewusst inactive
|
||||||
|
- Orchestrations: total, started, stopped, bound, unbound und unbekannt
|
||||||
|
|
||||||
## Build
|
Pro Service werden standardmaessig maximal fuenf betroffene Namen gezeigt.
|
||||||
|
Weitere Treffer erscheinen nur als `(+n more)`; Details sind zusaetzlich auf
|
||||||
|
1600 Zeichen begrenzt. Metriken bleiben trotzdem vollstaendig.
|
||||||
|
|
||||||
Voraussetzungen auf einem Windows-Build-Host:
|
Mit `EnvironmentName=ACC`, `DEV`, `TST` oder `PRD` wird die Umgebung in den
|
||||||
|
Servicenamen aufgenommen, zum Beispiel `BizTalk ACC Platform`.
|
||||||
|
|
||||||
- Visual Studio 2022 Build Tools oder Visual Studio
|
Statuslogik und Metriken: [docs/CheckmkServices.md](docs/CheckmkServices.md)
|
||||||
|
|
||||||
|
Beispielausgaben: [docs/ExampleOutput.md](docs/ExampleOutput.md)
|
||||||
|
|
||||||
|
## Voraussetzungen
|
||||||
|
|
||||||
|
Build-Host:
|
||||||
|
|
||||||
|
- Visual Studio 2019/2022 Build Tools oder Visual Studio
|
||||||
- MSBuild im `PATH`
|
- MSBuild im `PATH`
|
||||||
- .NET Framework 4.7.2 Developer Pack
|
- .NET Framework 4.7.2 Developer Pack
|
||||||
|
|
||||||
Build:
|
BizTalk-Server:
|
||||||
|
|
||||||
|
- Windows Server 2019
|
||||||
|
- BizTalk Server 2020 und lokaler Namespace
|
||||||
|
`root\MicrosoftBizTalkServer`
|
||||||
|
- .NET Framework 4.7.2
|
||||||
|
- Checkmk Windows Agent
|
||||||
|
- administrativer Zugriff fuer die einmalige Installation
|
||||||
|
- normales dediziertes AD-Servicekonto fuer den Provider
|
||||||
|
|
||||||
|
Das Provider-Konto benoetigt:
|
||||||
|
|
||||||
|
- lokales Recht zur Ausfuehrung als Scheduled Task
|
||||||
|
- lokalen Lese-/Ausfuehrungszugriff auf die installierte EXE
|
||||||
|
- Schreibzugriff nur auf Snapshot- und Logverzeichnis
|
||||||
|
- Mitgliedschaft in der exakt konfigurierten BizTalk Server Read Only
|
||||||
|
Users-Gruppe
|
||||||
|
|
||||||
|
Es soll weder lokaler Administrator noch SQL-`sysadmin` sein. Der Scheduled
|
||||||
|
Task laeuft mit `RunLevel Limited`. Das Servicekonto braucht ein gespeichertes
|
||||||
|
Task-Kennwort und das Recht `Log on as a batch job`. Ein gMSA bleibt optional,
|
||||||
|
ist aber fuer diese Installation nicht vorausgesetzt.
|
||||||
|
|
||||||
|
## Build und Tests
|
||||||
|
|
||||||
```cmd
|
```cmd
|
||||||
scripts\build-release.cmd
|
scripts\build-release.cmd
|
||||||
```
|
scripts\test-release.cmd
|
||||||
|
|
||||||
Deployment-Paket erstellen:
|
|
||||||
|
|
||||||
```cmd
|
|
||||||
scripts\package-release.cmd
|
scripts\package-release.cmd
|
||||||
```
|
```
|
||||||
|
|
||||||
Ergebnis:
|
Das Paket wird unter `artifacts\BizTalkCheckmkPulse-deploy` erzeugt:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
artifacts\BizTalkCheckmkPulse-deploy\
|
BizTalkCheckmkPulse-deploy\
|
||||||
|
Install-BizTalkCheckmkPulse.ps1
|
||||||
|
Uninstall-BizTalkCheckmkPulse.ps1
|
||||||
biztalk_checkmk_pulse.cmd
|
biztalk_checkmk_pulse.cmd
|
||||||
BizTalkCheckmkPulse\
|
application\
|
||||||
BizTalkCheckmkPulse.exe
|
BizTalkCheckmkPulse.exe
|
||||||
BizTalkCheckmkPulse.exe.config
|
BizTalkCheckmkPulse.exe.config
|
||||||
```
|
```
|
||||||
|
|
||||||
Validierung nach dem Build:
|
Format-Self-Test ohne WMI, SQL oder Event Log:
|
||||||
|
|
||||||
```cmd
|
```cmd
|
||||||
artifacts\BizTalkCheckmkPulse-deploy\biztalk_checkmk_pulse.cmd --self-test
|
artifacts\BizTalkCheckmkPulse-deploy\application\BizTalkCheckmkPulse.exe --self-test
|
||||||
```
|
```
|
||||||
|
|
||||||
## Installation auf dem BizTalk-Server
|
Erwartet werden exakt acht `OK`-Zeilen. Die Regressionstests pruefen
|
||||||
|
zusaetzlich Snapshot-Roundtrip, atomaren Ersatz, SHA-256-Manipulation,
|
||||||
|
Stale-Erkennung, stabile Fallbacks und die bestehenden BizTalk-WMI-Diagnosen.
|
||||||
|
Ein Mono-Build ist eine hilfreiche Quellcodepruefung, ersetzt aber nicht die
|
||||||
|
Windows-/BizTalk-Laufzeitvalidierung.
|
||||||
|
|
||||||
Kopiere den Inhalt von `artifacts\BizTalkCheckmkPulse-deploy` nach:
|
## Berechtigung vorbereiten
|
||||||
|
|
||||||
|
Die exakte Read-Only-Gruppe wird in der BizTalk Administration Console unter
|
||||||
|
den Eigenschaften der BizTalk-Gruppe abgelesen. Ein bereits berechtigtes Konto
|
||||||
|
kann sie alternativ ermitteln:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Get-CimInstance `
|
||||||
|
-Namespace root/MicrosoftBizTalkServer `
|
||||||
|
-ClassName MSBTS_GroupSetting |
|
||||||
|
Select-Object Name, BizTalkReadOnlyUserGroup, BizTalkOperatorGroup,
|
||||||
|
MgmtDbServerName, MgmtDbName
|
||||||
|
```
|
||||||
|
|
||||||
|
Ein AD-Administrator nimmt das neue Provider-Konto in
|
||||||
|
`BizTalkReadOnlyUserGroup` auf. Nach AD-Replikation muss das Servicekonto durch
|
||||||
|
einen neuen Tasklauf einen neuen Anmeldetoken erhalten.
|
||||||
|
|
||||||
|
Die BizTalk-Konfiguration muss die Domain-Gruppe bereits als Windows-Login und
|
||||||
|
in `BizTalkMgmtDb`, `BizTalkMsgBoxDb`, `BizTalkDTADb`,
|
||||||
|
`BizTalkRuleEngineDb` sowie gegebenenfalls `BAMPrimaryImport` mit
|
||||||
|
`BTS_READONLY_USERS` abbilden. Eine fehlende Abbildung wird durch BizTalk- und
|
||||||
|
SQL-Administration fuer die Gruppe repariert, nicht als Einzelberechtigung
|
||||||
|
fuer das Provider-Konto.
|
||||||
|
|
||||||
|
## Installation mit normalem Servicekonto
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Set-Location C:\Temp\BizTalkCheckmkPulse-deploy
|
||||||
|
.\Install-BizTalkCheckmkPulse.ps1 `
|
||||||
|
-CollectorAccount 'BEW\svc_biztalk_cmk' `
|
||||||
|
-EnvironmentName ACC
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Installer fragt das Kennwort ueber `Get-Credential` ab und speichert es
|
||||||
|
durch die Windows-Aufgabenplanung. Das Kennwort steht weder in der
|
||||||
|
Konfigurationsdatei noch in den Logs.
|
||||||
|
|
||||||
|
Wenn das Kennwort rotiert oder ablaeuft, muss es im Scheduled Task aktualisiert
|
||||||
|
werden. Bis dahin wird der Snapshot nach 180 Sekunden stale und Checkmk zeigt
|
||||||
|
alle acht Services als `UNKNOWN`.
|
||||||
|
|
||||||
|
Ein gMSA kann weiterhin optional mit `-Gmsa` installiert werden; die
|
||||||
|
produktive Standardbeschreibung geht vom normalen Servicekonto aus.
|
||||||
|
|
||||||
|
Der Installer:
|
||||||
|
|
||||||
|
1. kopiert EXE und Config nach
|
||||||
|
`%ProgramFiles%\BizTalkCheckmkPulse`,
|
||||||
|
2. erstellt `%ProgramData%\BizTalkCheckmkPulse\data` und `logs`,
|
||||||
|
3. setzt explizite ACLs fuer Administratoren, Provider und `LocalSystem`,
|
||||||
|
4. installiert nur den kleinen `.cmd`-Consumer unter
|
||||||
|
`%ProgramData%\checkmk\agent\local`,
|
||||||
|
5. registriert `BizTalk Checkmk Pulse Provider` minuetlich mit
|
||||||
|
`IgnoreNew`, fuenf Minuten Laufzeitlimit und zwei Wiederholungen,
|
||||||
|
6. fuehrt den Self-Test aus und startet den Provider einmalig.
|
||||||
|
|
||||||
|
PowerShell wird nur fuer Installation und Betriebsdiagnose verwendet. Der
|
||||||
|
minuetliche Provider und der Checkmk-Consumer sind .NET-/CMD-Laufzeitcode und
|
||||||
|
haengen nicht von der PowerShell Execution Policy ab.
|
||||||
|
|
||||||
|
## Verifikation auf dem Server
|
||||||
|
|
||||||
|
Task und letzter Lauf:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Get-ScheduledTask -TaskName 'BizTalk Checkmk Pulse Provider' |
|
||||||
|
Select-Object TaskName, State
|
||||||
|
Get-ScheduledTaskInfo -TaskName 'BizTalk Checkmk Pulse Provider' |
|
||||||
|
Select-Object LastRunTime, LastTaskResult, NextRunTime
|
||||||
|
```
|
||||||
|
|
||||||
|
Provider-Log:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Get-ChildItem "$env:ProgramData\BizTalkCheckmkPulse\logs" |
|
||||||
|
Sort-Object LastWriteTime -Descending |
|
||||||
|
Select-Object -First 3 Name, Length, LastWriteTime
|
||||||
|
|
||||||
|
Get-Content `
|
||||||
|
"$env:ProgramData\BizTalkCheckmkPulse\logs\biztalk-checkmk-pulse-*.log" `
|
||||||
|
-Tail 100
|
||||||
|
```
|
||||||
|
|
||||||
|
Snapshot und Consumer:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Get-Item `
|
||||||
|
"$env:ProgramData\BizTalkCheckmkPulse\data\biztalk-checkmk-pulse.snapshot" |
|
||||||
|
Select-Object FullName, Length, LastWriteTimeUtc
|
||||||
|
|
||||||
|
& "$env:ProgramFiles\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe" --consume
|
||||||
|
```
|
||||||
|
|
||||||
|
Verbindlicher Test im echten `LocalSystem`-Kontext:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
& "C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump |
|
||||||
|
Select-String -Pattern "BizTalk|UNKNOWN|Snapshot|Permission" -Context 0,1
|
||||||
|
```
|
||||||
|
|
||||||
|
Danach in Checkmk eine Service Discovery ausfuehren, die acht Services
|
||||||
|
aufnehmen und Changes aktivieren. Ein zusaetzlicher Checkmk-Async-Cache ist
|
||||||
|
nicht erforderlich: Der Consumer liest nur eine kleine lokale Datei und der
|
||||||
|
Provider besitzt bereits seinen eigenen Minutentakt.
|
||||||
|
|
||||||
|
## Dateisystem und Sicherheitsgrenzen
|
||||||
|
|
||||||
```text
|
```text
|
||||||
%ProgramData%\checkmk\agent\local
|
%ProgramFiles%\BizTalkCheckmkPulse\
|
||||||
|
BizTalkCheckmkPulse.exe
|
||||||
|
BizTalkCheckmkPulse.exe.config
|
||||||
|
|
||||||
|
%ProgramData%\BizTalkCheckmkPulse\
|
||||||
|
data\
|
||||||
|
biztalk-checkmk-pulse.snapshot
|
||||||
|
biztalk-checkmk-pulse.snapshot.provider.lock
|
||||||
|
logs\
|
||||||
|
biztalk-checkmk-pulse-YYYYMMDD.log
|
||||||
|
|
||||||
|
%ProgramData%\checkmk\agent\local\
|
||||||
|
biztalk_checkmk_pulse.cmd
|
||||||
```
|
```
|
||||||
|
|
||||||
Zielstruktur:
|
ACL-Soll:
|
||||||
|
|
||||||
```text
|
| Pfad | Provider | LocalSystem | Administratoren |
|
||||||
%ProgramData%\checkmk\agent\local\biztalk_checkmk_pulse.cmd
|
| --- | --- | --- | --- |
|
||||||
%ProgramData%\checkmk\agent\local\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe
|
| Programm | Lesen/Ausfuehren | Lesen/Ausfuehren | Vollzugriff |
|
||||||
%ProgramData%\checkmk\agent\local\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe.config
|
| `data` | Aendern | Lesen/Ausfuehren | Vollzugriff |
|
||||||
```
|
| `logs` | Aendern | Aendern | Vollzugriff |
|
||||||
|
|
||||||
Manueller Test auf dem BizTalk-Server:
|
Der Snapshot enthaelt Monitoringzustand und kompakte Fehlerdetails, aber keine
|
||||||
|
Passwoerter oder Nachrichteninhalte. Der Consumer validiert die Datei trotzdem
|
||||||
```cmd
|
vollstaendig, bevor er sie an Checkmk weitergibt.
|
||||||
"%ProgramData%\checkmk\agent\local\biztalk_checkmk_pulse.cmd" --self-test
|
|
||||||
"%ProgramData%\checkmk\agent\local\biztalk_checkmk_pulse.cmd"
|
|
||||||
```
|
|
||||||
|
|
||||||
Agent-Ausgabe wie Checkmk sie sieht:
|
|
||||||
|
|
||||||
```cmd
|
|
||||||
"C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump
|
|
||||||
```
|
|
||||||
|
|
||||||
Danach in Checkmk fuer den BizTalk-Host eine Service Discovery ausfuehren, die neuen Services aufnehmen und Changes aktivieren.
|
|
||||||
|
|
||||||
## Konfiguration
|
## Konfiguration
|
||||||
|
|
||||||
Die Konfiguration liegt neben der EXE:
|
Datei:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
%ProgramData%\checkmk\agent\local\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe.config
|
%ProgramFiles%\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe.config
|
||||||
```
|
```
|
||||||
|
|
||||||
Wichtige Werte:
|
Wichtige Werte:
|
||||||
|
|
||||||
| Key | Default | Bedeutung |
|
| Key | Default | Bedeutung |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `Server` | `.` | Lokaler BizTalk-Server. Remote-WMI ist moeglich, aber nicht empfohlen. |
|
| `EnvironmentName` | leer | Optional `ACC`, `DEV`, `TST`, `PRD`. |
|
||||||
| `ServicePrefix` | `BizTalk` | Prefix fuer alle Checkmk-Services. |
|
| `SnapshotPath` | `%ProgramData%\BizTalkCheckmkPulse\data\...` | Gemeinsame Provider-/Consumer-Datei. |
|
||||||
| `EnvironmentName` | leer | Optional `ACC`, `DEV`, `TST` oder `PRD`. |
|
| `SnapshotMaxAgeSeconds` | `180` | Ab diesem Alter liefert der Consumer `UNKNOWN`. |
|
||||||
| `QueryTimeoutSeconds` | `25` | WMI-Timeout pro Query. |
|
| `SnapshotMaxBytes` | `1048576` | Harte Eingabegroesse fuer den Consumer. |
|
||||||
| `ProbeSqlConnectivity` | `true` | Prueft die integrierte Windows-Anmeldung an den per WMI ermittelten Management- und MessageBox-Datenbanken. |
|
| `LogDirectory` | `%ProgramData%\BizTalkCheckmkPulse\logs` | Tageslogs. |
|
||||||
| `SqlConnectionTimeoutSeconds` | `5` | Timeout je SQL-Ziel fuer Verbindungsaufbau und harmlose Testabfrage. |
|
| `LogRetentionDays` | `30` | Provider bereinigt aeltere Logs. |
|
||||||
| `WarnResumableThreshold` | `1` | WARN ab n resumable suspended instances. |
|
| `QueryTimeoutSeconds` | `25` | WMI-Timeout je Query. |
|
||||||
| `CritNonResumableThreshold` | `1` | CRIT ab n non-resumable suspended instances. |
|
| `SqlConnectionTimeoutSeconds` | `5` | SQL-Timeout je Ziel. |
|
||||||
| `AlertOnArtifactRuntimeIssues` | `false` | Wenn `true`, werden deaktivierte Receive Locations und inaktive Ports/Orchestrations als WARN gewertet. |
|
| `WarnResumableThreshold` | `1` | WARN ab n resumable Suspensions. |
|
||||||
| `EmitPerApplicationSuspensionServices` | `false` | Erzeugt zusaetzliche Services pro Anwendung mit Suspensions. |
|
| `CritNonResumableThreshold` | `1` | CRIT ab n non-resumable Suspensions. |
|
||||||
| `ProbeEventLog` | `true` | Liest das Windows Application Log nach BizTalk-bezogenen Sources. |
|
| `CritRoutingFailureThreshold` | `1` | CRIT ab n Routing Failure Reports. |
|
||||||
| `EventLogLookbackMinutes` | `60` | Zeitraum fuer Event-Log-Auswertung. |
|
| `AlertOnArtifactRuntimeIssues` | `true` | Unerwartet deaktivierte Receive Locations bzw. inaktive Send Ports werden CRIT. |
|
||||||
| `EventLogWarnThreshold` | `1` | WARN ab n Errors oder Warnings. |
|
| `ExpectedDisabledReceiveLocations` | leer | Pipe-getrennte exakte Allowlist: `Name` oder `Anwendung\Name`. |
|
||||||
| `EventLogCritThreshold` | `10` | CRIT ab n Errors. |
|
| `ExpectedInactiveSendPorts` | leer | Pipe-getrennte exakte Allowlist: `Name` oder `Anwendung\Name`. |
|
||||||
|
| `AlertOnInactiveOrchestrations` | `false` | Optional WARN fuer stopped/bound/unbound Orchestrations. |
|
||||||
|
| `MaxSummaryItems` | `5` | Maximal angezeigte betroffene Artefakte je Service. |
|
||||||
|
| `MaxDetailCharacters` | `1600` | Harte Obergrenze fuer Checkmk-Summary. |
|
||||||
|
| `EmitPerApplicationSuspensionServices` | `false` | Zusaetzliche Anwendungsservices. |
|
||||||
|
| `EventLogLookbackMinutes` | `60` | Event-Log-Zeitfenster des Providers. |
|
||||||
|
|
||||||
Empfehlung:
|
Nach einer Config-Aenderung den Scheduled Task manuell starten. Der Consumer
|
||||||
|
liest den naechsten atomar publizierten Snapshot.
|
||||||
|
|
||||||
- In `DEV`, `TST`, `ACC`: `AlertOnArtifactRuntimeIssues=false`, damit bewusst gestoppte Artefakte nicht rauschen.
|
## Fehlerbilder
|
||||||
- In `PRD`: erst zwei Wochen beobachten; danach nur aktivieren, wenn die Runtime-Artefakte wirklich als Betriebsstandard immer aktiv sein muessen.
|
|
||||||
- `EmitPerApplicationSuspensionServices=true` nur verwenden, wenn Anwendungsteams eigene Services benoetigen. Sonst bleibt die Discovery schlanker.
|
|
||||||
|
|
||||||
## Integration in Checkmk Managed Services Edition 2.4
|
| Beobachtung | Ursache / Massnahme |
|
||||||
|
| --- | --- |
|
||||||
|
| Alle acht Services melden fehlenden Snapshot | Task, Provider-Log, Task-Konto/Kennwort und ACL pruefen. |
|
||||||
|
| Snapshot ist `stale` | `LastTaskResult`, Laufzeit, WMI-/SQL-Timeout und Log pruefen. |
|
||||||
|
| SHA-256 oder Format ungueltig | Datei nicht manuell bearbeiten; Datentraeger/AV und Schreibpfad pruefen, Task neu starten. |
|
||||||
|
| Provider meldet `Login failed` | Provider-Konto und exakt konfigurierte Read-Only-Gruppe sowie `BTS_READONLY_USERS` pruefen. |
|
||||||
|
| Receive Locations / Send Ports sind CRIT | `affected=` pruefen; nur fachlich bewusst inaktive Namen exakt in die jeweilige Allowlist aufnehmen. |
|
||||||
|
| `Wmi/Schema` | Klasse/Properties gegen BizTalk-2020-Schema pruefen; keine Rechte ausweiten. |
|
||||||
|
| Nur Event Log `UNKNOWN` | lokalen Application-Log-Zugriff des Provider-Kontos pruefen. |
|
||||||
|
| Task-Result `2` | Parallelstart oder Snapshot-I/O; Log und Lock/ACL pruefen. |
|
||||||
|
|
||||||
Aufgaben der Checkmk-Kollegen:
|
Ausfuehrliche Betriebs- und Architekturdetails stehen in
|
||||||
|
[Dokumentation.md](Dokumentation.md).
|
||||||
|
|
||||||
1. BizTalk-Hosts in Checkmk anlegen oder bestehende Hosts pruefen.
|
## Deinstallation
|
||||||
2. Sicherstellen, dass der Checkmk Windows Agent installiert, registriert und erreichbar ist.
|
|
||||||
3. Plugin-Dateien auf die BizTalk-Server verteilen, manuell oder per Agent Bakery.
|
|
||||||
4. Optional `EnvironmentName` je Umgebung setzen, z.B. `ACC`, `DEV`, `TST` oder `PRD`.
|
|
||||||
5. Agent-Ausgabe mit `cmk-agent-ctl.exe dump` pruefen.
|
|
||||||
6. Service Discovery fuer jeden BizTalk-Host ausfuehren.
|
|
||||||
7. Gefundene `BizTalk ...` Services aufnehmen und Changes aktivieren.
|
|
||||||
8. Views, Dashboards, Servicegruppen und Benachrichtigungen fuer die BizTalk-Services konfigurieren.
|
|
||||||
|
|
||||||
Manuelle Integration:
|
|
||||||
|
|
||||||
1. Dateien auf dem BizTalk-Server nach `%ProgramData%\checkmk\agent\local` kopieren.
|
|
||||||
2. Optional `BizTalkCheckmkPulse.exe.config` je Umgebung anpassen.
|
|
||||||
3. Agent-Dump pruefen.
|
|
||||||
4. Service Discovery auf dem BizTalk-Host ausfuehren.
|
|
||||||
5. Services in ein BizTalk-Dashboard aufnehmen.
|
|
||||||
|
|
||||||
Integration ueber Agent Bakery:
|
|
||||||
|
|
||||||
1. Deployment-Dateien in der Checkmk-Site als Custom-Agent-Dateien bereitstellen.
|
|
||||||
2. Windows-Agent-Regel fuer die BizTalk-Hosts erstellen.
|
|
||||||
3. Agent backen und auf ACC/DEV/TST/PRD-BizTalk-Hosts ausrollen.
|
|
||||||
4. Discovery und Dashboard-Aufnahme durchfuehren.
|
|
||||||
|
|
||||||
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
|
```powershell
|
||||||
& "C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump |
|
.\Uninstall-BizTalkCheckmkPulse.ps1
|
||||||
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 Skript fragt vor dem Entfernen. Mit `-KeepRuntimeData` bleiben Snapshot und
|
||||||
|
Logs erhalten:
|
||||||
|
|
||||||
Das Programm prueft Berechtigungen aktiv:
|
```powershell
|
||||||
|
.\Uninstall-BizTalkCheckmkPulse.ps1 -KeepRuntimeData
|
||||||
- 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
|
Die AD-Gruppenmitgliedschaft des Provider-Kontos wird bewusst nicht automatisch
|
||||||
|
geaendert und muss separat durch AD-/BizTalk-Administration entfernt werden.
|
||||||
|
|
||||||
1. Pruefen, unter welchem Computerkonto der BizTalk-Server im Netzwerk auftritt, normalerweise `DOMAIN\BIZTALKSERVER$`.
|
## Certutil-Transport
|
||||||
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.
|
Zu jeder Uebergabe wird ein Source-ZIP und eine certutil-kompatible
|
||||||
|
Base64-Textdatei erzeugt. Auf Windows:
|
||||||
|
|
||||||
Entscheidungsmatrix:
|
```cmd
|
||||||
|
certutil -decode biztalk-checkmk-pulse-source-<datum>-<commit>.zip.b64.txt biztalk-checkmk-pulse-source.zip
|
||||||
|
certutil -hashfile biztalk-checkmk-pulse-source.zip SHA256
|
||||||
|
tar -xf biztalk-checkmk-pulse-source.zip
|
||||||
|
```
|
||||||
|
|
||||||
| Ergebnis im Agent-Dump | Massnahme |
|
Die konkrete Datei und SHA-256-Summe werden bei der Uebergabe genannt.
|
||||||
| --- | --- |
|
|
||||||
| 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.
|
|
||||||
- `BizTalk Event Log`: Schwellwerte nach Beobachtungsphase anpassen.
|
|
||||||
|
|
||||||
## Quellen
|
## Quellen
|
||||||
|
|
||||||
- Checkmk Local Checks: https://docs.checkmk.com/latest/en/localchecks.html
|
- Microsoft: BizTalk `MSBTS_GroupSetting.BizTalkReadOnlyUserGroup`
|
||||||
- Checkmk Windows Agent und Plugin-Pfade: https://docs.checkmk.com/latest/en/agent_windows.html
|
- Microsoft: Windows Groups and User Accounts in BizTalk Server
|
||||||
- Checkmk Agent-Based Plugin-Entwicklung: https://docs.checkmk.com/latest/en/devel_check_plugins.html
|
- Microsoft: Managing BizTalk Server Security
|
||||||
- Checkmk Bakery API: https://docs.checkmk.com/latest/en/bakery_api.html
|
- Checkmk: Windows Agent und Local Checks
|
||||||
- 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
|
Die genauen Links stehen in [Dokumentation.md](Dokumentation.md).
|
||||||
- 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
|
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string]$CollectorAccount,
|
||||||
|
|
||||||
|
[switch]$Gmsa,
|
||||||
|
|
||||||
|
[ValidateSet("", "ACC", "DEV", "TST", "PRD")]
|
||||||
|
[string]$EnvironmentName = "",
|
||||||
|
|
||||||
|
[string]$InstallDirectory = "$env:ProgramFiles\BizTalkCheckmkPulse",
|
||||||
|
|
||||||
|
[string]$CheckmkLocalDirectory = "$env:ProgramData\checkmk\agent\local"
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
Set-StrictMode -Version 2.0
|
||||||
|
|
||||||
|
$taskName = "BizTalk Checkmk Pulse Provider"
|
||||||
|
$runtimeRoot = Join-Path $env:ProgramData "BizTalkCheckmkPulse"
|
||||||
|
$dataDirectory = Join-Path $runtimeRoot "data"
|
||||||
|
$logDirectory = Join-Path $runtimeRoot "logs"
|
||||||
|
$sourceApplication = Join-Path $PSScriptRoot "application"
|
||||||
|
$sourceWrapper = Join-Path $PSScriptRoot "biztalk_checkmk_pulse.cmd"
|
||||||
|
$targetExe = Join-Path $InstallDirectory "BizTalkCheckmkPulse.exe"
|
||||||
|
$targetConfig = "$targetExe.config"
|
||||||
|
$targetWrapper = Join-Path $CheckmkLocalDirectory "biztalk_checkmk_pulse.cmd"
|
||||||
|
|
||||||
|
function Assert-Administrator {
|
||||||
|
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||||
|
$principal = New-Object Security.Principal.WindowsPrincipal($identity)
|
||||||
|
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||||
|
throw "Die Installation muss in einer administrativen Windows PowerShell ausgefuehrt werden."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-Icacls {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)][string]$Path,
|
||||||
|
[Parameter(Mandatory = $true)][string[]]$Arguments
|
||||||
|
)
|
||||||
|
|
||||||
|
& icacls.exe $Path @Arguments | Out-Host
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "icacls ist fuer '$Path' mit Exitcode $LASTEXITCODE fehlgeschlagen."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert-Administrator
|
||||||
|
|
||||||
|
if (-not (Test-Path -LiteralPath $sourceApplication -PathType Container)) {
|
||||||
|
throw "Quellverzeichnis fehlt: $sourceApplication"
|
||||||
|
}
|
||||||
|
if (-not (Test-Path -LiteralPath $sourceWrapper -PathType Leaf)) {
|
||||||
|
throw "Checkmk-Wrapper fehlt: $sourceWrapper"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Gmsa -and -not $CollectorAccount.EndsWith('$')) {
|
||||||
|
throw "Ein gMSA-Kontoname muss mit '$' enden, z.B. DOMAIN\svc_biztalk_cmk$."
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Installiere Programmdateien nach $InstallDirectory ..."
|
||||||
|
New-Item -ItemType Directory -Path $InstallDirectory -Force | Out-Null
|
||||||
|
Copy-Item -Path (Join-Path $sourceApplication "*") -Destination $InstallDirectory -Force
|
||||||
|
|
||||||
|
[xml]$configuration = Get-Content -LiteralPath $targetConfig
|
||||||
|
$environmentSetting = $configuration.configuration.appSettings.add |
|
||||||
|
Where-Object { $_.key -eq "EnvironmentName" } |
|
||||||
|
Select-Object -First 1
|
||||||
|
if ($null -eq $environmentSetting) {
|
||||||
|
throw "EnvironmentName fehlt in $targetConfig."
|
||||||
|
}
|
||||||
|
$environmentSetting.value = $EnvironmentName
|
||||||
|
$configuration.Save($targetConfig)
|
||||||
|
|
||||||
|
Write-Host "Erzeuge Runtime-Verzeichnisse und Least-Privilege-ACLs ..."
|
||||||
|
New-Item -ItemType Directory -Path $runtimeRoot, $dataDirectory, $logDirectory -Force | Out-Null
|
||||||
|
|
||||||
|
# Runtime-Root: Provider lesen, SYSTEM lesen, lokale Administratoren verwalten.
|
||||||
|
Invoke-Icacls -Path $runtimeRoot -Arguments @(
|
||||||
|
"/inheritance:r",
|
||||||
|
"/grant:r",
|
||||||
|
"*S-1-5-18:(OI)(CI)(RX)",
|
||||||
|
"*S-1-5-32-544:(OI)(CI)(F)",
|
||||||
|
"${CollectorAccount}:(OI)(CI)(RX)"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Daten: ausschliesslich Provider schreibt, LocalSystem liest den Snapshot.
|
||||||
|
Invoke-Icacls -Path $dataDirectory -Arguments @(
|
||||||
|
"/inheritance:r",
|
||||||
|
"/grant:r",
|
||||||
|
"*S-1-5-18:(OI)(CI)(RX)",
|
||||||
|
"*S-1-5-32-544:(OI)(CI)(F)",
|
||||||
|
"${CollectorAccount}:(OI)(CI)(M)"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Logs: Provider und LocalSystem-Consumer duerfen getrennte Diagnosezeilen anhaengen.
|
||||||
|
Invoke-Icacls -Path $logDirectory -Arguments @(
|
||||||
|
"/inheritance:r",
|
||||||
|
"/grant:r",
|
||||||
|
"*S-1-5-18:(OI)(CI)(M)",
|
||||||
|
"*S-1-5-32-544:(OI)(CI)(F)",
|
||||||
|
"${CollectorAccount}:(OI)(CI)(M)"
|
||||||
|
)
|
||||||
|
|
||||||
|
Write-Host "Installiere Checkmk Local Check nach $CheckmkLocalDirectory ..."
|
||||||
|
New-Item -ItemType Directory -Path $CheckmkLocalDirectory -Force | Out-Null
|
||||||
|
Copy-Item -LiteralPath $sourceWrapper -Destination $targetWrapper -Force
|
||||||
|
|
||||||
|
Write-Host "Registriere Scheduled Task '$taskName' ..."
|
||||||
|
$action = New-ScheduledTaskAction `
|
||||||
|
-Execute $targetExe `
|
||||||
|
-Argument "--collect" `
|
||||||
|
-WorkingDirectory $InstallDirectory
|
||||||
|
$trigger = New-ScheduledTaskTrigger `
|
||||||
|
-Once `
|
||||||
|
-At (Get-Date).AddMinutes(1) `
|
||||||
|
-RepetitionInterval (New-TimeSpan -Minutes 1)
|
||||||
|
$settings = New-ScheduledTaskSettingsSet `
|
||||||
|
-MultipleInstances IgnoreNew `
|
||||||
|
-ExecutionTimeLimit (New-TimeSpan -Minutes 5) `
|
||||||
|
-StartWhenAvailable `
|
||||||
|
-RestartCount 2 `
|
||||||
|
-RestartInterval (New-TimeSpan -Minutes 1)
|
||||||
|
|
||||||
|
if ($Gmsa) {
|
||||||
|
$principal = New-ScheduledTaskPrincipal `
|
||||||
|
-UserId $CollectorAccount `
|
||||||
|
-LogonType Password `
|
||||||
|
-RunLevel Limited
|
||||||
|
$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings -Principal $principal
|
||||||
|
Register-ScheduledTask -TaskName $taskName -InputObject $task -Force | Out-Null
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$credential = Get-Credential `
|
||||||
|
-UserName $CollectorAccount `
|
||||||
|
-Message "Kennwort fuer das dedizierte BizTalk-Monitoringkonto eingeben"
|
||||||
|
if ($credential.UserName -ne $CollectorAccount) {
|
||||||
|
throw "Das eingegebene Konto stimmt nicht mit CollectorAccount ueberein."
|
||||||
|
}
|
||||||
|
|
||||||
|
$principal = New-ScheduledTaskPrincipal `
|
||||||
|
-UserId $CollectorAccount `
|
||||||
|
-LogonType Password `
|
||||||
|
-RunLevel Limited
|
||||||
|
$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings -Principal $principal
|
||||||
|
Register-ScheduledTask `
|
||||||
|
-TaskName $taskName `
|
||||||
|
-InputObject $task `
|
||||||
|
-User $CollectorAccount `
|
||||||
|
-Password $credential.GetNetworkCredential().Password `
|
||||||
|
-Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Fuehre formatseitigen Self-Test aus ..."
|
||||||
|
$selfTest = & $targetExe --self-test
|
||||||
|
if ($LASTEXITCODE -ne 0 -or @($selfTest).Count -ne 8) {
|
||||||
|
throw "Self-Test fehlgeschlagen. Erwartet wurden acht Checkmk-Zeilen."
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Starte den Provider einmalig ..."
|
||||||
|
Start-ScheduledTask -TaskName $taskName
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Installation abgeschlossen."
|
||||||
|
Write-Host "Naechste Pruefungen:"
|
||||||
|
Write-Host " Get-ScheduledTaskInfo -TaskName '$taskName'"
|
||||||
|
Write-Host " Get-Content '$logDirectory\biztalk-checkmk-pulse-*.log' -Tail 50"
|
||||||
|
Write-Host " & '$targetExe' --consume"
|
||||||
|
Write-Host " & 'C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe' dump"
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "High")]
|
||||||
|
param(
|
||||||
|
[string]$InstallDirectory = "$env:ProgramFiles\BizTalkCheckmkPulse",
|
||||||
|
[string]$CheckmkLocalDirectory = "$env:ProgramData\checkmk\agent\local",
|
||||||
|
[switch]$KeepRuntimeData
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$taskName = "BizTalk Checkmk Pulse Provider"
|
||||||
|
$runtimeRoot = Join-Path $env:ProgramData "BizTalkCheckmkPulse"
|
||||||
|
$wrapper = Join-Path $CheckmkLocalDirectory "biztalk_checkmk_pulse.cmd"
|
||||||
|
|
||||||
|
if ($PSCmdlet.ShouldProcess($taskName, "Scheduled Task entfernen")) {
|
||||||
|
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
if (Test-Path -LiteralPath $wrapper) {
|
||||||
|
if ($PSCmdlet.ShouldProcess($wrapper, "Checkmk Local Check entfernen")) {
|
||||||
|
Remove-Item -LiteralPath $wrapper -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Test-Path -LiteralPath $InstallDirectory) {
|
||||||
|
if ($PSCmdlet.ShouldProcess($InstallDirectory, "Programmdateien entfernen")) {
|
||||||
|
Remove-Item -LiteralPath $InstallDirectory -Recurse -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (-not $KeepRuntimeData -and (Test-Path -LiteralPath $runtimeRoot)) {
|
||||||
|
if ($PSCmdlet.ShouldProcess($runtimeRoot, "Snapshot und Logs entfernen")) {
|
||||||
|
Remove-Item -LiteralPath $runtimeRoot -Recurse -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,31 @@
|
|||||||
@echo off
|
@echo off
|
||||||
setlocal
|
setlocal
|
||||||
|
|
||||||
set "BASE=%~dp0BizTalkCheckmkPulse"
|
set "BASE=%ProgramFiles%\BizTalkCheckmkPulse"
|
||||||
set "EXE=%BASE%\BizTalkCheckmkPulse.exe"
|
set "EXE=%BASE%\BizTalkCheckmkPulse.exe"
|
||||||
|
|
||||||
if not exist "%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 Platform" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Install-BizTalkCheckmkPulse.ps1 erneut als Administrator ausfuehren 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 Receive Locations" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen.
|
||||||
|
echo 3 "BizTalk Send Ports" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen.
|
||||||
|
echo 3 "BizTalk Orchestrations" - 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
|
exit /b 0
|
||||||
)
|
)
|
||||||
|
|
||||||
"%EXE%" %*
|
"%EXE%" --consume %*
|
||||||
set "EXITCODE=%ERRORLEVEL%"
|
set "EXITCODE=%ERRORLEVEL%"
|
||||||
if not "%EXITCODE%"=="0" (
|
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 Platform" - Snapshot-Consumer konnte nicht erfolgreich gestartet werden, Exitcode=%EXITCODE%. Massnahme: .NET Framework 4.7.2, Installation und Provider-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 Receive Locations" - BizTalkCheckmkPulse.exe endete mit Exitcode=%EXITCODE%. Massnahme: Programmstart und Provider-Log pruefen.
|
||||||
|
echo 3 "BizTalk Send Ports" - BizTalkCheckmkPulse.exe endete mit Exitcode=%EXITCODE%. Massnahme: Programmstart und Provider-Log pruefen.
|
||||||
|
echo 3 "BizTalk Orchestrations" - BizTalkCheckmkPulse.exe endete mit Exitcode=%EXITCODE%. Massnahme: Programmstart und Provider-Log pruefen.
|
||||||
|
echo 3 "BizTalk Event Log" - BizTalkCheckmkPulse.exe endete mit Exitcode=%EXITCODE%. Massnahme: Programmstart und Windows Application Log pruefen.
|
||||||
)
|
)
|
||||||
exit /b 0
|
exit /b 0
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
BizTalk Checkmk Pulse - ACC Berechtigung und Architektur
|
||||||
|
Stand: 30.07.2026
|
||||||
|
|
||||||
|
BEFUND
|
||||||
|
======
|
||||||
|
|
||||||
|
Host: AV23AGPWBIO1
|
||||||
|
Checkmk-Identitaet: NT AUTHORITY\SYSTEM
|
||||||
|
Alte Netzwerkidentitaet: BEW\AV23AGPWBIO1$
|
||||||
|
Fehler: COMException HRESULT=0x80131904
|
||||||
|
Innerer Fehler: Login failed for user 'BEW\AV23AGPWBIO1$'
|
||||||
|
|
||||||
|
Der lokale BizTalk-WMI-Namespace war erreichbar. Datenbankgestuetzte
|
||||||
|
BizTalk-WMI-Abfragen scheiterten am SQL-Login des Maschinenkontos.
|
||||||
|
|
||||||
|
NEUE VERBINDLICHE ARCHITEKTUR
|
||||||
|
=============================
|
||||||
|
|
||||||
|
Das Maschinenkonto erhaelt keine BizTalk-Berechtigung.
|
||||||
|
|
||||||
|
1. Ein normales dediziertes Servicekonto laeuft minuetlich als
|
||||||
|
Scheduled Task "BizTalk Checkmk Pulse Provider".
|
||||||
|
2. Dieses Konto fuehrt BizTalkCheckmkPulse.exe --collect aus.
|
||||||
|
3. Es schreibt atomar einen SHA-256-geschuetzten Snapshot nach:
|
||||||
|
|
||||||
|
%ProgramData%\BizTalkCheckmkPulse\data\biztalk-checkmk-pulse.snapshot
|
||||||
|
|
||||||
|
4. Der Checkmk-Agent bleibt LocalSystem und startet nur den Consumer.
|
||||||
|
5. Der Consumer liest und validiert den Snapshot. Er fuehrt kein WMI und
|
||||||
|
keinen SQL-Zugriff aus.
|
||||||
|
|
||||||
|
BERECHTIGUNG DES PROVIDER-KONTOS
|
||||||
|
================================
|
||||||
|
|
||||||
|
Bevorzugte Rolle fuer BizTalk Server 2020:
|
||||||
|
|
||||||
|
BizTalk Server Read Only Users
|
||||||
|
SQL-Datenbankrolle: BTS_READONLY_USERS
|
||||||
|
|
||||||
|
Die exakt konfigurierte Gruppe mit einem bereits berechtigten Konto ermitteln:
|
||||||
|
|
||||||
|
Get-CimInstance -Namespace root/MicrosoftBizTalkServer `
|
||||||
|
-ClassName MSBTS_GroupSetting |
|
||||||
|
Select-Object Name, BizTalkReadOnlyUserGroup, BizTalkOperatorGroup,
|
||||||
|
MgmtDbServerName, MgmtDbName
|
||||||
|
|
||||||
|
Das Provider-Konto durch AD-Administration in BizTalkReadOnlyUserGroup
|
||||||
|
aufnehmen. Nicht vom Standardnamen ausgehen.
|
||||||
|
|
||||||
|
Die Operator-Gruppe ist nur eine begruendungspflichtige Eskalation, falls eine
|
||||||
|
konkret benoetigte WMI-Klasse trotz bestaetigter Read-Only-Zuordnung und neuem
|
||||||
|
Anmeldetoken verweigert wird.
|
||||||
|
|
||||||
|
NICHT DURCHFUEHREN
|
||||||
|
==================
|
||||||
|
|
||||||
|
- keinen SQL-Einzellogin fuer Provider- oder Maschinenkonto
|
||||||
|
- keine ad-hoc BizTalk-Datenbankrollen
|
||||||
|
- kein db_owner oder sysadmin
|
||||||
|
- keine pauschalen DCOM-/WMI-ACL-Erweiterungen
|
||||||
|
- Checkmk-Agentdienst nicht auf das Provider-Konto umstellen
|
||||||
|
- Maschinenkonto BEW\AV23AGPWBIO1$ nicht fuer diese Loesung berechtigen
|
||||||
|
|
||||||
|
INSTALLATION
|
||||||
|
============
|
||||||
|
|
||||||
|
Normales Servicekonto:
|
||||||
|
|
||||||
|
.\Install-BizTalkCheckmkPulse.ps1 `
|
||||||
|
-CollectorAccount 'BEW\svc_biztalk_cmk' `
|
||||||
|
-EnvironmentName ACC
|
||||||
|
|
||||||
|
VERIFIKATION
|
||||||
|
============
|
||||||
|
|
||||||
|
Get-ScheduledTaskInfo -TaskName 'BizTalk Checkmk Pulse Provider'
|
||||||
|
|
||||||
|
Get-Content `
|
||||||
|
"$env:ProgramData\BizTalkCheckmkPulse\logs\*.log" `
|
||||||
|
-Tail 100
|
||||||
|
|
||||||
|
Get-Item `
|
||||||
|
"$env:ProgramData\BizTalkCheckmkPulse\data\biztalk-checkmk-pulse.snapshot"
|
||||||
|
|
||||||
|
& "$env:ProgramFiles\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe" --consume
|
||||||
|
|
||||||
|
& "C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump |
|
||||||
|
Select-String -Pattern "BizTalk|UNKNOWN|Permission|snapshot" -Context 0,1
|
||||||
|
|
||||||
|
ERWARTET
|
||||||
|
========
|
||||||
|
|
||||||
|
- LastTaskResult = 0
|
||||||
|
- Snapshot wird minuetlich aktualisiert
|
||||||
|
- Provider-Log nennt das dedizierte Konto
|
||||||
|
- BizTalk Platform zeigt read_only_group=<exakte Gruppe>
|
||||||
|
- BizTalk SQL Access zeigt targets=2 und available=2
|
||||||
|
- Suspensionsmetriken enthalten resumable, non-resumable und Routing Failure Reports
|
||||||
|
- Receive Locations und Send Ports trennen expected von unexpected inactive
|
||||||
|
- keine berechtigungsbedingten UNKNOWN-Services
|
||||||
|
- LocalSystem besitzt auf data nur Lesezugriff
|
||||||
|
|
||||||
|
QUELLEN
|
||||||
|
=======
|
||||||
|
|
||||||
|
https://learn.microsoft.com/en-us/biztalk/core/managing-biztalk-server-security
|
||||||
|
https://learn.microsoft.com/en-us/biztalk/core/windows-groups-and-user-accounts-in-biztalk-server
|
||||||
|
https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-groupsetting-biztalkreadonlyusergroup-property-wmi
|
||||||
|
https://docs.checkmk.com/latest/en/agent_windows.html
|
||||||
|
https://docs.checkmk.com/latest/en/localchecks.html
|
||||||
+81
-22
@@ -8,19 +8,24 @@ Alle Services werden als Checkmk Local Checks ausgegeben. Das Format ist:
|
|||||||
|
|
||||||
Die Ausgabe ist so gebaut, dass sie auch mit Checkmk 2.4.0p4 kompatibel bleibt: zwischen den vier Feldern steht genau ein normales Leerzeichen.
|
Die Ausgabe ist so gebaut, dass sie auch mit Checkmk 2.4.0p4 kompatibel bleibt: zwischen den vier Feldern steht genau ein normales Leerzeichen.
|
||||||
|
|
||||||
|
Die fachlichen Daten werden minuetlich vom privilegierten Scheduled-Task-
|
||||||
|
Provider erzeugt. Der Checkmk-Agent liest als `LocalSystem` nur den
|
||||||
|
integritaets- und altersgeprueften Snapshot. Ist dieser Transport nicht
|
||||||
|
verfuegbar, liefern alle acht stabilen Services `UNKNOWN`.
|
||||||
|
|
||||||
## BizTalk Platform
|
## BizTalk Platform
|
||||||
|
|
||||||
Zweck:
|
Zweck:
|
||||||
|
|
||||||
- Prueft, ob der BizTalk-WMI-Namespace erreichbar ist.
|
- Prueft, ob der BizTalk-WMI-Namespace erreichbar ist.
|
||||||
- Zeigt Server, Gruppe, Management-DB und MessageBox-DB, soweit WMI diese Werte liefert.
|
- Zeigt Server, Gruppe, konfigurierte BizTalk-Read-Only-/Operator-Gruppe, Management-DB und Master-MessageBox-DB aus `MSBTS_GroupSetting`.
|
||||||
|
|
||||||
Status:
|
Status:
|
||||||
|
|
||||||
| Status | Bedingung |
|
| Status | Bedingung |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `OK` | WMI-Verbindung und beide Plattform-Pflichtklassen erfolgreich. |
|
| `OK` | WMI-Verbindung erfolgreich und `MSBTS_GroupSetting` liefert beide vollstaendigen Datenbankziele. |
|
||||||
| `UNKNOWN` | Namespace, `MSBTS_GroupSetting`, `MSBTS_MessageBoxSetting` oder Programmstart fehlgeschlagen. |
|
| `UNKNOWN` | Namespace, `MSBTS_GroupSetting`, ein erforderliches Datenbankziel oder der Programmstart fehlgeschlagen. |
|
||||||
|
|
||||||
Metriken:
|
Metriken:
|
||||||
|
|
||||||
@@ -30,12 +35,13 @@ Metriken:
|
|||||||
|
|
||||||
Zweck:
|
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.
|
- Oeffnet mit der Windows-Identitaet des privilegierten Provider-Tasks eine integrierte SQL-Verbindung.
|
||||||
- Fuehrt die harmlose Abfrage `SELECT 1` aus und schliesst die Verbindung sofort wieder.
|
- Fuehrt die harmlose Abfrage `SELECT 1` aus und schliesst die Verbindung sofort wieder.
|
||||||
- Zeigt lokale Ausfuehrungsidentitaet und erwartete Netzwerkidentitaet an.
|
- 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.
|
Der Service ersetzt nicht das Checkmk-MSSQL-Plugin. Er prueft den fuer das
|
||||||
|
BizTalk-Monitoring relevanten Zugriff des dedizierten Provider-Kontos.
|
||||||
|
|
||||||
Status:
|
Status:
|
||||||
|
|
||||||
@@ -53,7 +59,9 @@ Fehlerkategorien:
|
|||||||
| `Sql/Connectivity` | DNS, SQL-Dienst, Instanz, TCP-Port oder Firewall nicht erreichbar. |
|
| `Sql/Connectivity` | DNS, SQL-Dienst, Instanz, TCP-Port oder Firewall nicht erreichbar. |
|
||||||
| `Sql/Timeout` | Verbindungsaufbau oder Testabfrage ueberschreitet das konfigurierte Zeitlimit. |
|
| `Sql/Timeout` | Verbindungsaufbau oder Testabfrage ueberschreitet das konfigurierte Zeitlimit. |
|
||||||
| `Sql/Provider` | Sonstiger von SQL Server oder SqlClient gemeldeter Providerfehler. |
|
| `Sql/Provider` | Sonstiger von SQL Server oder SqlClient gemeldeter Providerfehler. |
|
||||||
| `Sql/Configuration` | WMI-Ziele unvollstaendig oder TLS-, Zertifikats-, SPN-/SSPI-Konfiguration fehlerhaft. |
|
| `Sql/Configuration` | WMI-Ziele aus einem Nicht-Berechtigungsgrund unvollstaendig oder TLS-, Zertifikats-, SPN-/SSPI-Konfiguration fehlerhaft. |
|
||||||
|
|
||||||
|
Ist die Zielermittlung wegen eines eingebetteten SQL-Loginfehlers des BizTalk-WMI-Providers unvollstaendig, wird die Folgediagnose als `Sql/Permission` statt als `Sql/Configuration` ausgegeben.
|
||||||
|
|
||||||
Metriken:
|
Metriken:
|
||||||
|
|
||||||
@@ -69,6 +77,7 @@ Zweck:
|
|||||||
|
|
||||||
- Ermittelt suspendierte BizTalk-Service-Instanzen.
|
- Ermittelt suspendierte BizTalk-Service-Instanzen.
|
||||||
- Unterscheidet resumable und non-resumable.
|
- Unterscheidet resumable und non-resumable.
|
||||||
|
- Zaehlt Routing Failure Reports (`ServiceClass=64`) separat.
|
||||||
- Zeigt betroffene Anwendungen und Beispiele in der Summary.
|
- Zeigt betroffene Anwendungen und Beispiele in der Summary.
|
||||||
|
|
||||||
Status:
|
Status:
|
||||||
@@ -78,6 +87,7 @@ Status:
|
|||||||
| `OK` | keine suspendierten Instanzen. |
|
| `OK` | keine suspendierten Instanzen. |
|
||||||
| `WARN` | `biztalk_suspended_resumable >= WarnResumableThreshold`. |
|
| `WARN` | `biztalk_suspended_resumable >= WarnResumableThreshold`. |
|
||||||
| `CRIT` | `biztalk_suspended_nonresumable >= CritNonResumableThreshold`. |
|
| `CRIT` | `biztalk_suspended_nonresumable >= CritNonResumableThreshold`. |
|
||||||
|
| `CRIT` | `biztalk_routing_failure_reports >= CritRoutingFailureThreshold`. |
|
||||||
| `UNKNOWN` | `MSBTS_ServiceInstance` nicht verlaesslich lesbar oder Programmfehler. |
|
| `UNKNOWN` | `MSBTS_ServiceInstance` nicht verlaesslich lesbar oder Programmfehler. |
|
||||||
|
|
||||||
Metriken:
|
Metriken:
|
||||||
@@ -87,12 +97,14 @@ Metriken:
|
|||||||
| `biztalk_suspended_total` | Summe aller suspendierten Instanzen. |
|
| `biztalk_suspended_total` | Summe aller suspendierten Instanzen. |
|
||||||
| `biztalk_suspended_resumable` | Resumable suspended instances. |
|
| `biztalk_suspended_resumable` | Resumable suspended instances. |
|
||||||
| `biztalk_suspended_nonresumable` | Non-resumable suspended instances. |
|
| `biztalk_suspended_nonresumable` | Non-resumable suspended instances. |
|
||||||
|
| `biztalk_routing_failure_reports` | Routing Failure Reports; Teilmenge der non-resumable Instanzen. |
|
||||||
|
|
||||||
## BizTalk Host Instances
|
## BizTalk Host Instances
|
||||||
|
|
||||||
Zweck:
|
Zweck:
|
||||||
|
|
||||||
- Ueberwacht BizTalk Host Instances auf dem BizTalk-Server.
|
- 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.
|
- Nicht gestartete Host Instances sind normalerweise betriebsrelevant.
|
||||||
|
|
||||||
Status:
|
Status:
|
||||||
@@ -114,38 +126,85 @@ Metriken:
|
|||||||
| `biztalk_host_instances_pending` | StartPending/StopPending. |
|
| `biztalk_host_instances_pending` | StartPending/StopPending. |
|
||||||
| `biztalk_host_instances_unknown` | Nicht interpretierbarer Status. |
|
| `biztalk_host_instances_unknown` | Nicht interpretierbarer Status. |
|
||||||
|
|
||||||
## BizTalk Runtime Artifacts
|
## BizTalk Receive Locations
|
||||||
|
|
||||||
Zweck:
|
Zweck:
|
||||||
|
|
||||||
- Summiert Laufzeitzustaende von Receive Locations, Send Ports und Orchestrations.
|
- Zaehlt alle konfigurierten Receive Locations und ihren `IsDisabled`-Zustand.
|
||||||
- Liefert dashboard-taugliche Zahlen zur Laufzeitlandschaft.
|
- Trennt bewusst deaktivierte von unerwartet deaktivierten Namen.
|
||||||
|
|
||||||
Status:
|
Status:
|
||||||
|
|
||||||
| Status | Bedingung |
|
| Status | Bedingung |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `OK` | Artefakte lesbar; keine unbekannten Statuswerte. |
|
| `OK` | Daten lesbar und keine unerwartet deaktivierte Receive Location. |
|
||||||
| `WARN` | Nur bei `AlertOnArtifactRuntimeIssues=true` und inaktiven/deaktivierten Artefakten. |
|
| `CRIT` | Mindestens eine unerwartet deaktivierte Receive Location bei `AlertOnArtifactRuntimeIssues=true`. |
|
||||||
| `CRIT` | Unbekannte Send-Port- oder Orchestration-Statuswerte. |
|
| `UNKNOWN` | `MSBTS_ReceiveLocation` nicht verlaesslich lesbar oder `IsDisabled` eines Objekts nicht interpretierbar. |
|
||||||
| `UNKNOWN` | Mindestens eine erforderliche Artefaktklasse nicht lesbar oder Programmfehler. |
|
|
||||||
|
|
||||||
Metriken:
|
Metriken:
|
||||||
|
|
||||||
| Metrik | Bedeutung |
|
| Metrik | Bedeutung |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `biztalk_applications` | Anzahl erkannter BizTalk-Anwendungen. |
|
| `biztalk_receive_locations_total` | Gesamtzahl Receive Locations. |
|
||||||
| `biztalk_receive_locations` | Gesamtzahl Receive Locations. |
|
| `biztalk_receive_locations_enabled` | Aktivierte Receive Locations. |
|
||||||
| `biztalk_receive_locations_disabled` | Deaktivierte Receive Locations. |
|
| `biztalk_receive_locations_disabled` | Deaktivierte Receive Locations. |
|
||||||
| `biztalk_send_ports` | Gesamtzahl Send Ports. |
|
| `biztalk_receive_locations_unexpected_disabled` | Deaktiviert und nicht in der Allowlist; CRIT ab 1. |
|
||||||
|
| `biztalk_receive_locations_expected_disabled` | Bewusst deaktiviert und exakt allowlisted. |
|
||||||
|
| `biztalk_receive_locations_unknown` | Fehlender oder nicht interpretierbarer `IsDisabled`-Wert. |
|
||||||
|
|
||||||
|
## BizTalk Send Ports
|
||||||
|
|
||||||
|
Status:
|
||||||
|
|
||||||
|
| Status | Bedingung |
|
||||||
|
| --- | --- |
|
||||||
|
| `OK` | Alle nicht allowlisted Send Ports sind started. |
|
||||||
|
| `CRIT` | Mindestens ein unerwartet stopped/bound Port oder unbekannter Status. |
|
||||||
|
| `UNKNOWN` | `MSBTS_SendPort` nicht verlaesslich lesbar. |
|
||||||
|
|
||||||
|
Metriken:
|
||||||
|
|
||||||
|
| Metrik | Bedeutung |
|
||||||
|
| --- | --- |
|
||||||
|
| `biztalk_send_ports_total` | Gesamtzahl Send Ports. |
|
||||||
| `biztalk_send_ports_started` | Gestartete Send Ports. |
|
| `biztalk_send_ports_started` | Gestartete Send Ports. |
|
||||||
| `biztalk_send_ports_inactive` | Bound oder stopped Send Ports. |
|
| `biztalk_send_ports_stopped` | Enlisted, Subscription deaktiviert (`Status=2`). |
|
||||||
|
| `biztalk_send_ports_bound` | Bound/unenlisted (`Status=1`). |
|
||||||
|
| `biztalk_send_ports_unexpected_inactive` | Stopped/bound und nicht allowlisted; CRIT ab 1. |
|
||||||
|
| `biztalk_send_ports_expected_inactive` | Bewusst inactive und exakt allowlisted. |
|
||||||
| `biztalk_send_ports_unknown` | Nicht interpretierbarer Send-Port-Status. |
|
| `biztalk_send_ports_unknown` | Nicht interpretierbarer Send-Port-Status. |
|
||||||
| `biztalk_orchestrations` | Gesamtzahl Orchestrations. |
|
|
||||||
|
## BizTalk Orchestrations
|
||||||
|
|
||||||
|
Status:
|
||||||
|
|
||||||
|
| Status | Bedingung |
|
||||||
|
| --- | --- |
|
||||||
|
| `OK` | Daten lesbar und kein unbekannter Status; inaktive Orchestrations sind standardmaessig nur Information. |
|
||||||
|
| `WARN` | stopped/bound/unbound bei `AlertOnInactiveOrchestrations=true`. |
|
||||||
|
| `CRIT` | Mindestens ein unbekannter Statuswert. |
|
||||||
|
| `UNKNOWN` | `MSBTS_Orchestration` nicht verlaesslich lesbar. |
|
||||||
|
|
||||||
|
Metriken:
|
||||||
|
|
||||||
|
| Metrik | Bedeutung |
|
||||||
|
| --- | --- |
|
||||||
|
| `biztalk_orchestrations_total` | Gesamtzahl Orchestrations. |
|
||||||
| `biztalk_orchestrations_started` | Gestartete Orchestrations. |
|
| `biztalk_orchestrations_started` | Gestartete Orchestrations. |
|
||||||
| `biztalk_orchestrations_inactive` | Bound, unbound oder stopped Orchestrations. |
|
| `biztalk_orchestrations_stopped` | Gestoppte Orchestrations. |
|
||||||
|
| `biztalk_orchestrations_bound` | Bound Orchestrations. |
|
||||||
|
| `biztalk_orchestrations_unbound` | Unbound Orchestrations. |
|
||||||
| `biztalk_orchestrations_unknown` | Nicht interpretierbarer Orchestration-Status. |
|
| `biztalk_orchestrations_unknown` | Nicht interpretierbarer Orchestration-Status. |
|
||||||
|
|
||||||
|
Allowlist-Syntax fuer Receive Locations und Send Ports:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Name|Anwendung\Name|Weiterer Name
|
||||||
|
```
|
||||||
|
|
||||||
|
Die Auswertung ist case-insensitive und exakt. Wildcards werden bewusst nicht
|
||||||
|
unterstuetzt, damit eine breite Regel keine neuen Artefaktfehler verdeckt.
|
||||||
|
|
||||||
## BizTalk Event Log
|
## BizTalk Event Log
|
||||||
|
|
||||||
Zweck:
|
Zweck:
|
||||||
@@ -171,10 +230,10 @@ Metriken:
|
|||||||
|
|
||||||
## Optionale per-Application-Services
|
## 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
|
```text
|
||||||
BizTalk Suspended <ApplicationName>
|
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.
|
||||||
|
|||||||
+43
-15
@@ -6,39 +6,67 @@
|
|||||||
0 "BizTalk Platform" - Self test OK. Checkmk local check output is valid.
|
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 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 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 Receive Locations" biztalk_receive_locations_total=0;;;0 Self test OK. No WMI query was executed.
|
||||||
|
0 "BizTalk Send Ports" biztalk_send_ports_total=0;;;0 Self test OK. No WMI query was executed.
|
||||||
|
0 "BizTalk Orchestrations" biztalk_orchestrations_total=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
|
## Gesundes kompaktes Servicebild
|
||||||
|
|
||||||
```text
|
```text
|
||||||
0 "BizTalk Platform" - BizTalk WMI and platform data reachable, server=BIZTALK01, group=BizTalkMgmtDb, mgmt_db=SQL01\BizTalkMgmtDb, msgbox_db=SQL01\BizTalkMsgBoxDb
|
0 "BizTalk Suspended Instances" biztalk_suspended_total=0;;;0|biztalk_suspended_resumable=0;1;;0|biztalk_suspended_nonresumable=0;;1;0|biztalk_routing_failure_reports=0;;1;0 Suspended total=0, resumable=0, nonresumable=0, routing_failure_reports=0.
|
||||||
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 Receive Locations" biztalk_receive_locations_total=24;;;0|biztalk_receive_locations_enabled=23;;;0|biztalk_receive_locations_disabled=1;;;0|biztalk_receive_locations_unexpected_disabled=0;;1;0|biztalk_receive_locations_expected_disabled=1;;;0|biztalk_receive_locations_unknown=0;;1;0 Receive locations total=24, enabled=23, unexpected_disabled=0, expected_disabled=1, unknown=0
|
||||||
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 Send Ports" biztalk_send_ports_total=31;;;0|biztalk_send_ports_started=30;;;0|biztalk_send_ports_stopped=1;;;0|biztalk_send_ports_bound=0;;;0|biztalk_send_ports_unexpected_inactive=0;;1;0|biztalk_send_ports_expected_inactive=1;;;0|biztalk_send_ports_unknown=0;;1;0 Send ports total=31, started=30, stopped=1, bound=0, unexpected_inactive=0, expected_inactive=1, unknown=0
|
||||||
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 Orchestrations" biztalk_orchestrations_total=12;;;0|biztalk_orchestrations_started=11;;;0|biztalk_orchestrations_stopped=1;;;0|biztalk_orchestrations_bound=0;;;0|biztalk_orchestrations_unbound=0;;;0|biztalk_orchestrations_unknown=0;;1;0 Orchestrations total=12, started=11, stopped=1, bound=0, unbound=0, unknown=0; affected_apps=Maintenance(inactive=1,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
|
|
||||||
0 "BizTalk Event Log" biztalk_eventlog_errors=0;1;10;0|biztalk_eventlog_warnings=0;1;;0 BizTalk-related Application log entries in last 60 minutes: errors=0, warnings=0
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Suspended Instances
|
Die bewusst inaktiven Receive-/Send-Artefakte stehen in den exakten
|
||||||
|
Allowlisten. Sie bleiben als Metrik sichtbar, erzeugen aber keinen Alarm.
|
||||||
|
|
||||||
|
## Suspensions und Routing Failure Report
|
||||||
|
|
||||||
```text
|
```text
|
||||||
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
|
2 "BizTalk Suspended Instances" biztalk_suspended_total=3;;;0|biztalk_suspended_resumable=2;1;;0|biztalk_suspended_nonresumable=1;;1;0|biztalk_routing_failure_reports=1;;1;0 Suspended total=3, resumable=2, nonresumable=1, routing_failure_reports=1; applications=OrderProcessing(R=2,NR=0); recent=Receive.Order@ProcessingHost, Send.Invoice@ProcessingHost
|
||||||
```
|
```
|
||||||
|
|
||||||
## SQL-Berechtigungsfehler
|
Routing Failure Reports sind eine Teilmenge der non-resumable Instanzen und
|
||||||
|
werden nicht doppelt zum Total addiert.
|
||||||
|
|
||||||
|
## Unerwartet deaktivierte Receive Locations
|
||||||
|
|
||||||
```text
|
```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
|
2 "BizTalk Receive Locations" biztalk_receive_locations_total=24;;;0|biztalk_receive_locations_enabled=22;;;0|biztalk_receive_locations_disabled=2;;;0|biztalk_receive_locations_unexpected_disabled=1;;1;0|biztalk_receive_locations_expected_disabled=1;;;0|biztalk_receive_locations_unknown=0;;1;0 Receive locations total=24, enabled=22, unexpected_disabled=1, expected_disabled=1, unknown=0; affected=Orders\RL_Order_In
|
||||||
```
|
```
|
||||||
|
|
||||||
## WMI-Berechtigungsfehler
|
## Unerwartet inaktive Send Ports
|
||||||
|
|
||||||
```text
|
```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
|
2 "BizTalk Send Ports" biztalk_send_ports_total=31;;;0|biztalk_send_ports_started=29;;;0|biztalk_send_ports_stopped=1;;;0|biztalk_send_ports_bound=1;;;0|biztalk_send_ports_unexpected_inactive=1;;1;0|biztalk_send_ports_expected_inactive=1;;;0|biztalk_send_ports_unknown=0;;1;0 Send ports total=31, started=29, stopped=1, bound=1, unexpected_inactive=1, expected_inactive=1, unknown=0; affected=Orders\SP_Order_Out(bound)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Fehlendes Deployment
|
## Begrenzte Detailausgabe
|
||||||
|
|
||||||
```text
|
```text
|
||||||
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.
|
2 "BizTalk Receive Locations" ... affected=App\RL1, App\RL2, App\RL3, App\RL4, App\RL5 (+17 more)
|
||||||
|
```
|
||||||
|
|
||||||
|
Die Zaehler und Performance-Daten enthalten weiterhin alle 22 betroffenen
|
||||||
|
Artefakte. Nur die Textliste ist begrenzt.
|
||||||
|
|
||||||
|
## Fehlender oder veralteter Provider-Snapshot
|
||||||
|
|
||||||
|
Alle acht Services werden `UNKNOWN`, zum Beispiel:
|
||||||
|
|
||||||
|
```text
|
||||||
|
3 "BizTalk Platform" - Privilegierter BizTalk-Datensnapshot nicht verfuegbar: Snapshot is stale: age=241s, maximum=180s. Massnahme: Scheduled Task 'BizTalk Checkmk Pulse Provider', Provider-Log, Snapshot-Alter und ACL pruefen.
|
||||||
|
```
|
||||||
|
|
||||||
|
## WMI-Schemafehler
|
||||||
|
|
||||||
|
Nur der fachlich betroffene Service wird `UNKNOWN`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
3 "BizTalk Orchestrations" - Orchestrations konnten nicht verlaesslich gelesen werden.; diagnostics: Wmi/Schema [MSBTS_Orchestration]: WMI-Klasse oder WQL wird nicht unterstuetzt. Massnahme: Klassen- und Property-Namen gegen das installierte BizTalk-WMI-Schema pruefen.
|
||||||
```
|
```
|
||||||
|
|||||||
+108
-151
@@ -1,179 +1,136 @@
|
|||||||
# Integration in Checkmk
|
# Checkmk-Integration und Betriebsuebergabe
|
||||||
|
|
||||||
## Zielstruktur auf dem BizTalk-Server
|
## Verantwortungsgrenzen
|
||||||
|
|
||||||
|
| Team | Aufgabe |
|
||||||
|
| --- | --- |
|
||||||
|
| AD/Security | normales dediziertes Servicekonto; Aufnahme in exakt konfigurierte BizTalk-Read-Only-Gruppe |
|
||||||
|
| BizTalk/SQL | Gruppenabbildung und `BTS_READONLY_USERS` bestaetigen; keine Einzelrechte |
|
||||||
|
| Windows | Paket installieren, ACL und Scheduled Task pruefen |
|
||||||
|
| Checkmk | Wrapper verteilen beziehungsweise Installation koordinieren, Discovery und Alarmierung |
|
||||||
|
|
||||||
|
Der Checkmk-Agent bleibt `LocalSystem`. Er greift weder auf BizTalk-WMI noch
|
||||||
|
auf SQL zu. Der minuetliche Scheduled Task sammelt unter dem privilegierten
|
||||||
|
Provider-Konto und publiziert einen validierbaren Snapshot.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Normales Servicekonto:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Install-BizTalkCheckmkPulse.ps1 `
|
||||||
|
-CollectorAccount 'BEW\svc_biztalk_cmk' `
|
||||||
|
-EnvironmentName ACC
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Installer legt den Local Check hier ab:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
%ProgramData%\checkmk\agent\local\biztalk_checkmk_pulse.cmd
|
%ProgramData%\checkmk\agent\local\biztalk_checkmk_pulse.cmd
|
||||||
%ProgramData%\checkmk\agent\local\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe
|
|
||||||
%ProgramData%\checkmk\agent\local\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe.config
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Manuelle Installation
|
Die EXE liegt zentral hier:
|
||||||
|
|
||||||
1. Release-Paket auf den BizTalk-Server kopieren.
|
|
||||||
2. Inhalt nach `%ProgramData%\checkmk\agent\local` kopieren.
|
|
||||||
3. Optional `EnvironmentName` in der `.exe.config` setzen.
|
|
||||||
4. Test ausfuehren:
|
|
||||||
|
|
||||||
```cmd
|
|
||||||
"%ProgramData%\checkmk\agent\local\biztalk_checkmk_pulse.cmd" --self-test
|
|
||||||
"%ProgramData%\checkmk\agent\local\biztalk_checkmk_pulse.cmd"
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Agent-Ausgabe pruefen:
|
|
||||||
|
|
||||||
```cmd
|
|
||||||
"C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump
|
|
||||||
```
|
|
||||||
|
|
||||||
6. In Checkmk:
|
|
||||||
|
|
||||||
- Host oeffnen
|
|
||||||
- Service Discovery ausfuehren
|
|
||||||
- gefundene `BizTalk ...` Services aufnehmen
|
|
||||||
- Changes aktivieren
|
|
||||||
|
|
||||||
## Aufgaben in Checkmk
|
|
||||||
|
|
||||||
Die Checkmk-Kollegen muessen kein serverseitiges Python-Check-Plugin installieren. Das Plugin liefert bereits fertige Local-Check-Services ueber den Windows Agent. In Checkmk selbst sind diese Schritte erforderlich:
|
|
||||||
|
|
||||||
1. BizTalk-Hosts fuer `ACC`, `DEV`, `TST` und `PRD` anlegen oder vorhandene Hosts pruefen.
|
|
||||||
2. Windows-Agent-Status pruefen: Host muss Agent-Daten liefern.
|
|
||||||
3. Nach Installation des Local Checks den Agent-Dump pruefen.
|
|
||||||
4. Service Discovery fuer jeden BizTalk-Host ausfuehren.
|
|
||||||
5. Gefundene `BizTalk ...` Services aufnehmen.
|
|
||||||
6. Changes aktivieren.
|
|
||||||
7. Views oder Dashboards mit Filter `Service starts with: BizTalk` anlegen.
|
|
||||||
8. Benachrichtigungen und Eskalationen je Umgebung definieren.
|
|
||||||
|
|
||||||
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. |
|
|
||||||
| `BizTalk Event Log` | Schwellwerte nach Beobachtungsphase feinjustieren. |
|
|
||||||
|
|
||||||
## Agent Bakery
|
|
||||||
|
|
||||||
Checkmk Managed Services Edition 2.4 enthaelt die kommerziellen Mechanismen fuer Agent Bakery. Fuer einen sauberen Rollout:
|
|
||||||
|
|
||||||
1. Deployment-Dateien als Custom Files oder ueber ein spaeteres MKP bereitstellen.
|
|
||||||
2. Regel nur auf BizTalk-Hosts anwenden, z.B. Host-Tag `application:biztalk`.
|
|
||||||
3. Gebackenen Windows-Agenten fuer die BizTalk-Hosts installieren.
|
|
||||||
4. Service Discovery ausfuehren.
|
|
||||||
|
|
||||||
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
|
```text
|
||||||
DOMAIN\BIZTALKSERVER$
|
%ProgramFiles%\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe
|
||||||
```
|
```
|
||||||
|
|
||||||
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.
|
## Checkmk-Agent-Konfiguration
|
||||||
|
|
||||||
### Verbindlicher Funktionstest
|
Der Wrapper darf synchron laufen, da er nur einen kleinen lokalen Snapshot
|
||||||
|
liest. Eine alte Async-Regel mit `cache_age: 300` aus der direkten
|
||||||
|
WMI-Architektur soll entfernt werden; andernfalls addiert sie eine unnoetige
|
||||||
|
Verzoegerung zum minuetlichen Provider.
|
||||||
|
|
||||||
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:
|
Aktive Agentkonfiguration pruefen:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
& "C:\Program Files (x86)\checkmk\service\check_mk_agent.exe" showconfig local
|
||||||
|
```
|
||||||
|
|
||||||
|
Verbindlicher Agent-Dump:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
& "C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump |
|
& "C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump |
|
||||||
Select-String -Pattern "BizTalk|Access denied|Unauthorized|UNKNOWN" -Context 0,1
|
Select-String -Pattern "BizTalk|UNKNOWN|snapshot|Permission" -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.
|
## Service Discovery
|
||||||
|
|
||||||
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.
|
1. Provider dreimal erfolgreich laufen lassen.
|
||||||
|
2. `LastTaskResult=0`, frischen Snapshot und Log pruefen.
|
||||||
|
3. Agent-Dump im `LocalSystem`-Kontext pruefen.
|
||||||
|
4. Service Discovery fuer den BizTalk-Host ausfuehren.
|
||||||
|
5. acht stabile Services aufnehmen und den alten
|
||||||
|
`BizTalk Runtime Artifacts`-Service entfernen.
|
||||||
|
6. Changes aktivieren.
|
||||||
|
7. Views, Servicegruppen und Benachrichtigungen einrichten.
|
||||||
|
|
||||||
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`:
|
Host-Tags:
|
||||||
|
|
||||||
```yaml
|
```text
|
||||||
local:
|
env:ACC|DEV|TST|PRD
|
||||||
enabled: yes
|
app:biztalk
|
||||||
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.
|
Servicefilter:
|
||||||
|
|
||||||
### 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:
|
|
||||||
|
|
||||||
- `env:ACC`
|
|
||||||
- `env:DEV`
|
|
||||||
- `env:TST`
|
|
||||||
- `env:PRD`
|
|
||||||
- `app:biztalk`
|
|
||||||
|
|
||||||
Service-Filter fuer Views:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Service starts with: BizTalk
|
Service starts with: BizTalk
|
||||||
```
|
```
|
||||||
|
|
||||||
Dashboard-Kacheln:
|
## Bakery-/Softwareverteilung
|
||||||
|
|
||||||
- Host/Service state fuer BizTalk-Host
|
Die komplette Installation umfasst mehr als eine Dateiablage:
|
||||||
- Service state fuer `BizTalk Suspended Instances`
|
|
||||||
- Service state fuer `BizTalk SQL Access`
|
- Programmdateien unter `%ProgramFiles%`,
|
||||||
- Graph `biztalk_suspended_total`
|
- Runtimeverzeichnisse und ACLs,
|
||||||
- Graph `biztalk_host_instances_stopped`
|
- Scheduled Task mit Providerkonto,
|
||||||
- Graph `biztalk_eventlog_errors`
|
- Local-Check-Wrapper.
|
||||||
- MSSQL-Services des SQL-Servers daneben
|
|
||||||
|
Der Wrapper allein kann per Agent Bakery verteilt werden, ersetzt aber nicht
|
||||||
|
die lokale Providerinstallation und die Kontofreigabe. Fuer den ersten Rollout
|
||||||
|
ist das signierte/abgenommene Deployment-Paket mit administrativer
|
||||||
|
Installationsautomation die klarere Variante.
|
||||||
|
|
||||||
|
## Healthchecks
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Get-ScheduledTaskInfo -TaskName 'BizTalk Checkmk Pulse Provider'
|
||||||
|
Get-Item "$env:ProgramData\BizTalkCheckmkPulse\data\biztalk-checkmk-pulse.snapshot"
|
||||||
|
Get-Content "$env:ProgramData\BizTalkCheckmkPulse\logs\*.log" -Tail 50
|
||||||
|
& "$env:ProgramFiles\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe" --consume
|
||||||
|
```
|
||||||
|
|
||||||
|
Soll:
|
||||||
|
|
||||||
|
- Task laeuft jede Minute und endet mit `0`.
|
||||||
|
- Snapshot ist kleiner als `SnapshotMaxBytes` und juenger als 180 Sekunden.
|
||||||
|
- Log nennt das dedizierte Providerkonto.
|
||||||
|
- Platform zeigt `read_only_group=...`.
|
||||||
|
- SQL Access zeigt `targets=2`, `available=2`.
|
||||||
|
- Suspended Instances zeigt total/resumable/non-resumable/routing failures.
|
||||||
|
- Receive Locations und Send Ports zeigen getrennte expected/unexpected Werte.
|
||||||
|
- keine berechtigungsbedingten `UNKNOWN`-Services.
|
||||||
|
|
||||||
|
## Alarmierung der Transportkette
|
||||||
|
|
||||||
|
Ein Ausfall des Providers wird ueber alle acht Services als `UNKNOWN`
|
||||||
|
sichtbar. Die Summary nennt fehlenden, unlesbaren, ungueltigen oder stale
|
||||||
|
Snapshot. Als Betriebsregel sollte `UNKNOWN` dieser Services genauso
|
||||||
|
eskaliert werden wie ein technischer Monitoringausfall.
|
||||||
|
|
||||||
|
Optional kann Windows Task Scheduler zusaetzlich durch vorhandene Checkmk
|
||||||
|
Task-/Event-Log-Regeln ueberwacht werden. Das ist eine Ergaenzung, kein Ersatz
|
||||||
|
fuer die eingebaute Stale-Pruefung.
|
||||||
|
|
||||||
|
## Rollout
|
||||||
|
|
||||||
|
Empfohlene Reihenfolge:
|
||||||
|
|
||||||
|
1. ACC: Berechtigung, Task, Snapshot, Stale-Test und Discovery abnehmen.
|
||||||
|
2. DEV/TST: gleiche Automatisierung und umgebungsspezifische Config.
|
||||||
|
3. PRD: Change, Wartungsfenster, Healthcheck und fachliche Plausibilitaet.
|
||||||
|
|
||||||
|
Ausfuehrliche Architektur, ACL, Fehlerbilder und Abnahmekriterien:
|
||||||
|
[Dokumentation.md](../Dokumentation.md).
|
||||||
|
|||||||
@@ -7,6 +7,5 @@ if errorlevel 1 (
|
|||||||
exit /b 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%
|
exit /b %ERRORLEVEL%
|
||||||
|
|
||||||
|
|||||||
@@ -6,15 +6,16 @@ if errorlevel 1 exit /b 1
|
|||||||
|
|
||||||
set "ROOT=%~dp0.."
|
set "ROOT=%~dp0.."
|
||||||
set "OUT=%ROOT%\artifacts\BizTalkCheckmkPulse-deploy"
|
set "OUT=%ROOT%\artifacts\BizTalkCheckmkPulse-deploy"
|
||||||
set "APP=%OUT%\BizTalkCheckmkPulse"
|
set "APP=%OUT%\application"
|
||||||
|
|
||||||
if exist "%OUT%" rmdir /s /q "%OUT%"
|
if exist "%OUT%" rmdir /s /q "%OUT%"
|
||||||
mkdir "%APP%"
|
mkdir "%APP%"
|
||||||
|
|
||||||
copy "%ROOT%\deployment\checkmk\biztalk_checkmk_pulse.cmd" "%OUT%\" >nul
|
copy "%ROOT%\deployment\checkmk\biztalk_checkmk_pulse.cmd" "%OUT%\" >nul
|
||||||
|
copy "%ROOT%\deployment\Install-BizTalkCheckmkPulse.ps1" "%OUT%\" >nul
|
||||||
|
copy "%ROOT%\deployment\Uninstall-BizTalkCheckmkPulse.ps1" "%OUT%\" >nul
|
||||||
copy "%ROOT%\src\BizTalkCheckmkPulse\bin\Release\BizTalkCheckmkPulse.exe" "%APP%\" >nul
|
copy "%ROOT%\src\BizTalkCheckmkPulse\bin\Release\BizTalkCheckmkPulse.exe" "%APP%\" >nul
|
||||||
copy "%ROOT%\src\BizTalkCheckmkPulse\bin\Release\BizTalkCheckmkPulse.exe.config" "%APP%\" >nul
|
copy "%ROOT%\src\BizTalkCheckmkPulse\bin\Release\BizTalkCheckmkPulse.exe.config" "%APP%\" >nul
|
||||||
|
|
||||||
echo Deployment package written to %OUT%
|
echo Deployment package written to %OUT%
|
||||||
exit /b 0
|
exit /b 0
|
||||||
|
|
||||||
|
|||||||
@@ -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%
|
||||||
@@ -6,16 +6,31 @@
|
|||||||
<add key="ServicePrefix" value="BizTalk" />
|
<add key="ServicePrefix" value="BizTalk" />
|
||||||
<add key="EnvironmentName" value="" />
|
<add key="EnvironmentName" value="" />
|
||||||
|
|
||||||
|
<!-- Provider/Consumer-Datei. Der Scheduled Task schreibt, LocalSystem liest. -->
|
||||||
|
<add key="SnapshotPath" value="%ProgramData%\BizTalkCheckmkPulse\data\biztalk-checkmk-pulse.snapshot" />
|
||||||
|
<!-- Bei minuetlicher Provider-Ausfuehrung nach drei Minuten als UNKNOWN bewerten. -->
|
||||||
|
<add key="SnapshotMaxAgeSeconds" value="180" />
|
||||||
|
<add key="SnapshotMaxBytes" value="1048576" />
|
||||||
|
<add key="LogDirectory" value="%ProgramData%\BizTalkCheckmkPulse\logs" />
|
||||||
|
<add key="LogRetentionDays" value="30" />
|
||||||
|
|
||||||
<add key="QueryTimeoutSeconds" value="25" />
|
<add key="QueryTimeoutSeconds" value="25" />
|
||||||
<!-- Testet integrierte Windows-Anmeldung an den per WMI ermittelten BizTalk-Datenbanken. -->
|
<!-- Testet die Anmeldung des privilegierten Provider-Kontos an den ermittelten BizTalk-Datenbanken. -->
|
||||||
<add key="ProbeSqlConnectivity" value="true" />
|
<add key="ProbeSqlConnectivity" value="true" />
|
||||||
<add key="SqlConnectionTimeoutSeconds" value="5" />
|
<add key="SqlConnectionTimeoutSeconds" value="5" />
|
||||||
<add key="WarnResumableThreshold" value="1" />
|
<add key="WarnResumableThreshold" value="1" />
|
||||||
<add key="CritNonResumableThreshold" value="1" />
|
<add key="CritNonResumableThreshold" value="1" />
|
||||||
<add key="MaxSummaryItems" value="12" />
|
<add key="CritRoutingFailureThreshold" value="1" />
|
||||||
|
<add key="MaxSummaryItems" value="5" />
|
||||||
|
<add key="MaxDetailCharacters" value="1600" />
|
||||||
|
|
||||||
<!-- Gestoppte Artefakte koennen in BizTalk-Landschaften fachlich beabsichtigt sein. -->
|
<!-- Unerwartet deaktivierte Receive Locations und inaktive Send Ports sind standardmaessig CRIT. -->
|
||||||
<add key="AlertOnArtifactRuntimeIssues" value="false" />
|
<add key="AlertOnArtifactRuntimeIssues" value="true" />
|
||||||
|
<!-- Exakte Namen, Pipe-getrennt: Name oder Anwendung\Name. Keine Wildcards. -->
|
||||||
|
<add key="ExpectedDisabledReceiveLocations" value="" />
|
||||||
|
<add key="ExpectedInactiveSendPorts" value="" />
|
||||||
|
<!-- Gestoppte/bound Orchestrations koennen fachlich beabsichtigt sein. -->
|
||||||
|
<add key="AlertOnInactiveOrchestrations" value="false" />
|
||||||
<add key="EmitPerApplicationSuspensionServices" value="false" />
|
<add key="EmitPerApplicationSuspensionServices" value="false" />
|
||||||
|
|
||||||
<add key="ProbeEventLog" value="true" />
|
<add key="ProbeEventLog" value="true" />
|
||||||
|
|||||||
@@ -40,12 +40,15 @@
|
|||||||
<Reference Include="System.Management" />
|
<Reference Include="System.Management" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="CheckmkLocalFormatter.cs" />
|
<Compile Include="CheckmkLocalFormatter.cs" />
|
||||||
<Compile Include="EventLogProbe.cs" />
|
<Compile Include="EventLogProbe.cs" />
|
||||||
|
<Compile Include="FileLogger.cs" />
|
||||||
<Compile Include="MonitoringOptions.cs" />
|
<Compile Include="MonitoringOptions.cs" />
|
||||||
<Compile Include="Models.cs" />
|
<Compile Include="Models.cs" />
|
||||||
<Compile Include="Program.cs" />
|
<Compile Include="Program.cs" />
|
||||||
<Compile Include="SqlConnectivityProbe.cs" />
|
<Compile Include="SqlConnectivityProbe.cs" />
|
||||||
|
<Compile Include="SnapshotStore.cs" />
|
||||||
<Compile Include="WmiBizTalkProbe.cs" />
|
<Compile Include="WmiBizTalkProbe.cs" />
|
||||||
<Compile Include="WmiHelpers.cs" />
|
<Compile Include="WmiHelpers.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ namespace BizTalkCheckmkPulse
|
|||||||
private const int HostPausePending = 6;
|
private const int HostPausePending = 6;
|
||||||
private const int HostPaused = 7;
|
private const int HostPaused = 7;
|
||||||
private const int HostUnknown = 8;
|
private const int HostUnknown = 8;
|
||||||
|
private const int SendPortBound = 1;
|
||||||
|
private const int SendPortStopped = 2;
|
||||||
|
private const int SendPortStarted = 3;
|
||||||
private readonly MonitoringOptions _options;
|
private readonly MonitoringOptions _options;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -41,7 +44,9 @@ namespace BizTalkCheckmkPulse
|
|||||||
yield return FormatSqlAccess(result);
|
yield return FormatSqlAccess(result);
|
||||||
yield return FormatSuspendedInstances(result);
|
yield return FormatSuspendedInstances(result);
|
||||||
yield return FormatHostInstances(result);
|
yield return FormatHostInstances(result);
|
||||||
yield return FormatRuntimeArtifacts(result);
|
yield return FormatReceiveLocations(result);
|
||||||
|
yield return FormatSendPorts(result);
|
||||||
|
yield return FormatOrchestrations(result);
|
||||||
yield return FormatEventLog(result);
|
yield return FormatEventLog(result);
|
||||||
|
|
||||||
if (_options.EmitPerApplicationSuspensionServices && result.Platform.SuspendedInstancesDataAvailable)
|
if (_options.EmitPerApplicationSuspensionServices && result.Platform.SuspendedInstancesDataAvailable)
|
||||||
@@ -62,6 +67,11 @@ 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("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("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("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("Receive Locations"), "biztalk_receive_locations_total=0;;;0", "Self test OK. No WMI query was executed.");
|
||||||
|
yield return BuildLine(CheckState.Ok, _options.ServiceName("Send Ports"), "biztalk_send_ports_total=0;;;0", "Self test OK. No WMI query was executed.");
|
||||||
|
yield return BuildLine(CheckState.Ok, _options.ServiceName("Orchestrations"), "biztalk_orchestrations_total=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>
|
/// <summary>
|
||||||
@@ -76,10 +86,30 @@ namespace BizTalkCheckmkPulse
|
|||||||
yield return BuildLine(CheckState.Unknown, _options.ServiceName("SQL Access"), "-", 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("Suspended Instances"), "-", action);
|
||||||
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Host 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("Receive Locations"), "-", action);
|
||||||
|
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Send Ports"), "-", action);
|
||||||
|
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Orchestrations"), "-", action);
|
||||||
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Event Log"), "-", action);
|
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Event Log"), "-", action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Liefert stabile UNKNOWN-Services, wenn der Consumer keinen gueltigen Provider-Snapshot lesen kann.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<string> FormatSnapshotFailure(string reason)
|
||||||
|
{
|
||||||
|
var detail = "Privilegierter BizTalk-Datensnapshot nicht verfuegbar: "
|
||||||
|
+ SanitizeDetail(reason)
|
||||||
|
+ " Massnahme: Scheduled Task 'BizTalk Checkmk Pulse Provider', Provider-Log, Snapshot-Alter und ACL pruefen.";
|
||||||
|
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Platform"), "-", detail);
|
||||||
|
yield return BuildLine(CheckState.Unknown, _options.ServiceName("SQL Access"), "-", detail);
|
||||||
|
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Suspended Instances"), "-", detail);
|
||||||
|
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Host Instances"), "-", detail);
|
||||||
|
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Receive Locations"), "-", detail);
|
||||||
|
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Send Ports"), "-", detail);
|
||||||
|
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Orchestrations"), "-", detail);
|
||||||
|
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Event Log"), "-", detail);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Formatiert Erreichbarkeit und Basisdaten des BizTalk-WMI-Providers.
|
/// Formatiert Erreichbarkeit und Basisdaten des BizTalk-WMI-Providers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -93,10 +123,12 @@ namespace BizTalkCheckmkPulse
|
|||||||
detail.Append(available ? "BizTalk WMI and platform data reachable" : "BizTalk WMI or required platform data not readable");
|
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));
|
detail.Append(", server=").Append(EmptyAsUnknown(result.Platform.ServerName));
|
||||||
AppendOptional(detail, "group", result.Platform.GroupName);
|
AppendOptional(detail, "group", result.Platform.GroupName);
|
||||||
|
AppendOptional(detail, "read_only_group", result.Platform.ReadOnlyUserGroup);
|
||||||
|
AppendOptional(detail, "operator_group", result.Platform.OperatorGroup);
|
||||||
AppendOptional(detail, "mgmt_db", JoinDb(result.Platform.ManagementDbServer, result.Platform.ManagementDbName));
|
AppendOptional(detail, "mgmt_db", JoinDb(result.Platform.ManagementDbServer, result.Platform.ManagementDbName));
|
||||||
AppendOptional(detail, "msgbox_db", JoinDb(result.Platform.MessageBoxDbServer, result.Platform.MessageBoxDbName));
|
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));
|
return BuildLine(state, _options.ServiceName("Platform"), "-", AppendDiagnostics(detail.ToString(), diagnostics));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,19 +193,22 @@ namespace BizTalkCheckmkPulse
|
|||||||
var total = result.SuspendedInstances.Count;
|
var total = result.SuspendedInstances.Count;
|
||||||
var resumable = result.SuspendedInstances.Count(x => x.Kind == SuspendedKind.Resumable);
|
var resumable = result.SuspendedInstances.Count(x => x.Kind == SuspendedKind.Resumable);
|
||||||
var nonresumable = result.SuspendedInstances.Count(x => x.Kind == SuspendedKind.NonResumable);
|
var nonresumable = result.SuspendedInstances.Count(x => x.Kind == SuspendedKind.NonResumable);
|
||||||
var state = DetermineSuspensionState(resumable, nonresumable);
|
var routingFailureReports = result.SuspendedInstances.Count(x => x.IsRoutingFailureReport);
|
||||||
|
var state = DetermineSuspensionState(resumable, nonresumable, routingFailureReports);
|
||||||
var metrics = string.Format(
|
var metrics = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
"biztalk_suspended_total={0};;;0|biztalk_suspended_resumable={1};{2};;0|biztalk_suspended_nonresumable={3};;{4};0",
|
"biztalk_suspended_total={0};;;0|biztalk_suspended_resumable={1};{2};;0|biztalk_suspended_nonresumable={3};;{4};0|biztalk_routing_failure_reports={5};;{6};0",
|
||||||
total,
|
total,
|
||||||
resumable,
|
resumable,
|
||||||
_options.WarnResumableThreshold,
|
_options.WarnResumableThreshold,
|
||||||
nonresumable,
|
nonresumable,
|
||||||
_options.CritNonResumableThreshold);
|
_options.CritNonResumableThreshold,
|
||||||
|
routingFailureReports,
|
||||||
|
_options.CritRoutingFailureThreshold);
|
||||||
|
|
||||||
var detail = total == 0
|
var detail = total == 0
|
||||||
? "No suspended BizTalk service instances found."
|
? "Suspended total=0, resumable=0, nonresumable=0, routing_failure_reports=0."
|
||||||
: BuildSuspensionDetail(result.SuspendedInstances, total, resumable, nonresumable);
|
: BuildSuspensionDetail(result.SuspendedInstances, total, resumable, nonresumable, routingFailureReports);
|
||||||
return BuildLine(state, _options.ServiceName("Suspended Instances"), metrics, detail);
|
return BuildLine(state, _options.ServiceName("Suspended Instances"), metrics, detail);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,65 +259,147 @@ namespace BizTalkCheckmkPulse
|
|||||||
return BuildLine(state, _options.ServiceName("Host Instances"), metrics, detail.ToString());
|
return BuildLine(state, _options.ServiceName("Host Instances"), metrics, detail.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private string FormatReceiveLocations(ProbeResult result)
|
||||||
/// 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)
|
if (!result.Platform.ReceiveLocationsDataAvailable)
|
||||||
{
|
{
|
||||||
var errorDetail = "BizTalk Runtime Artifacts konnten nicht vollstaendig gelesen werden. Teilwerte werden nicht als OK gewertet.";
|
|
||||||
return BuildLine(
|
return BuildLine(
|
||||||
CheckState.Unknown,
|
CheckState.Unknown,
|
||||||
_options.ServiceName("Runtime Artifacts"),
|
_options.ServiceName("Receive Locations"),
|
||||||
"-",
|
"-",
|
||||||
AppendDiagnostics(errorDetail, SelectDiagnostics(result, DiagnosticArea.Wmi, "WMI namespace connection", "MSBTS_ReceiveLocation", "MSBTS_SendPort", "MSBTS_Orchestration")));
|
AppendDiagnostics(
|
||||||
|
"Receive Locations konnten nicht verlaesslich gelesen werden.",
|
||||||
|
SelectDiagnostics(result, DiagnosticArea.Wmi, "WMI namespace connection", "MSBTS_ReceiveLocation")));
|
||||||
}
|
}
|
||||||
|
|
||||||
var receiveLocations = result.Applications.Sum(x => x.ReceiveLocationTotal);
|
var disabled = result.ReceiveLocations.Where(x => x.IsDisabled == true).ToArray();
|
||||||
var receiveLocationsDisabled = result.Applications.Sum(x => x.ReceiveLocationDisabled);
|
var unknown = result.ReceiveLocations.Where(x => !x.IsDisabled.HasValue).ToArray();
|
||||||
var sendPorts = result.Applications.Sum(x => x.SendPortTotal);
|
var expectedDisabled = disabled
|
||||||
var sendPortsStarted = result.Applications.Sum(x => x.SendPortStarted);
|
.Where(x => _options.IsExpectedDisabledReceiveLocation(x.ApplicationName, x.Name))
|
||||||
var sendPortsInactive = result.Applications.Sum(x => x.SendPortStopped + x.SendPortBound);
|
.ToArray();
|
||||||
var sendPortsUnknown = result.Applications.Sum(x => x.SendPortUnknown);
|
var unexpectedDisabled = disabled.Except(expectedDisabled).ToArray();
|
||||||
var orchestrations = result.Applications.Sum(x => x.OrchestrationTotal);
|
var enabled = result.ReceiveLocations.Count - disabled.Length - unknown.Length;
|
||||||
var orchestrationsStarted = result.Applications.Sum(x => x.OrchestrationStarted);
|
var state = unknown.Length > 0
|
||||||
var orchestrationsInactive = result.Applications.Sum(x => x.OrchestrationStopped + x.OrchestrationBound + x.OrchestrationUnbound);
|
? CheckState.Unknown
|
||||||
var orchestrationsUnknown = result.Applications.Sum(x => x.OrchestrationUnknown);
|
: _options.AlertOnArtifactRuntimeIssues && unexpectedDisabled.Length > 0
|
||||||
var state = sendPortsUnknown > 0 || orchestrationsUnknown > 0
|
? CheckState.Critical
|
||||||
|
: CheckState.Ok;
|
||||||
|
var metrics = string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"biztalk_receive_locations_total={0};;;0|biztalk_receive_locations_enabled={1};;;0|biztalk_receive_locations_disabled={2};;;0|biztalk_receive_locations_unexpected_disabled={3};;1;0|biztalk_receive_locations_expected_disabled={4};;;0|biztalk_receive_locations_unknown={5};;1;0",
|
||||||
|
result.ReceiveLocations.Count,
|
||||||
|
enabled,
|
||||||
|
disabled.Length,
|
||||||
|
unexpectedDisabled.Length,
|
||||||
|
expectedDisabled.Length,
|
||||||
|
unknown.Length);
|
||||||
|
var detail = new StringBuilder()
|
||||||
|
.Append("Receive locations total=").Append(result.ReceiveLocations.Count)
|
||||||
|
.Append(", enabled=").Append(enabled)
|
||||||
|
.Append(", unexpected_disabled=").Append(unexpectedDisabled.Length)
|
||||||
|
.Append(", expected_disabled=").Append(expectedDisabled.Length)
|
||||||
|
.Append(", unknown=").Append(unknown.Length);
|
||||||
|
AppendLimitedList(detail, "affected", unexpectedDisabled.Concat(unknown).Select(ReceiveLocationDisplay));
|
||||||
|
return BuildLine(state, _options.ServiceName("Receive Locations"), metrics, detail.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private string FormatSendPorts(ProbeResult result)
|
||||||
|
{
|
||||||
|
if (!result.Platform.SendPortsDataAvailable)
|
||||||
|
{
|
||||||
|
return BuildLine(
|
||||||
|
CheckState.Unknown,
|
||||||
|
_options.ServiceName("Send Ports"),
|
||||||
|
"-",
|
||||||
|
AppendDiagnostics(
|
||||||
|
"Send Ports konnten nicht verlaesslich gelesen werden.",
|
||||||
|
SelectDiagnostics(result, DiagnosticArea.Wmi, "WMI namespace connection", "MSBTS_SendPort")));
|
||||||
|
}
|
||||||
|
|
||||||
|
var started = result.SendPorts.Count(x => x.Status == SendPortStarted);
|
||||||
|
var stopped = result.SendPorts.Count(x => x.Status == SendPortStopped);
|
||||||
|
var bound = result.SendPorts.Count(x => x.Status == SendPortBound);
|
||||||
|
var unknown = result.SendPorts.Where(x => x.Status != SendPortStarted && x.Status != SendPortStopped && x.Status != SendPortBound).ToArray();
|
||||||
|
var inactive = result.SendPorts.Where(x => x.Status == SendPortStopped || x.Status == SendPortBound).ToArray();
|
||||||
|
var expectedInactive = inactive
|
||||||
|
.Where(x => _options.IsExpectedInactiveSendPort(x.ApplicationName, x.Name))
|
||||||
|
.ToArray();
|
||||||
|
var unexpectedInactive = inactive.Except(expectedInactive).ToArray();
|
||||||
|
var state = unknown.Length > 0 || (_options.AlertOnArtifactRuntimeIssues && unexpectedInactive.Length > 0)
|
||||||
? CheckState.Critical
|
? CheckState.Critical
|
||||||
: _options.AlertOnArtifactRuntimeIssues && (receiveLocationsDisabled > 0 || sendPortsInactive > 0 || orchestrationsInactive > 0)
|
: CheckState.Ok;
|
||||||
|
var metrics = string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"biztalk_send_ports_total={0};;;0|biztalk_send_ports_started={1};;;0|biztalk_send_ports_stopped={2};;;0|biztalk_send_ports_bound={3};;;0|biztalk_send_ports_unexpected_inactive={4};;1;0|biztalk_send_ports_expected_inactive={5};;;0|biztalk_send_ports_unknown={6};;1;0",
|
||||||
|
result.SendPorts.Count,
|
||||||
|
started,
|
||||||
|
stopped,
|
||||||
|
bound,
|
||||||
|
unexpectedInactive.Length,
|
||||||
|
expectedInactive.Length,
|
||||||
|
unknown.Length);
|
||||||
|
var detail = new StringBuilder()
|
||||||
|
.Append("Send ports total=").Append(result.SendPorts.Count)
|
||||||
|
.Append(", started=").Append(started)
|
||||||
|
.Append(", stopped=").Append(stopped)
|
||||||
|
.Append(", bound=").Append(bound)
|
||||||
|
.Append(", unexpected_inactive=").Append(unexpectedInactive.Length)
|
||||||
|
.Append(", expected_inactive=").Append(expectedInactive.Length)
|
||||||
|
.Append(", unknown=").Append(unknown.Length);
|
||||||
|
AppendLimitedList(detail, "affected", unexpectedInactive.Concat(unknown).Select(SendPortDisplay));
|
||||||
|
return BuildLine(state, _options.ServiceName("Send Ports"), metrics, detail.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private string FormatOrchestrations(ProbeResult result)
|
||||||
|
{
|
||||||
|
if (!result.Platform.OrchestrationsDataAvailable)
|
||||||
|
{
|
||||||
|
return BuildLine(
|
||||||
|
CheckState.Unknown,
|
||||||
|
_options.ServiceName("Orchestrations"),
|
||||||
|
"-",
|
||||||
|
AppendDiagnostics(
|
||||||
|
"Orchestrations konnten nicht verlaesslich gelesen werden.",
|
||||||
|
SelectDiagnostics(result, DiagnosticArea.Wmi, "WMI namespace connection", "MSBTS_Orchestration")));
|
||||||
|
}
|
||||||
|
|
||||||
|
var total = result.Applications.Sum(x => x.OrchestrationTotal);
|
||||||
|
var started = result.Applications.Sum(x => x.OrchestrationStarted);
|
||||||
|
var stopped = result.Applications.Sum(x => x.OrchestrationStopped);
|
||||||
|
var bound = result.Applications.Sum(x => x.OrchestrationBound);
|
||||||
|
var unbound = result.Applications.Sum(x => x.OrchestrationUnbound);
|
||||||
|
var unknown = result.Applications.Sum(x => x.OrchestrationUnknown);
|
||||||
|
var inactive = stopped + bound + unbound;
|
||||||
|
var state = unknown > 0
|
||||||
|
? CheckState.Critical
|
||||||
|
: _options.AlertOnInactiveOrchestrations && inactive > 0
|
||||||
? CheckState.Warning
|
? CheckState.Warning
|
||||||
: CheckState.Ok;
|
: CheckState.Ok;
|
||||||
var metrics = string.Format(
|
var metrics = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
"biztalk_applications={0};;;0|biztalk_receive_locations={1};;;0|biztalk_receive_locations_disabled={2};1;;0|biztalk_send_ports={3};;;0|biztalk_send_ports_started={4};;;0|biztalk_send_ports_inactive={5};1;;0|biztalk_send_ports_unknown={6};;1;0|biztalk_orchestrations={7};;;0|biztalk_orchestrations_started={8};;;0|biztalk_orchestrations_inactive={9};1;;0|biztalk_orchestrations_unknown={10};;1;0",
|
"biztalk_orchestrations_total={0};;;0|biztalk_orchestrations_started={1};;;0|biztalk_orchestrations_stopped={2};;;0|biztalk_orchestrations_bound={3};;;0|biztalk_orchestrations_unbound={4};;;0|biztalk_orchestrations_unknown={5};;1;0",
|
||||||
result.Applications.Count,
|
total,
|
||||||
receiveLocations,
|
started,
|
||||||
receiveLocationsDisabled,
|
stopped,
|
||||||
sendPorts,
|
bound,
|
||||||
sendPortsStarted,
|
unbound,
|
||||||
sendPortsInactive,
|
unknown);
|
||||||
sendPortsUnknown,
|
var detail = new StringBuilder()
|
||||||
orchestrations,
|
.Append("Orchestrations total=").Append(total)
|
||||||
orchestrationsStarted,
|
.Append(", started=").Append(started)
|
||||||
orchestrationsInactive,
|
.Append(", stopped=").Append(stopped)
|
||||||
orchestrationsUnknown);
|
.Append(", bound=").Append(bound)
|
||||||
|
.Append(", unbound=").Append(unbound)
|
||||||
var detail = new StringBuilder();
|
.Append(", unknown=").Append(unknown);
|
||||||
detail.Append("Applications=").Append(result.Applications.Count)
|
|
||||||
.Append("; receive_locations total=").Append(receiveLocations).Append(", disabled=").Append(receiveLocationsDisabled)
|
|
||||||
.Append("; send_ports total=").Append(sendPorts).Append(", started=").Append(sendPortsStarted).Append(", inactive=").Append(sendPortsInactive).Append(", unknown=").Append(sendPortsUnknown)
|
|
||||||
.Append("; orchestrations total=").Append(orchestrations).Append(", started=").Append(orchestrationsStarted).Append(", inactive=").Append(orchestrationsInactive).Append(", unknown=").Append(orchestrationsUnknown);
|
|
||||||
|
|
||||||
var affected = result.Applications
|
var affected = result.Applications
|
||||||
.Where(x => x.ReceiveLocationDisabled > 0 || x.SendPortStopped + x.SendPortBound + x.SendPortUnknown > 0 || x.OrchestrationStopped + x.OrchestrationBound + x.OrchestrationUnbound + x.OrchestrationUnknown > 0)
|
.Where(x => x.OrchestrationStopped + x.OrchestrationBound + x.OrchestrationUnbound + x.OrchestrationUnknown > 0)
|
||||||
.Take(_options.MaxSummaryItems)
|
.OrderByDescending(x => x.OrchestrationUnknown)
|
||||||
.Select(x => x.ApplicationName + "(rl_disabled=" + x.ReceiveLocationDisabled + ", sp_inactive=" + (x.SendPortStopped + x.SendPortBound) + ", orch_inactive=" + (x.OrchestrationStopped + x.OrchestrationBound + x.OrchestrationUnbound) + ")");
|
.ThenByDescending(x => x.OrchestrationStopped + x.OrchestrationBound + x.OrchestrationUnbound)
|
||||||
AppendList(detail, "notable_apps", affected);
|
.Select(x => x.ApplicationName + "(inactive="
|
||||||
return BuildLine(state, _options.ServiceName("Runtime Artifacts"), metrics, detail.ToString());
|
+ (x.OrchestrationStopped + x.OrchestrationBound + x.OrchestrationUnbound)
|
||||||
|
+ ",unknown=" + x.OrchestrationUnknown + ")");
|
||||||
|
AppendLimitedList(detail, "affected_apps", affected);
|
||||||
|
return BuildLine(state, _options.ServiceName("Orchestrations"), metrics, detail.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -303,7 +420,7 @@ namespace BizTalkCheckmkPulse
|
|||||||
CheckState.Unknown,
|
CheckState.Unknown,
|
||||||
_options.ServiceName("Event Log"),
|
_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.");
|
"Application Event Log konnte nicht gelesen werden: " + result.EventLog.Failure + " Massnahme: lokalen Event-Log-Zugriff des Provider-Kontos und den Windows Event Log Dienst pruefen.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var state = _options.EventLogCritThreshold > 0 && result.EventLog.Errors >= _options.EventLogCritThreshold
|
var state = _options.EventLogCritThreshold > 0 && result.EventLog.Errors >= _options.EventLogCritThreshold
|
||||||
@@ -334,20 +451,31 @@ namespace BizTalkCheckmkPulse
|
|||||||
/// <returns>Checkmk-Zeilen je betroffener BizTalk-Anwendung.</returns>
|
/// <returns>Checkmk-Zeilen je betroffener BizTalk-Anwendung.</returns>
|
||||||
private IEnumerable<string> FormatApplicationSuspensions(ProbeResult result)
|
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 resumable = group.Count(x => x.Kind == SuspendedKind.Resumable);
|
||||||
var nonresumable = group.Count(x => x.Kind == SuspendedKind.NonResumable);
|
var nonresumable = group.Count(x => x.Kind == SuspendedKind.NonResumable);
|
||||||
var state = DetermineSuspensionState(resumable, nonresumable);
|
var routingFailureReports = group.Count(x => x.IsRoutingFailureReport);
|
||||||
|
var state = DetermineSuspensionState(resumable, nonresumable, routingFailureReports);
|
||||||
var metrics = string.Format(
|
var metrics = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
"biztalk_suspended_total={0};;;0|biztalk_suspended_resumable={1};{2};;0|biztalk_suspended_nonresumable={3};;{4};0",
|
"biztalk_suspended_total={0};;;0|biztalk_suspended_resumable={1};{2};;0|biztalk_suspended_nonresumable={3};;{4};0|biztalk_routing_failure_reports={5};;{6};0",
|
||||||
group.Count(),
|
group.Count(),
|
||||||
resumable,
|
resumable,
|
||||||
_options.WarnResumableThreshold,
|
_options.WarnResumableThreshold,
|
||||||
nonresumable,
|
nonresumable,
|
||||||
_options.CritNonResumableThreshold);
|
_options.CritNonResumableThreshold,
|
||||||
yield return BuildLine(state, _options.ServiceName("Suspended " + group.Key), metrics, "Suspended instances: resumable=" + resumable + ", nonresumable=" + nonresumable);
|
routingFailureReports,
|
||||||
|
_options.CritRoutingFailureThreshold);
|
||||||
|
yield return BuildLine(
|
||||||
|
state,
|
||||||
|
_options.ServiceName("Suspended " + group.Key),
|
||||||
|
metrics,
|
||||||
|
"Suspended total=" + group.Count() + ", resumable=" + resumable
|
||||||
|
+ ", nonresumable=" + nonresumable + ", routing_failure_reports=" + routingFailureReports);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,8 +485,14 @@ namespace BizTalkCheckmkPulse
|
|||||||
/// <param name="resumable">Anzahl fortsetzbarer Instanzen.</param>
|
/// <param name="resumable">Anzahl fortsetzbarer Instanzen.</param>
|
||||||
/// <param name="nonresumable">Anzahl nicht fortsetzbarer Instanzen.</param>
|
/// <param name="nonresumable">Anzahl nicht fortsetzbarer Instanzen.</param>
|
||||||
/// <returns>Zustand gemaess konfigurierten Schwellwerten.</returns>
|
/// <returns>Zustand gemaess konfigurierten Schwellwerten.</returns>
|
||||||
private CheckState DetermineSuspensionState(int resumable, int nonresumable)
|
private CheckState DetermineSuspensionState(int resumable, int nonresumable, int routingFailureReports)
|
||||||
{
|
{
|
||||||
|
if (_options.CritRoutingFailureThreshold > 0
|
||||||
|
&& routingFailureReports >= _options.CritRoutingFailureThreshold)
|
||||||
|
{
|
||||||
|
return CheckState.Critical;
|
||||||
|
}
|
||||||
|
|
||||||
if (_options.CritNonResumableThreshold > 0 && nonresumable >= _options.CritNonResumableThreshold)
|
if (_options.CritNonResumableThreshold > 0 && nonresumable >= _options.CritNonResumableThreshold)
|
||||||
{
|
{
|
||||||
return CheckState.Critical;
|
return CheckState.Critical;
|
||||||
@@ -380,24 +514,32 @@ namespace BizTalkCheckmkPulse
|
|||||||
/// <param name="resumable">Anzahl fortsetzbarer Instanzen.</param>
|
/// <param name="resumable">Anzahl fortsetzbarer Instanzen.</param>
|
||||||
/// <param name="nonresumable">Anzahl nicht fortsetzbarer Instanzen.</param>
|
/// <param name="nonresumable">Anzahl nicht fortsetzbarer Instanzen.</param>
|
||||||
/// <returns>Kompakter Detailtext.</returns>
|
/// <returns>Kompakter Detailtext.</returns>
|
||||||
private string BuildSuspensionDetail(IEnumerable<SuspendedInstance> instances, int total, int resumable, int nonresumable)
|
private string BuildSuspensionDetail(
|
||||||
|
IEnumerable<SuspendedInstance> instances,
|
||||||
|
int total,
|
||||||
|
int resumable,
|
||||||
|
int nonresumable,
|
||||||
|
int routingFailureReports)
|
||||||
{
|
{
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
builder.Append(total).Append(" suspended BizTalk service instance(s): resumable=").Append(resumable)
|
builder.Append("Suspended total=").Append(total)
|
||||||
.Append(", nonresumable=").Append(nonresumable);
|
.Append(", resumable=").Append(resumable)
|
||||||
|
.Append(", nonresumable=").Append(nonresumable)
|
||||||
|
.Append(", routing_failure_reports=").Append(routingFailureReports);
|
||||||
|
|
||||||
var apps = instances
|
var apps = instances
|
||||||
|
.Where(x => !x.IsRoutingFailureReport
|
||||||
|
&& !string.Equals(x.ApplicationName, "(unknown)", StringComparison.OrdinalIgnoreCase))
|
||||||
.GroupBy(x => x.ApplicationName)
|
.GroupBy(x => x.ApplicationName)
|
||||||
.OrderByDescending(x => x.Count())
|
.OrderByDescending(x => x.Count())
|
||||||
.Take(_options.MaxSummaryItems)
|
.Select(x => x.Key + "(R=" + x.Count(y => y.Kind == SuspendedKind.Resumable) + ",NR=" + x.Count(y => y.Kind == SuspendedKind.NonResumable) + ")");
|
||||||
.Select(x => x.Key + "(R=" + x.Count(y => y.Kind == SuspendedKind.Resumable) + ", NR=" + x.Count(y => y.Kind == SuspendedKind.NonResumable) + ")");
|
AppendLimitedList(builder, "applications", apps);
|
||||||
AppendList(builder, "applications", apps);
|
|
||||||
|
|
||||||
var examples = instances
|
var examples = instances
|
||||||
|
.Where(x => !x.IsRoutingFailureReport)
|
||||||
.OrderByDescending(x => x.SuspendTime ?? DateTime.MinValue)
|
.OrderByDescending(x => x.SuspendTime ?? DateTime.MinValue)
|
||||||
.Take(Math.Min(_options.MaxSummaryItems, 5))
|
.Select(x => EmptyAsUnknown(x.ServiceName) + "@" + EmptyAsUnknown(x.HostName));
|
||||||
.Select(x => EmptyAsUnknown(x.ServiceName) + " on " + EmptyAsUnknown(x.HostName));
|
AppendLimitedList(builder, "recent", examples);
|
||||||
AppendList(builder, "examples", examples);
|
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,13 +551,20 @@ namespace BizTalkCheckmkPulse
|
|||||||
/// <param name="metrics">Performance-Daten oder Bindestrich.</param>
|
/// <param name="metrics">Performance-Daten oder Bindestrich.</param>
|
||||||
/// <param name="detail">Menschenlesbare Service-Ausgabe.</param>
|
/// <param name="detail">Menschenlesbare Service-Ausgabe.</param>
|
||||||
/// <returns>Vollstaendige Local-Check-Zeile.</returns>
|
/// <returns>Vollstaendige Local-Check-Zeile.</returns>
|
||||||
private static string BuildLine(CheckState state, string serviceName, string metrics, string detail)
|
private string BuildLine(CheckState state, string serviceName, string metrics, string detail)
|
||||||
{
|
{
|
||||||
|
var sanitizedDetail = SanitizeDetail(detail);
|
||||||
|
if (sanitizedDetail.Length > _options.MaxDetailCharacters)
|
||||||
|
{
|
||||||
|
sanitizedDetail = sanitizedDetail.Substring(0, _options.MaxDetailCharacters - 16).TrimEnd()
|
||||||
|
+ "... [truncated]";
|
||||||
|
}
|
||||||
|
|
||||||
return ((int)state).ToString(CultureInfo.InvariantCulture)
|
return ((int)state).ToString(CultureInfo.InvariantCulture)
|
||||||
+ " \"" + SanitizeService(serviceName) + "\" "
|
+ " \"" + SanitizeService(serviceName) + "\" "
|
||||||
+ (string.IsNullOrWhiteSpace(metrics) ? "-" : metrics)
|
+ (string.IsNullOrWhiteSpace(metrics) ? "-" : metrics)
|
||||||
+ " "
|
+ " "
|
||||||
+ SanitizeDetail(detail);
|
+ sanitizedDetail;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -465,6 +614,57 @@ namespace BizTalkCheckmkPulse
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AppendLimitedList(StringBuilder builder, string label, IEnumerable<string> values)
|
||||||
|
{
|
||||||
|
var all = values.Where(x => !string.IsNullOrWhiteSpace(x)).ToArray();
|
||||||
|
if (all.Length == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var visible = all.Take(_options.MaxSummaryItems).ToArray();
|
||||||
|
builder.Append("; ").Append(label).Append("=").Append(string.Join(", ", visible));
|
||||||
|
if (all.Length > visible.Length)
|
||||||
|
{
|
||||||
|
builder.Append(" (+").Append(all.Length - visible.Length).Append(" more)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ReceiveLocationDisplay(ReceiveLocationState item)
|
||||||
|
{
|
||||||
|
return ArtifactDisplay(item.ApplicationName, item.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string SendPortDisplay(SendPortState item)
|
||||||
|
{
|
||||||
|
return ArtifactDisplay(item.ApplicationName, item.Name)
|
||||||
|
+ "(" + SendPortStateName(item.Status) + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ArtifactDisplay(string applicationName, string artifactName)
|
||||||
|
{
|
||||||
|
var application = EmptyAsUnknown(applicationName);
|
||||||
|
var name = EmptyAsUnknown(artifactName);
|
||||||
|
return string.Equals(application, "(unknown)", StringComparison.OrdinalIgnoreCase)
|
||||||
|
? name
|
||||||
|
: application + "\\" + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string SendPortStateName(int status)
|
||||||
|
{
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
case SendPortStarted:
|
||||||
|
return "started";
|
||||||
|
case SendPortStopped:
|
||||||
|
return "stopped";
|
||||||
|
case SendPortBound:
|
||||||
|
return "bound";
|
||||||
|
default:
|
||||||
|
return "unknown:" + status.ToString(CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Haengt einen optionalen benannten Wert an einen Detailtext an.
|
/// Haengt einen optionalen benannten Wert an einen Detailtext an.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Security.Principal;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace BizTalkCheckmkPulse
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Kleine, ausfallsichere Tagesdatei-Protokollierung ohne externe Abhaengigkeiten.
|
||||||
|
/// Logging-Fehler duerfen weder Provider noch Checkmk-Ausgabe blockieren.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class FileLogger
|
||||||
|
{
|
||||||
|
private readonly string _directory;
|
||||||
|
private readonly int _retentionDays;
|
||||||
|
private readonly string _component;
|
||||||
|
|
||||||
|
public FileLogger(string directory, int retentionDays, string component)
|
||||||
|
{
|
||||||
|
_directory = directory;
|
||||||
|
_retentionDays = retentionDays;
|
||||||
|
_component = string.IsNullOrWhiteSpace(component) ? "application" : component;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Info(string message)
|
||||||
|
{
|
||||||
|
Write("INFO", message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Warning(string message)
|
||||||
|
{
|
||||||
|
Write("WARN", message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Error(string message, Exception exception)
|
||||||
|
{
|
||||||
|
Write("ERROR", message, exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Prune()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(_directory))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cutoff = DateTime.UtcNow.Date.AddDays(-_retentionDays);
|
||||||
|
foreach (var file in Directory.GetFiles(_directory, "biztalk-checkmk-pulse-*.log"))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.GetLastWriteTimeUtc(file) < cutoff)
|
||||||
|
{
|
||||||
|
File.Delete(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Write(string level, string message, Exception exception)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(_directory);
|
||||||
|
var path = Path.Combine(
|
||||||
|
_directory,
|
||||||
|
"biztalk-checkmk-pulse-" + DateTime.UtcNow.ToString("yyyyMMdd", CultureInfo.InvariantCulture) + ".log");
|
||||||
|
var line = new StringBuilder()
|
||||||
|
.Append(DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture))
|
||||||
|
.Append(" level=").Append(level)
|
||||||
|
.Append(" component=").Append(_component)
|
||||||
|
.Append(" pid=").Append(System.Diagnostics.Process.GetCurrentProcess().Id)
|
||||||
|
.Append(" identity=").Append(CurrentIdentity())
|
||||||
|
.Append(" message=").Append(SingleLine(message));
|
||||||
|
|
||||||
|
if (exception != null)
|
||||||
|
{
|
||||||
|
line.Append(" exception=").Append(SingleLine(exception.ToString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
line.Append(Environment.NewLine);
|
||||||
|
var bytes = new UTF8Encoding(false).GetBytes(line.ToString());
|
||||||
|
for (var attempt = 0; attempt < 3; attempt++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var stream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read))
|
||||||
|
{
|
||||||
|
stream.Write(bytes, 0, bytes.Length);
|
||||||
|
stream.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
if (attempt == 2)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.Sleep(25 * (attempt + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// Logging bleibt bewusst best effort.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string CurrentIdentity()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var identity = WindowsIdentity.GetCurrent();
|
||||||
|
return identity == null ? "(unknown)" : SingleLine(identity.Name);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return "(unknown)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string SingleLine(string value)
|
||||||
|
{
|
||||||
|
return (value ?? string.Empty)
|
||||||
|
.Replace("\r", "\\r")
|
||||||
|
.Replace("\n", "\\n")
|
||||||
|
.Replace("\t", " ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,6 +43,7 @@ namespace BizTalkCheckmkPulse
|
|||||||
Connectivity,
|
Connectivity,
|
||||||
Timeout,
|
Timeout,
|
||||||
Configuration,
|
Configuration,
|
||||||
|
Schema,
|
||||||
Provider,
|
Provider,
|
||||||
Unexpected
|
Unexpected
|
||||||
}
|
}
|
||||||
@@ -95,6 +96,8 @@ namespace BizTalkCheckmkPulse
|
|||||||
Platform = new PlatformState();
|
Platform = new PlatformState();
|
||||||
HostInstances = new List<HostInstanceState>();
|
HostInstances = new List<HostInstanceState>();
|
||||||
SuspendedInstances = new List<SuspendedInstance>();
|
SuspendedInstances = new List<SuspendedInstance>();
|
||||||
|
ReceiveLocations = new List<ReceiveLocationState>();
|
||||||
|
SendPorts = new List<SendPortState>();
|
||||||
Applications = new List<ApplicationRuntimeState>();
|
Applications = new List<ApplicationRuntimeState>();
|
||||||
SqlTargets = new List<SqlAccessState>();
|
SqlTargets = new List<SqlAccessState>();
|
||||||
EventLog = new EventLogState();
|
EventLog = new EventLogState();
|
||||||
@@ -104,6 +107,8 @@ namespace BizTalkCheckmkPulse
|
|||||||
public PlatformState Platform { get; private set; }
|
public PlatformState Platform { get; private set; }
|
||||||
public List<HostInstanceState> HostInstances { get; private set; }
|
public List<HostInstanceState> HostInstances { get; private set; }
|
||||||
public List<SuspendedInstance> SuspendedInstances { get; private set; }
|
public List<SuspendedInstance> SuspendedInstances { get; private set; }
|
||||||
|
public List<ReceiveLocationState> ReceiveLocations { get; private set; }
|
||||||
|
public List<SendPortState> SendPorts { get; private set; }
|
||||||
public List<ApplicationRuntimeState> Applications { get; private set; }
|
public List<ApplicationRuntimeState> Applications { get; private set; }
|
||||||
public List<SqlAccessState> SqlTargets { get; private set; }
|
public List<SqlAccessState> SqlTargets { get; private set; }
|
||||||
public string ExecutionIdentity { get; set; }
|
public string ExecutionIdentity { get; set; }
|
||||||
@@ -120,9 +125,13 @@ namespace BizTalkCheckmkPulse
|
|||||||
public bool PlatformDataAvailable { get; set; }
|
public bool PlatformDataAvailable { get; set; }
|
||||||
public bool HostInstancesDataAvailable { get; set; }
|
public bool HostInstancesDataAvailable { get; set; }
|
||||||
public bool SuspendedInstancesDataAvailable { get; set; }
|
public bool SuspendedInstancesDataAvailable { get; set; }
|
||||||
public bool RuntimeArtifactsDataAvailable { get; set; }
|
public bool ReceiveLocationsDataAvailable { get; set; }
|
||||||
|
public bool SendPortsDataAvailable { get; set; }
|
||||||
|
public bool OrchestrationsDataAvailable { get; set; }
|
||||||
public string ServerName { get; set; }
|
public string ServerName { get; set; }
|
||||||
public string GroupName { get; set; }
|
public string GroupName { get; set; }
|
||||||
|
public string OperatorGroup { get; set; }
|
||||||
|
public string ReadOnlyUserGroup { get; set; }
|
||||||
public string ManagementDbServer { get; set; }
|
public string ManagementDbServer { get; set; }
|
||||||
public string ManagementDbName { get; set; }
|
public string ManagementDbName { get; set; }
|
||||||
public string MessageBoxDbServer { get; set; }
|
public string MessageBoxDbServer { get; set; }
|
||||||
@@ -166,7 +175,33 @@ namespace BizTalkCheckmkPulse
|
|||||||
public string InstanceId { get; set; }
|
public string InstanceId { get; set; }
|
||||||
public string ErrorDescription { get; set; }
|
public string ErrorDescription { get; set; }
|
||||||
public DateTime? SuspendTime { get; set; }
|
public DateTime? SuspendTime { get; set; }
|
||||||
|
public int ServiceClassId { get; set; }
|
||||||
public SuspendedKind Kind { get; set; }
|
public SuspendedKind Kind { get; set; }
|
||||||
|
|
||||||
|
public bool IsRoutingFailureReport
|
||||||
|
{
|
||||||
|
get { return ServiceClassId == 64; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Konfigurations- und Laufzeitzustand einer Receive Location.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class ReceiveLocationState
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string ApplicationName { get; set; }
|
||||||
|
public bool? IsDisabled { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Laufzeitzustand eines Send Ports.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class SendPortState
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string ApplicationName { get; set; }
|
||||||
|
public int Status { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Configuration;
|
using System.Configuration;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace BizTalkCheckmkPulse
|
namespace BizTalkCheckmkPulse
|
||||||
@@ -19,14 +20,25 @@ namespace BizTalkCheckmkPulse
|
|||||||
public int SqlConnectionTimeoutSeconds { get; set; }
|
public int SqlConnectionTimeoutSeconds { get; set; }
|
||||||
public int WarnResumableThreshold { get; set; }
|
public int WarnResumableThreshold { get; set; }
|
||||||
public int CritNonResumableThreshold { get; set; }
|
public int CritNonResumableThreshold { get; set; }
|
||||||
|
public int CritRoutingFailureThreshold { get; set; }
|
||||||
public int MaxSummaryItems { get; set; }
|
public int MaxSummaryItems { get; set; }
|
||||||
|
public int MaxDetailCharacters { get; set; }
|
||||||
public bool AlertOnArtifactRuntimeIssues { get; set; }
|
public bool AlertOnArtifactRuntimeIssues { get; set; }
|
||||||
|
public bool AlertOnInactiveOrchestrations { get; set; }
|
||||||
|
public IReadOnlyList<string> ExpectedDisabledReceiveLocations { get; set; }
|
||||||
|
public IReadOnlyList<string> ExpectedInactiveSendPorts { get; set; }
|
||||||
public bool EmitPerApplicationSuspensionServices { get; set; }
|
public bool EmitPerApplicationSuspensionServices { get; set; }
|
||||||
public bool ProbeEventLog { get; set; }
|
public bool ProbeEventLog { get; set; }
|
||||||
public int EventLogLookbackMinutes { get; set; }
|
public int EventLogLookbackMinutes { get; set; }
|
||||||
public int EventLogWarnThreshold { get; set; }
|
public int EventLogWarnThreshold { get; set; }
|
||||||
public int EventLogCritThreshold { get; set; }
|
public int EventLogCritThreshold { get; set; }
|
||||||
public IReadOnlyList<string> EventLogSources { get; set; }
|
public IReadOnlyList<string> EventLogSources { get; set; }
|
||||||
|
public string SnapshotPath { get; set; }
|
||||||
|
public int SnapshotMaxAgeSeconds { get; set; }
|
||||||
|
public int SnapshotMaxBytes { get; set; }
|
||||||
|
public string LogDirectory { get; set; }
|
||||||
|
public int LogRetentionDays { get; set; }
|
||||||
|
public bool Collect { get; set; }
|
||||||
public bool SelfTest { get; set; }
|
public bool SelfTest { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -42,14 +54,25 @@ namespace BizTalkCheckmkPulse
|
|||||||
SqlConnectionTimeoutSeconds = 5;
|
SqlConnectionTimeoutSeconds = 5;
|
||||||
WarnResumableThreshold = 1;
|
WarnResumableThreshold = 1;
|
||||||
CritNonResumableThreshold = 1;
|
CritNonResumableThreshold = 1;
|
||||||
MaxSummaryItems = 12;
|
CritRoutingFailureThreshold = 1;
|
||||||
AlertOnArtifactRuntimeIssues = false;
|
MaxSummaryItems = 5;
|
||||||
|
MaxDetailCharacters = 1600;
|
||||||
|
AlertOnArtifactRuntimeIssues = true;
|
||||||
|
AlertOnInactiveOrchestrations = false;
|
||||||
|
ExpectedDisabledReceiveLocations = new string[0];
|
||||||
|
ExpectedInactiveSendPorts = new string[0];
|
||||||
EmitPerApplicationSuspensionServices = false;
|
EmitPerApplicationSuspensionServices = false;
|
||||||
ProbeEventLog = true;
|
ProbeEventLog = true;
|
||||||
EventLogLookbackMinutes = 60;
|
EventLogLookbackMinutes = 60;
|
||||||
EventLogWarnThreshold = 1;
|
EventLogWarnThreshold = 1;
|
||||||
EventLogCritThreshold = 10;
|
EventLogCritThreshold = 10;
|
||||||
EventLogSources = new[] { "BizTalk Server", "XLANG/s", "ENTSSO", "BizTalk Server Application", "BizTalk Server EDI" };
|
EventLogSources = new[] { "BizTalk Server", "XLANG/s", "ENTSSO", "BizTalk Server Application", "BizTalk Server EDI" };
|
||||||
|
var commonData = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
|
||||||
|
SnapshotPath = Path.Combine(commonData, "BizTalkCheckmkPulse", "data", "biztalk-checkmk-pulse.snapshot");
|
||||||
|
SnapshotMaxAgeSeconds = 180;
|
||||||
|
SnapshotMaxBytes = 1048576;
|
||||||
|
LogDirectory = Path.Combine(commonData, "BizTalkCheckmkPulse", "logs");
|
||||||
|
LogRetentionDays = 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -83,16 +106,30 @@ namespace BizTalkCheckmkPulse
|
|||||||
options.SqlConnectionTimeoutSeconds = ReadInt(settings, "SqlConnectionTimeoutSeconds", options.SqlConnectionTimeoutSeconds, 3, 60);
|
options.SqlConnectionTimeoutSeconds = ReadInt(settings, "SqlConnectionTimeoutSeconds", options.SqlConnectionTimeoutSeconds, 3, 60);
|
||||||
options.WarnResumableThreshold = ReadInt(settings, "WarnResumableThreshold", options.WarnResumableThreshold, 0, 1000000);
|
options.WarnResumableThreshold = ReadInt(settings, "WarnResumableThreshold", options.WarnResumableThreshold, 0, 1000000);
|
||||||
options.CritNonResumableThreshold = ReadInt(settings, "CritNonResumableThreshold", options.CritNonResumableThreshold, 0, 1000000);
|
options.CritNonResumableThreshold = ReadInt(settings, "CritNonResumableThreshold", options.CritNonResumableThreshold, 0, 1000000);
|
||||||
|
options.CritRoutingFailureThreshold = ReadInt(settings, "CritRoutingFailureThreshold", options.CritRoutingFailureThreshold, 0, 1000000);
|
||||||
options.MaxSummaryItems = ReadInt(settings, "MaxSummaryItems", options.MaxSummaryItems, 1, 100);
|
options.MaxSummaryItems = ReadInt(settings, "MaxSummaryItems", options.MaxSummaryItems, 1, 100);
|
||||||
|
options.MaxDetailCharacters = ReadInt(settings, "MaxDetailCharacters", options.MaxDetailCharacters, 256, 8192);
|
||||||
options.AlertOnArtifactRuntimeIssues = ReadBool(settings, "AlertOnArtifactRuntimeIssues", options.AlertOnArtifactRuntimeIssues);
|
options.AlertOnArtifactRuntimeIssues = ReadBool(settings, "AlertOnArtifactRuntimeIssues", options.AlertOnArtifactRuntimeIssues);
|
||||||
|
options.AlertOnInactiveOrchestrations = ReadBool(settings, "AlertOnInactiveOrchestrations", options.AlertOnInactiveOrchestrations);
|
||||||
|
options.ExpectedDisabledReceiveLocations = ReadExactList(ReadString(settings, "ExpectedDisabledReceiveLocations", string.Empty));
|
||||||
|
options.ExpectedInactiveSendPorts = ReadExactList(ReadString(settings, "ExpectedInactiveSendPorts", string.Empty));
|
||||||
options.EmitPerApplicationSuspensionServices = ReadBool(settings, "EmitPerApplicationSuspensionServices", options.EmitPerApplicationSuspensionServices);
|
options.EmitPerApplicationSuspensionServices = ReadBool(settings, "EmitPerApplicationSuspensionServices", options.EmitPerApplicationSuspensionServices);
|
||||||
options.ProbeEventLog = ReadBool(settings, "ProbeEventLog", options.ProbeEventLog);
|
options.ProbeEventLog = ReadBool(settings, "ProbeEventLog", options.ProbeEventLog);
|
||||||
options.EventLogLookbackMinutes = ReadInt(settings, "EventLogLookbackMinutes", options.EventLogLookbackMinutes, 1, 10080);
|
options.EventLogLookbackMinutes = ReadInt(settings, "EventLogLookbackMinutes", options.EventLogLookbackMinutes, 1, 10080);
|
||||||
options.EventLogWarnThreshold = ReadInt(settings, "EventLogWarnThreshold", options.EventLogWarnThreshold, 0, 1000000);
|
options.EventLogWarnThreshold = ReadInt(settings, "EventLogWarnThreshold", options.EventLogWarnThreshold, 0, 1000000);
|
||||||
options.EventLogCritThreshold = ReadInt(settings, "EventLogCritThreshold", options.EventLogCritThreshold, 0, 1000000);
|
options.EventLogCritThreshold = ReadInt(settings, "EventLogCritThreshold", options.EventLogCritThreshold, 0, 1000000);
|
||||||
options.EventLogSources = ReadSources(ReadString(settings, "EventLogSources", string.Join("|", options.EventLogSources)));
|
options.EventLogSources = ReadSources(ReadString(settings, "EventLogSources", string.Join("|", options.EventLogSources)));
|
||||||
|
options.SnapshotPath = Environment.ExpandEnvironmentVariables(ReadString(settings, "SnapshotPath", options.SnapshotPath));
|
||||||
|
options.SnapshotMaxAgeSeconds = ReadInt(settings, "SnapshotMaxAgeSeconds", options.SnapshotMaxAgeSeconds, 60, 86400);
|
||||||
|
options.SnapshotMaxBytes = ReadInt(settings, "SnapshotMaxBytes", options.SnapshotMaxBytes, 4096, 16777216);
|
||||||
|
options.LogDirectory = Environment.ExpandEnvironmentVariables(ReadString(settings, "LogDirectory", options.LogDirectory));
|
||||||
|
options.LogRetentionDays = ReadInt(settings, "LogRetentionDays", options.LogRetentionDays, 1, 365);
|
||||||
|
|
||||||
ApplyArguments(options, args ?? new string[0]);
|
ApplyArguments(options, args ?? new string[0]);
|
||||||
|
if (!options.SelfTest)
|
||||||
|
{
|
||||||
|
Validate(options);
|
||||||
|
}
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,6 +147,14 @@ namespace BizTalkCheckmkPulse
|
|||||||
{
|
{
|
||||||
options.SelfTest = true;
|
options.SelfTest = true;
|
||||||
}
|
}
|
||||||
|
else if (EqualsAny(arg, "--collect", "/collect"))
|
||||||
|
{
|
||||||
|
options.Collect = true;
|
||||||
|
}
|
||||||
|
else if (EqualsAny(arg, "--consume", "/consume"))
|
||||||
|
{
|
||||||
|
options.Collect = false;
|
||||||
|
}
|
||||||
else if (EqualsAny(arg, "--server", "/server") && i + 1 < args.Length)
|
else if (EqualsAny(arg, "--server", "/server") && i + 1 < args.Length)
|
||||||
{
|
{
|
||||||
options.Server = args[++i];
|
options.Server = args[++i];
|
||||||
@@ -133,6 +178,22 @@ namespace BizTalkCheckmkPulse
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verhindert unsichere oder mehrdeutige Laufzeitpfade.
|
||||||
|
/// </summary>
|
||||||
|
private static void Validate(MonitoringOptions options)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(options.SnapshotPath) || !Path.IsPathRooted(options.SnapshotPath))
|
||||||
|
{
|
||||||
|
throw new ConfigurationErrorsException("SnapshotPath must be an absolute path.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(options.LogDirectory) || !Path.IsPathRooted(options.LogDirectory))
|
||||||
|
{
|
||||||
|
throw new ConfigurationErrorsException("LogDirectory must be an absolute path.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Liest einen getrimmten Konfigurationswert.
|
/// Liest einen getrimmten Konfigurationswert.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -199,6 +260,49 @@ namespace BizTalkCheckmkPulse
|
|||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Liest eine Pipe-getrennte, exakte Allowlist. Wildcards werden bewusst nicht interpretiert.
|
||||||
|
/// </summary>
|
||||||
|
private static IReadOnlyList<string> ReadExactList(string value)
|
||||||
|
{
|
||||||
|
return (value ?? string.Empty)
|
||||||
|
.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(x => x.Trim())
|
||||||
|
.Where(x => x.Length > 0)
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool IsExpectedDisabledReceiveLocation(string applicationName, string artifactName)
|
||||||
|
{
|
||||||
|
return MatchesArtifact(ExpectedDisabledReceiveLocations, applicationName, artifactName);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool IsExpectedInactiveSendPort(string applicationName, string artifactName)
|
||||||
|
{
|
||||||
|
return MatchesArtifact(ExpectedInactiveSendPorts, applicationName, artifactName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool MatchesArtifact(
|
||||||
|
IEnumerable<string> configuredEntries,
|
||||||
|
string applicationName,
|
||||||
|
string artifactName)
|
||||||
|
{
|
||||||
|
var name = (artifactName ?? string.Empty).Trim();
|
||||||
|
var application = (applicationName ?? string.Empty).Trim();
|
||||||
|
if (name.Length == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var qualified = application.Length == 0 || string.Equals(application, "(unknown)", StringComparison.OrdinalIgnoreCase)
|
||||||
|
? string.Empty
|
||||||
|
: application + "\\" + name;
|
||||||
|
return configuredEntries.Any(x =>
|
||||||
|
string.Equals(x, name, StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| (qualified.Length > 0 && string.Equals(x, qualified, StringComparison.OrdinalIgnoreCase)));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Prueft einen Wert ohne Beachtung der Gross-/Kleinschreibung gegen Kandidaten.
|
/// Prueft einen Wert ohne Beachtung der Gross-/Kleinschreibung gegen Kandidaten.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Principal;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace BizTalkCheckmkPulse
|
namespace BizTalkCheckmkPulse
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Einstiegspunkt des Checkmk Local Checks.
|
/// Einstiegspunkt fuer privilegierten Provider und unprivilegierten Checkmk-Consumer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class Program
|
internal static class Program
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Laedt die Konfiguration, fuehrt die Probes aus und schreibt Checkmk-Zeilen nach STDOUT.
|
/// Laedt die Konfiguration und fuehrt je nach Modus Provider oder Snapshot-Consumer aus.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="args">Kommandozeilenargumente wie <c>--self-test</c> oder <c>--environment</c>.</param>
|
/// <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>
|
/// <returns>Consumer immer 0; Provider 0 bei Erfolg und ungleich 0 bei einem Laufzeitfehler.</returns>
|
||||||
private static int Main(string[] args)
|
private static int Main(string[] args)
|
||||||
{
|
{
|
||||||
MonitoringOptions options = null;
|
MonitoringOptions options = null;
|
||||||
@@ -34,38 +37,170 @@ namespace BizTalkCheckmkPulse
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = new ProbeResult();
|
return options.Collect
|
||||||
var wmiProbe = new WmiBizTalkProbe(options);
|
? RunProvider(options, formatter)
|
||||||
wmiProbe.Query(result);
|
: RunConsumer(options, formatter);
|
||||||
|
|
||||||
// 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);
|
|
||||||
eventLogProbe.Query(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var line in formatter.Format(result))
|
|
||||||
{
|
|
||||||
Console.WriteLine(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
var fallbackOptions = options ?? new MonitoringOptions();
|
var fallbackOptions = options ?? new MonitoringOptions();
|
||||||
var formatter = new CheckmkLocalFormatter(fallbackOptions);
|
var formatter = new CheckmkLocalFormatter(fallbackOptions);
|
||||||
foreach (var line in formatter.FormatFatal("BizTalk Checkmk Pulse failed: " + ex.GetType().Name + ": " + ex.Message))
|
foreach (var line in formatter.FormatSnapshotFailure(
|
||||||
|
"Programmstart fehlgeschlagen: " + ex.GetType().Name + ": " + ex.Message))
|
||||||
|
{
|
||||||
|
Console.WriteLine(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
return options != null && options.Collect ? 1 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int RunProvider(MonitoringOptions options, CheckmkLocalFormatter formatter)
|
||||||
|
{
|
||||||
|
var logger = new FileLogger(options.LogDirectory, options.LogRetentionDays, "provider");
|
||||||
|
logger.Prune();
|
||||||
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
var lockPath = options.SnapshotPath + ".provider.lock";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
EnsureProviderIdentity();
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(options.SnapshotPath));
|
||||||
|
using (new FileStream(lockPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
|
||||||
|
{
|
||||||
|
logger.Info("Collection started. snapshot=" + options.SnapshotPath);
|
||||||
|
var result = Collect(options);
|
||||||
|
foreach (var diagnostic in result.Diagnostics)
|
||||||
|
{
|
||||||
|
logger.Warning("Probe diagnostic: " + diagnostic.ToDisplayText());
|
||||||
|
}
|
||||||
|
|
||||||
|
var lines = formatter.Format(result).ToArray();
|
||||||
|
new SnapshotStore(options.SnapshotPath, options.SnapshotMaxBytes)
|
||||||
|
.Write(lines, DateTime.UtcNow, CurrentIdentity());
|
||||||
|
stopwatch.Stop();
|
||||||
|
logger.Info(
|
||||||
|
"Collection completed. lines="
|
||||||
|
+ lines.Length
|
||||||
|
+ " diagnostics="
|
||||||
|
+ result.Diagnostics.Count
|
||||||
|
+ " suspended_total="
|
||||||
|
+ result.SuspendedInstances.Count
|
||||||
|
+ " routing_failure_reports="
|
||||||
|
+ result.SuspendedInstances.Count(x => x.IsRoutingFailureReport)
|
||||||
|
+ " receive_locations_total="
|
||||||
|
+ result.ReceiveLocations.Count
|
||||||
|
+ " receive_locations_disabled="
|
||||||
|
+ result.ReceiveLocations.Count(x => x.IsDisabled == true)
|
||||||
|
+ " receive_locations_unknown="
|
||||||
|
+ result.ReceiveLocations.Count(x => !x.IsDisabled.HasValue)
|
||||||
|
+ " send_ports_total="
|
||||||
|
+ result.SendPorts.Count
|
||||||
|
+ " send_ports_not_started="
|
||||||
|
+ result.SendPorts.Count(x => x.Status != 3)
|
||||||
|
+ " elapsed_ms="
|
||||||
|
+ stopwatch.ElapsedMilliseconds);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
logger.Warning("Collection skipped or snapshot I/O failed: " + ex.Message);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
stopwatch.Stop();
|
||||||
|
logger.Error("Collection failed after " + stopwatch.ElapsedMilliseconds + " ms.", ex);
|
||||||
|
TryWriteFatalSnapshot(options, formatter, ex, logger);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureProviderIdentity()
|
||||||
|
{
|
||||||
|
using (var identity = WindowsIdentity.GetCurrent())
|
||||||
|
{
|
||||||
|
if (identity != null
|
||||||
|
&& identity.User != null
|
||||||
|
&& identity.User.IsWellKnown(WellKnownSidType.LocalSystemSid))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"Provider mode must not run as LocalSystem. Configure the dedicated Scheduled Task account.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int RunConsumer(MonitoringOptions options, CheckmkLocalFormatter formatter)
|
||||||
|
{
|
||||||
|
var snapshot = new SnapshotStore(options.SnapshotPath, options.SnapshotMaxBytes)
|
||||||
|
.Read(DateTime.UtcNow, TimeSpan.FromSeconds(options.SnapshotMaxAgeSeconds));
|
||||||
|
if (snapshot.IsSuccess)
|
||||||
|
{
|
||||||
|
foreach (var line in snapshot.Lines)
|
||||||
{
|
{
|
||||||
Console.WriteLine(line);
|
Console.WriteLine(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
new FileLogger(options.LogDirectory, options.LogRetentionDays, "consumer")
|
||||||
|
.Warning("Snapshot rejected. reason=" + snapshot.Error + " path=" + options.SnapshotPath);
|
||||||
|
foreach (var line in formatter.FormatSnapshotFailure(snapshot.Error))
|
||||||
|
{
|
||||||
|
Console.WriteLine(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ProbeResult Collect(MonitoringOptions options)
|
||||||
|
{
|
||||||
|
var result = new ProbeResult();
|
||||||
|
new WmiBizTalkProbe(options).Query(result);
|
||||||
|
new SqlConnectivityProbe(options).Query(result);
|
||||||
|
|
||||||
|
if (options.ProbeEventLog)
|
||||||
|
{
|
||||||
|
new EventLogProbe(options).Query(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TryWriteFatalSnapshot(
|
||||||
|
MonitoringOptions options,
|
||||||
|
CheckmkLocalFormatter formatter,
|
||||||
|
Exception exception,
|
||||||
|
FileLogger logger)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var lines = formatter
|
||||||
|
.FormatFatal("Privilegierter Datenprovider fehlgeschlagen: " + exception.GetType().Name + ": " + exception.Message)
|
||||||
|
.ToArray();
|
||||||
|
new SnapshotStore(options.SnapshotPath, options.SnapshotMaxBytes)
|
||||||
|
.Write(lines, DateTime.UtcNow, CurrentIdentity());
|
||||||
|
logger.Warning("A current UNKNOWN snapshot was written after the provider failure.");
|
||||||
|
}
|
||||||
|
catch (Exception snapshotException)
|
||||||
|
{
|
||||||
|
logger.Error("Fatal UNKNOWN snapshot could not be written.", snapshotException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string CurrentIdentity()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var identity = WindowsIdentity.GetCurrent();
|
||||||
|
return identity == null ? "(unknown)" : identity.Name;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return "(unknown)";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("BizTalkCheckmkPulse.Tests")]
|
||||||
|
[assembly: AssemblyTitle("BizTalk Checkmk Pulse")]
|
||||||
|
[assembly: AssemblyDescription("Privileged BizTalk data provider and validated Checkmk snapshot consumer")]
|
||||||
|
[assembly: AssemblyCompany("BEW")]
|
||||||
|
[assembly: AssemblyProduct("BizTalk Checkmk Pulse")]
|
||||||
|
[assembly: AssemblyVersion("2.1.0.0")]
|
||||||
|
[assembly: AssemblyFileVersion("2.1.0.0")]
|
||||||
@@ -0,0 +1,353 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace BizTalkCheckmkPulse
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Schreibt und liest einen atomaren, integritaetsgeschuetzten Checkmk-Snapshot.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class SnapshotStore
|
||||||
|
{
|
||||||
|
internal const string Magic = "BIZTALK_CHECKMK_PULSE_SNAPSHOT_V2";
|
||||||
|
private static readonly UTF8Encoding StrictUtf8 = new UTF8Encoding(false, true);
|
||||||
|
private readonly string _path;
|
||||||
|
private readonly int _maxBytes;
|
||||||
|
|
||||||
|
public SnapshotStore(string path, int maxBytes)
|
||||||
|
{
|
||||||
|
_path = path;
|
||||||
|
_maxBytes = maxBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(IReadOnlyCollection<string> lines, DateTime generatedUtc, string identity)
|
||||||
|
{
|
||||||
|
ValidatePayload(lines);
|
||||||
|
var payload = string.Join("\n", lines) + "\n";
|
||||||
|
var payloadBytes = StrictUtf8.GetBytes(payload);
|
||||||
|
var content = BuildHeader(generatedUtc, identity, lines.Count, Hash(payloadBytes)) + payload;
|
||||||
|
var contentBytes = StrictUtf8.GetBytes(content);
|
||||||
|
if (contentBytes.Length > _maxBytes)
|
||||||
|
{
|
||||||
|
throw new InvalidDataException("Snapshot exceeds configured SnapshotMaxBytes.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var directory = Path.GetDirectoryName(_path);
|
||||||
|
if (string.IsNullOrWhiteSpace(directory))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Snapshot path has no parent directory.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
var temporaryPath = Path.Combine(
|
||||||
|
directory,
|
||||||
|
Path.GetFileName(_path) + "." + Process.GetCurrentProcess().Id.ToString(CultureInfo.InvariantCulture) + "." + Guid.NewGuid().ToString("N") + ".tmp");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var stream = new FileStream(
|
||||||
|
temporaryPath,
|
||||||
|
FileMode.CreateNew,
|
||||||
|
FileAccess.Write,
|
||||||
|
FileShare.None,
|
||||||
|
4096,
|
||||||
|
FileOptions.WriteThrough))
|
||||||
|
{
|
||||||
|
stream.Write(contentBytes, 0, contentBytes.Length);
|
||||||
|
stream.Flush(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (File.Exists(_path))
|
||||||
|
{
|
||||||
|
File.Replace(temporaryPath, _path, null, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
File.Move(temporaryPath, _path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(temporaryPath))
|
||||||
|
{
|
||||||
|
File.Delete(temporaryPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SnapshotReadResult Read(DateTime utcNow, TimeSpan maximumAge)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var bytes = ReadBytesWithRetry();
|
||||||
|
var text = StrictUtf8.GetString(bytes).Replace("\r\n", "\n");
|
||||||
|
var separator = text.IndexOf("\n\n", StringComparison.Ordinal);
|
||||||
|
if (separator < 0)
|
||||||
|
{
|
||||||
|
return SnapshotReadResult.Failed("Snapshot header separator is missing.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var headerLines = text.Substring(0, separator).Split('\n');
|
||||||
|
if (headerLines.Length != 6 || !string.Equals(headerLines[0], Magic, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return SnapshotReadResult.Failed("Snapshot format or version is invalid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTime generatedUtc;
|
||||||
|
int expectedLineCount;
|
||||||
|
if (!TryReadDate(headerLines[1], "generatedUtc=", out generatedUtc)
|
||||||
|
|| !TryReadInt(headerLines[4], "payloadLines=", out expectedLineCount))
|
||||||
|
{
|
||||||
|
return SnapshotReadResult.Failed("Snapshot metadata is invalid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var expectedMachine = DecodeHeader(headerLines[2], "machineBase64=");
|
||||||
|
DecodeHeader(headerLines[3], "identityBase64=");
|
||||||
|
var expectedHash = ReadHeaderValue(headerLines[5], "payloadSha256=");
|
||||||
|
if (!string.Equals(expectedMachine, Environment.MachineName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return SnapshotReadResult.Failed("Snapshot was created for a different machine.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload = text.Substring(separator + 2);
|
||||||
|
if (!payload.EndsWith("\n", StringComparison.Ordinal)
|
||||||
|
|| payload.IndexOf("\n\n", StringComparison.Ordinal) >= 0)
|
||||||
|
{
|
||||||
|
return SnapshotReadResult.Failed("Snapshot payload framing is invalid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var payloadBytes = StrictUtf8.GetBytes(payload);
|
||||||
|
if (!FixedTimeEquals(expectedHash, Hash(payloadBytes)))
|
||||||
|
{
|
||||||
|
return SnapshotReadResult.Failed("Snapshot SHA-256 validation failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var lines = payload
|
||||||
|
.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.ToArray();
|
||||||
|
if (lines.Length != expectedLineCount)
|
||||||
|
{
|
||||||
|
return SnapshotReadResult.Failed("Snapshot payload line count is invalid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidatePayload(lines);
|
||||||
|
var age = utcNow - generatedUtc;
|
||||||
|
if (age < TimeSpan.FromMinutes(-5))
|
||||||
|
{
|
||||||
|
return SnapshotReadResult.Failed("Snapshot timestamp is too far in the future.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (age > maximumAge)
|
||||||
|
{
|
||||||
|
return SnapshotReadResult.Failed(
|
||||||
|
"Snapshot is stale: age="
|
||||||
|
+ Math.Floor(age.TotalSeconds).ToString(CultureInfo.InvariantCulture)
|
||||||
|
+ "s, maximum="
|
||||||
|
+ Math.Floor(maximumAge.TotalSeconds).ToString(CultureInfo.InvariantCulture)
|
||||||
|
+ "s.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return SnapshotReadResult.Success(lines, generatedUtc);
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException)
|
||||||
|
{
|
||||||
|
return SnapshotReadResult.Failed("Snapshot file does not exist.");
|
||||||
|
}
|
||||||
|
catch (DirectoryNotFoundException)
|
||||||
|
{
|
||||||
|
return SnapshotReadResult.Failed("Snapshot directory does not exist.");
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException ex)
|
||||||
|
{
|
||||||
|
return SnapshotReadResult.Failed("Snapshot cannot be read: " + ex.Message);
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
return SnapshotReadResult.Failed("Snapshot I/O failed: " + ex.Message);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return SnapshotReadResult.Failed("Snapshot validation failed: " + ex.GetType().Name + ": " + ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] ReadBytesWithRetry()
|
||||||
|
{
|
||||||
|
for (var attempt = 0; ; attempt++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var stream = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete))
|
||||||
|
{
|
||||||
|
if (stream.Length <= 0 || stream.Length > _maxBytes)
|
||||||
|
{
|
||||||
|
throw new InvalidDataException("Snapshot size is outside the allowed range.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes = new byte[(int)stream.Length];
|
||||||
|
var offset = 0;
|
||||||
|
while (offset < bytes.Length)
|
||||||
|
{
|
||||||
|
var read = stream.Read(bytes, offset, bytes.Length - offset);
|
||||||
|
if (read == 0)
|
||||||
|
{
|
||||||
|
throw new EndOfStreamException("Unexpected end of snapshot.");
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += read;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
if (attempt >= 2)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.Sleep(25 * (attempt + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildHeader(DateTime generatedUtc, string identity, int lineCount, string hash)
|
||||||
|
{
|
||||||
|
return Magic + "\n"
|
||||||
|
+ "generatedUtc=" + generatedUtc.ToUniversalTime().ToString("o", CultureInfo.InvariantCulture) + "\n"
|
||||||
|
+ "machineBase64=" + Convert.ToBase64String(StrictUtf8.GetBytes(Environment.MachineName)) + "\n"
|
||||||
|
+ "identityBase64=" + Convert.ToBase64String(StrictUtf8.GetBytes(identity ?? string.Empty)) + "\n"
|
||||||
|
+ "payloadLines=" + lineCount.ToString(CultureInfo.InvariantCulture) + "\n"
|
||||||
|
+ "payloadSha256=" + hash + "\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ValidatePayload(IEnumerable<string> lines)
|
||||||
|
{
|
||||||
|
if (lines == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("lines");
|
||||||
|
}
|
||||||
|
|
||||||
|
var count = 0;
|
||||||
|
foreach (var line in lines)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
if (string.IsNullOrWhiteSpace(line)
|
||||||
|
|| line.IndexOf('\r') >= 0
|
||||||
|
|| line.IndexOf('\n') >= 0
|
||||||
|
|| line.Length < 5
|
||||||
|
|| line[1] != ' '
|
||||||
|
|| line[2] != '"'
|
||||||
|
|| line[0] < '0'
|
||||||
|
|| line[0] > '3')
|
||||||
|
{
|
||||||
|
throw new InvalidDataException("Snapshot contains an invalid Checkmk local-check line.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count < 8)
|
||||||
|
{
|
||||||
|
throw new InvalidDataException("Snapshot must contain all eight stable services.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Hash(byte[] bytes)
|
||||||
|
{
|
||||||
|
using (var sha = SHA256.Create())
|
||||||
|
{
|
||||||
|
return string.Concat(sha.ComputeHash(bytes).Select(x => x.ToString("x2", CultureInfo.InvariantCulture)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool FixedTimeEquals(string left, string right)
|
||||||
|
{
|
||||||
|
if (left == null || right == null || left.Length != right.Length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var difference = 0;
|
||||||
|
for (var i = 0; i < left.Length; i++)
|
||||||
|
{
|
||||||
|
difference |= left[i] ^ right[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return difference == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ReadHeaderValue(string line, string prefix)
|
||||||
|
{
|
||||||
|
if (line == null || !line.StartsWith(prefix, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
throw new InvalidDataException("Missing snapshot header " + prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
return line.Substring(prefix.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string DecodeHeader(string line, string prefix)
|
||||||
|
{
|
||||||
|
return StrictUtf8.GetString(Convert.FromBase64String(ReadHeaderValue(line, prefix)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryReadDate(string line, string prefix, out DateTime result)
|
||||||
|
{
|
||||||
|
return DateTime.TryParseExact(
|
||||||
|
ReadHeaderValue(line, prefix),
|
||||||
|
"o",
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal,
|
||||||
|
out result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryReadInt(string line, string prefix, out int result)
|
||||||
|
{
|
||||||
|
return int.TryParse(ReadHeaderValue(line, prefix), NumberStyles.Integer, CultureInfo.InvariantCulture, out result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class SnapshotReadResult
|
||||||
|
{
|
||||||
|
private SnapshotReadResult()
|
||||||
|
{
|
||||||
|
Lines = new string[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSuccess { get; private set; }
|
||||||
|
public string Error { get; private set; }
|
||||||
|
public IReadOnlyList<string> Lines { get; private set; }
|
||||||
|
public DateTime GeneratedUtc { get; private set; }
|
||||||
|
|
||||||
|
public static SnapshotReadResult Success(IReadOnlyList<string> lines, DateTime generatedUtc)
|
||||||
|
{
|
||||||
|
return new SnapshotReadResult
|
||||||
|
{
|
||||||
|
IsSuccess = true,
|
||||||
|
Lines = lines,
|
||||||
|
GeneratedUtc = generatedUtc
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SnapshotReadResult Failed(string error)
|
||||||
|
{
|
||||||
|
return new SnapshotReadResult
|
||||||
|
{
|
||||||
|
IsSuccess = false,
|
||||||
|
Error = error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,18 +35,29 @@ namespace BizTalkCheckmkPulse
|
|||||||
}
|
}
|
||||||
|
|
||||||
AddTarget(result, "Management", result.Platform.ManagementDbServer, result.Platform.ManagementDbName);
|
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)
|
if (!result.Platform.PlatformDataAvailable)
|
||||||
{
|
{
|
||||||
|
var wmiPermissionFailure = result.Diagnostics.Any(x =>
|
||||||
|
x.Area == DiagnosticArea.Wmi
|
||||||
|
&& x.Category == DiagnosticCategory.Permission
|
||||||
|
&& x.Required
|
||||||
|
&& string.Equals(x.Component, "MSBTS_GroupSetting", StringComparison.OrdinalIgnoreCase));
|
||||||
result.Diagnostics.Add(new ProbeDiagnostic
|
result.Diagnostics.Add(new ProbeDiagnostic
|
||||||
{
|
{
|
||||||
Area = DiagnosticArea.Sql,
|
Area = DiagnosticArea.Sql,
|
||||||
Category = DiagnosticCategory.Configuration,
|
Category = wmiPermissionFailure
|
||||||
|
? DiagnosticCategory.Permission
|
||||||
|
: DiagnosticCategory.Configuration,
|
||||||
Component = "BizTalk database discovery",
|
Component = "BizTalk database discovery",
|
||||||
Required = true,
|
Required = true,
|
||||||
Summary = "SQL-Zielermittlung ist unvollstaendig, weil nicht alle erforderlichen BizTalk-Plattformklassen gelesen wurden.",
|
Summary = wmiPermissionFailure
|
||||||
Action = "Zuerst den Service 'BizTalk Platform' sowie MSBTS_GroupSetting und MSBTS_MessageBoxSetting pruefen. Danach den Agent-Dump erneut ausfuehren."
|
? "SQL-Zielermittlung ist unvollstaendig, weil der BizTalk-WMI-Provider beim SQL-Zugriff abgewiesen wurde."
|
||||||
|
: "SQL-Zielermittlung ist unvollstaendig, weil nicht alle erforderlichen BizTalk-Plattformklassen gelesen wurden.",
|
||||||
|
Action = wmiPermissionFailure
|
||||||
|
? "Zuerst die Wmi/Permission-Diagnose sowie Provider-Konto, konfigurierte BizTalk-Read-Only-Gruppe und BTS_READONLY_USERS pruefen."
|
||||||
|
: "Zuerst den Service 'BizTalk Platform' und MSBTS_GroupSetting im Provider-Log pruefen. Danach den Scheduled Task erneut starten."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,9 +86,19 @@ namespace BizTalkCheckmkPulse
|
|||||||
var isLocalSystem = identity != null
|
var isLocalSystem = identity != null
|
||||||
&& identity.User != null
|
&& identity.User != null
|
||||||
&& identity.User.IsWellKnown(WellKnownSidType.LocalSystemSid);
|
&& identity.User.IsWellKnown(WellKnownSidType.LocalSystemSid);
|
||||||
result.NetworkIdentityHint = isLocalSystem
|
if (isLocalSystem)
|
||||||
? "<DOMAIN>\\" + Environment.MachineName + "$"
|
{
|
||||||
: result.ExecutionIdentity;
|
if (string.IsNullOrWhiteSpace(result.NetworkIdentityHint)
|
||||||
|
|| string.Equals(result.NetworkIdentityHint, "unknown", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| result.NetworkIdentityHint.StartsWith("<DOMAIN>\\", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
result.NetworkIdentityHint = "<DOMAIN>\\" + Environment.MachineName + "$";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.NetworkIdentityHint = result.ExecutionIdentity;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -91,7 +112,7 @@ namespace BizTalkCheckmkPulse
|
|||||||
Component = "Windows identity",
|
Component = "Windows identity",
|
||||||
Required = false,
|
Required = false,
|
||||||
Summary = "Die Windows-Ausfuehrungsidentitaet konnte nicht bestimmt werden.",
|
Summary = "Die Windows-Ausfuehrungsidentitaet konnte nicht bestimmt werden.",
|
||||||
Action = "Agent-Dienstkonto mit 'sc.exe qc CheckMKService' beziehungsweise in services.msc pruefen.",
|
Action = "Konto des Scheduled Tasks 'BizTalk Checkmk Pulse Provider' pruefen.",
|
||||||
TechnicalDetails = ex.GetType().Name + ": " + ex.Message
|
TechnicalDetails = ex.GetType().Name + ": " + ex.Message
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -247,7 +268,7 @@ namespace BizTalkCheckmkPulse
|
|||||||
case DiagnosticCategory.Timeout:
|
case DiagnosticCategory.Timeout:
|
||||||
return "SQL-Verbindungsaufbau oder Testabfrage hat das konfigurierte Zeitlimit ueberschritten.";
|
return "SQL-Verbindungsaufbau oder Testabfrage hat das konfigurierte Zeitlimit ueberschritten.";
|
||||||
case DiagnosticCategory.Connectivity:
|
case DiagnosticCategory.Connectivity:
|
||||||
return "SQL Server oder die konfigurierte SQL-Instanz ist aus dem Checkmk-Agent-Kontext nicht erreichbar.";
|
return "SQL Server oder die konfigurierte SQL-Instanz ist aus dem Provider-Kontext nicht erreichbar.";
|
||||||
case DiagnosticCategory.Configuration:
|
case DiagnosticCategory.Configuration:
|
||||||
return "SQL-Verbindung scheitert an TLS-, Zertifikats-, SPN- oder SSPI-Konfiguration.";
|
return "SQL-Verbindung scheitert an TLS-, Zertifikats-, SPN- oder SSPI-Konfiguration.";
|
||||||
default:
|
default:
|
||||||
@@ -267,7 +288,7 @@ namespace BizTalkCheckmkPulse
|
|||||||
switch (category)
|
switch (category)
|
||||||
{
|
{
|
||||||
case DiagnosticCategory.Permission:
|
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.";
|
return "Provider-Konto " + networkIdentity + " und dessen Mitgliedschaft in der konfigurierten BizTalk-Read-Only-Gruppe sowie BTS_READONLY_USERS pruefen; keine direkten BizTalk-DB-Rollen vergeben. Danach den Scheduled Task mit neuem Anmeldetoken starten.";
|
||||||
case DiagnosticCategory.Timeout:
|
case DiagnosticCategory.Timeout:
|
||||||
return "Netzwerkpfad, DNS, SQL-Port, Firewall und Auslastung fuer " + target.Server + " pruefen; Timeout nur nach Ursachenanalyse erhoehen.";
|
return "Netzwerkpfad, DNS, SQL-Port, Firewall und Auslastung fuer " + target.Server + " pruefen; Timeout nur nach Ursachenanalyse erhoehen.";
|
||||||
case DiagnosticCategory.Connectivity:
|
case DiagnosticCategory.Connectivity:
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Management;
|
using System.Management;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
@@ -23,6 +24,10 @@ namespace BizTalkCheckmkPulse
|
|||||||
private const int OrchestrationStopped = 3;
|
private const int OrchestrationStopped = 3;
|
||||||
private const int OrchestrationStarted = 4;
|
private const int OrchestrationStarted = 4;
|
||||||
private const int EAccessDenied = unchecked((int)0x80070005);
|
private const int EAccessDenied = unchecked((int)0x80070005);
|
||||||
|
internal const string GroupSettingQuery =
|
||||||
|
"SELECT Name, BizTalkOperatorGroup, BizTalkReadOnlyUserGroup, MgmtDbServerName, MgmtDbName, SubscriptionDBServerName, SubscriptionDBName FROM MSBTS_GroupSetting";
|
||||||
|
internal const string SuspendedInstancesQuery =
|
||||||
|
"SELECT * FROM MSBTS_ServiceInstance WHERE ServiceStatus = 4 OR ServiceStatus = 32 OR ServiceClass = 64";
|
||||||
private readonly MonitoringOptions _options;
|
private readonly MonitoringOptions _options;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -53,9 +58,14 @@ namespace BizTalkCheckmkPulse
|
|||||||
|
|
||||||
result.Platform.WmiConnected = true;
|
result.Platform.WmiConnected = true;
|
||||||
result.Platform.PlatformDataAvailable = QueryPlatform(scope, result);
|
result.Platform.PlatformDataAvailable = QueryPlatform(scope, result);
|
||||||
result.Platform.HostInstancesDataAvailable = QueryHostInstances(scope, result);
|
result.Platform.HostInstancesDataAvailable = QueryHostInstances(scope, result, server);
|
||||||
var applicationIndex = BuildApplicationIndex(scope, result);
|
var applicationIndex = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
result.Platform.RuntimeArtifactsDataAvailable = QueryApplicationRuntime(scope, result);
|
QueryApplicationRuntime(scope, result, applicationIndex);
|
||||||
|
if (_options.EmitPerApplicationSuspensionServices)
|
||||||
|
{
|
||||||
|
AddArtifactIndex(scope, result, applicationIndex, "MSBTS_ReceivePort");
|
||||||
|
}
|
||||||
|
|
||||||
result.Platform.SuspendedInstancesDataAvailable = QuerySuspendedInstances(scope, result, applicationIndex);
|
result.Platform.SuspendedInstancesDataAvailable = QuerySuspendedInstances(scope, result, applicationIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +84,7 @@ namespace BizTalkCheckmkPulse
|
|||||||
}
|
}
|
||||||
catch (Exception ex) when (IsExpectedWmiException(ex))
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,23 +98,21 @@ namespace BizTalkCheckmkPulse
|
|||||||
private bool QueryPlatform(ManagementScope scope, ProbeResult result)
|
private bool QueryPlatform(ManagementScope scope, ProbeResult result)
|
||||||
{
|
{
|
||||||
var groupFound = false;
|
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;
|
groupFound = true;
|
||||||
result.Platform.GroupName = FirstNonEmpty(WmiHelpers.GetString(item, "Name"), WmiHelpers.GetString(item, "MgmtDbName"));
|
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.OperatorGroup = WmiHelpers.GetString(item, "BizTalkOperatorGroup");
|
||||||
result.Platform.ManagementDbName = FirstNonEmpty(WmiHelpers.GetString(item, "MgmtDbName"), WmiHelpers.GetString(item, "DatabaseName"));
|
result.Platform.ReadOnlyUserGroup = WmiHelpers.GetString(item, "BizTalkReadOnlyUserGroup");
|
||||||
}, true);
|
result.Platform.ManagementDbServer = WmiHelpers.GetString(item, "MgmtDbServerName");
|
||||||
|
result.Platform.ManagementDbName = WmiHelpers.GetString(item, "MgmtDbName");
|
||||||
var messageBoxFound = false;
|
result.Platform.MessageBoxDbServer = WmiHelpers.GetString(item, "SubscriptionDBServerName");
|
||||||
var messageBoxAvailable = TryQuery(scope, result, "MSBTS_MessageBoxSetting", "SELECT * FROM MSBTS_MessageBoxSetting", item =>
|
result.Platform.MessageBoxDbName = WmiHelpers.GetString(item, "SubscriptionDBName");
|
||||||
{
|
|
||||||
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);
|
}, true);
|
||||||
|
|
||||||
if (groupAvailable && !groupFound)
|
if (groupAvailable && !groupFound)
|
||||||
@@ -112,32 +120,33 @@ namespace BizTalkCheckmkPulse
|
|||||||
AddMissingDataDiagnostic(result, "MSBTS_GroupSetting", "Keine BizTalk-Gruppe gefunden.");
|
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>
|
/// <summary>
|
||||||
/// Erstellt einen Index, um suspendierte Instanzen BizTalk-Anwendungen zuzuordnen.
|
/// 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>
|
|
||||||
/// <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.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="scope">Verbundener BizTalk-WMI-Scope.</param>
|
/// <param name="scope">Verbundener BizTalk-WMI-Scope.</param>
|
||||||
/// <param name="result">Ergebnisobjekt fuer optionale Diagnosen.</param>
|
/// <param name="result">Ergebnisobjekt fuer optionale Diagnosen.</param>
|
||||||
@@ -147,14 +156,7 @@ namespace BizTalkCheckmkPulse
|
|||||||
{
|
{
|
||||||
TryQuery(scope, result, className + " application index", "SELECT * FROM " + className, item =>
|
TryQuery(scope, result, className + " application index", "SELECT * FROM " + className, item =>
|
||||||
{
|
{
|
||||||
var app = GetApplicationName(item);
|
AddArtifactIndexEntry(index, item);
|
||||||
foreach (var key in CandidateKeys(item))
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(key) && !index.ContainsKey(key))
|
|
||||||
{
|
|
||||||
index.Add(key, app);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, false);
|
}, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,16 +165,28 @@ namespace BizTalkCheckmkPulse
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="scope">Verbundener BizTalk-WMI-Scope.</param>
|
/// <param name="scope">Verbundener BizTalk-WMI-Scope.</param>
|
||||||
/// <param name="result">Zu aktualisierendes Ergebnisobjekt.</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>
|
/// <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
|
result.HostInstances.Add(new HostInstanceState
|
||||||
{
|
{
|
||||||
InstanceName = FirstNonEmpty(WmiHelpers.GetString(item, "InstanceName"), WmiHelpers.GetString(item, "Name")),
|
InstanceName = FirstNonEmpty(WmiHelpers.GetString(item, "InstanceName"), WmiHelpers.GetString(item, "Name")),
|
||||||
HostName = WmiHelpers.GetString(item, "HostName"),
|
HostName = WmiHelpers.GetString(item, "HostName"),
|
||||||
RunningServer = WmiHelpers.GetString(item, "RunningServer"),
|
RunningServer = runningServer,
|
||||||
ServiceState = WmiHelpers.GetInt32(item, "ServiceState", 0)
|
ServiceState = WmiHelpers.GetInt32(item, "ServiceState", 0)
|
||||||
});
|
});
|
||||||
}, true);
|
}, true);
|
||||||
@@ -183,26 +197,53 @@ namespace BizTalkCheckmkPulse
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="scope">Verbundener BizTalk-WMI-Scope.</param>
|
/// <param name="scope">Verbundener BizTalk-WMI-Scope.</param>
|
||||||
/// <param name="result">Zu aktualisierendes Ergebnisobjekt.</param>
|
/// <param name="result">Zu aktualisierendes Ergebnisobjekt.</param>
|
||||||
/// <returns><c>true</c>, wenn alle drei Pflichtabfragen erfolgreich waren.</returns>
|
/// <param name="applicationIndex">Best-Effort-Index fuer Suspensionsdetails.</param>
|
||||||
private bool QueryApplicationRuntime(ManagementScope scope, ProbeResult result)
|
private void QueryApplicationRuntime(
|
||||||
|
ManagementScope scope,
|
||||||
|
ProbeResult result,
|
||||||
|
Dictionary<string, string> applicationIndex)
|
||||||
{
|
{
|
||||||
var apps = new Dictionary<string, ApplicationRuntimeState>(StringComparer.OrdinalIgnoreCase);
|
var apps = new Dictionary<string, ApplicationRuntimeState>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
var receiveLocationsAvailable = TryQuery(scope, result, "MSBTS_ReceiveLocation", "SELECT * FROM MSBTS_ReceiveLocation", item =>
|
result.Platform.ReceiveLocationsDataAvailable = TryQuery(scope, result, "MSBTS_ReceiveLocation", "SELECT * FROM MSBTS_ReceiveLocation", item =>
|
||||||
{
|
{
|
||||||
var app = GetApplication(apps, GetApplicationName(item));
|
AddArtifactIndexEntry(applicationIndex, item);
|
||||||
|
var applicationName = GetApplicationName(item);
|
||||||
|
var name = FirstNonEmpty(WmiHelpers.GetString(item, "Name"), WmiHelpers.GetString(item, "ReceivePortName")) ?? "(unknown)";
|
||||||
|
bool parsedIsDisabled;
|
||||||
|
var isDisabledText = WmiHelpers.GetString(item, "IsDisabled");
|
||||||
|
var isDisabled = bool.TryParse(isDisabledText, out parsedIsDisabled)
|
||||||
|
? (bool?)parsedIsDisabled
|
||||||
|
: null;
|
||||||
|
result.ReceiveLocations.Add(new ReceiveLocationState
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
ApplicationName = applicationName,
|
||||||
|
IsDisabled = isDisabled
|
||||||
|
});
|
||||||
|
var app = GetApplication(apps, applicationName);
|
||||||
app.ReceiveLocationTotal++;
|
app.ReceiveLocationTotal++;
|
||||||
if (WmiHelpers.GetBoolean(item, "IsDisabled", false))
|
if (isDisabled == true)
|
||||||
{
|
{
|
||||||
app.ReceiveLocationDisabled++;
|
app.ReceiveLocationDisabled++;
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
var sendPortsAvailable = TryQuery(scope, result, "MSBTS_SendPort", "SELECT * FROM MSBTS_SendPort", item =>
|
result.Platform.SendPortsDataAvailable = TryQuery(scope, result, "MSBTS_SendPort", "SELECT * FROM MSBTS_SendPort", item =>
|
||||||
{
|
{
|
||||||
var app = GetApplication(apps, GetApplicationName(item));
|
AddArtifactIndexEntry(applicationIndex, item);
|
||||||
|
var applicationName = GetApplicationName(item);
|
||||||
|
var name = WmiHelpers.GetString(item, "Name");
|
||||||
|
var status = WmiHelpers.GetInt32(item, "Status", 0);
|
||||||
|
result.SendPorts.Add(new SendPortState
|
||||||
|
{
|
||||||
|
Name = string.IsNullOrWhiteSpace(name) ? "(unknown)" : name,
|
||||||
|
ApplicationName = applicationName,
|
||||||
|
Status = status
|
||||||
|
});
|
||||||
|
var app = GetApplication(apps, applicationName);
|
||||||
app.SendPortTotal++;
|
app.SendPortTotal++;
|
||||||
switch (WmiHelpers.GetInt32(item, "Status", 0))
|
switch (status)
|
||||||
{
|
{
|
||||||
case SendPortStarted:
|
case SendPortStarted:
|
||||||
app.SendPortStarted++;
|
app.SendPortStarted++;
|
||||||
@@ -219,8 +260,9 @@ namespace BizTalkCheckmkPulse
|
|||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
var orchestrationsAvailable = TryQuery(scope, result, "MSBTS_Orchestration", "SELECT * FROM MSBTS_Orchestration", item =>
|
result.Platform.OrchestrationsDataAvailable = TryQuery(scope, result, "MSBTS_Orchestration", "SELECT * FROM MSBTS_Orchestration", item =>
|
||||||
{
|
{
|
||||||
|
AddArtifactIndexEntry(applicationIndex, item);
|
||||||
var app = GetApplication(apps, GetApplicationName(item));
|
var app = GetApplication(apps, GetApplicationName(item));
|
||||||
app.OrchestrationTotal++;
|
app.OrchestrationTotal++;
|
||||||
switch (WmiHelpers.GetInt32(item, "OrchestrationStatus", 0))
|
switch (WmiHelpers.GetInt32(item, "OrchestrationStatus", 0))
|
||||||
@@ -244,7 +286,6 @@ namespace BizTalkCheckmkPulse
|
|||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
result.Applications.AddRange(apps.Values.OrderBy(x => x.ApplicationName, StringComparer.OrdinalIgnoreCase));
|
result.Applications.AddRange(apps.Values.OrderBy(x => x.ApplicationName, StringComparer.OrdinalIgnoreCase));
|
||||||
return receiveLocationsAvailable && sendPortsAvailable && orchestrationsAvailable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -256,9 +297,10 @@ namespace BizTalkCheckmkPulse
|
|||||||
/// <returns><c>true</c>, wenn die Pflichtabfrage erfolgreich war.</returns>
|
/// <returns><c>true</c>, wenn die Pflichtabfrage erfolgreich war.</returns>
|
||||||
private bool QuerySuspendedInstances(ManagementScope scope, ProbeResult result, Dictionary<string, string> applicationIndex)
|
private bool QuerySuspendedInstances(ManagementScope scope, ProbeResult result, Dictionary<string, string> applicationIndex)
|
||||||
{
|
{
|
||||||
return TryQuery(scope, result, "MSBTS_ServiceInstance", "SELECT * FROM MSBTS_ServiceInstance WHERE ServiceStatus = 4 OR ServiceStatus = 32", item =>
|
return TryQuery(scope, result, "MSBTS_ServiceInstance", SuspendedInstancesQuery, item =>
|
||||||
{
|
{
|
||||||
var serviceStatus = WmiHelpers.GetInt32(item, "ServiceStatus", 0);
|
var serviceStatus = WmiHelpers.GetInt32(item, "ServiceStatus", 0);
|
||||||
|
var serviceClass = WmiHelpers.GetInt32(item, "ServiceClass", 0);
|
||||||
result.SuspendedInstances.Add(new SuspendedInstance
|
result.SuspendedInstances.Add(new SuspendedInstance
|
||||||
{
|
{
|
||||||
ApplicationName = ResolveApplication(item, applicationIndex),
|
ApplicationName = ResolveApplication(item, applicationIndex),
|
||||||
@@ -268,7 +310,10 @@ namespace BizTalkCheckmkPulse
|
|||||||
InstanceId = WmiHelpers.GetString(item, "InstanceID"),
|
InstanceId = WmiHelpers.GetString(item, "InstanceID"),
|
||||||
ErrorDescription = WmiHelpers.GetString(item, "ErrorDescription"),
|
ErrorDescription = WmiHelpers.GetString(item, "ErrorDescription"),
|
||||||
SuspendTime = WmiHelpers.GetDmtfDateTime(item, "SuspendTime"),
|
SuspendTime = WmiHelpers.GetDmtfDateTime(item, "SuspendTime"),
|
||||||
Kind = serviceStatus == SuspendedNonResumable ? SuspendedKind.NonResumable : SuspendedKind.Resumable
|
ServiceClassId = serviceClass,
|
||||||
|
Kind = serviceStatus == SuspendedNonResumable || serviceClass == 64
|
||||||
|
? SuspendedKind.NonResumable
|
||||||
|
: SuspendedKind.Resumable
|
||||||
});
|
});
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
@@ -285,6 +330,7 @@ namespace BizTalkCheckmkPulse
|
|||||||
/// <returns><c>true</c>, wenn die Abfrage vollstaendig ausgefuehrt wurde.</returns>
|
/// <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)
|
private bool TryQuery(ManagementScope scope, ProbeResult result, string label, string queryText, Action<ManagementObject> action, bool required)
|
||||||
{
|
{
|
||||||
|
var stopwatch = Stopwatch.StartNew();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ForEachObject(scope, queryText, action);
|
ForEachObject(scope, queryText, action);
|
||||||
@@ -292,7 +338,7 @@ namespace BizTalkCheckmkPulse
|
|||||||
}
|
}
|
||||||
catch (Exception ex) when (IsExpectedWmiException(ex))
|
catch (Exception ex) when (IsExpectedWmiException(ex))
|
||||||
{
|
{
|
||||||
AddWmiDiagnostic(result, label, ex, required, false);
|
AddWmiDiagnostic(result, label, ex, required, false, queryText, stopwatch.Elapsed);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -344,6 +390,28 @@ namespace BizTalkCheckmkPulse
|
|||||||
return app;
|
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>
|
/// <summary>
|
||||||
/// Ermittelt den Anwendungsnamen direkt oder ueber den Artefaktindex.
|
/// Ermittelt den Anwendungsnamen direkt oder ueber den Artefaktindex.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -406,6 +474,32 @@ namespace BizTalkCheckmkPulse
|
|||||||
return values == null ? null : values.FirstOrDefault(x => !string.IsNullOrWhiteSpace(x));
|
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>
|
/// <summary>
|
||||||
/// Prueft, ob eine Exception aus dem erwarteten WMI-/COM-Fehlerbereich stammt.
|
/// Prueft, ob eine Exception aus dem erwarteten WMI-/COM-Fehlerbereich stammt.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -426,18 +520,44 @@ namespace BizTalkCheckmkPulse
|
|||||||
/// <param name="exception">Ausgeloeste Exception.</param>
|
/// <param name="exception">Ausgeloeste Exception.</param>
|
||||||
/// <param name="required">Kennzeichen fuer eine Pflichtabfrage.</param>
|
/// <param name="required">Kennzeichen fuer eine Pflichtabfrage.</param>
|
||||||
/// <param name="connectionFailure">Kennzeichen fuer Fehler beim Namespace-Verbindungsaufbau.</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 category = ClassifyWmiException(exception);
|
||||||
|
var sqlLoginPrincipal = ExtractSqlLoginPrincipal(exception.Message);
|
||||||
|
if (!string.IsNullOrWhiteSpace(sqlLoginPrincipal))
|
||||||
|
{
|
||||||
|
result.NetworkIdentityHint = sqlLoginPrincipal;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
result.Diagnostics.Add(new ProbeDiagnostic
|
||||||
{
|
{
|
||||||
Area = DiagnosticArea.Wmi,
|
Area = DiagnosticArea.Wmi,
|
||||||
Category = category,
|
Category = category,
|
||||||
Component = component,
|
Component = component,
|
||||||
Required = required,
|
Required = required,
|
||||||
Summary = BuildWmiSummary(category, component, connectionFailure),
|
Summary = BuildWmiSummary(category, component, connectionFailure, sqlLoginPrincipal),
|
||||||
Action = BuildWmiAction(category, connectionFailure),
|
Action = BuildWmiAction(category, connectionFailure, sqlLoginPrincipal),
|
||||||
TechnicalDetails = exception.GetType().Name + " HRESULT=0x" + exception.HResult.ToString("X8") + ": " + exception.Message
|
TechnicalDetails = technicalDetails
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,7 +585,7 @@ namespace BizTalkCheckmkPulse
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="exception">Zu klassifizierende Exception.</param>
|
/// <param name="exception">Zu klassifizierende Exception.</param>
|
||||||
/// <returns>Handlungsorientierte Diagnosekategorie.</returns>
|
/// <returns>Handlungsorientierte Diagnosekategorie.</returns>
|
||||||
private static DiagnosticCategory ClassifyWmiException(Exception exception)
|
internal static DiagnosticCategory ClassifyWmiException(Exception exception)
|
||||||
{
|
{
|
||||||
var managementException = exception as ManagementException;
|
var managementException = exception as ManagementException;
|
||||||
var comException = exception as COMException;
|
var comException = exception as COMException;
|
||||||
@@ -477,13 +597,35 @@ namespace BizTalkCheckmkPulse
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (managementException != null
|
if (managementException != null
|
||||||
&& (managementException.ErrorCode == ManagementStatus.InvalidNamespace
|
&& managementException.ErrorCode == ManagementStatus.InvalidNamespace)
|
||||||
|| managementException.ErrorCode == ManagementStatus.InvalidClass))
|
|
||||||
{
|
{
|
||||||
return DiagnosticCategory.Configuration;
|
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;
|
var message = exception.Message ?? string.Empty;
|
||||||
|
if (ContainsAny(
|
||||||
|
message,
|
||||||
|
"Login failed for user",
|
||||||
|
"Cannot open database",
|
||||||
|
"not associated with a trusted SQL Server connection"))
|
||||||
|
{
|
||||||
|
return DiagnosticCategory.Permission;
|
||||||
|
}
|
||||||
|
|
||||||
if (ContainsAny(message, "timed out", "timeout", "Zeitlimit"))
|
if (ContainsAny(message, "timed out", "timeout", "Zeitlimit"))
|
||||||
{
|
{
|
||||||
return DiagnosticCategory.Timeout;
|
return DiagnosticCategory.Timeout;
|
||||||
@@ -497,6 +639,45 @@ namespace BizTalkCheckmkPulse
|
|||||||
return DiagnosticCategory.Provider;
|
return DiagnosticCategory.Provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extrahiert das von SQL Server abgewiesene Konto aus der Provider-Fehlermeldung.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">WMI-/OLEDB-Fehlermeldung.</param>
|
||||||
|
/// <returns>Abgewiesenes Konto oder eine leere Zeichenfolge.</returns>
|
||||||
|
internal static string ExtractSqlLoginPrincipal(string message)
|
||||||
|
{
|
||||||
|
const string marker = "Login failed for user";
|
||||||
|
if (string.IsNullOrWhiteSpace(message))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var markerIndex = message.IndexOf(marker, StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (markerIndex < 0)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var valueStart = markerIndex + marker.Length;
|
||||||
|
while (valueStart < message.Length
|
||||||
|
&& (char.IsWhiteSpace(message[valueStart]) || message[valueStart] == ':'))
|
||||||
|
{
|
||||||
|
valueStart++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valueStart >= message.Length
|
||||||
|
|| (message[valueStart] != '\'' && message[valueStart] != '"'))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var quote = message[valueStart];
|
||||||
|
var valueEnd = message.IndexOf(quote, valueStart + 1);
|
||||||
|
return valueEnd > valueStart + 1
|
||||||
|
? message.Substring(valueStart + 1, valueEnd - valueStart - 1).Trim()
|
||||||
|
: string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Erstellt eine kurze Fehlerursache fuer einen WMI-Fehler.
|
/// Erstellt eine kurze Fehlerursache fuer einen WMI-Fehler.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -504,16 +685,27 @@ namespace BizTalkCheckmkPulse
|
|||||||
/// <param name="component">Betroffene Komponente.</param>
|
/// <param name="component">Betroffene Komponente.</param>
|
||||||
/// <param name="connectionFailure">Kennzeichen fuer den Namespace-Verbindungsaufbau.</param>
|
/// <param name="connectionFailure">Kennzeichen fuer den Namespace-Verbindungsaufbau.</param>
|
||||||
/// <returns>Menschenlesbare Fehlerzusammenfassung.</returns>
|
/// <returns>Menschenlesbare Fehlerzusammenfassung.</returns>
|
||||||
private static string BuildWmiSummary(DiagnosticCategory category, string component, bool connectionFailure)
|
private static string BuildWmiSummary(
|
||||||
|
DiagnosticCategory category,
|
||||||
|
string component,
|
||||||
|
bool connectionFailure,
|
||||||
|
string sqlLoginPrincipal)
|
||||||
{
|
{
|
||||||
switch (category)
|
switch (category)
|
||||||
{
|
{
|
||||||
case DiagnosticCategory.Permission:
|
case DiagnosticCategory.Permission:
|
||||||
return "Zugriff auf " + component + " wurde im LocalSystem-Kontext verweigert.";
|
if (!string.IsNullOrWhiteSpace(sqlLoginPrincipal))
|
||||||
|
{
|
||||||
|
return "BizTalk-WMI-Provider konnte den SQL-Zugriff fuer " + sqlLoginPrincipal + " nicht anmelden.";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Zugriff des privilegierten Provider-Kontos auf " + component + " wurde verweigert.";
|
||||||
case DiagnosticCategory.Configuration:
|
case DiagnosticCategory.Configuration:
|
||||||
return connectionFailure
|
return connectionFailure
|
||||||
? "BizTalk-WMI-Namespace ist nicht vorhanden oder nicht korrekt registriert."
|
? "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:
|
case DiagnosticCategory.Timeout:
|
||||||
return "WMI-Abfrage fuer " + component + " hat das konfigurierte Zeitlimit ueberschritten.";
|
return "WMI-Abfrage fuer " + component + " hat das konfigurierte Zeitlimit ueberschritten.";
|
||||||
case DiagnosticCategory.Connectivity:
|
case DiagnosticCategory.Connectivity:
|
||||||
@@ -529,16 +721,27 @@ namespace BizTalkCheckmkPulse
|
|||||||
/// <param name="category">Klassifizierte Fehlerart.</param>
|
/// <param name="category">Klassifizierte Fehlerart.</param>
|
||||||
/// <param name="connectionFailure">Kennzeichen fuer den Namespace-Verbindungsaufbau.</param>
|
/// <param name="connectionFailure">Kennzeichen fuer den Namespace-Verbindungsaufbau.</param>
|
||||||
/// <returns>Empfohlener Diagnoseschritt.</returns>
|
/// <returns>Empfohlener Diagnoseschritt.</returns>
|
||||||
private static string BuildWmiAction(DiagnosticCategory category, bool connectionFailure)
|
private static string BuildWmiAction(
|
||||||
|
DiagnosticCategory category,
|
||||||
|
bool connectionFailure,
|
||||||
|
string sqlLoginPrincipal)
|
||||||
{
|
{
|
||||||
switch (category)
|
switch (category)
|
||||||
{
|
{
|
||||||
case DiagnosticCategory.Permission:
|
case DiagnosticCategory.Permission:
|
||||||
|
if (!string.IsNullOrWhiteSpace(sqlLoginPrincipal))
|
||||||
|
{
|
||||||
|
return "Provider-Konto " + sqlLoginPrincipal
|
||||||
|
+ " und dessen Mitgliedschaft in der konfigurierten BizTalk-Read-Only-Gruppe sowie BTS_READONLY_USERS pruefen; keine direkten SQL-Logins oder Datenbankrollen vergeben.";
|
||||||
|
}
|
||||||
|
|
||||||
return connectionFailure
|
return connectionFailure
|
||||||
? "Namespace-ACL fuer root\\MicrosoftBizTalkServer gezielt pruefen. LocalSystem benoetigt lokalen Lesezugriff; keine pauschalen WMI-Rechte vergeben."
|
? "Lokalen Namespace-Zugriff des Scheduled-Task-Kontos auf root\\MicrosoftBizTalkServer gezielt pruefen; keine pauschalen WMI-Rechte vergeben."
|
||||||
: "Agent-Dump pruefen und Computerkonto <DOMAIN>\\" + Environment.MachineName + "$ zunaechst der konfigurierten BizTalk-Operator-Gruppe zuordnen.";
|
: "Provider-Identitaet und Mitgliedschaft in der konfigurierten BizTalk-Read-Only-Gruppe pruefen; Operator-Rechte nur nach klassenspezifischer Analyse erwaegen.";
|
||||||
case DiagnosticCategory.Configuration:
|
case DiagnosticCategory.Configuration:
|
||||||
return "BizTalk-WMI-Provider/Namespace auf dem BizTalk-Server pruefen und gegebenenfalls mit dem BizTalk-Setup reparieren.";
|
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:
|
case DiagnosticCategory.Timeout:
|
||||||
return "WMI-Dienst, BizTalk-/SQL-Auslastung und Event Logs pruefen; Timeout erst danach erhoehen.";
|
return "WMI-Dienst, BizTalk-/SQL-Auslastung und Event Logs pruefen; Timeout erst danach erhoehen.";
|
||||||
case DiagnosticCategory.Connectivity:
|
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>
|
||||||
@@ -0,0 +1,488 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace BizTalkCheckmkPulse.Tests
|
||||||
|
{
|
||||||
|
internal static class Program
|
||||||
|
{
|
||||||
|
private static readonly List<string> Failures = new List<string>();
|
||||||
|
private static int ExecutedTests;
|
||||||
|
|
||||||
|
private static int Main()
|
||||||
|
{
|
||||||
|
Run("SelfTestEmitsAllStableServices", SelfTestEmitsAllStableServices);
|
||||||
|
Run("UnavailableSourcesAreUnknown", UnavailableSourcesAreUnknown);
|
||||||
|
Run("UnknownApplicationDoesNotCreateService", UnknownApplicationDoesNotCreateService);
|
||||||
|
Run("KnownApplicationCreatesService", KnownApplicationCreatesService);
|
||||||
|
Run("ServerNamesAreComparedWithoutWql", ServerNamesAreComparedWithoutWql);
|
||||||
|
Run("GroupSettingQueryUsesDocumentedSchema", GroupSettingQueryUsesDocumentedSchema);
|
||||||
|
Run("ProviderSqlLoginFailureIsPermission", ProviderSqlLoginFailureIsPermission);
|
||||||
|
Run("RejectedSqlPrincipalIsExtracted", RejectedSqlPrincipalIsExtracted);
|
||||||
|
Run("PlatformPermissionPropagatesToSqlDiscovery", PlatformPermissionPropagatesToSqlDiscovery);
|
||||||
|
Run("RoutingFailureReportsAreCounted", RoutingFailureReportsAreCounted);
|
||||||
|
Run("ReceiveLocationAllowlistSeparatesExpectedState", ReceiveLocationAllowlistSeparatesExpectedState);
|
||||||
|
Run("UnknownReceiveLocationStateIsNotGreen", UnknownReceiveLocationStateIsNotGreen);
|
||||||
|
Run("SendPortAllowlistSeparatesExpectedState", SendPortAllowlistSeparatesExpectedState);
|
||||||
|
Run("ArtifactSummaryIsBounded", ArtifactSummaryIsBounded);
|
||||||
|
Run("SnapshotRoundTripPreservesLines", SnapshotRoundTripPreservesLines);
|
||||||
|
Run("SnapshotRejectsTampering", SnapshotRejectsTampering);
|
||||||
|
Run("SnapshotRejectsStaleData", SnapshotRejectsStaleData);
|
||||||
|
Run("SnapshotRejectsDifferentMachine", SnapshotRejectsDifferentMachine);
|
||||||
|
Run("SnapshotReportsMissingFile", SnapshotReportsMissingFile);
|
||||||
|
Run("SnapshotFailureEmitsStableUnknownServices", SnapshotFailureEmitsStableUnknownServices);
|
||||||
|
|
||||||
|
if (Failures.Count == 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine("PASS: " + ExecutedTests + " 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(8, lines.Length, "self-test line count");
|
||||||
|
AssertEqual(8, 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");
|
||||||
|
Assert(lines.Any(x => x.Contains("\"BizTalk Receive Locations\"")), "Receive Locations service missing");
|
||||||
|
Assert(lines.Any(x => x.Contains("\"BizTalk Send Ports\"")), "Send Ports service missing");
|
||||||
|
Assert(lines.Any(x => x.Contains("\"BizTalk Orchestrations\"")), "Orchestrations service missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UnavailableSourcesAreUnknown()
|
||||||
|
{
|
||||||
|
var lines = new CheckmkLocalFormatter(new MonitoringOptions()).Format(new ProbeResult()).Take(8).ToArray();
|
||||||
|
AssertEqual(8, 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(8, 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(9, 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("BizTalkOperatorGroup"), "configured operator group property missing");
|
||||||
|
Assert(query.Contains("BizTalkReadOnlyUserGroup"), "configured read-only group property 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");
|
||||||
|
Assert(WmiBizTalkProbe.SuspendedInstancesQuery.Contains("ServiceClass = 64"), "routing failure report filter missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ProviderSqlLoginFailureIsPermission()
|
||||||
|
{
|
||||||
|
var exception = new COMException(
|
||||||
|
"Internal error from OLEDB provider: 'Login failed for user 'BEW\\AV23AGPWBIO1$'.'",
|
||||||
|
unchecked((int)0x80131904));
|
||||||
|
AssertEqual(
|
||||||
|
DiagnosticCategory.Permission,
|
||||||
|
WmiBizTalkProbe.ClassifyWmiException(exception),
|
||||||
|
"provider SQL login classification");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RejectedSqlPrincipalIsExtracted()
|
||||||
|
{
|
||||||
|
var message = "Internal error from OLEDB provider: 'Login failed for user 'BEW\\AV23AGPWBIO1$'.'";
|
||||||
|
AssertEqual(
|
||||||
|
"BEW\\AV23AGPWBIO1$",
|
||||||
|
WmiBizTalkProbe.ExtractSqlLoginPrincipal(message),
|
||||||
|
"rejected SQL principal");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PlatformPermissionPropagatesToSqlDiscovery()
|
||||||
|
{
|
||||||
|
var result = new ProbeResult();
|
||||||
|
result.Diagnostics.Add(new ProbeDiagnostic
|
||||||
|
{
|
||||||
|
Area = DiagnosticArea.Wmi,
|
||||||
|
Category = DiagnosticCategory.Permission,
|
||||||
|
Component = "MSBTS_GroupSetting",
|
||||||
|
Required = true
|
||||||
|
});
|
||||||
|
|
||||||
|
new SqlConnectivityProbe(new MonitoringOptions()).Query(result);
|
||||||
|
|
||||||
|
var discovery = result.Diagnostics.Single(x =>
|
||||||
|
x.Area == DiagnosticArea.Sql
|
||||||
|
&& x.Component == "BizTalk database discovery");
|
||||||
|
AssertEqual(
|
||||||
|
DiagnosticCategory.Permission,
|
||||||
|
discovery.Category,
|
||||||
|
"SQL discovery classification");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RoutingFailureReportsAreCounted()
|
||||||
|
{
|
||||||
|
var result = new ProbeResult();
|
||||||
|
result.Platform.SuspendedInstancesDataAvailable = true;
|
||||||
|
result.SuspendedInstances.Add(new SuspendedInstance
|
||||||
|
{
|
||||||
|
ApplicationName = "(unknown)",
|
||||||
|
ServiceName = string.Empty,
|
||||||
|
ServiceClassId = 64,
|
||||||
|
Kind = SuspendedKind.NonResumable
|
||||||
|
});
|
||||||
|
result.SuspendedInstances.Add(new SuspendedInstance
|
||||||
|
{
|
||||||
|
ApplicationName = "Orders",
|
||||||
|
ServiceName = "Receive.Order",
|
||||||
|
Kind = SuspendedKind.Resumable
|
||||||
|
});
|
||||||
|
|
||||||
|
var line = FindServiceLine(result, "Suspended Instances");
|
||||||
|
Assert(line.StartsWith("2 \"BizTalk Suspended Instances\"", StringComparison.Ordinal), "routing failure must be CRIT");
|
||||||
|
Assert(line.Contains("biztalk_suspended_total=2"), "suspended total metric missing");
|
||||||
|
Assert(line.Contains("biztalk_suspended_nonresumable=1"), "non-resumable metric missing");
|
||||||
|
Assert(line.Contains("biztalk_routing_failure_reports=1"), "routing failure metric missing");
|
||||||
|
Assert(line.Contains("routing_failure_reports=1"), "routing failure summary missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReceiveLocationAllowlistSeparatesExpectedState()
|
||||||
|
{
|
||||||
|
var options = new MonitoringOptions
|
||||||
|
{
|
||||||
|
ExpectedDisabledReceiveLocations = new[] { "Maintenance\\RL Expected" }
|
||||||
|
};
|
||||||
|
var result = new ProbeResult();
|
||||||
|
result.Platform.ReceiveLocationsDataAvailable = true;
|
||||||
|
result.ReceiveLocations.Add(new ReceiveLocationState
|
||||||
|
{
|
||||||
|
ApplicationName = "Maintenance",
|
||||||
|
Name = "RL Expected",
|
||||||
|
IsDisabled = true
|
||||||
|
});
|
||||||
|
result.ReceiveLocations.Add(new ReceiveLocationState
|
||||||
|
{
|
||||||
|
ApplicationName = "Orders",
|
||||||
|
Name = "RL Orders",
|
||||||
|
IsDisabled = true
|
||||||
|
});
|
||||||
|
result.ReceiveLocations.Add(new ReceiveLocationState
|
||||||
|
{
|
||||||
|
ApplicationName = "Orders",
|
||||||
|
Name = "RL Active",
|
||||||
|
IsDisabled = false
|
||||||
|
});
|
||||||
|
|
||||||
|
var line = FindServiceLine(result, "Receive Locations", options);
|
||||||
|
Assert(line.StartsWith("2 \"BizTalk Receive Locations\"", StringComparison.Ordinal), "unexpected disabled receive location must be CRIT");
|
||||||
|
Assert(line.Contains("biztalk_receive_locations_enabled=1"), "enabled receive metric missing");
|
||||||
|
Assert(line.Contains("biztalk_receive_locations_unexpected_disabled=1"), "unexpected disabled metric missing");
|
||||||
|
Assert(line.Contains("biztalk_receive_locations_expected_disabled=1"), "expected disabled metric missing");
|
||||||
|
Assert(line.Contains(@"Orders\RL Orders"), "affected receive location missing");
|
||||||
|
Assert(line.IndexOf("RL Expected", StringComparison.Ordinal) < 0, "expected disabled receive location should not clutter affected list");
|
||||||
|
|
||||||
|
options.ExpectedDisabledReceiveLocations = new[] { "Maintenance\\RL Expected", "RL Orders" };
|
||||||
|
var allExpected = FindServiceLine(result, "Receive Locations", options);
|
||||||
|
Assert(allExpected.StartsWith("0 \"BizTalk Receive Locations\"", StringComparison.Ordinal), "all expected disabled receive locations should be OK");
|
||||||
|
Assert(allExpected.Contains("biztalk_receive_locations_expected_disabled=2"), "all expected receive count missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SendPortAllowlistSeparatesExpectedState()
|
||||||
|
{
|
||||||
|
var options = new MonitoringOptions
|
||||||
|
{
|
||||||
|
ExpectedInactiveSendPorts = new[] { "Maintenance\\SP Expected" }
|
||||||
|
};
|
||||||
|
var result = new ProbeResult();
|
||||||
|
result.Platform.SendPortsDataAvailable = true;
|
||||||
|
result.SendPorts.Add(new SendPortState
|
||||||
|
{
|
||||||
|
ApplicationName = "Maintenance",
|
||||||
|
Name = "SP Expected",
|
||||||
|
Status = 2
|
||||||
|
});
|
||||||
|
result.SendPorts.Add(new SendPortState
|
||||||
|
{
|
||||||
|
ApplicationName = "Orders",
|
||||||
|
Name = "SP Orders",
|
||||||
|
Status = 1
|
||||||
|
});
|
||||||
|
result.SendPorts.Add(new SendPortState
|
||||||
|
{
|
||||||
|
ApplicationName = "Orders",
|
||||||
|
Name = "SP Active",
|
||||||
|
Status = 3
|
||||||
|
});
|
||||||
|
|
||||||
|
var line = FindServiceLine(result, "Send Ports", options);
|
||||||
|
Assert(line.StartsWith("2 \"BizTalk Send Ports\"", StringComparison.Ordinal), "unexpected inactive send port must be CRIT");
|
||||||
|
Assert(line.Contains("biztalk_send_ports_started=1"), "started send port metric missing");
|
||||||
|
Assert(line.Contains("biztalk_send_ports_unexpected_inactive=1"), "unexpected inactive send metric missing");
|
||||||
|
Assert(line.Contains("biztalk_send_ports_expected_inactive=1"), "expected inactive send metric missing");
|
||||||
|
Assert(line.Contains(@"Orders\SP Orders(bound)"), "affected send port missing");
|
||||||
|
Assert(line.IndexOf("SP Expected", StringComparison.Ordinal) < 0, "expected inactive send port should not clutter affected list");
|
||||||
|
|
||||||
|
options.ExpectedInactiveSendPorts = new[] { "Maintenance\\SP Expected", "SP Orders" };
|
||||||
|
var allExpected = FindServiceLine(result, "Send Ports", options);
|
||||||
|
Assert(allExpected.StartsWith("0 \"BizTalk Send Ports\"", StringComparison.Ordinal), "all expected inactive send ports should be OK");
|
||||||
|
Assert(allExpected.Contains("biztalk_send_ports_expected_inactive=2"), "all expected send count missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UnknownReceiveLocationStateIsNotGreen()
|
||||||
|
{
|
||||||
|
var result = new ProbeResult();
|
||||||
|
result.Platform.ReceiveLocationsDataAvailable = true;
|
||||||
|
result.ReceiveLocations.Add(new ReceiveLocationState
|
||||||
|
{
|
||||||
|
ApplicationName = "Orders",
|
||||||
|
Name = "RL Missing State",
|
||||||
|
IsDisabled = null
|
||||||
|
});
|
||||||
|
|
||||||
|
var line = FindServiceLine(result, "Receive Locations");
|
||||||
|
Assert(line.StartsWith("3 \"BizTalk Receive Locations\"", StringComparison.Ordinal), "unknown receive state must be UNKNOWN");
|
||||||
|
Assert(line.Contains("biztalk_receive_locations_unknown=1"), "unknown receive metric missing");
|
||||||
|
Assert(line.Contains(@"Orders\RL Missing State"), "unknown receive location must be visible");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ArtifactSummaryIsBounded()
|
||||||
|
{
|
||||||
|
var options = new MonitoringOptions
|
||||||
|
{
|
||||||
|
MaxSummaryItems = 2,
|
||||||
|
MaxDetailCharacters = 256
|
||||||
|
};
|
||||||
|
var result = new ProbeResult();
|
||||||
|
result.Platform.ReceiveLocationsDataAvailable = true;
|
||||||
|
for (var i = 1; i <= 6; i++)
|
||||||
|
{
|
||||||
|
result.ReceiveLocations.Add(new ReceiveLocationState
|
||||||
|
{
|
||||||
|
ApplicationName = "Application",
|
||||||
|
Name = "Disabled-" + i,
|
||||||
|
IsDisabled = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var line = FindServiceLine(result, "Receive Locations", options);
|
||||||
|
Assert(line.Contains("(+4 more)"), "omitted item count missing");
|
||||||
|
Assert(line.Length < 700, "local check line should remain compact");
|
||||||
|
|
||||||
|
var longResult = new ProbeResult();
|
||||||
|
longResult.Platform.ReceiveLocationsDataAvailable = true;
|
||||||
|
longResult.ReceiveLocations.Add(new ReceiveLocationState
|
||||||
|
{
|
||||||
|
ApplicationName = new string('A', 400),
|
||||||
|
Name = new string('R', 400),
|
||||||
|
IsDisabled = true
|
||||||
|
});
|
||||||
|
var truncated = FindServiceLine(longResult, "Receive Locations", options);
|
||||||
|
Assert(truncated.Contains("[truncated]"), "detail character limit should be visible");
|
||||||
|
Assert(truncated.Length < 700, "truncated local check line should remain bounded");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SnapshotRoundTripPreservesLines()
|
||||||
|
{
|
||||||
|
WithTemporarySnapshot((path, store) =>
|
||||||
|
{
|
||||||
|
var lines = new CheckmkLocalFormatter(new MonitoringOptions()).FormatSelfTest().ToArray();
|
||||||
|
var generated = DateTime.UtcNow;
|
||||||
|
store.Write(lines, generated, "DOMAIN\\collector$");
|
||||||
|
var result = store.Read(generated.AddSeconds(10), TimeSpan.FromMinutes(3));
|
||||||
|
|
||||||
|
Assert(result.IsSuccess, "snapshot should be readable: " + result.Error);
|
||||||
|
AssertEqual(lines.Length, result.Lines.Count, "snapshot line count");
|
||||||
|
Assert(lines.SequenceEqual(result.Lines), "snapshot payload changed");
|
||||||
|
|
||||||
|
var replacement = lines.Select(x => x.Replace("Self test OK", "Replacement OK")).ToArray();
|
||||||
|
store.Write(replacement, generated.AddSeconds(30), "DOMAIN\\collector$");
|
||||||
|
var replaced = store.Read(generated.AddSeconds(40), TimeSpan.FromMinutes(3));
|
||||||
|
Assert(replaced.IsSuccess, "replaced snapshot should be readable");
|
||||||
|
Assert(replacement.SequenceEqual(replaced.Lines), "atomic replacement payload changed");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SnapshotRejectsTampering()
|
||||||
|
{
|
||||||
|
WithTemporarySnapshot((path, store) =>
|
||||||
|
{
|
||||||
|
var lines = new CheckmkLocalFormatter(new MonitoringOptions()).FormatSelfTest().ToArray();
|
||||||
|
store.Write(lines, DateTime.UtcNow, "DOMAIN\\collector$");
|
||||||
|
var content = File.ReadAllText(path, Encoding.UTF8);
|
||||||
|
File.WriteAllText(path, content.Replace("Self test OK", "Tampered output"), new UTF8Encoding(false));
|
||||||
|
|
||||||
|
var result = store.Read(DateTime.UtcNow, TimeSpan.FromMinutes(3));
|
||||||
|
Assert(!result.IsSuccess, "tampered snapshot must be rejected");
|
||||||
|
Assert(result.Error.IndexOf("SHA-256", StringComparison.OrdinalIgnoreCase) >= 0, "tamper reason should mention SHA-256");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SnapshotRejectsStaleData()
|
||||||
|
{
|
||||||
|
WithTemporarySnapshot((path, store) =>
|
||||||
|
{
|
||||||
|
var lines = new CheckmkLocalFormatter(new MonitoringOptions()).FormatSelfTest().ToArray();
|
||||||
|
var generated = DateTime.UtcNow.AddMinutes(-10);
|
||||||
|
store.Write(lines, generated, "DOMAIN\\collector$");
|
||||||
|
|
||||||
|
var result = store.Read(DateTime.UtcNow, TimeSpan.FromMinutes(3));
|
||||||
|
Assert(!result.IsSuccess, "stale snapshot must be rejected");
|
||||||
|
Assert(result.Error.IndexOf("stale", StringComparison.OrdinalIgnoreCase) >= 0, "stale reason missing");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SnapshotRejectsDifferentMachine()
|
||||||
|
{
|
||||||
|
WithTemporarySnapshot((path, store) =>
|
||||||
|
{
|
||||||
|
var lines = new CheckmkLocalFormatter(new MonitoringOptions()).FormatSelfTest().ToArray();
|
||||||
|
store.Write(lines, DateTime.UtcNow, "DOMAIN\\collector$");
|
||||||
|
var content = File.ReadAllText(path, Encoding.UTF8);
|
||||||
|
var currentMachine = Convert.ToBase64String(Encoding.UTF8.GetBytes(Environment.MachineName));
|
||||||
|
var otherMachine = Convert.ToBase64String(Encoding.UTF8.GetBytes("OTHER-SERVER"));
|
||||||
|
File.WriteAllText(
|
||||||
|
path,
|
||||||
|
content.Replace("machineBase64=" + currentMachine, "machineBase64=" + otherMachine),
|
||||||
|
new UTF8Encoding(false));
|
||||||
|
|
||||||
|
var result = store.Read(DateTime.UtcNow, TimeSpan.FromMinutes(3));
|
||||||
|
Assert(!result.IsSuccess, "snapshot for another machine must be rejected");
|
||||||
|
Assert(result.Error.IndexOf("different machine", StringComparison.OrdinalIgnoreCase) >= 0, "machine mismatch reason missing");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SnapshotReportsMissingFile()
|
||||||
|
{
|
||||||
|
WithTemporarySnapshot((path, store) =>
|
||||||
|
{
|
||||||
|
var result = store.Read(DateTime.UtcNow, TimeSpan.FromMinutes(3));
|
||||||
|
Assert(!result.IsSuccess, "missing snapshot must be rejected");
|
||||||
|
Assert(result.Error.IndexOf("does not exist", StringComparison.OrdinalIgnoreCase) >= 0, "missing file reason missing");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SnapshotFailureEmitsStableUnknownServices()
|
||||||
|
{
|
||||||
|
var lines = new CheckmkLocalFormatter(new MonitoringOptions())
|
||||||
|
.FormatSnapshotFailure("Snapshot file does not exist.")
|
||||||
|
.ToArray();
|
||||||
|
AssertEqual(8, lines.Length, "snapshot failure stable service count");
|
||||||
|
Assert(lines.All(x => x.StartsWith("3 \"BizTalk ", StringComparison.Ordinal)), "snapshot failure must be UNKNOWN");
|
||||||
|
Assert(lines.All(x => x.Contains("Scheduled Task")), "snapshot failure must contain provider action");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WithTemporarySnapshot(Action<string, SnapshotStore> test)
|
||||||
|
{
|
||||||
|
var directory = Path.Combine(Path.GetTempPath(), "BizTalkCheckmkPulse.Tests." + Guid.NewGuid().ToString("N"));
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
var path = Path.Combine(directory, "snapshot.txt");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
test(path, new SnapshotStore(path, 1048576));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (Directory.Exists(directory))
|
||||||
|
{
|
||||||
|
Directory.Delete(directory, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 string FindServiceLine(
|
||||||
|
ProbeResult result,
|
||||||
|
string serviceSuffix,
|
||||||
|
MonitoringOptions options = null)
|
||||||
|
{
|
||||||
|
var effectiveOptions = options ?? new MonitoringOptions();
|
||||||
|
var token = "\"" + effectiveOptions.ServiceName(serviceSuffix) + "\"";
|
||||||
|
return new CheckmkLocalFormatter(effectiveOptions)
|
||||||
|
.Format(result)
|
||||||
|
.Single(x => x.Contains(token));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Run(string name, Action test)
|
||||||
|
{
|
||||||
|
ExecutedTests++;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssertEqual(DiagnosticCategory expected, DiagnosticCategory actual, string label)
|
||||||
|
{
|
||||||
|
if (expected != actual)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(label + ": expected " + expected + ", actual " + actual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssertEqual(string expected, string actual, string label)
|
||||||
|
{
|
||||||
|
if (!string.Equals(expected, actual, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(label + ": expected " + expected + ", actual " + actual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user