Add BizTalk Checkmk Pulse local check

This commit is contained in:
2026-07-22 11:34:28 +02:00
commit 7660dc4555
20 changed files with 1919 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
root = true
[*]
charset = utf-8
end_of_line = crlf
insert_final_newline = true
indent_style = space
indent_size = 4
[*.md]
end_of_line = lf
trim_trailing_whitespace = false
+8
View File
@@ -0,0 +1,8 @@
bin/
obj/
artifacts/
.vs/
*.user
*.suo
*.nupkg
+22
View File
@@ -0,0 +1,22 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizTalkCheckmkPulse", "src\BizTalkCheckmkPulse\BizTalkCheckmkPulse.csproj", "{A4D4D050-9EA7-4A71-B510-7D9D699B9F38}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A4D4D050-9EA7-4A71-B510-7D9D699B9F38}.Debug|Any CPU.ActiveCfg = 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.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
+265
View File
@@ -0,0 +1,265 @@
# Dokumentation: BizTalk Checkmk Pulse
## Zielbild
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.
Das Monitoring soll:
- auf jeder BizTalk-Maschine lokal laufen
- ohne PowerShell-Abhaengigkeit funktionieren
- keine BizTalk-DLLs im Build erzwingen
- Checkmk-2.4-kompatible Services und Metriken erzeugen
- service-discovery-freundlich und dashboard-tauglich sein
- bei Fehlern gueltige `UNKNOWN`-Services statt kaputter Agent-Ausgaben liefern
## Technische Bewertung
### Option A: Checkmk Local Check mit C#/.NET Framework
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
Checkmk Windows Agent
|
| startet lokale Checks aus %ProgramData%\checkmk\agent\local
v
biztalk_checkmk_pulse.cmd
|
| startet
v
BizTalkCheckmkPulse.exe
|
| liest lokal
+-- WMI root\MicrosoftBizTalkServer
+-- Windows Application Event Log
|
v
Checkmk Local Check Zeilen nach STDOUT
```
Der `.cmd`-Wrapper liefert auch dann eine gueltige `UNKNOWN`-Zeile, wenn die EXE fehlt. Die EXE selbst faengt Laufzeitfehler ab und schreibt fuer alle stabilen Services `UNKNOWN`, damit unvollstaendige Deployments oder WMI-Probleme in Checkmk sichtbar bleiben.
## Datenquellen
### BizTalk WMI Namespace
Namespace:
```text
root\MicrosoftBizTalkServer
```
Genutzte Klassen:
| Klasse | Zweck |
| --- | --- |
| `MSBTS_GroupSetting` | BizTalk-Gruppe und Management-DB-Hinweise. |
| `MSBTS_MessageBoxSetting` | MessageBox-DB-Hinweise. |
| `MSBTS_HostInstance` | Host-Instance-Zustand. |
| `MSBTS_ServiceInstance` | Suspended service instances. |
| `MSBTS_ReceiveLocation` | Receive-Location-Zustand. |
| `MSBTS_SendPort` | Send-Port-Zustand. |
| `MSBTS_Orchestration` | Orchestration-Zustand. |
| `MSBTS_ReceivePort` | Application-Mapping fuer Details. |
### Windows Application Event Log
Der Check liest standardmaessig das lokale Application Log fuer die letzten 60 Minuten und filtert auf Quellen wie:
- `BizTalk Server`
- `XLANG/s`
- `ENTSSO`
- `BizTalk Server Application`
- `BizTalk Server EDI`
Die Liste ist ueber `EventLogSources` konfigurierbar.
## Resilienz
Das Plugin ist bewusst defensiv gebaut:
- WMI-Queries haben ein konfigurierbares Timeout.
- 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 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.
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 fuenf BizTalk-Services
- Graphen fuer `biztalk_suspended_total`, `biztalk_host_instances_stopped`, `biztalk_eventlog_errors`
- Hostgruppe/Ordner pro Umgebung
- Optional eine View gefiltert auf `Service starts with BizTalk`
Empfohlene Reihenfolge im Dashboard:
1. `BizTalk Platform`
2. `BizTalk Suspended Instances`
3. `BizTalk Host Instances`
4. `BizTalk Event Log`
5. `BizTalk Runtime Artifacts`
6. SQL-Server-Services aus dem Checkmk-MSSQL-Plugin
## Rollout-Vorgehen
1. Build-Paket erzeugen.
2. In `DEV` auf dem BizTalk-Server installieren.
3. `--self-test` und normalen Lauf ausfuehren.
4. Agent-Dump pruefen.
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
### Service bleibt UNKNOWN
Pruefen:
```cmd
"%ProgramData%\checkmk\agent\local\biztalk_checkmk_pulse.cmd"
```
Wenn WMI nicht erreichbar ist:
```cmd
wmic /namespace:\\root\MicrosoftBizTalkServer path MSBTS_HostInstance get HostName,RunningServer,ServiceState
```
Der Checkmk Windows Agent laeuft normalerweise als LocalSystem. Daher immer auch den Agent-Dump verwenden:
```cmd
"C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump
```
### Keine Services in Discovery
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.
+157
View File
@@ -0,0 +1,157 @@
# 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.
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.
## Warum Local Check statt serverseitigem Check-Plugin?
Checkmk 2.4 kann Windows-Agent-Plugins und Local Checks direkt ausfuehren. Fuer diese Umgebung ist ein C#/.NET-Framework-Programm mit `.cmd`-Wrapper die robusteste Variante:
- keine PowerShell-Ausfuehrung erforderlich
- 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
Standardmaessig entstehen diese stabilen Services:
- `BizTalk Platform`
- `BizTalk Suspended Instances`
- `BizTalk Host Instances`
- `BizTalk Runtime Artifacts`
- `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.
Details zu Statuslogik und Metriken stehen in [docs/CheckmkServices.md](docs/CheckmkServices.md).
Beispielausgaben stehen in [docs/ExampleOutput.md](docs/ExampleOutput.md).
## Build
Voraussetzungen auf einem Windows-Build-Host:
- Visual Studio 2022 Build Tools oder Visual Studio
- MSBuild im `PATH`
- .NET Framework 4.7.2 Developer Pack
Build:
```cmd
scripts\build-release.cmd
```
Deployment-Paket erstellen:
```cmd
scripts\package-release.cmd
```
Ergebnis:
```text
artifacts\BizTalkCheckmkPulse-deploy\
biztalk_checkmk_pulse.cmd
BizTalkCheckmkPulse\
BizTalkCheckmkPulse.exe
BizTalkCheckmkPulse.exe.config
```
Validierung nach dem Build:
```cmd
artifacts\BizTalkCheckmkPulse-deploy\biztalk_checkmk_pulse.cmd --self-test
```
## Installation auf dem BizTalk-Server
Kopiere den Inhalt von `artifacts\BizTalkCheckmkPulse-deploy` nach:
```text
%ProgramData%\checkmk\agent\local
```
Zielstruktur:
```text
%ProgramData%\checkmk\agent\local\biztalk_checkmk_pulse.cmd
%ProgramData%\checkmk\agent\local\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe
%ProgramData%\checkmk\agent\local\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe.config
```
Manueller Test auf dem BizTalk-Server:
```cmd
"%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
Die Konfiguration liegt neben der EXE:
```text
%ProgramData%\checkmk\agent\local\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe.config
```
Wichtige Werte:
| Key | Default | Bedeutung |
| --- | --- | --- |
| `Server` | `.` | Lokaler BizTalk-Server. Remote-WMI ist moeglich, aber nicht empfohlen. |
| `ServicePrefix` | `BizTalk` | Prefix fuer alle Checkmk-Services. |
| `EnvironmentName` | leer | Optional `ACC`, `DEV`, `TST` oder `PRD`. |
| `QueryTimeoutSeconds` | `25` | WMI-Timeout pro Query. |
| `WarnResumableThreshold` | `1` | WARN ab n resumable suspended instances. |
| `CritNonResumableThreshold` | `1` | CRIT ab n non-resumable suspended instances. |
| `AlertOnArtifactRuntimeIssues` | `false` | Wenn `true`, werden deaktivierte Receive Locations und inaktive Ports/Orchestrations als WARN gewertet. |
| `EmitPerApplicationSuspensionServices` | `false` | Erzeugt zusaetzliche Services pro Anwendung mit Suspensions. |
| `ProbeEventLog` | `true` | Liest das Windows Application Log nach BizTalk-bezogenen Sources. |
| `EventLogLookbackMinutes` | `60` | Zeitraum fuer Event-Log-Auswertung. |
| `EventLogWarnThreshold` | `1` | WARN ab n Errors oder Warnings. |
| `EventLogCritThreshold` | `10` | CRIT ab n Errors. |
Empfehlung:
- In `DEV`, `TST`, `ACC`: `AlertOnArtifactRuntimeIssues=false`, damit bewusst gestoppte Artefakte nicht rauschen.
- 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
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.
## Quellen
- Checkmk Local Checks: https://docs.checkmk.com/latest/en/localchecks.html
- Checkmk Windows Agent und Plugin-Pfade: https://docs.checkmk.com/latest/en/agent_windows.html
- Checkmk Agent-Based Plugin-Entwicklung: https://docs.checkmk.com/latest/en/devel_check_plugins.html
- Checkmk Bakery API: https://docs.checkmk.com/latest/en/bakery_api.html
- Checkmk MKP-Pakete: https://docs.checkmk.com/latest/en/mkps.html
- Microsoft BizTalk WMI `MSBTS_ServiceInstance`: https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-serviceinstance-wmi
- Microsoft BizTalk WMI `ServiceStatus`: https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-serviceinstance-servicestatus-property-wmi
@@ -0,0 +1,14 @@
@echo off
setlocal
set "BASE=%~dp0BizTalkCheckmkPulse"
set "EXE=%BASE%\BizTalkCheckmkPulse.exe"
if not exist "%EXE%" (
echo 3 "BizTalk Platform" - BizTalkCheckmkPulse.exe not found below %BASE%
exit /b 0
)
"%EXE%" %*
exit /b 0
+144
View File
@@ -0,0 +1,144 @@
# Checkmk Services und Metriken
Alle Services werden als Checkmk Local Checks ausgegeben. Das Format ist:
```text
<state> "<service name>" <metrics-or-minus> <summary>
```
Die Ausgabe ist so gebaut, dass sie auch mit Checkmk 2.4.0p4 kompatibel bleibt: zwischen den vier Feldern steht genau ein normales Leerzeichen.
## BizTalk Platform
Zweck:
- Prueft, ob der BizTalk-WMI-Namespace erreichbar ist.
- Zeigt Server, Gruppe, Management-DB und MessageBox-DB, soweit WMI diese Werte liefert.
Status:
| Status | Bedingung |
| --- | --- |
| `OK` | WMI-Verbindung erfolgreich. |
| `UNKNOWN` | WMI-Verbindung oder Programmstart fehlgeschlagen. |
Metriken:
- keine
## BizTalk Suspended Instances
Zweck:
- Ermittelt suspendierte BizTalk-Service-Instanzen.
- Unterscheidet resumable und non-resumable.
- Zeigt betroffene Anwendungen und Beispiele in der Summary.
Status:
| Status | Bedingung |
| --- | --- |
| `OK` | keine suspendierten Instanzen. |
| `WARN` | `biztalk_suspended_resumable >= WarnResumableThreshold`. |
| `CRIT` | `biztalk_suspended_nonresumable >= CritNonResumableThreshold`. |
| `UNKNOWN` | WMI oder Programmfehler. |
Metriken:
| Metrik | Bedeutung |
| --- | --- |
| `biztalk_suspended_total` | Summe aller suspendierten Instanzen. |
| `biztalk_suspended_resumable` | Resumable suspended instances. |
| `biztalk_suspended_nonresumable` | Non-resumable suspended instances. |
## BizTalk Host Instances
Zweck:
- Ueberwacht BizTalk Host Instances auf dem BizTalk-Server.
- Nicht gestartete Host Instances sind normalerweise betriebsrelevant.
Status:
| Status | Bedingung |
| --- | --- |
| `OK` | alle Host Instances sind started. |
| `WARN` | mindestens eine Host Instance ist pending. |
| `CRIT` | mindestens eine Host Instance ist stopped oder unknown. |
| `UNKNOWN` | keine Host Instances gefunden oder WMI/Programmfehler. |
Metriken:
| Metrik | Bedeutung |
| --- | --- |
| `biztalk_host_instances_total` | Gesamtzahl Host Instances. |
| `biztalk_host_instances_started` | Gestartete Host Instances. |
| `biztalk_host_instances_stopped` | Gestoppte Host Instances. |
| `biztalk_host_instances_pending` | StartPending/StopPending. |
| `biztalk_host_instances_unknown` | Nicht interpretierbarer Status. |
## BizTalk Runtime Artifacts
Zweck:
- Summiert Laufzeitzustaende von Receive Locations, Send Ports und Orchestrations.
- Liefert dashboard-taugliche Zahlen zur Laufzeitlandschaft.
Status:
| Status | Bedingung |
| --- | --- |
| `OK` | Artefakte lesbar; keine unbekannten Statuswerte. |
| `WARN` | Nur bei `AlertOnArtifactRuntimeIssues=true` und inaktiven/deaktivierten Artefakten. |
| `CRIT` | Unbekannte Send-Port- oder Orchestration-Statuswerte. |
| `UNKNOWN` | Programmfehler. |
Metriken:
| Metrik | Bedeutung |
| --- | --- |
| `biztalk_applications` | Anzahl erkannter BizTalk-Anwendungen. |
| `biztalk_receive_locations` | Gesamtzahl Receive Locations. |
| `biztalk_receive_locations_disabled` | Deaktivierte Receive Locations. |
| `biztalk_send_ports` | Gesamtzahl Send Ports. |
| `biztalk_send_ports_started` | Gestartete Send Ports. |
| `biztalk_send_ports_inactive` | Bound oder stopped Send Ports. |
| `biztalk_send_ports_unknown` | Nicht interpretierbarer Send-Port-Status. |
| `biztalk_orchestrations` | Gesamtzahl Orchestrations. |
| `biztalk_orchestrations_started` | Gestartete Orchestrations. |
| `biztalk_orchestrations_inactive` | Bound, unbound oder stopped Orchestrations. |
| `biztalk_orchestrations_unknown` | Nicht interpretierbarer Orchestration-Status. |
## BizTalk Event Log
Zweck:
- Erfasst BizTalk-bezogene Errors und Warnings aus dem Windows Application Log.
- Ergaenzt WMI-Zustandsdaten um aktuelle Laufzeitereignisse.
Status:
| Status | Bedingung |
| --- | --- |
| `OK` | Schwellwerte nicht erreicht. |
| `WARN` | Errors oder Warnings ab `EventLogWarnThreshold`. |
| `CRIT` | Errors ab `EventLogCritThreshold`. |
| `UNKNOWN` | Application Event Log nicht lesbar. |
Metriken:
| Metrik | Bedeutung |
| --- | --- |
| `biztalk_eventlog_errors` | BizTalk-bezogene Errors im Lookback-Fenster. |
| `biztalk_eventlog_warnings` | BizTalk-bezogene Warnings im Lookback-Fenster. |
## Optionale per-Application-Services
Wenn `EmitPerApplicationSuspensionServices=true` gesetzt ist, werden zusaetzliche Services fuer Anwendungen erzeugt, die suspendierte Instanzen haben:
```text
BizTalk Suspended <ApplicationName>
```
Das ist fuer Anwendungsteams hilfreich, kann aber die Service-Anzahl erhoehen. Fuer den Basisausbau wird empfohlen, diese Option deaktiviert zu lassen.
+31
View File
@@ -0,0 +1,31 @@
# Beispielausgaben
## Self-Test
```text
0 "BizTalk Platform" - Self test OK. Checkmk local check output is valid.
0 "BizTalk Suspended Instances" biztalk_suspended_total=0;;;0 Self test OK.
```
## Gesunder BizTalk-Server
```text
0 "BizTalk Platform" - BizTalk WMI reachable, server=BIZTALK01, group=BizTalkMgmtDb, mgmt_db=SQL01\BizTalkMgmtDb, msgbox_db=SQL01\BizTalkMsgBoxDb
0 "BizTalk Suspended Instances" biztalk_suspended_total=0;;;0|biztalk_suspended_resumable=0;1;;0|biztalk_suspended_nonresumable=0;;1;0 No suspended BizTalk service instances found.
0 "BizTalk Host Instances" biztalk_host_instances_total=4;;;0|biztalk_host_instances_started=4;;;0|biztalk_host_instances_stopped=0;;1;0|biztalk_host_instances_pending=0;1;;0|biztalk_host_instances_unknown=0;;1;0 Host instances total=4, started=4, stopped=0, pending=0, unknown=0
0 "BizTalk Runtime Artifacts" biztalk_applications=8;;;0|biztalk_receive_locations=24;;;0|biztalk_receive_locations_disabled=0;1;;0|biztalk_send_ports=31;;;0|biztalk_send_ports_started=31;;;0|biztalk_send_ports_inactive=0;1;;0|biztalk_send_ports_unknown=0;;1;0|biztalk_orchestrations=12;;;0|biztalk_orchestrations_started=12;;;0|biztalk_orchestrations_inactive=0;1;;0|biztalk_orchestrations_unknown=0;;1;0 Applications=8; receive_locations total=24, disabled=0; send_ports total=31, started=31, inactive=0, unknown=0; orchestrations total=12, started=12, inactive=0, unknown=0
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
```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
```
## Fehlendes Deployment
```text
3 "BizTalk Platform" - BizTalkCheckmkPulse.exe not found below C:\ProgramData\checkmk\agent\local\BizTalkCheckmkPulse
```
+71
View File
@@ -0,0 +1,71 @@
# Integration in Checkmk
## Zielstruktur auf dem BizTalk-Server
```text
%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
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
## 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.
## Empfohlene Host-Struktur
Host-Tags oder Ordner:
- `env:ACC`
- `env:DEV`
- `env:TST`
- `env:PRD`
- `app:biztalk`
Service-Filter fuer Views:
```text
Service starts with: BizTalk
```
Dashboard-Kacheln:
- Host/Service state fuer BizTalk-Host
- Service state fuer `BizTalk Suspended Instances`
- Graph `biztalk_suspended_total`
- Graph `biztalk_host_instances_stopped`
- Graph `biztalk_eventlog_errors`
- MSSQL-Services des SQL-Servers daneben
+12
View File
@@ -0,0 +1,12 @@
@echo off
setlocal
where msbuild.exe >nul 2>nul
if errorlevel 1 (
echo MSBuild.exe was not found in PATH. Run this from a Visual Studio Developer Command Prompt.
exit /b 1
)
msbuild BizTalkCheckmkPulse.sln /p:Configuration=Release /p:Platform="Any CPU" /m /v:minimal
exit /b %ERRORLEVEL%
+20
View File
@@ -0,0 +1,20 @@
@echo off
setlocal
call "%~dp0build-release.cmd"
if errorlevel 1 exit /b 1
set "ROOT=%~dp0.."
set "OUT=%ROOT%\artifacts\BizTalkCheckmkPulse-deploy"
set "APP=%OUT%\BizTalkCheckmkPulse"
if exist "%OUT%" rmdir /s /q "%OUT%"
mkdir "%APP%"
copy "%ROOT%\deployment\checkmk\biztalk_checkmk_pulse.cmd" "%OUT%\" >nul
copy "%ROOT%\src\BizTalkCheckmkPulse\bin\Release\BizTalkCheckmkPulse.exe" "%APP%\" >nul
copy "%ROOT%\src\BizTalkCheckmkPulse\bin\Release\BizTalkCheckmkPulse.exe.config" "%APP%\" >nul
echo Deployment package written to %OUT%
exit /b 0
+28
View File
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<!-- Empty or "." means local BizTalk machine. -->
<add key="Server" value="." />
<add key="ServicePrefix" value="BizTalk" />
<add key="EnvironmentName" value="" />
<add key="QueryTimeoutSeconds" value="25" />
<add key="WarnResumableThreshold" value="1" />
<add key="CritNonResumableThreshold" value="1" />
<add key="MaxSummaryItems" value="12" />
<!-- In many BizTalk landscapes stopped artifacts can be intentional. -->
<add key="AlertOnArtifactRuntimeIssues" value="false" />
<add key="EmitPerApplicationSuspensionServices" value="false" />
<add key="ProbeEventLog" value="true" />
<add key="EventLogLookbackMinutes" value="60" />
<add key="EventLogWarnThreshold" value="1" />
<add key="EventLogCritThreshold" value="10" />
<add key="EventLogSources" value="BizTalk Server|XLANG/s|ENTSSO|BizTalk Server Application|BizTalk Server EDI" />
</appSettings>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>
@@ -0,0 +1,55 @@
<?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>{A4D4D050-9EA7-4A71-B510-7D9D699B9F38}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>BizTalkCheckmkPulse</RootNamespace>
<AssemblyName>BizTalkCheckmkPulse</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Management" />
</ItemGroup>
<ItemGroup>
<Compile Include="CheckmkLocalFormatter.cs" />
<Compile Include="EventLogProbe.cs" />
<Compile Include="MonitoringOptions.cs" />
<Compile Include="Models.cs" />
<Compile Include="Program.cs" />
<Compile Include="WmiBizTalkProbe.cs" />
<Compile Include="WmiHelpers.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
@@ -0,0 +1,355 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
namespace BizTalkCheckmkPulse
{
internal sealed class CheckmkLocalFormatter
{
private const int HostStopped = 1;
private const int HostStartPending = 2;
private const int HostStopPending = 3;
private const int HostStarted = 4;
private const int HostContinuePending = 5;
private const int HostPausePending = 6;
private const int HostPaused = 7;
private const int HostUnknown = 8;
private readonly MonitoringOptions _options;
public CheckmkLocalFormatter(MonitoringOptions options)
{
_options = options;
}
public IEnumerable<string> Format(ProbeResult result)
{
yield return FormatPlatform(result);
yield return FormatSuspendedInstances(result);
yield return FormatHostInstances(result);
yield return FormatRuntimeArtifacts(result);
yield return FormatEventLog(result);
if (_options.EmitPerApplicationSuspensionServices)
{
foreach (var line in FormatApplicationSuspensions(result))
{
yield return line;
}
}
}
public IEnumerable<string> FormatSelfTest()
{
yield return BuildLine(CheckState.Ok, _options.ServiceName("Platform"), "-", "Self test OK. Checkmk local check output is valid.");
yield return BuildLine(CheckState.Ok, _options.ServiceName("Suspended Instances"), "biztalk_suspended_total=0;;;0", "Self test OK.");
}
public IEnumerable<string> FormatFatal(string message)
{
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Platform"), "-", message);
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Suspended Instances"), "-", message);
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Host Instances"), "-", message);
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Runtime Artifacts"), "-", message);
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Event Log"), "-", message);
}
private string FormatPlatform(ProbeResult result)
{
var state = result.Platform.WmiConnected ? CheckState.Ok : CheckState.Unknown;
var detail = new StringBuilder();
detail.Append(result.Platform.WmiConnected ? "BizTalk WMI reachable" : "BizTalk WMI not reachable");
detail.Append(", server=").Append(EmptyAsUnknown(result.Platform.ServerName));
AppendOptional(detail, "group", result.Platform.GroupName);
AppendOptional(detail, "mgmt_db", JoinDb(result.Platform.ManagementDbServer, result.Platform.ManagementDbName));
AppendOptional(detail, "msgbox_db", JoinDb(result.Platform.MessageBoxDbServer, result.Platform.MessageBoxDbName));
return BuildLine(state, _options.ServiceName("Platform"), "-", AppendDiagnostics(detail.ToString(), result.Diagnostics));
}
private string FormatSuspendedInstances(ProbeResult result)
{
var total = result.SuspendedInstances.Count;
var resumable = result.SuspendedInstances.Count(x => x.Kind == SuspendedKind.Resumable);
var nonresumable = result.SuspendedInstances.Count(x => x.Kind == SuspendedKind.NonResumable);
var state = DetermineSuspensionState(resumable, nonresumable);
var metrics = string.Format(
CultureInfo.InvariantCulture,
"biztalk_suspended_total={0};;;0|biztalk_suspended_resumable={1};{2};;0|biztalk_suspended_nonresumable={3};;{4};0",
total,
resumable,
_options.WarnResumableThreshold,
nonresumable,
_options.CritNonResumableThreshold);
var detail = total == 0
? "No suspended BizTalk service instances found."
: BuildSuspensionDetail(result.SuspendedInstances, total, resumable, nonresumable);
return BuildLine(state, _options.ServiceName("Suspended Instances"), metrics, detail);
}
private string FormatHostInstances(ProbeResult result)
{
var total = result.HostInstances.Count;
var started = result.HostInstances.Count(x => x.ServiceState == HostStarted);
var stopped = result.HostInstances.Count(x => x.ServiceState == HostStopped || x.ServiceState == HostPaused);
var pending = result.HostInstances.Count(x => x.ServiceState == HostStartPending || x.ServiceState == HostStopPending || x.ServiceState == HostContinuePending || x.ServiceState == HostPausePending);
var unknown = total - started - stopped - pending;
var state = total == 0 ? CheckState.Unknown : stopped > 0 || unknown > 0 ? CheckState.Critical : pending > 0 ? CheckState.Warning : CheckState.Ok;
var metrics = string.Format(
CultureInfo.InvariantCulture,
"biztalk_host_instances_total={0};;;0|biztalk_host_instances_started={1};;;0|biztalk_host_instances_stopped={2};;1;0|biztalk_host_instances_pending={3};1;;0|biztalk_host_instances_unknown={4};;1;0",
total,
started,
stopped,
pending,
unknown);
var detail = new StringBuilder();
detail.Append("Host instances total=").Append(total)
.Append(", started=").Append(started)
.Append(", stopped=").Append(stopped)
.Append(", pending=").Append(pending)
.Append(", unknown=").Append(unknown);
var affected = result.HostInstances
.Where(x => x.ServiceState != HostStarted)
.Take(_options.MaxSummaryItems)
.Select(x => EmptyAsUnknown(x.HostName) + "@" + EmptyAsUnknown(x.RunningServer) + "=" + HostStateName(x.ServiceState));
AppendList(detail, "affected", affected);
return BuildLine(state, _options.ServiceName("Host Instances"), metrics, detail.ToString());
}
private string FormatRuntimeArtifacts(ProbeResult result)
{
var receiveLocations = result.Applications.Sum(x => x.ReceiveLocationTotal);
var receiveLocationsDisabled = result.Applications.Sum(x => x.ReceiveLocationDisabled);
var sendPorts = result.Applications.Sum(x => x.SendPortTotal);
var sendPortsStarted = result.Applications.Sum(x => x.SendPortStarted);
var sendPortsInactive = result.Applications.Sum(x => x.SendPortStopped + x.SendPortBound);
var sendPortsUnknown = result.Applications.Sum(x => x.SendPortUnknown);
var orchestrations = result.Applications.Sum(x => x.OrchestrationTotal);
var orchestrationsStarted = result.Applications.Sum(x => x.OrchestrationStarted);
var orchestrationsInactive = result.Applications.Sum(x => x.OrchestrationStopped + x.OrchestrationBound + x.OrchestrationUnbound);
var orchestrationsUnknown = result.Applications.Sum(x => x.OrchestrationUnknown);
var state = sendPortsUnknown > 0 || orchestrationsUnknown > 0
? CheckState.Critical
: _options.AlertOnArtifactRuntimeIssues && (receiveLocationsDisabled > 0 || sendPortsInactive > 0 || orchestrationsInactive > 0)
? CheckState.Warning
: CheckState.Ok;
var metrics = string.Format(
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",
result.Applications.Count,
receiveLocations,
receiveLocationsDisabled,
sendPorts,
sendPortsStarted,
sendPortsInactive,
sendPortsUnknown,
orchestrations,
orchestrationsStarted,
orchestrationsInactive,
orchestrationsUnknown);
var detail = new StringBuilder();
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
.Where(x => x.ReceiveLocationDisabled > 0 || x.SendPortStopped + x.SendPortBound + x.SendPortUnknown > 0 || x.OrchestrationStopped + x.OrchestrationBound + x.OrchestrationUnbound + x.OrchestrationUnknown > 0)
.Take(_options.MaxSummaryItems)
.Select(x => x.ApplicationName + "(rl_disabled=" + x.ReceiveLocationDisabled + ", sp_inactive=" + (x.SendPortStopped + x.SendPortBound) + ", orch_inactive=" + (x.OrchestrationStopped + x.OrchestrationBound + x.OrchestrationUnbound) + ")");
AppendList(detail, "notable_apps", affected);
return BuildLine(state, _options.ServiceName("Runtime Artifacts"), metrics, detail.ToString());
}
private string FormatEventLog(ProbeResult result)
{
if (!_options.ProbeEventLog)
{
return BuildLine(CheckState.Ok, _options.ServiceName("Event Log"), "-", "Event log probe disabled.");
}
if (!result.EventLog.Available)
{
return BuildLine(CheckState.Unknown, _options.ServiceName("Event Log"), "-", "Application event log could not be read: " + result.EventLog.Failure);
}
var state = _options.EventLogCritThreshold > 0 && result.EventLog.Errors >= _options.EventLogCritThreshold
? CheckState.Critical
: _options.EventLogWarnThreshold > 0 && (result.EventLog.Errors >= _options.EventLogWarnThreshold || result.EventLog.Warnings >= _options.EventLogWarnThreshold)
? CheckState.Warning
: CheckState.Ok;
var metrics = string.Format(
CultureInfo.InvariantCulture,
"biztalk_eventlog_errors={0};{1};{2};0|biztalk_eventlog_warnings={3};{1};;0",
result.EventLog.Errors,
_options.EventLogWarnThreshold,
_options.EventLogCritThreshold,
result.EventLog.Warnings);
var detail = string.Format(
CultureInfo.InvariantCulture,
"BizTalk-related Application log entries in last {0} minutes: errors={1}, warnings={2}",
_options.EventLogLookbackMinutes,
result.EventLog.Errors,
result.EventLog.Warnings);
return BuildLine(state, _options.ServiceName("Event Log"), metrics, detail);
}
private IEnumerable<string> FormatApplicationSuspensions(ProbeResult result)
{
foreach (var group in result.SuspendedInstances.GroupBy(x => x.ApplicationName).OrderBy(x => x.Key, StringComparer.OrdinalIgnoreCase))
{
var resumable = group.Count(x => x.Kind == SuspendedKind.Resumable);
var nonresumable = group.Count(x => x.Kind == SuspendedKind.NonResumable);
var state = DetermineSuspensionState(resumable, nonresumable);
var metrics = string.Format(
CultureInfo.InvariantCulture,
"biztalk_suspended_total={0};;;0|biztalk_suspended_resumable={1};{2};;0|biztalk_suspended_nonresumable={3};;{4};0",
group.Count(),
resumable,
_options.WarnResumableThreshold,
nonresumable,
_options.CritNonResumableThreshold);
yield return BuildLine(state, _options.ServiceName("Suspended " + group.Key), metrics, "Suspended instances: resumable=" + resumable + ", nonresumable=" + nonresumable);
}
}
private CheckState DetermineSuspensionState(int resumable, int nonresumable)
{
if (_options.CritNonResumableThreshold > 0 && nonresumable >= _options.CritNonResumableThreshold)
{
return CheckState.Critical;
}
if (_options.WarnResumableThreshold > 0 && resumable >= _options.WarnResumableThreshold)
{
return CheckState.Warning;
}
return CheckState.Ok;
}
private string BuildSuspensionDetail(IEnumerable<SuspendedInstance> instances, int total, int resumable, int nonresumable)
{
var builder = new StringBuilder();
builder.Append(total).Append(" suspended BizTalk service instance(s): resumable=").Append(resumable)
.Append(", nonresumable=").Append(nonresumable);
var apps = instances
.GroupBy(x => x.ApplicationName)
.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) + ")");
AppendList(builder, "applications", apps);
var examples = instances
.OrderByDescending(x => x.SuspendTime ?? DateTime.MinValue)
.Take(Math.Min(_options.MaxSummaryItems, 5))
.Select(x => EmptyAsUnknown(x.ServiceName) + " on " + EmptyAsUnknown(x.HostName));
AppendList(builder, "examples", examples);
return builder.ToString();
}
private static string BuildLine(CheckState state, string serviceName, string metrics, string detail)
{
return ((int)state).ToString(CultureInfo.InvariantCulture)
+ " \"" + SanitizeService(serviceName) + "\" "
+ (string.IsNullOrWhiteSpace(metrics) ? "-" : metrics)
+ " "
+ SanitizeDetail(detail);
}
private static string AppendDiagnostics(string detail, IEnumerable<string> diagnostics)
{
var visible = diagnostics == null ? new string[0] : diagnostics.Take(3).ToArray();
if (visible.Length == 0)
{
return detail;
}
return detail + "; diagnostics: " + string.Join("; ", visible);
}
private static void AppendList(StringBuilder builder, string label, IEnumerable<string> values)
{
var list = values.Where(x => !string.IsNullOrWhiteSpace(x)).ToArray();
if (list.Length > 0)
{
builder.Append("; ").Append(label).Append("=").Append(string.Join(", ", list));
}
}
private static void AppendOptional(StringBuilder builder, string label, string value)
{
if (!string.IsNullOrWhiteSpace(value))
{
builder.Append(", ").Append(label).Append("=").Append(value);
}
}
private static string JoinDb(string server, string database)
{
if (string.IsNullOrWhiteSpace(server) && string.IsNullOrWhiteSpace(database))
{
return string.Empty;
}
return EmptyAsUnknown(server) + "\\" + EmptyAsUnknown(database);
}
private static string HostStateName(int state)
{
switch (state)
{
case HostStopped:
return "Stopped";
case HostStartPending:
return "StartPending";
case HostStopPending:
return "StopPending";
case HostStarted:
return "Started";
case HostContinuePending:
return "ContinuePending";
case HostPausePending:
return "PausePending";
case HostPaused:
return "Paused";
case HostUnknown:
return "Unknown";
default:
return "Unknown(" + state.ToString(CultureInfo.InvariantCulture) + ")";
}
}
private static string EmptyAsUnknown(string value)
{
return string.IsNullOrWhiteSpace(value) ? "unknown" : value.Trim();
}
private static string SanitizeService(string value)
{
return SanitizeDetail(value).Replace("\"", "'");
}
private static string SanitizeDetail(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return "No details.";
}
return value.Replace('\r', ' ').Replace('\n', ' ').Replace('\t', ' ').Trim();
}
}
}
+69
View File
@@ -0,0 +1,69 @@
using System;
using System.Diagnostics;
using System.Linq;
namespace BizTalkCheckmkPulse
{
internal sealed class EventLogProbe
{
private readonly MonitoringOptions _options;
public EventLogProbe(MonitoringOptions options)
{
_options = options;
}
public void Query(ProbeResult result)
{
result.EventLog.Since = DateTime.Now.AddMinutes(-_options.EventLogLookbackMinutes);
try
{
using (var log = new EventLog("Application", "."))
{
var entries = log.Entries;
for (var i = entries.Count - 1; i >= 0; i--)
{
var entry = entries[i];
if (entry.TimeGenerated < result.EventLog.Since)
{
break;
}
if (!IsRelevantSource(entry.Source))
{
continue;
}
if (entry.EntryType == EventLogEntryType.Error)
{
result.EventLog.Errors++;
}
else if (entry.EntryType == EventLogEntryType.Warning)
{
result.EventLog.Warnings++;
}
}
}
result.EventLog.Available = true;
}
catch (Exception ex)
{
result.EventLog.Available = false;
result.EventLog.Failure = ex.GetType().Name + ": " + ex.Message;
}
}
private bool IsRelevantSource(string source)
{
if (string.IsNullOrWhiteSpace(source))
{
return false;
}
return _options.EventLogSources.Any(x => source.IndexOf(x, StringComparison.OrdinalIgnoreCase) >= 0);
}
}
}
+98
View File
@@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
namespace BizTalkCheckmkPulse
{
internal enum CheckState
{
Ok = 0,
Warning = 1,
Critical = 2,
Unknown = 3
}
internal enum SuspendedKind
{
Resumable,
NonResumable
}
internal sealed class ProbeResult
{
public ProbeResult()
{
Diagnostics = new List<string>();
Platform = new PlatformState();
HostInstances = new List<HostInstanceState>();
SuspendedInstances = new List<SuspendedInstance>();
Applications = new List<ApplicationRuntimeState>();
EventLog = new EventLogState();
}
public List<string> Diagnostics { get; private set; }
public PlatformState Platform { get; private set; }
public List<HostInstanceState> HostInstances { get; private set; }
public List<SuspendedInstance> SuspendedInstances { get; private set; }
public List<ApplicationRuntimeState> Applications { get; private set; }
public EventLogState EventLog { get; private set; }
}
internal sealed class PlatformState
{
public bool WmiConnected { get; set; }
public string ServerName { get; set; }
public string GroupName { get; set; }
public string ManagementDbServer { get; set; }
public string ManagementDbName { get; set; }
public string MessageBoxDbServer { get; set; }
public string MessageBoxDbName { get; set; }
}
internal sealed class HostInstanceState
{
public string InstanceName { get; set; }
public string HostName { get; set; }
public string RunningServer { get; set; }
public int ServiceState { get; set; }
}
internal sealed class SuspendedInstance
{
public string ApplicationName { get; set; }
public string ServiceName { get; set; }
public string HostName { get; set; }
public string ServiceClass { get; set; }
public string InstanceId { get; set; }
public string ErrorDescription { get; set; }
public DateTime? SuspendTime { get; set; }
public SuspendedKind Kind { get; set; }
}
internal sealed class ApplicationRuntimeState
{
public string ApplicationName { get; set; }
public int ReceiveLocationTotal { get; set; }
public int ReceiveLocationDisabled { get; set; }
public int SendPortTotal { get; set; }
public int SendPortStarted { get; set; }
public int SendPortStopped { get; set; }
public int SendPortBound { get; set; }
public int SendPortUnknown { get; set; }
public int OrchestrationTotal { get; set; }
public int OrchestrationStarted { get; set; }
public int OrchestrationStopped { get; set; }
public int OrchestrationBound { get; set; }
public int OrchestrationUnbound { get; set; }
public int OrchestrationUnknown { get; set; }
}
internal sealed class EventLogState
{
public bool Available { get; set; }
public int Errors { get; set; }
public int Warnings { get; set; }
public DateTime Since { get; set; }
public string Failure { get; set; }
}
}
@@ -0,0 +1,149 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.Linq;
namespace BizTalkCheckmkPulse
{
internal sealed class MonitoringOptions
{
public string Server { get; set; }
public string ServicePrefix { get; set; }
public string EnvironmentName { get; set; }
public int QueryTimeoutSeconds { get; set; }
public int WarnResumableThreshold { get; set; }
public int CritNonResumableThreshold { get; set; }
public int MaxSummaryItems { get; set; }
public bool AlertOnArtifactRuntimeIssues { get; set; }
public bool EmitPerApplicationSuspensionServices { get; set; }
public bool ProbeEventLog { get; set; }
public int EventLogLookbackMinutes { get; set; }
public int EventLogWarnThreshold { get; set; }
public int EventLogCritThreshold { get; set; }
public IReadOnlyList<string> EventLogSources { get; set; }
public bool SelfTest { get; set; }
public MonitoringOptions()
{
Server = ".";
ServicePrefix = "BizTalk";
EnvironmentName = string.Empty;
QueryTimeoutSeconds = 25;
WarnResumableThreshold = 1;
CritNonResumableThreshold = 1;
MaxSummaryItems = 12;
AlertOnArtifactRuntimeIssues = false;
EmitPerApplicationSuspensionServices = false;
ProbeEventLog = true;
EventLogLookbackMinutes = 60;
EventLogWarnThreshold = 1;
EventLogCritThreshold = 10;
EventLogSources = new[] { "BizTalk Server", "XLANG/s", "ENTSSO", "BizTalk Server Application", "BizTalk Server EDI" };
}
public string ServiceName(string suffix)
{
var prefix = string.IsNullOrWhiteSpace(EnvironmentName)
? ServicePrefix
: ServicePrefix + " " + EnvironmentName.Trim();
return prefix.Trim() + " " + suffix;
}
public static MonitoringOptions Load(string[] args)
{
var options = new MonitoringOptions();
var settings = ConfigurationManager.AppSettings;
options.Server = ReadString(settings, "Server", options.Server);
options.ServicePrefix = ReadString(settings, "ServicePrefix", options.ServicePrefix);
options.EnvironmentName = ReadString(settings, "EnvironmentName", options.EnvironmentName);
options.QueryTimeoutSeconds = ReadInt(settings, "QueryTimeoutSeconds", options.QueryTimeoutSeconds, 5, 120);
options.WarnResumableThreshold = ReadInt(settings, "WarnResumableThreshold", options.WarnResumableThreshold, 0, 1000000);
options.CritNonResumableThreshold = ReadInt(settings, "CritNonResumableThreshold", options.CritNonResumableThreshold, 0, 1000000);
options.MaxSummaryItems = ReadInt(settings, "MaxSummaryItems", options.MaxSummaryItems, 1, 100);
options.AlertOnArtifactRuntimeIssues = ReadBool(settings, "AlertOnArtifactRuntimeIssues", options.AlertOnArtifactRuntimeIssues);
options.EmitPerApplicationSuspensionServices = ReadBool(settings, "EmitPerApplicationSuspensionServices", options.EmitPerApplicationSuspensionServices);
options.ProbeEventLog = ReadBool(settings, "ProbeEventLog", options.ProbeEventLog);
options.EventLogLookbackMinutes = ReadInt(settings, "EventLogLookbackMinutes", options.EventLogLookbackMinutes, 1, 10080);
options.EventLogWarnThreshold = ReadInt(settings, "EventLogWarnThreshold", options.EventLogWarnThreshold, 0, 1000000);
options.EventLogCritThreshold = ReadInt(settings, "EventLogCritThreshold", options.EventLogCritThreshold, 0, 1000000);
options.EventLogSources = ReadSources(ReadString(settings, "EventLogSources", string.Join("|", options.EventLogSources)));
ApplyArguments(options, args ?? new string[0]);
return options;
}
private static void ApplyArguments(MonitoringOptions options, string[] args)
{
for (var i = 0; i < args.Length; i++)
{
var arg = args[i];
if (EqualsAny(arg, "--self-test", "/self-test"))
{
options.SelfTest = true;
}
else if (EqualsAny(arg, "--server", "/server") && i + 1 < args.Length)
{
options.Server = args[++i];
}
else if (EqualsAny(arg, "--environment", "/environment") && i + 1 < args.Length)
{
options.EnvironmentName = args[++i];
}
else if (EqualsAny(arg, "--emit-app-services", "/emit-app-services"))
{
options.EmitPerApplicationSuspensionServices = true;
}
else if (EqualsAny(arg, "--alert-artifacts", "/alert-artifacts"))
{
options.AlertOnArtifactRuntimeIssues = true;
}
}
}
private static string ReadString(System.Collections.Specialized.NameValueCollection settings, string key, string fallback)
{
var value = settings[key];
return value == null ? fallback : value.Trim();
}
private static int ReadInt(System.Collections.Specialized.NameValueCollection settings, string key, int fallback, int min, int max)
{
int parsed;
if (!int.TryParse(settings[key], NumberStyles.Integer, CultureInfo.InvariantCulture, out parsed))
{
return fallback;
}
if (parsed < min)
{
return min;
}
return parsed > max ? max : parsed;
}
private static bool ReadBool(System.Collections.Specialized.NameValueCollection settings, string key, bool fallback)
{
bool parsed;
return bool.TryParse(settings[key], out parsed) ? parsed : fallback;
}
private static IReadOnlyList<string> ReadSources(string value)
{
return (value ?? string.Empty)
.Split(new[] { '|', ';', ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.Where(x => x.Length > 0)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
}
private static bool EqualsAny(string value, params string[] candidates)
{
return candidates.Any(x => string.Equals(value, x, StringComparison.OrdinalIgnoreCase));
}
}
}
+60
View File
@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BizTalkCheckmkPulse
{
internal static class Program
{
private static int Main(string[] args)
{
MonitoringOptions options = null;
try
{
Console.OutputEncoding = new UTF8Encoding(false);
options = MonitoringOptions.Load(args);
var formatter = new CheckmkLocalFormatter(options);
if (options.SelfTest)
{
foreach (var line in formatter.FormatSelfTest())
{
Console.WriteLine(line);
}
return 0;
}
var result = new ProbeResult();
var wmiProbe = new WmiBizTalkProbe(options);
wmiProbe.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)
{
var fallbackOptions = options ?? new MonitoringOptions();
var formatter = new CheckmkLocalFormatter(fallbackOptions);
foreach (var line in formatter.FormatFatal("BizTalk Checkmk Pulse failed: " + ex.GetType().Name + ": " + ex.Message))
{
Console.WriteLine(line);
}
return 0;
}
}
}
}
+288
View File
@@ -0,0 +1,288 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management;
namespace BizTalkCheckmkPulse
{
internal sealed class WmiBizTalkProbe
{
private const string NamespacePath = "root\\MicrosoftBizTalkServer";
private const string UnknownApplication = "(unknown)";
private const int SuspendedResumable = 4;
private const int SuspendedNonResumable = 32;
private const int SendPortBound = 1;
private const int SendPortStopped = 2;
private const int SendPortStarted = 3;
private const int OrchestrationUnbound = 1;
private const int OrchestrationBound = 2;
private const int OrchestrationStopped = 3;
private const int OrchestrationStarted = 4;
private readonly MonitoringOptions _options;
public WmiBizTalkProbe(MonitoringOptions options)
{
_options = options;
}
public void Query(ProbeResult result)
{
var server = string.IsNullOrWhiteSpace(_options.Server) || _options.Server == "."
? Environment.MachineName
: _options.Server.Trim();
result.Platform.ServerName = server;
var scope = new ManagementScope("\\\\" + server + "\\" + NamespacePath);
scope.Connect();
result.Platform.WmiConnected = true;
QueryPlatform(scope, result);
QueryHostInstances(scope, result);
var applicationIndex = BuildApplicationIndex(scope, result);
QueryApplicationRuntime(scope, result);
QuerySuspendedInstances(scope, result, applicationIndex);
}
private void QueryPlatform(ManagementScope scope, ProbeResult result)
{
TryQuery(scope, result, "MSBTS_GroupSetting", "SELECT * FROM MSBTS_GroupSetting", item =>
{
result.Platform.GroupName = FirstNonEmpty(WmiHelpers.GetString(item, "Name"), WmiHelpers.GetString(item, "MgmtDbName"));
result.Platform.ManagementDbServer = FirstNonEmpty(WmiHelpers.GetString(item, "MgmtDbServerName"), WmiHelpers.GetString(item, "DBServerName"));
result.Platform.ManagementDbName = FirstNonEmpty(WmiHelpers.GetString(item, "MgmtDbName"), WmiHelpers.GetString(item, "DatabaseName"));
}, true);
TryQuery(scope, result, "MSBTS_MessageBoxSetting", "SELECT * FROM MSBTS_MessageBoxSetting", item =>
{
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);
}
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;
}
private void AddArtifactIndex(ManagementScope scope, ProbeResult result, Dictionary<string, string> index, string className)
{
TryQuery(scope, result, className, "SELECT * FROM " + className, item =>
{
var app = GetApplicationName(item);
foreach (var key in CandidateKeys(item))
{
if (!string.IsNullOrWhiteSpace(key) && !index.ContainsKey(key))
{
index.Add(key, app);
}
}
}, false);
}
private void QueryHostInstances(ManagementScope scope, ProbeResult result)
{
TryQuery(scope, result, "MSBTS_HostInstance", "SELECT * FROM MSBTS_HostInstance", item =>
{
result.HostInstances.Add(new HostInstanceState
{
InstanceName = FirstNonEmpty(WmiHelpers.GetString(item, "InstanceName"), WmiHelpers.GetString(item, "Name")),
HostName = WmiHelpers.GetString(item, "HostName"),
RunningServer = WmiHelpers.GetString(item, "RunningServer"),
ServiceState = WmiHelpers.GetInt32(item, "ServiceState", 0)
});
}, true);
}
private void QueryApplicationRuntime(ManagementScope scope, ProbeResult result)
{
var apps = new Dictionary<string, ApplicationRuntimeState>(StringComparer.OrdinalIgnoreCase);
TryQuery(scope, result, "MSBTS_ReceiveLocation", "SELECT * FROM MSBTS_ReceiveLocation", item =>
{
var app = GetApplication(apps, GetApplicationName(item));
app.ReceiveLocationTotal++;
if (WmiHelpers.GetBoolean(item, "IsDisabled", false))
{
app.ReceiveLocationDisabled++;
}
}, true);
TryQuery(scope, result, "MSBTS_SendPort", "SELECT * FROM MSBTS_SendPort", item =>
{
var app = GetApplication(apps, GetApplicationName(item));
app.SendPortTotal++;
switch (WmiHelpers.GetInt32(item, "Status", 0))
{
case SendPortStarted:
app.SendPortStarted++;
break;
case SendPortStopped:
app.SendPortStopped++;
break;
case SendPortBound:
app.SendPortBound++;
break;
default:
app.SendPortUnknown++;
break;
}
}, true);
TryQuery(scope, result, "MSBTS_Orchestration", "SELECT * FROM MSBTS_Orchestration", item =>
{
var app = GetApplication(apps, GetApplicationName(item));
app.OrchestrationTotal++;
switch (WmiHelpers.GetInt32(item, "OrchestrationStatus", 0))
{
case OrchestrationStarted:
app.OrchestrationStarted++;
break;
case OrchestrationStopped:
app.OrchestrationStopped++;
break;
case OrchestrationBound:
app.OrchestrationBound++;
break;
case OrchestrationUnbound:
app.OrchestrationUnbound++;
break;
default:
app.OrchestrationUnknown++;
break;
}
}, true);
result.Applications.AddRange(apps.Values.OrderBy(x => x.ApplicationName, StringComparer.OrdinalIgnoreCase));
}
private void QuerySuspendedInstances(ManagementScope scope, ProbeResult result, Dictionary<string, string> applicationIndex)
{
TryQuery(scope, result, "MSBTS_ServiceInstance", "SELECT * FROM MSBTS_ServiceInstance WHERE ServiceStatus = 4 OR ServiceStatus = 32", item =>
{
var serviceStatus = WmiHelpers.GetInt32(item, "ServiceStatus", 0);
result.SuspendedInstances.Add(new SuspendedInstance
{
ApplicationName = ResolveApplication(item, applicationIndex),
ServiceName = FirstNonEmpty(WmiHelpers.GetString(item, "ServiceName"), WmiHelpers.GetString(item, "Name")),
HostName = WmiHelpers.GetString(item, "HostName"),
ServiceClass = WmiHelpers.GetString(item, "ServiceClass"),
InstanceId = WmiHelpers.GetString(item, "InstanceID"),
ErrorDescription = WmiHelpers.GetString(item, "ErrorDescription"),
SuspendTime = WmiHelpers.GetDmtfDateTime(item, "SuspendTime"),
Kind = serviceStatus == SuspendedNonResumable ? SuspendedKind.NonResumable : SuspendedKind.Resumable
});
}, true);
}
private void TryQuery(ManagementScope scope, ProbeResult result, string label, string queryText, Action<ManagementObject> action, bool required)
{
try
{
ForEachObject(scope, queryText, action);
}
catch (ManagementException ex)
{
AddDiagnostic(result, label, ex.Message, required);
}
catch (UnauthorizedAccessException ex)
{
AddDiagnostic(result, label, ex.Message, required);
}
catch (System.Runtime.InteropServices.COMException ex)
{
AddDiagnostic(result, label, ex.Message, required);
}
}
private void ForEachObject(ManagementScope scope, string queryText, Action<ManagementObject> action)
{
var queryOptions = new EnumerationOptions
{
ReturnImmediately = true,
Rewindable = false,
Timeout = TimeSpan.FromSeconds(_options.QueryTimeoutSeconds)
};
using (var searcher = new ManagementObjectSearcher(scope, new ObjectQuery(queryText), queryOptions))
using (var collection = searcher.Get())
{
foreach (ManagementObject item in collection)
{
using (item)
{
action(item);
}
}
}
}
private static ApplicationRuntimeState GetApplication(Dictionary<string, ApplicationRuntimeState> apps, string applicationName)
{
var name = string.IsNullOrWhiteSpace(applicationName) ? UnknownApplication : applicationName.Trim();
ApplicationRuntimeState app;
if (!apps.TryGetValue(name, out app))
{
app = new ApplicationRuntimeState { ApplicationName = name };
apps.Add(name, app);
}
return app;
}
private static string ResolveApplication(ManagementBaseObject item, Dictionary<string, string> applicationIndex)
{
var direct = GetApplicationName(item);
if (!string.Equals(direct, UnknownApplication, StringComparison.OrdinalIgnoreCase))
{
return direct;
}
foreach (var key in CandidateKeys(item))
{
string app;
if (!string.IsNullOrWhiteSpace(key) && applicationIndex.TryGetValue(key, out app))
{
return app;
}
}
return UnknownApplication;
}
private static IEnumerable<string> CandidateKeys(ManagementBaseObject item)
{
yield return WmiHelpers.GetString(item, "ApplicationName");
yield return WmiHelpers.GetString(item, "ServiceName");
yield return WmiHelpers.GetString(item, "Name");
yield return WmiHelpers.GetString(item, "HostName");
}
private static string GetApplicationName(ManagementBaseObject item)
{
return FirstNonEmpty(
WmiHelpers.GetString(item, "ApplicationName"),
WmiHelpers.GetString(item, "Application"),
WmiHelpers.GetString(item, "BizTalkApplication")) ?? UnknownApplication;
}
private static string FirstNonEmpty(params string[] values)
{
return values == null ? null : values.FirstOrDefault(x => !string.IsNullOrWhiteSpace(x));
}
private static void AddDiagnostic(ProbeResult result, string label, string message, bool required)
{
result.Diagnostics.Add((required ? "Required" : "Optional") + " WMI query " + label + " failed: " + message);
}
}
}
+60
View File
@@ -0,0 +1,60 @@
using System;
using System.Globalization;
using System.Management;
namespace BizTalkCheckmkPulse
{
internal static class WmiHelpers
{
public static string GetString(ManagementBaseObject item, string propertyName)
{
if (item == null || string.IsNullOrWhiteSpace(propertyName))
{
return string.Empty;
}
try
{
var value = item.Properties[propertyName] == null ? null : item.Properties[propertyName].Value;
return value == null ? string.Empty : Convert.ToString(value, CultureInfo.InvariantCulture).Trim();
}
catch (ManagementException)
{
return string.Empty;
}
}
public static int GetInt32(ManagementBaseObject item, string propertyName, int fallback)
{
var value = GetString(item, propertyName);
int parsed;
return int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out parsed) ? parsed : fallback;
}
public static bool GetBoolean(ManagementBaseObject item, string propertyName, bool fallback)
{
var value = GetString(item, propertyName);
bool parsed;
return bool.TryParse(value, out parsed) ? parsed : fallback;
}
public static DateTime? GetDmtfDateTime(ManagementBaseObject item, string propertyName)
{
var value = GetString(item, propertyName);
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
try
{
return ManagementDateTimeConverter.ToDateTime(value);
}
catch (ArgumentOutOfRangeException)
{
return null;
}
}
}
}