Initial commit for the biztalk suspended isntances local check
build / build (push) Has been cancelled
build / build (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = crlf
|
||||||
|
insert_final_newline = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.{md,yml,yaml}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
name: build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: windows
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Build release
|
||||||
|
shell: cmd
|
||||||
|
run: scripts\build-release.cmd
|
||||||
|
- name: Package deployment folder
|
||||||
|
shell: cmd
|
||||||
|
run: scripts\package-release.cmd
|
||||||
|
- name: Upload deployment artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: BizTalkSuspendedInstancesCheck-deploy
|
||||||
|
path: artifacts\BizTalkSuspendedInstancesCheck-deploy
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
artifacts/
|
||||||
|
*.user
|
||||||
|
*.suo
|
||||||
|
.vs/
|
||||||
|
TestResults/
|
||||||
|
|
||||||
@@ -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}") = "BizTalkSuspendedInstancesCheck", "src\BizTalkSuspendedInstancesCheck\BizTalkSuspendedInstancesCheck.csproj", "{ED7AF215-F12B-4E2E-811B-693A537C9493}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{ED7AF215-F12B-4E2E-811B-693A537C9493}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{ED7AF215-F12B-4E2E-811B-693A537C9493}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{ED7AF215-F12B-4E2E-811B-693A537C9493}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{ED7AF215-F12B-4E2E-811B-693A537C9493}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
|
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
# Dokumentation: BizTalk Runtime Checkmk Local Check
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
|
||||||
|
Der Check beantwortet fuer den lokalen BizTalk-Server:
|
||||||
|
|
||||||
|
- Gibt es suspendierte BizTalk-Service-Instanzen?
|
||||||
|
- Sind sie resumable oder non-resumable?
|
||||||
|
- Welche BizTalk-Anwendungen sind betroffen?
|
||||||
|
- Sind BizTalk Host-Instanzen gestartet?
|
||||||
|
- Wie ist der aggregierte Laufzeitstatus wichtiger BizTalk-Artefakte?
|
||||||
|
|
||||||
|
Er ist fuer Checkmk Windows Agent Local Checks gebaut und benoetigt keine PowerShell.
|
||||||
|
|
||||||
|
## Architektur
|
||||||
|
|
||||||
|
Komponenten:
|
||||||
|
|
||||||
|
- `BizTalkSuspendedInstancesCheck.exe`: C#/.NET-Framework-Konsolenprogramm
|
||||||
|
- `BizTalkSuspendedInstancesCheck.exe.config`: Konfiguration
|
||||||
|
- `biztalk_suspended_instances.cmd`: Checkmk-Wrapper fuer `%ProgramData%\checkmk\agent\local`
|
||||||
|
|
||||||
|
Laufzeitfluss:
|
||||||
|
|
||||||
|
1. Checkmk Windows Agent ruft `biztalk_suspended_instances.cmd` auf.
|
||||||
|
2. Der Wrapper startet die EXE.
|
||||||
|
3. Die EXE fragt `root\MicrosoftBizTalkServer` per WMI ab.
|
||||||
|
4. Die EXE schreibt mehrere Checkmk-konforme Local-Check-Zeilen nach STDOUT.
|
||||||
|
|
||||||
|
Die Anwendung gibt selbst bei Fehlern eine syntaktisch gueltige Checkmk-Zeile aus. Dadurch wird der Monitoring-Zustand `UNKNOWN` sichtbar, statt den Agenten still zu stoeren.
|
||||||
|
|
||||||
|
## Ausfuehrung durch Checkmk
|
||||||
|
|
||||||
|
Der Check wird als Checkmk Local Check betrieben. Das bedeutet: Checkmk fuehrt auf dem BizTalk-Server keine zentrale Abfrage gegen BizTalk aus, sondern der Checkmk Windows Agent startet lokal abgelegte Checks und sammelt deren STDOUT ein.
|
||||||
|
|
||||||
|
Der relevante Agent-Pfad auf Windows ist:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
%ProgramData%\checkmk\agent\local
|
||||||
|
```
|
||||||
|
|
||||||
|
In diesem Verzeichnis liegt unser mitgelieferter Wrapper:
|
||||||
|
|
||||||
|
```text
|
||||||
|
%ProgramData%\checkmk\agent\local\biztalk_suspended_instances.cmd
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Wrapper ruft die eigentliche C#-EXE auf:
|
||||||
|
|
||||||
|
```text
|
||||||
|
%ProgramData%\checkmk\agent\local\BizTalkSuspendedInstancesCheck\BizTalkSuspendedInstancesCheck.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
Die vollstaendige Zielstruktur ist:
|
||||||
|
|
||||||
|
```text
|
||||||
|
%ProgramData%\checkmk\agent\local\biztalk_suspended_instances.cmd
|
||||||
|
%ProgramData%\checkmk\agent\local\BizTalkSuspendedInstancesCheck\BizTalkSuspendedInstancesCheck.exe
|
||||||
|
%ProgramData%\checkmk\agent\local\BizTalkSuspendedInstancesCheck\BizTalkSuspendedInstancesCheck.exe.config
|
||||||
|
```
|
||||||
|
|
||||||
|
Das Aufrufskript muss nicht separat neu entwickelt werden. Es ist Bestandteil dieses Repositories:
|
||||||
|
|
||||||
|
```text
|
||||||
|
deployment\checkmk\biztalk_suspended_instances.cmd
|
||||||
|
```
|
||||||
|
|
||||||
|
Beim Paketieren mit `scripts\package-release.cmd` wird dieses Skript in den Deployment-Ordner kopiert. Der Inhalt von `artifacts\BizTalkSuspendedInstancesCheck-deploy` kann komplett nach `%ProgramData%\checkmk\agent\local` kopiert werden.
|
||||||
|
|
||||||
|
Der technische Ablauf ist:
|
||||||
|
|
||||||
|
1. Checkmk Windows Agent startet `biztalk_suspended_instances.cmd`.
|
||||||
|
2. Die `.cmd` sucht `BizTalkSuspendedInstancesCheck.exe` zuerst im Unterordner `BizTalkSuspendedInstancesCheck`.
|
||||||
|
3. Falls die EXE dort nicht liegt, sucht die `.cmd` die EXE direkt neben dem Skript.
|
||||||
|
4. Falls die EXE nicht gefunden wird, erzeugt die `.cmd` eine gueltige Checkmk-Zeile mit Status `UNKNOWN`.
|
||||||
|
5. Falls die EXE gefunden wird, fragt sie BizTalk per WMI ab.
|
||||||
|
6. Die EXE schreibt die Checkmk-Services nach STDOUT.
|
||||||
|
7. Der Checkmk Agent nimmt diese Zeilen in seine Agentenausgabe auf.
|
||||||
|
8. Nach Checkmk Service Discovery koennen die Services uebernommen werden.
|
||||||
|
|
||||||
|
Manueller Test auf dem BizTalk-Server:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
"%ProgramData%\checkmk\agent\local\biztalk_suspended_instances.cmd"
|
||||||
|
```
|
||||||
|
|
||||||
|
Agentenausgabe pruefen:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
"C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump
|
||||||
|
```
|
||||||
|
|
||||||
|
## WMI-Abfragen
|
||||||
|
|
||||||
|
Primaere Abfrage:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT * FROM MSBTS_ServiceInstance WHERE ServiceStatus = 4 OR ServiceStatus = 32
|
||||||
|
```
|
||||||
|
|
||||||
|
Statuswerte:
|
||||||
|
|
||||||
|
- `4`: Suspended (resumable)
|
||||||
|
- `32`: Suspended (not resumable)
|
||||||
|
|
||||||
|
Weitere Betriebsabfragen:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT * FROM MSBTS_HostInstance
|
||||||
|
SELECT * FROM MSBTS_ReceiveLocation
|
||||||
|
SELECT * FROM MSBTS_SendPort
|
||||||
|
SELECT * FROM MSBTS_Orchestration
|
||||||
|
```
|
||||||
|
|
||||||
|
Aus Betriebssicht werden damit folgende Risiken sichtbar:
|
||||||
|
|
||||||
|
- Gestoppte oder haengende Host-Instanzen.
|
||||||
|
- Deaktivierte Receive Locations.
|
||||||
|
- Nicht gestartete Send Ports.
|
||||||
|
- Nicht gestartete oder nicht gebundene Orchestrations.
|
||||||
|
- Unbekannte WMI-Statuswerte, die auf Versions-/Schemaabweichungen oder Lesefehler hindeuten koennen.
|
||||||
|
|
||||||
|
Zur Zuordnung der Anwendung wird zuerst versucht, eine Anwendungs-Property direkt aus der Service-Instanz zu lesen. Falls BizTalk diese nicht liefert, baut der Check einen lokalen Index ueber bekannte BizTalk-Artefakte:
|
||||||
|
|
||||||
|
- `MSBTS_SendPort`
|
||||||
|
- `MSBTS_ReceivePort`
|
||||||
|
- `MSBTS_ReceiveLocation`
|
||||||
|
- `MSBTS_Orchestration`
|
||||||
|
|
||||||
|
Wenn keine Zuordnung moeglich ist, erscheint die Instanz unter `(Unknown Application)`. Das ist absichtlich kein Fehler, weil BizTalk nicht fuer jede Instanz eine direkte Anwendungseigenschaft liefern muss.
|
||||||
|
|
||||||
|
## Checkmk-Ausgabe
|
||||||
|
|
||||||
|
Der Check gibt standardmaessig drei stabile Services aus. Es werden keine dynamischen Services pro Anwendung erzeugt, damit Checkmk Service Discovery stabil bleibt.
|
||||||
|
|
||||||
|
Beispiel ohne Befund:
|
||||||
|
|
||||||
|
```text
|
||||||
|
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;;;0|biztalk_host_instances_pending=0;;;0|biztalk_host_instances_unknown=0;;;0 total=4, started=4, stopped=0, pending=0, unknown=0
|
||||||
|
0 "BizTalk Runtime Artifacts" biztalk_applications=6;;;0|biztalk_receive_locations=20;;;0|biztalk_receive_locations_disabled=0;;;0|biztalk_send_ports=30;;;0|biztalk_send_ports_started=30;;;0|biztalk_send_ports_stopped=0;;;0|biztalk_send_ports_bound=0;;;0|biztalk_send_ports_unknown=0;;;0|biztalk_orchestrations=12;;;0|biztalk_orchestrations_started=12;;;0|biztalk_orchestrations_stopped=0;;;0|biztalk_orchestrations_bound=0;;;0|biztalk_orchestrations_unbound=0;;;0|biztalk_orchestrations_unknown=0;;;0 applications=6 | ReceiveLocations total=20, disabled=0 | SendPorts total=30, started=30, stopped=0, bound=0, unknown=0 | Orchestrations total=12, started=12, stopped=0, bound=0, unbound=0, unknown=0
|
||||||
|
```
|
||||||
|
|
||||||
|
Beispiel mit non-resumable Instanz:
|
||||||
|
|
||||||
|
```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=2. ApplicationA R=2 NR=0; ApplicationB R=0 NR=1
|
||||||
|
```
|
||||||
|
|
||||||
|
Beispiel fuer Host-Instanzen:
|
||||||
|
|
||||||
|
```text
|
||||||
|
2 "BizTalk Host Instances" biztalk_host_instances_total=4;;;0|biztalk_host_instances_started=3;;;0|biztalk_host_instances_stopped=1;;;0|biztalk_host_instances_pending=0;;;0|biztalk_host_instances_unknown=0;;;0 total=4, started=3, stopped=1, pending=0, unknown=0 | Not started: ProcessingHost@BIZTALK01=Stopped
|
||||||
|
```
|
||||||
|
|
||||||
|
Beispiel fuer Artefaktstatus:
|
||||||
|
|
||||||
|
```text
|
||||||
|
0 "BizTalk Runtime Artifacts" biztalk_applications=6;;;0|biztalk_receive_locations=20;;;0|biztalk_receive_locations_disabled=1;;;0|biztalk_send_ports=30;;;0|biztalk_send_ports_started=28;;;0|biztalk_send_ports_stopped=2;;;0|biztalk_send_ports_bound=0;;;0|biztalk_send_ports_unknown=0;;;0|biztalk_orchestrations=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;;;0 applications=6 | ReceiveLocations total=20, disabled=1 | SendPorts total=30, started=28, stopped=2, bound=0, unknown=0 | Orchestrations total=12, started=11, stopped=1, bound=0, unbound=0, unknown=0 | Notable apps: ApplicationA RL disabled=1, SP stopped/bound/unknown=2/0/0, Orch stopped/bound/unbound/unknown=1/0/0/0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Schwellwerte
|
||||||
|
|
||||||
|
Default:
|
||||||
|
|
||||||
|
- Resumable ab `1`: `WARN`
|
||||||
|
- Non-resumable ab `1`: `CRIT`
|
||||||
|
- Host-Instanz `Stopped` oder unbekannt: `CRIT`
|
||||||
|
- Host-Instanz `StartPending` oder `StopPending`: `WARN`
|
||||||
|
- Artefakte mit unbekanntem Status: `CRIT`
|
||||||
|
- Deaktivierte/inaktive Artefakte: nur Anzeige, optional `WARN` mit `AlertOnArtifactRuntimeIssues=true`
|
||||||
|
|
||||||
|
Begruendung:
|
||||||
|
|
||||||
|
- Resumable Suspensions sind stoerungsrelevant, aber potentiell wiederaufnehmbar.
|
||||||
|
- Non-resumable Suspensions verlangen normalerweise manuelle Bereinigung oder fachliche Analyse und werden daher kritisch bewertet.
|
||||||
|
- Gestoppte Host-Instanzen wirken direkt auf die Verarbeitung und werden daher kritisch bewertet.
|
||||||
|
- Deaktivierte Receive Locations, gestoppte Send Ports und gestoppte Orchestrations koennen gewollt sein; deshalb werden sie zuerst als Betriebsinformation ausgegeben.
|
||||||
|
|
||||||
|
## Betrieb
|
||||||
|
|
||||||
|
Empfohlener Installationspfad:
|
||||||
|
|
||||||
|
```text
|
||||||
|
%ProgramData%\checkmk\agent\local\biztalk_suspended_instances.cmd
|
||||||
|
%ProgramData%\checkmk\agent\local\BizTalkSuspendedInstancesCheck\BizTalkSuspendedInstancesCheck.exe
|
||||||
|
%ProgramData%\checkmk\agent\local\BizTalkSuspendedInstancesCheck\BizTalkSuspendedInstancesCheck.exe.config
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Wrapper sucht die EXE zuerst im Unterordner `BizTalkSuspendedInstancesCheck` und danach direkt neben der `.cmd`. Damit funktioniert sowohl eine saubere Unterordner-Installation als auch ein flaches Deployment.
|
||||||
|
|
||||||
|
Nach Aenderungen an den Service-Namen muss in Checkmk eine erneute Service Discovery erfolgen. Reine Schwellwert- oder Anzeigeaenderungen brauchen keine neue Discovery.
|
||||||
|
|
||||||
|
## Rechte
|
||||||
|
|
||||||
|
Der Check muss unter dem Konto des Checkmk Windows Agent auf dem BizTalk-Server WMI-Leserechte fuer `root\MicrosoftBizTalkServer` haben. In der Praxis ist das meist `LocalSystem` oder ein dediziertes Agent-Servicekonto mit lokalen Rechten.
|
||||||
|
|
||||||
|
## Resilienz
|
||||||
|
|
||||||
|
- Keine PowerShell-Abhaengigkeit.
|
||||||
|
- Keine BizTalk-Assembly-Abhaengigkeit im Build.
|
||||||
|
- UTF-8-Ausgabe wird in der EXE gesetzt.
|
||||||
|
- WMI-Timeout ist konfigurierbar.
|
||||||
|
- Fehler erzeugen `UNKNOWN` statt leerer Ausgabe.
|
||||||
|
- Artefakt-Index ist best effort; fehlende Anwendungszuordnung verhindert den Status nicht.
|
||||||
|
- Ein stabiler Checkmk-Service vermeidet Service-Discovery-Flapping bei wechselnden Anwendungsnamen.
|
||||||
|
- Runtime-Artefakte werden aggregiert und begrenzt ausgegeben, damit Checkmk-Summaries lesbar bleiben.
|
||||||
|
|
||||||
|
## Konfiguration
|
||||||
|
|
||||||
|
Wichtige `appSettings`:
|
||||||
|
|
||||||
|
- `ServiceName`: Service fuer suspendierte Instanzen.
|
||||||
|
- `HostInstancesServiceName`: Service fuer Host-Instanzen.
|
||||||
|
- `RuntimeArtifactsServiceName`: Service fuer Artefaktstatus.
|
||||||
|
- `MaxApplicationDetails`: maximale Anzahl Anwendungen in der Suspensions-Summary.
|
||||||
|
- `MaxRuntimeApplicationDetails`: maximale Anzahl auffaelliger Anwendungen in der Runtime-Summary.
|
||||||
|
- `AlertOnStoppedHostInstances`: Host-Instanz-Abweichungen alarmieren.
|
||||||
|
- `AlertOnArtifactRuntimeIssues`: deaktivierte/inaktive Artefakte als `WARN` werten.
|
||||||
|
- `EmitPerApplicationServices`: zusaetzliche Suspensions-Services pro Anwendung erzeugen. Nur verwenden, wenn dynamische Services in Checkmk gewuenscht sind.
|
||||||
|
|
||||||
|
## Gitea
|
||||||
|
|
||||||
|
Das Repository enthaelt `.gitea/workflows/build.yml`. Der Workflow erwartet einen Windows Runner mit Visual Studio Build Tools und .NET Framework 4.6.1 Developer Pack.
|
||||||
|
|
||||||
|
Wenn euer Gitea Runner anders gelabelt ist, `runs-on` in `.gitea/workflows/build.yml` anpassen.
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
# BizTalk Runtime Checkmk Local Check
|
||||||
|
|
||||||
|
Lokaler Checkmk-Check fuer Microsoft BizTalk Server 2020. Der Check prueft ueber WMI im Namespace `root\MicrosoftBizTalkServer`, ob suspendierte BizTalk-Service-Instanzen vorhanden sind, und liefert zusaetzliche Betriebsinformationen zu Host-Instanzen und BizTalk-Artefakten.
|
||||||
|
|
||||||
|
## Entscheidung
|
||||||
|
|
||||||
|
PowerShell ist in der Zielumgebung abgeriegelt. Die sinnvollste Option ist deshalb ein kleines C#/.NET-Framework-Konsolenprogramm, das per `.cmd` aus dem Checkmk-Agent-Local-Verzeichnis gestartet wird.
|
||||||
|
|
||||||
|
Bewertung:
|
||||||
|
|
||||||
|
- C#/.NET Framework: geeignet. Auf Windows Server mit BizTalk vorhanden, direkte WMI-Unterstuetzung via `System.Management`, keine PowerShell-Ausfuehrung, keine BizTalk-DLL als Compile-Time-Abhaengigkeit.
|
||||||
|
- PowerShell: fachlich moeglich, aber in der Umgebung ausgeschlossen.
|
||||||
|
- VBScript/JScript: technisch moeglich, aber schlechter wartbar und fehleranfaelliger bei Encoding und Fehlerbehandlung.
|
||||||
|
- BizTalk OperationsOM/ExplorerOM DLLs: fachlich moeglich, aber deploy- und buildseitig unnoetig schwer, da die DLLs auf Build- und Laufzeitumgebung passen muessen.
|
||||||
|
|
||||||
|
Der Check nutzt fuer Suspensions die WMI-Klasse `MSBTS_ServiceInstance`. Microsoft dokumentiert fuer `ServiceStatus` den Wert `4` als `Suspended (resumable)` und `32` als `Suspended (not resumable)`. Fuer die weiteren Betriebsinformationen werden `MSBTS_HostInstance`, `MSBTS_ReceiveLocation`, `MSBTS_SendPort` und `MSBTS_Orchestration` gelesen.
|
||||||
|
|
||||||
|
## Checkmk-Verhalten
|
||||||
|
|
||||||
|
Der Check erzeugt standardmaessig drei stabile Checkmk-Services:
|
||||||
|
|
||||||
|
- `BizTalk Suspended Instances`
|
||||||
|
- `BizTalk Host Instances`
|
||||||
|
- `BizTalk Runtime Artifacts`
|
||||||
|
|
||||||
|
### BizTalk Suspended Instances
|
||||||
|
|
||||||
|
Statuslogik:
|
||||||
|
|
||||||
|
- `OK`: keine suspendierten Instanzen
|
||||||
|
- `WARN`: mindestens eine resumable suspendierte Instanz
|
||||||
|
- `CRIT`: mindestens eine non-resumable suspendierte Instanz
|
||||||
|
- `UNKNOWN`: WMI-Zugriff, Konfiguration oder Laufzeitfehler
|
||||||
|
|
||||||
|
Metriken:
|
||||||
|
|
||||||
|
- `biztalk_suspended_total`
|
||||||
|
- `biztalk_suspended_resumable`
|
||||||
|
- `biztalk_suspended_nonresumable`
|
||||||
|
|
||||||
|
Die betroffenen BizTalk-Anwendungen stehen in der Checkmk-Summary, z.B. `ApplicationA R=2 NR=0; ApplicationB R=0 NR=1`.
|
||||||
|
|
||||||
|
### BizTalk Host Instances
|
||||||
|
|
||||||
|
Statuslogik:
|
||||||
|
|
||||||
|
- `OK`: alle Host-Instanzen sind `Started`
|
||||||
|
- `WARN`: Host-Instanzen sind in `StartPending` oder `StopPending`
|
||||||
|
- `CRIT`: mindestens eine Host-Instanz ist `Stopped` oder hat einen unbekannten Zustand
|
||||||
|
- `UNKNOWN`: WMI liefert keine Host-Instanzen
|
||||||
|
|
||||||
|
Metriken:
|
||||||
|
|
||||||
|
- `biztalk_host_instances_total`
|
||||||
|
- `biztalk_host_instances_started`
|
||||||
|
- `biztalk_host_instances_stopped`
|
||||||
|
- `biztalk_host_instances_pending`
|
||||||
|
- `biztalk_host_instances_unknown`
|
||||||
|
|
||||||
|
Die Summary zeigt Gesamtzahlen und bis zu 12 nicht gestartete Host-Instanzen im Format `Host@Server=Status`.
|
||||||
|
|
||||||
|
### BizTalk Runtime Artifacts
|
||||||
|
|
||||||
|
Dieser Service ist standardmaessig informativ (`OK`), solange keine unbekannten WMI-Zustaende auftreten. Grund: deaktivierte Receive Locations, gestoppte Send Ports oder gestoppte Orchestrations koennen in BizTalk fachlich beabsichtigt sein.
|
||||||
|
|
||||||
|
Statuslogik:
|
||||||
|
|
||||||
|
- `OK`: Artefaktstatus lesbar, keine unbekannten Statuswerte
|
||||||
|
- `CRIT`: Send Ports oder Orchestrations haben unbekannte WMI-Statuswerte
|
||||||
|
- Optional `WARN`: wenn `AlertOnArtifactRuntimeIssues=true` und deaktivierte/inaktive Artefakte gefunden werden
|
||||||
|
|
||||||
|
Metriken:
|
||||||
|
|
||||||
|
- `biztalk_applications`
|
||||||
|
- `biztalk_receive_locations`, `biztalk_receive_locations_disabled`
|
||||||
|
- `biztalk_send_ports`, `biztalk_send_ports_started`, `biztalk_send_ports_stopped`, `biztalk_send_ports_bound`, `biztalk_send_ports_unknown`
|
||||||
|
- `biztalk_orchestrations`, `biztalk_orchestrations_started`, `biztalk_orchestrations_stopped`, `biztalk_orchestrations_bound`, `biztalk_orchestrations_unbound`, `biztalk_orchestrations_unknown`
|
||||||
|
|
||||||
|
Die Summary ist bewusst bereits lesbar formatiert und enthaelt Abschnittstrenner, z.B. `ReceiveLocations total=20, disabled=1 | SendPorts ... | Notable apps: ...`.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Voraussetzungen auf dem Build-Host:
|
||||||
|
|
||||||
|
- Windows
|
||||||
|
- Visual Studio Build Tools oder Visual Studio mit MSBuild
|
||||||
|
- .NET Framework 4.6.1 Developer Pack
|
||||||
|
|
||||||
|
Build:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
scripts\build-release.cmd
|
||||||
|
```
|
||||||
|
|
||||||
|
Deployment-Ordner erzeugen:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
scripts\package-release.cmd
|
||||||
|
```
|
||||||
|
|
||||||
|
Das Ergebnis liegt unter `artifacts\BizTalkSuspendedInstancesCheck-deploy`.
|
||||||
|
|
||||||
|
Der Gitea-Workflow `.gitea/workflows/build.yml` fuehrt denselben Release-Build und die Paketierung aus.
|
||||||
|
|
||||||
|
## Wie Checkmk den Check ausfuehrt
|
||||||
|
|
||||||
|
Checkmk ruft unser C#-Programm nicht direkt per zentraler Checkmk-Regel auf. Der Checkmk Windows Agent startet lokale Checks auf dem BizTalk-Server automatisch aus dem lokalen Agent-Verzeichnis:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
%ProgramData%\checkmk\agent\local
|
||||||
|
```
|
||||||
|
|
||||||
|
Dort muss eine ausfuehrbare Datei liegen. Fuer diesen Check ist das der bereits mitgelieferte Wrapper:
|
||||||
|
|
||||||
|
```text
|
||||||
|
%ProgramData%\checkmk\agent\local\biztalk_suspended_instances.cmd
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Wrapper startet anschliessend die eigentliche EXE:
|
||||||
|
|
||||||
|
```text
|
||||||
|
%ProgramData%\checkmk\agent\local\BizTalkSuspendedInstancesCheck\BizTalkSuspendedInstancesCheck.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
Zielstruktur auf dem BizTalk-Server:
|
||||||
|
|
||||||
|
```text
|
||||||
|
%ProgramData%\checkmk\agent\local\biztalk_suspended_instances.cmd
|
||||||
|
%ProgramData%\checkmk\agent\local\BizTalkSuspendedInstancesCheck\BizTalkSuspendedInstancesCheck.exe
|
||||||
|
%ProgramData%\checkmk\agent\local\BizTalkSuspendedInstancesCheck\BizTalkSuspendedInstancesCheck.exe.config
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Ablauf ist:
|
||||||
|
|
||||||
|
1. Checkmk Windows Agent startet `biztalk_suspended_instances.cmd`.
|
||||||
|
2. Die `.cmd` sucht die EXE im Unterordner `BizTalkSuspendedInstancesCheck`.
|
||||||
|
3. Die EXE liest BizTalk per WMI.
|
||||||
|
4. Die EXE schreibt Checkmk-Local-Check-Zeilen nach STDOUT.
|
||||||
|
5. Der Checkmk Agent liefert diese Zeilen an den Checkmk Server.
|
||||||
|
6. Nach Service Discovery erscheinen die Services in Checkmk.
|
||||||
|
|
||||||
|
Das Skript muss also nicht mehr neu erstellt werden. Es liegt im Repository unter `deployment\checkmk\biztalk_suspended_instances.cmd` und wird beim Paketieren in den Deployment-Ordner kopiert. Falls die EXE fehlt, gibt die `.cmd` eine gueltige `UNKNOWN`-Zeile aus, damit ein unvollstaendiges Deployment in Checkmk sichtbar wird.
|
||||||
|
|
||||||
|
## Deployment auf BizTalk-Server
|
||||||
|
|
||||||
|
1. Release bauen oder aus Gitea-Artefakt laden.
|
||||||
|
2. Inhalt von `artifacts\BizTalkSuspendedInstancesCheck-deploy` nach `%ProgramData%\checkmk\agent\local` kopieren.
|
||||||
|
3. Auf dem BizTalk-Server als lokaler Administrator testen:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
"%ProgramData%\checkmk\agent\local\biztalk_suspended_instances.cmd"
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Agentenausgabe pruefen:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
"C:\Program Files (x86)\checkmk\service\cmk-agent-ctl.exe" dump
|
||||||
|
```
|
||||||
|
|
||||||
|
5. In Checkmk Service Discovery fuer den Host ausfuehren und den Service aufnehmen.
|
||||||
|
|
||||||
|
## Konfiguration
|
||||||
|
|
||||||
|
Die Datei `BizTalkSuspendedInstancesCheck.exe.config` kann neben der EXE angepasst werden.
|
||||||
|
|
||||||
|
Wichtige Werte:
|
||||||
|
|
||||||
|
- `ServiceName`: Checkmk-Service-Name
|
||||||
|
- `HostInstancesServiceName`: Service-Name fuer Host-Instanzen
|
||||||
|
- `RuntimeArtifactsServiceName`: Service-Name fuer Artefaktstatus
|
||||||
|
- `Server`: leer oder `.` fuer lokalen Server
|
||||||
|
- `WarnResumableThreshold`: Default `1`
|
||||||
|
- `CritNonResumableThreshold`: Default `1`
|
||||||
|
- `QueryTimeoutSeconds`: Default `25`
|
||||||
|
- `MaxApplicationDetails`: Anzahl Anwendungen in der Summary, Default `12`
|
||||||
|
- `MaxRuntimeApplicationDetails`: Anzahl auffaelliger Anwendungen in der Runtime-Summary, Default `10`
|
||||||
|
- `AlertOnStoppedHostInstances`: Host-Instanzen mit Status `Stopped`/unbekannt alarmieren, Default `true`
|
||||||
|
- `AlertOnArtifactRuntimeIssues`: deaktivierte/inaktive Artefakte als `WARN` werten, Default `false`
|
||||||
|
- `EmitPerApplicationServices`: optional zusaetzliche Services pro Anwendung fuer Suspensions, Default `false`
|
||||||
|
|
||||||
|
## Gitea
|
||||||
|
|
||||||
|
Das Verzeichnis ist als eigenes Git-Repository mit Branch `main` angelegt. Fuer ein neues Gitea-Repository:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
git remote add origin <gitea-url>
|
||||||
|
git add .
|
||||||
|
git commit -m "Add BizTalk Checkmk local check"
|
||||||
|
git push -u origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quellen
|
||||||
|
|
||||||
|
- Checkmk Local Checks: https://docs.checkmk.com/latest/de/localchecks.html
|
||||||
|
- Microsoft BizTalk `MSBTS_ServiceInstance`: https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-serviceinstance-wmi
|
||||||
|
- Microsoft BizTalk `ServiceStatus`: https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-serviceinstance-servicestatus-property-wmi
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
@echo off
|
||||||
|
setlocal
|
||||||
|
|
||||||
|
set "SERVICE_NAME=BizTalk Suspended Instances"
|
||||||
|
set "CHECK_DIR=%~dp0BizTalkSuspendedInstancesCheck"
|
||||||
|
set "CHECK_EXE=%CHECK_DIR%\BizTalkSuspendedInstancesCheck.exe"
|
||||||
|
|
||||||
|
if not exist "%CHECK_EXE%" (
|
||||||
|
set "CHECK_EXE=%~dp0BizTalkSuspendedInstancesCheck.exe"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not exist "%CHECK_EXE%" (
|
||||||
|
echo 3 "%SERVICE_NAME%" - Check executable not found. Expected "%CHECK_DIR%\BizTalkSuspendedInstancesCheck.exe".
|
||||||
|
exit /b 0
|
||||||
|
)
|
||||||
|
|
||||||
|
"%CHECK_EXE%" %*
|
||||||
|
exit /b 0
|
||||||
|
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# Checkmk Services und Metriken
|
||||||
|
|
||||||
|
Dieser Check erzeugt standardmaessig drei stabile Local-Check-Services.
|
||||||
|
|
||||||
|
## Aufruf
|
||||||
|
|
||||||
|
Checkmk startet unter Windows lokale Checks aus:
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
%ProgramData%\checkmk\agent\local
|
||||||
|
```
|
||||||
|
|
||||||
|
Der mitgelieferte Wrapper ist:
|
||||||
|
|
||||||
|
```text
|
||||||
|
%ProgramData%\checkmk\agent\local\biztalk_suspended_instances.cmd
|
||||||
|
```
|
||||||
|
|
||||||
|
Er ruft die EXE im Unterordner auf:
|
||||||
|
|
||||||
|
```text
|
||||||
|
%ProgramData%\checkmk\agent\local\BizTalkSuspendedInstancesCheck\BizTalkSuspendedInstancesCheck.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
Das Skript liegt im Repository unter `deployment\checkmk\biztalk_suspended_instances.cmd` und wird durch `scripts\package-release.cmd` in den Deployment-Ordner kopiert.
|
||||||
|
|
||||||
|
## BizTalk Suspended Instances
|
||||||
|
|
||||||
|
Zweck: suspendierte Service-Instanzen erkennen und betroffene Anwendungen direkt in der Summary anzeigen.
|
||||||
|
|
||||||
|
Status:
|
||||||
|
|
||||||
|
- `OK`: keine Suspensions
|
||||||
|
- `WARN`: resumable Suspensions ab Schwellwert
|
||||||
|
- `CRIT`: non-resumable Suspensions ab Schwellwert
|
||||||
|
- `UNKNOWN`: Check- oder WMI-Fehler
|
||||||
|
|
||||||
|
Metriken:
|
||||||
|
|
||||||
|
- `biztalk_suspended_total`
|
||||||
|
- `biztalk_suspended_resumable`
|
||||||
|
- `biztalk_suspended_nonresumable`
|
||||||
|
|
||||||
|
## BizTalk Host Instances
|
||||||
|
|
||||||
|
Zweck: gestartete, gestoppte, pending und unbekannte Host-Instanzen sichtbar machen.
|
||||||
|
|
||||||
|
Status:
|
||||||
|
|
||||||
|
- `OK`: alle Host-Instanzen gestartet
|
||||||
|
- `WARN`: mindestens eine Host-Instanz pending
|
||||||
|
- `CRIT`: mindestens eine Host-Instanz stopped oder unknown
|
||||||
|
- `UNKNOWN`: WMI liefert keine Host-Instanzen
|
||||||
|
|
||||||
|
Metriken:
|
||||||
|
|
||||||
|
- `biztalk_host_instances_total`
|
||||||
|
- `biztalk_host_instances_started`
|
||||||
|
- `biztalk_host_instances_stopped`
|
||||||
|
- `biztalk_host_instances_pending`
|
||||||
|
- `biztalk_host_instances_unknown`
|
||||||
|
|
||||||
|
## BizTalk Runtime Artifacts
|
||||||
|
|
||||||
|
Zweck: aggregierte Betriebsuebersicht fuer Receive Locations, Send Ports und Orchestrations.
|
||||||
|
|
||||||
|
Status:
|
||||||
|
|
||||||
|
- `OK`: Statuswerte lesbar
|
||||||
|
- `CRIT`: unbekannte Send-Port- oder Orchestration-Statuswerte
|
||||||
|
- Optional `WARN`: wenn `AlertOnArtifactRuntimeIssues=true` und deaktivierte/inaktive Artefakte vorhanden sind
|
||||||
|
|
||||||
|
Metriken:
|
||||||
|
|
||||||
|
- `biztalk_applications`
|
||||||
|
- `biztalk_receive_locations`
|
||||||
|
- `biztalk_receive_locations_disabled`
|
||||||
|
- `biztalk_send_ports`
|
||||||
|
- `biztalk_send_ports_started`
|
||||||
|
- `biztalk_send_ports_stopped`
|
||||||
|
- `biztalk_send_ports_bound`
|
||||||
|
- `biztalk_send_ports_unknown`
|
||||||
|
- `biztalk_orchestrations`
|
||||||
|
- `biztalk_orchestrations_started`
|
||||||
|
- `biztalk_orchestrations_stopped`
|
||||||
|
- `biztalk_orchestrations_bound`
|
||||||
|
- `biztalk_orchestrations_unbound`
|
||||||
|
- `biztalk_orchestrations_unknown`
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
@echo off
|
||||||
|
setlocal
|
||||||
|
|
||||||
|
set "ROOT=%~dp0.."
|
||||||
|
set "SOLUTION=%ROOT%\BizTalkSuspendedInstancesCheck.sln"
|
||||||
|
|
||||||
|
if not defined MSBUILD (
|
||||||
|
set "VSWHERE=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe"
|
||||||
|
if exist "%VSWHERE%" (
|
||||||
|
for /f "usebackq tokens=*" %%i in (`"%VSWHERE%" -latest -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe`) do (
|
||||||
|
set "MSBUILD=%%i"
|
||||||
|
goto :found_msbuild
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
:found_msbuild
|
||||||
|
if not defined MSBUILD (
|
||||||
|
for /f "tokens=*" %%i in ('where msbuild 2^>nul') do (
|
||||||
|
set "MSBUILD=%%i"
|
||||||
|
goto :found_msbuild_path
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
:found_msbuild_path
|
||||||
|
if not defined MSBUILD (
|
||||||
|
echo MSBuild was not found. Install Visual Studio Build Tools and the .NET Framework 4.6.1 Developer Pack.
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo Using MSBuild: %MSBUILD%
|
||||||
|
"%MSBUILD%" "%SOLUTION%" /m /restore /p:Configuration=Release /p:Platform="Any CPU"
|
||||||
|
exit /b %ERRORLEVEL%
|
||||||
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
@echo off
|
||||||
|
setlocal
|
||||||
|
|
||||||
|
set "ROOT=%~dp0.."
|
||||||
|
set "PROJECT_BIN=%ROOT%\src\BizTalkSuspendedInstancesCheck\bin\Release"
|
||||||
|
set "ARTIFACTS=%ROOT%\artifacts"
|
||||||
|
set "DEPLOY=%ARTIFACTS%\BizTalkSuspendedInstancesCheck-deploy"
|
||||||
|
set "APPDIR=%DEPLOY%\BizTalkSuspendedInstancesCheck"
|
||||||
|
|
||||||
|
call "%ROOT%\scripts\build-release.cmd"
|
||||||
|
if errorlevel 1 exit /b %ERRORLEVEL%
|
||||||
|
|
||||||
|
if exist "%DEPLOY%" rmdir /s /q "%DEPLOY%"
|
||||||
|
mkdir "%APPDIR%"
|
||||||
|
|
||||||
|
copy "%ROOT%\deployment\checkmk\biztalk_suspended_instances.cmd" "%DEPLOY%\" >nul
|
||||||
|
copy "%PROJECT_BIN%\BizTalkSuspendedInstancesCheck.exe" "%APPDIR%\" >nul
|
||||||
|
copy "%PROJECT_BIN%\BizTalkSuspendedInstancesCheck.exe.config" "%APPDIR%\" >nul
|
||||||
|
if exist "%PROJECT_BIN%\BizTalkSuspendedInstancesCheck.pdb" copy "%PROJECT_BIN%\BizTalkSuspendedInstancesCheck.pdb" "%APPDIR%\" >nul
|
||||||
|
copy "%ROOT%\README.md" "%DEPLOY%\" >nul
|
||||||
|
copy "%ROOT%\Dokumentation.md" "%DEPLOY%\" >nul
|
||||||
|
if exist "%ROOT%\docs" xcopy "%ROOT%\docs" "%DEPLOY%\docs\" /e /i /y >nul
|
||||||
|
|
||||||
|
echo Deployment folder created: %DEPLOY%
|
||||||
|
exit /b 0
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<configuration>
|
||||||
|
<appSettings>
|
||||||
|
<add key="ServiceName" value="BizTalk Suspended Instances" />
|
||||||
|
<add key="HostInstancesServiceName" value="BizTalk Host Instances" />
|
||||||
|
<add key="RuntimeArtifactsServiceName" value="BizTalk Runtime Artifacts" />
|
||||||
|
<add key="Server" value="." />
|
||||||
|
<add key="WarnResumableThreshold" value="1" />
|
||||||
|
<add key="CritNonResumableThreshold" value="1" />
|
||||||
|
<add key="QueryTimeoutSeconds" value="25" />
|
||||||
|
<add key="MaxApplicationDetails" value="12" />
|
||||||
|
<add key="MaxRuntimeApplicationDetails" value="10" />
|
||||||
|
<add key="AlertOnStoppedHostInstances" value="true" />
|
||||||
|
<add key="AlertOnArtifactRuntimeIssues" value="false" />
|
||||||
|
<add key="EmitPerApplicationServices" value="false" />
|
||||||
|
</appSettings>
|
||||||
|
<startup>
|
||||||
|
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
|
||||||
|
</startup>
|
||||||
|
</configuration>
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Management;
|
||||||
|
|
||||||
|
namespace BizTalkSuspendedInstancesCheck
|
||||||
|
{
|
||||||
|
internal sealed class ApplicationIndex
|
||||||
|
{
|
||||||
|
private const string UnknownApplication = "(Unknown Application)";
|
||||||
|
private readonly Dictionary<string, string> _byArtifactName = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
public void Add(ManagementBaseObject item)
|
||||||
|
{
|
||||||
|
var application = FirstNonEmpty(
|
||||||
|
SafeGetString(item, "ApplicationName"),
|
||||||
|
SafeGetString(item, "Application"),
|
||||||
|
SafeGetString(item, "BizTalkApplication"));
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(application))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddName(SafeGetString(item, "Name"), application);
|
||||||
|
AddName(SafeGetString(item, "ServiceName"), application);
|
||||||
|
AddName(SafeGetString(item, "ReceivePortName"), application);
|
||||||
|
AddName(SafeGetString(item, "SendPortName"), application);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Resolve(ManagementBaseObject serviceInstance)
|
||||||
|
{
|
||||||
|
var direct = FirstNonEmpty(
|
||||||
|
SafeGetString(serviceInstance, "ApplicationName"),
|
||||||
|
SafeGetString(serviceInstance, "Application"),
|
||||||
|
SafeGetString(serviceInstance, "BizTalkApplication"));
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(direct))
|
||||||
|
{
|
||||||
|
return direct;
|
||||||
|
}
|
||||||
|
|
||||||
|
var serviceName = SafeGetString(serviceInstance, "ServiceName");
|
||||||
|
var name = SafeGetString(serviceInstance, "Name");
|
||||||
|
var serviceClass = SafeGetString(serviceInstance, "ServiceClass");
|
||||||
|
|
||||||
|
return ResolveByName(serviceName)
|
||||||
|
?? ResolveByName(name)
|
||||||
|
?? ResolveByName(LastSegment(serviceName))
|
||||||
|
?? ResolveByName(LastSegment(name))
|
||||||
|
?? ResolveByName(serviceClass)
|
||||||
|
?? UnknownApplication;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddName(string name, string application)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalized = name.Trim();
|
||||||
|
if (!_byArtifactName.ContainsKey(normalized))
|
||||||
|
{
|
||||||
|
_byArtifactName.Add(normalized, application.Trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastSegment = LastSegment(normalized);
|
||||||
|
if (!string.IsNullOrWhiteSpace(lastSegment) && !_byArtifactName.ContainsKey(lastSegment))
|
||||||
|
{
|
||||||
|
_byArtifactName.Add(lastSegment, application.Trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ResolveByName(string name)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
string application;
|
||||||
|
return _byArtifactName.TryGetValue(name.Trim(), out application) ? application : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string LastSegment(string value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var trimmed = value.Trim();
|
||||||
|
var index = trimmed.LastIndexOf('.');
|
||||||
|
if (index < 0 || index + 1 >= trimmed.Length)
|
||||||
|
{
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return trimmed.Substring(index + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FirstNonEmpty(params string[] values)
|
||||||
|
{
|
||||||
|
foreach (var value in values)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
return value.Trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string SafeGetString(ManagementBaseObject item, string propertyName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (PropertyData property in item.Properties)
|
||||||
|
{
|
||||||
|
if (string.Equals(property.Name, propertyName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var value = item[property.Name];
|
||||||
|
return value == null ? string.Empty : Convert.ToString(value, System.Globalization.CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ManagementException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.Common.props" Condition="Exists('$(MSBuildToolsPath)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{ED7AF215-F12B-4E2E-811B-693A537C9493}</ProjectGuid>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<RootNamespace>BizTalkSuspendedInstancesCheck</RootNamespace>
|
||||||
|
<AssemblyName>BizTalkSuspendedInstancesCheck</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.6.1</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>
|
||||||
|
<Prefer32Bit>false</Prefer32Bit>
|
||||||
|
</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>
|
||||||
|
<Prefer32Bit>false</Prefer32Bit>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Configuration" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.Management" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="ApplicationIndex.cs" />
|
||||||
|
<Compile Include="CheckmkFormatter.cs" />
|
||||||
|
<Compile Include="Models.cs" />
|
||||||
|
<Compile Include="MonitoringOptions.cs" />
|
||||||
|
<Compile Include="Program.cs" />
|
||||||
|
<Compile Include="WmiBizTalkProbe.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="App.config" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
</Project>
|
||||||
|
|
||||||
@@ -0,0 +1,449 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace BizTalkSuspendedInstancesCheck
|
||||||
|
{
|
||||||
|
internal sealed class CheckmkFormatter
|
||||||
|
{
|
||||||
|
private const int HostStopped = 1;
|
||||||
|
private const int HostStartPending = 2;
|
||||||
|
private const int HostStopPending = 3;
|
||||||
|
private const int HostStarted = 4;
|
||||||
|
private readonly MonitoringOptions _options;
|
||||||
|
|
||||||
|
public CheckmkFormatter(MonitoringOptions options)
|
||||||
|
{
|
||||||
|
_options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> Format(ProbeResult result)
|
||||||
|
{
|
||||||
|
yield return FormatAggregate(result);
|
||||||
|
yield return FormatHostInstances(result);
|
||||||
|
yield return FormatRuntimeArtifacts(result);
|
||||||
|
|
||||||
|
if (_options.EmitPerApplicationServices)
|
||||||
|
{
|
||||||
|
foreach (var line in FormatApplications(result))
|
||||||
|
{
|
||||||
|
yield return line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string FormatUnknown(string message)
|
||||||
|
{
|
||||||
|
return BuildLine(CheckState.Unknown, _options.ServiceName, "-", SanitizeDetail(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string FormatSelfTest()
|
||||||
|
{
|
||||||
|
return BuildLine(CheckState.Ok, _options.ServiceName, "-", "Self test OK. Checkmk local check output format is valid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private string FormatHostInstances(ProbeResult result)
|
||||||
|
{
|
||||||
|
var hosts = result.HostInstances;
|
||||||
|
var total = hosts.Count;
|
||||||
|
var started = hosts.Count(x => x.ServiceState == HostStarted);
|
||||||
|
var stopped = hosts.Count(x => x.ServiceState == HostStopped);
|
||||||
|
var pending = hosts.Count(x => x.ServiceState == HostStartPending || x.ServiceState == HostStopPending);
|
||||||
|
var unknown = total - started - stopped - pending;
|
||||||
|
var state = DetermineHostState(total, stopped, pending, unknown);
|
||||||
|
var metrics = string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"biztalk_host_instances_total={0};;;0|biztalk_host_instances_started={1};;;0|biztalk_host_instances_stopped={2};;;0|biztalk_host_instances_pending={3};;;0|biztalk_host_instances_unknown={4};;;0",
|
||||||
|
total,
|
||||||
|
started,
|
||||||
|
stopped,
|
||||||
|
pending,
|
||||||
|
unknown);
|
||||||
|
var detail = BuildHostDetail(hosts, total, started, stopped, pending, unknown);
|
||||||
|
|
||||||
|
return BuildLine(state, _options.HostInstancesServiceName, metrics, AppendDiagnostics(detail, result.Diagnostics));
|
||||||
|
}
|
||||||
|
|
||||||
|
private string FormatRuntimeArtifacts(ProbeResult result)
|
||||||
|
{
|
||||||
|
var applications = result.Applications;
|
||||||
|
var receiveLocations = applications.Sum(x => x.ReceiveLocationTotal);
|
||||||
|
var receiveLocationsDisabled = applications.Sum(x => x.ReceiveLocationDisabled);
|
||||||
|
var sendPorts = applications.Sum(x => x.SendPortTotal);
|
||||||
|
var sendPortsStarted = applications.Sum(x => x.SendPortStarted);
|
||||||
|
var sendPortsStopped = applications.Sum(x => x.SendPortStopped);
|
||||||
|
var sendPortsBound = applications.Sum(x => x.SendPortBound);
|
||||||
|
var sendPortsUnknown = applications.Sum(x => x.SendPortUnknown);
|
||||||
|
var orchestrations = applications.Sum(x => x.OrchestrationTotal);
|
||||||
|
var orchestrationsStarted = applications.Sum(x => x.OrchestrationStarted);
|
||||||
|
var orchestrationsStopped = applications.Sum(x => x.OrchestrationStopped);
|
||||||
|
var orchestrationsBound = applications.Sum(x => x.OrchestrationBound);
|
||||||
|
var orchestrationsUnbound = applications.Sum(x => x.OrchestrationUnbound);
|
||||||
|
var orchestrationsUnknown = applications.Sum(x => x.OrchestrationUnknown);
|
||||||
|
var state = DetermineRuntimeArtifactState(
|
||||||
|
receiveLocationsDisabled,
|
||||||
|
sendPortsStopped + sendPortsBound,
|
||||||
|
sendPortsUnknown,
|
||||||
|
orchestrationsStopped + orchestrationsBound + orchestrationsUnbound,
|
||||||
|
orchestrationsUnknown);
|
||||||
|
var metrics = string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"biztalk_applications={0};;;0|biztalk_receive_locations={1};;;0|biztalk_receive_locations_disabled={2};;;0|biztalk_send_ports={3};;;0|biztalk_send_ports_started={4};;;0|biztalk_send_ports_stopped={5};;;0|biztalk_send_ports_bound={6};;;0|biztalk_send_ports_unknown={7};;;0|biztalk_orchestrations={8};;;0|biztalk_orchestrations_started={9};;;0|biztalk_orchestrations_stopped={10};;;0|biztalk_orchestrations_bound={11};;;0|biztalk_orchestrations_unbound={12};;;0|biztalk_orchestrations_unknown={13};;;0",
|
||||||
|
applications.Count,
|
||||||
|
receiveLocations,
|
||||||
|
receiveLocationsDisabled,
|
||||||
|
sendPorts,
|
||||||
|
sendPortsStarted,
|
||||||
|
sendPortsStopped,
|
||||||
|
sendPortsBound,
|
||||||
|
sendPortsUnknown,
|
||||||
|
orchestrations,
|
||||||
|
orchestrationsStarted,
|
||||||
|
orchestrationsStopped,
|
||||||
|
orchestrationsBound,
|
||||||
|
orchestrationsUnbound,
|
||||||
|
orchestrationsUnknown);
|
||||||
|
var detail = BuildRuntimeArtifactDetail(
|
||||||
|
applications,
|
||||||
|
receiveLocations,
|
||||||
|
receiveLocationsDisabled,
|
||||||
|
sendPorts,
|
||||||
|
sendPortsStarted,
|
||||||
|
sendPortsStopped,
|
||||||
|
sendPortsBound,
|
||||||
|
sendPortsUnknown,
|
||||||
|
orchestrations,
|
||||||
|
orchestrationsStarted,
|
||||||
|
orchestrationsStopped,
|
||||||
|
orchestrationsBound,
|
||||||
|
orchestrationsUnbound,
|
||||||
|
orchestrationsUnknown);
|
||||||
|
|
||||||
|
return BuildLine(state, _options.RuntimeArtifactsServiceName, metrics, AppendDiagnostics(detail, result.Diagnostics));
|
||||||
|
}
|
||||||
|
|
||||||
|
private string FormatAggregate(ProbeResult result)
|
||||||
|
{
|
||||||
|
var instances = result.SuspendedInstances;
|
||||||
|
var resumable = instances.Count(x => x.Kind == SuspendedKind.Resumable);
|
||||||
|
var nonresumable = instances.Count(x => x.Kind == SuspendedKind.NonResumable);
|
||||||
|
var state = DetermineState(resumable, nonresumable);
|
||||||
|
var metrics = BuildMetrics(instances.Count, resumable, nonresumable);
|
||||||
|
var detail = BuildAggregateDetail(result, instances.Count, resumable, nonresumable);
|
||||||
|
|
||||||
|
return BuildLine(state, _options.ServiceName, metrics, detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<string> FormatApplications(ProbeResult result)
|
||||||
|
{
|
||||||
|
foreach (var group in result.SuspendedInstances.GroupBy(x => x.ApplicationName).OrderBy(x => x.Key))
|
||||||
|
{
|
||||||
|
var resumable = group.Count(x => x.Kind == SuspendedKind.Resumable);
|
||||||
|
var nonresumable = group.Count(x => x.Kind == SuspendedKind.NonResumable);
|
||||||
|
var state = DetermineState(resumable, nonresumable);
|
||||||
|
var serviceName = _options.ServiceName + " " + group.Key;
|
||||||
|
var metrics = BuildMetrics(group.Count(), resumable, nonresumable);
|
||||||
|
var detail = group.Count() + " suspended instance(s): resumable=" + resumable + ", nonresumable=" + nonresumable;
|
||||||
|
yield return BuildLine(state, serviceName, metrics, detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CheckState DetermineState(int resumable, int nonresumable)
|
||||||
|
{
|
||||||
|
if (_options.CritNonResumableThreshold > 0 && nonresumable >= _options.CritNonResumableThreshold)
|
||||||
|
{
|
||||||
|
return CheckState.Critical;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_options.WarnResumableThreshold > 0 && resumable >= _options.WarnResumableThreshold)
|
||||||
|
{
|
||||||
|
return CheckState.Warn;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CheckState.Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CheckState DetermineHostState(int total, int stopped, int pending, int unknown)
|
||||||
|
{
|
||||||
|
if (total == 0)
|
||||||
|
{
|
||||||
|
return CheckState.Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_options.AlertOnStoppedHostInstances)
|
||||||
|
{
|
||||||
|
return unknown > 0 ? CheckState.Warn : CheckState.Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stopped > 0 || unknown > 0)
|
||||||
|
{
|
||||||
|
return CheckState.Critical;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pending > 0)
|
||||||
|
{
|
||||||
|
return CheckState.Warn;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CheckState.Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CheckState DetermineRuntimeArtifactState(int disabledReceiveLocations, int inactiveSendPorts, int unknownSendPorts, int inactiveOrchestrations, int unknownOrchestrations)
|
||||||
|
{
|
||||||
|
if (unknownSendPorts > 0 || unknownOrchestrations > 0)
|
||||||
|
{
|
||||||
|
return CheckState.Critical;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_options.AlertOnArtifactRuntimeIssues && (disabledReceiveLocations > 0 || inactiveSendPorts > 0 || inactiveOrchestrations > 0))
|
||||||
|
{
|
||||||
|
return CheckState.Warn;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CheckState.Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildMetrics(int total, int resumable, int nonresumable)
|
||||||
|
{
|
||||||
|
return 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildAggregateDetail(ProbeResult result, int total, int resumable, int nonresumable)
|
||||||
|
{
|
||||||
|
if (total == 0)
|
||||||
|
{
|
||||||
|
return AppendDiagnostics("No suspended BizTalk service instances found.", result.Diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
builder.Append(total)
|
||||||
|
.Append(" suspended BizTalk service instance(s): resumable=")
|
||||||
|
.Append(resumable)
|
||||||
|
.Append(", nonresumable=")
|
||||||
|
.Append(nonresumable)
|
||||||
|
.Append(", applications=")
|
||||||
|
.Append(result.SuspendedInstances.Select(x => x.ApplicationName).Distinct(StringComparer.OrdinalIgnoreCase).Count())
|
||||||
|
.Append(". ");
|
||||||
|
|
||||||
|
builder.Append(BuildApplicationSummary(result.SuspendedInstances));
|
||||||
|
AppendInstanceHints(builder, result.SuspendedInstances);
|
||||||
|
|
||||||
|
return AppendDiagnostics(builder.ToString(), result.Diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildHostDetail(IEnumerable<HostInstanceState> hosts, int total, int started, int stopped, int pending, int unknown)
|
||||||
|
{
|
||||||
|
if (total == 0)
|
||||||
|
{
|
||||||
|
return "No BizTalk host instances returned by WMI.";
|
||||||
|
}
|
||||||
|
|
||||||
|
var detail = "total=" + total
|
||||||
|
+ ", started=" + started
|
||||||
|
+ ", stopped=" + stopped
|
||||||
|
+ ", pending=" + pending
|
||||||
|
+ ", unknown=" + unknown;
|
||||||
|
|
||||||
|
var notStarted = hosts
|
||||||
|
.Where(x => x.ServiceState != HostStarted)
|
||||||
|
.OrderBy(x => x.RunningServer)
|
||||||
|
.ThenBy(x => x.HostName)
|
||||||
|
.ThenBy(x => x.InstanceName)
|
||||||
|
.Take(12)
|
||||||
|
.Select(x => EmptyAsUnknown(x.HostName) + "@" + EmptyAsUnknown(x.RunningServer) + "=" + FormatHostState(x.ServiceState));
|
||||||
|
|
||||||
|
var notStartedText = string.Join("; ", notStarted);
|
||||||
|
if (!string.IsNullOrWhiteSpace(notStartedText))
|
||||||
|
{
|
||||||
|
detail += " | Not started: " + notStartedText;
|
||||||
|
}
|
||||||
|
|
||||||
|
return detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildRuntimeArtifactDetail(
|
||||||
|
IEnumerable<ApplicationRuntimeState> applications,
|
||||||
|
int receiveLocations,
|
||||||
|
int receiveLocationsDisabled,
|
||||||
|
int sendPorts,
|
||||||
|
int sendPortsStarted,
|
||||||
|
int sendPortsStopped,
|
||||||
|
int sendPortsBound,
|
||||||
|
int sendPortsUnknown,
|
||||||
|
int orchestrations,
|
||||||
|
int orchestrationsStarted,
|
||||||
|
int orchestrationsStopped,
|
||||||
|
int orchestrationsBound,
|
||||||
|
int orchestrationsUnbound,
|
||||||
|
int orchestrationsUnknown)
|
||||||
|
{
|
||||||
|
var appList = applications.ToList();
|
||||||
|
var detail = "applications=" + appList.Count
|
||||||
|
+ " | ReceiveLocations total=" + receiveLocations + ", disabled=" + receiveLocationsDisabled
|
||||||
|
+ " | SendPorts total=" + sendPorts + ", started=" + sendPortsStarted + ", stopped=" + sendPortsStopped + ", bound=" + sendPortsBound + ", unknown=" + sendPortsUnknown
|
||||||
|
+ " | Orchestrations total=" + orchestrations + ", started=" + orchestrationsStarted + ", stopped=" + orchestrationsStopped + ", bound=" + orchestrationsBound + ", unbound=" + orchestrationsUnbound + ", unknown=" + orchestrationsUnknown;
|
||||||
|
|
||||||
|
var notable = appList
|
||||||
|
.Where(HasRuntimeDifference)
|
||||||
|
.OrderByDescending(x => x.SendPortUnknown + x.OrchestrationUnknown)
|
||||||
|
.ThenByDescending(x => x.ReceiveLocationDisabled + x.SendPortStopped + x.SendPortBound + x.OrchestrationStopped + x.OrchestrationBound + x.OrchestrationUnbound)
|
||||||
|
.ThenBy(x => x.ApplicationName)
|
||||||
|
.Take(_options.MaxRuntimeApplicationDetails)
|
||||||
|
.Select(FormatApplicationRuntime);
|
||||||
|
|
||||||
|
var notableText = string.Join("; ", notable);
|
||||||
|
if (!string.IsNullOrWhiteSpace(notableText))
|
||||||
|
{
|
||||||
|
detail += " | Notable apps: " + notableText;
|
||||||
|
}
|
||||||
|
|
||||||
|
return detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildApplicationSummary(IEnumerable<SuspendedInstance> instances)
|
||||||
|
{
|
||||||
|
var groups = instances
|
||||||
|
.GroupBy(x => x.ApplicationName)
|
||||||
|
.OrderByDescending(x => x.Count(y => y.Kind == SuspendedKind.NonResumable))
|
||||||
|
.ThenByDescending(x => x.Count())
|
||||||
|
.ThenBy(x => x.Key)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var shown = groups.Take(_options.MaxApplicationDetails)
|
||||||
|
.Select(group => group.Key + " R=" + group.Count(x => x.Kind == SuspendedKind.Resumable) + " NR=" + group.Count(x => x.Kind == SuspendedKind.NonResumable));
|
||||||
|
|
||||||
|
var summary = string.Join("; ", shown);
|
||||||
|
if (groups.Count > _options.MaxApplicationDetails)
|
||||||
|
{
|
||||||
|
summary += "; +" + (groups.Count - _options.MaxApplicationDetails).ToString(CultureInfo.InvariantCulture) + " more application(s)";
|
||||||
|
}
|
||||||
|
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AppendInstanceHints(StringBuilder builder, IEnumerable<SuspendedInstance> instances)
|
||||||
|
{
|
||||||
|
var hints = instances
|
||||||
|
.OrderByDescending(x => x.Kind == SuspendedKind.NonResumable)
|
||||||
|
.ThenBy(x => x.ApplicationName)
|
||||||
|
.Take(5)
|
||||||
|
.Select(x => "host=" + EmptyAsUnknown(x.HostName) + ", service=" + EmptyAsUnknown(x.ServiceName) + ", id=" + EmptyAsUnknown(x.InstanceId));
|
||||||
|
|
||||||
|
builder.Append("\\nExamples: ").Append(string.Join(" | ", hints));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HasRuntimeDifference(ApplicationRuntimeState app)
|
||||||
|
{
|
||||||
|
return app.ReceiveLocationDisabled > 0
|
||||||
|
|| app.SendPortStopped > 0
|
||||||
|
|| app.SendPortBound > 0
|
||||||
|
|| app.SendPortUnknown > 0
|
||||||
|
|| app.OrchestrationStopped > 0
|
||||||
|
|| app.OrchestrationBound > 0
|
||||||
|
|| app.OrchestrationUnbound > 0
|
||||||
|
|| app.OrchestrationUnknown > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatApplicationRuntime(ApplicationRuntimeState app)
|
||||||
|
{
|
||||||
|
return app.ApplicationName
|
||||||
|
+ " RL disabled=" + app.ReceiveLocationDisabled
|
||||||
|
+ ", SP stopped/bound/unknown=" + app.SendPortStopped + "/" + app.SendPortBound + "/" + app.SendPortUnknown
|
||||||
|
+ ", Orch stopped/bound/unbound/unknown=" + app.OrchestrationStopped + "/" + app.OrchestrationBound + "/" + app.OrchestrationUnbound + "/" + app.OrchestrationUnknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatHostState(int state)
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case HostStarted: return "Started";
|
||||||
|
case HostStopped: return "Stopped";
|
||||||
|
case HostStartPending: return "StartPending";
|
||||||
|
case HostStopPending: return "StopPending";
|
||||||
|
default: return "Unknown " + state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string AppendDiagnostics(string detail, IEnumerable<string> diagnostics)
|
||||||
|
{
|
||||||
|
var diagnosticText = string.Join("; ", diagnostics.Where(x => !string.IsNullOrWhiteSpace(x)));
|
||||||
|
if (string.IsNullOrWhiteSpace(diagnosticText))
|
||||||
|
{
|
||||||
|
return detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
return detail + "\\nDiagnostics: " + diagnosticText;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildLine(CheckState state, string serviceName, string metrics, string detail)
|
||||||
|
{
|
||||||
|
return ((int)state).ToString(CultureInfo.InvariantCulture)
|
||||||
|
+ " \""
|
||||||
|
+ SanitizeServiceName(serviceName)
|
||||||
|
+ "\" "
|
||||||
|
+ (string.IsNullOrWhiteSpace(metrics) ? "-" : metrics)
|
||||||
|
+ " "
|
||||||
|
+ SanitizeDetail(detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string SanitizeServiceName(string value)
|
||||||
|
{
|
||||||
|
var sanitized = string.IsNullOrWhiteSpace(value) ? "BizTalk Suspended Instances" : value.Trim();
|
||||||
|
var invalid = new[] { ';', '~', '!', '$', '%', '^', '&', '*', '|', '\\', '\'', '"', '<', '>', '?', ',', '(', ')', '=', '`' };
|
||||||
|
foreach (var item in invalid)
|
||||||
|
{
|
||||||
|
sanitized = sanitized.Replace(item, ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
return CollapseSpaces(sanitized);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string SanitizeDetail(string value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
return "No details.";
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.Replace("\r\n", "\\n").Replace("\r", "\\n").Replace("\n", "\\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string EmptyAsUnknown(string value)
|
||||||
|
{
|
||||||
|
return string.IsNullOrWhiteSpace(value) ? "<unknown>" : value.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string CollapseSpaces(string value)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
var previousSpace = false;
|
||||||
|
foreach (var character in value)
|
||||||
|
{
|
||||||
|
if (char.IsWhiteSpace(character))
|
||||||
|
{
|
||||||
|
if (!previousSpace)
|
||||||
|
{
|
||||||
|
builder.Append(' ');
|
||||||
|
previousSpace = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Append(character);
|
||||||
|
previousSpace = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString().Trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace BizTalkSuspendedInstancesCheck
|
||||||
|
{
|
||||||
|
internal enum CheckState
|
||||||
|
{
|
||||||
|
Ok = 0,
|
||||||
|
Warn = 1,
|
||||||
|
Critical = 2,
|
||||||
|
Unknown = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum SuspendedKind
|
||||||
|
{
|
||||||
|
Resumable,
|
||||||
|
NonResumable
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ErrorId { get; set; }
|
||||||
|
|
||||||
|
public string ErrorCategory { get; set; }
|
||||||
|
|
||||||
|
public string ErrorDescription { get; set; }
|
||||||
|
|
||||||
|
public string SuspendTime { get; set; }
|
||||||
|
|
||||||
|
public int ServiceStatus { get; set; }
|
||||||
|
|
||||||
|
public SuspendedKind Kind { 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 ApplicationRuntimeState
|
||||||
|
{
|
||||||
|
public ApplicationRuntimeState()
|
||||||
|
{
|
||||||
|
ReceiveLocationTotal = 0;
|
||||||
|
SendPortTotal = 0;
|
||||||
|
OrchestrationTotal = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ProbeResult
|
||||||
|
{
|
||||||
|
public ProbeResult()
|
||||||
|
{
|
||||||
|
SuspendedInstances = new List<SuspendedInstance>();
|
||||||
|
HostInstances = new List<HostInstanceState>();
|
||||||
|
Applications = new List<ApplicationRuntimeState>();
|
||||||
|
Diagnostics = new List<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SuspendedInstance> SuspendedInstances { get; private set; }
|
||||||
|
|
||||||
|
public List<HostInstanceState> HostInstances { get; private set; }
|
||||||
|
|
||||||
|
public List<ApplicationRuntimeState> Applications { get; private set; }
|
||||||
|
|
||||||
|
public List<string> Diagnostics { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Configuration;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace BizTalkSuspendedInstancesCheck
|
||||||
|
{
|
||||||
|
internal sealed class MonitoringOptions
|
||||||
|
{
|
||||||
|
public MonitoringOptions()
|
||||||
|
{
|
||||||
|
ServiceName = "BizTalk Suspended Instances";
|
||||||
|
HostInstancesServiceName = "BizTalk Host Instances";
|
||||||
|
RuntimeArtifactsServiceName = "BizTalk Runtime Artifacts";
|
||||||
|
Server = ".";
|
||||||
|
WarnResumableThreshold = 1;
|
||||||
|
CritNonResumableThreshold = 1;
|
||||||
|
QueryTimeoutSeconds = 25;
|
||||||
|
MaxApplicationDetails = 12;
|
||||||
|
MaxRuntimeApplicationDetails = 10;
|
||||||
|
AlertOnStoppedHostInstances = true;
|
||||||
|
AlertOnArtifactRuntimeIssues = false;
|
||||||
|
EmitPerApplicationServices = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ServiceName { get; set; }
|
||||||
|
|
||||||
|
public string HostInstancesServiceName { get; set; }
|
||||||
|
|
||||||
|
public string RuntimeArtifactsServiceName { get; set; }
|
||||||
|
|
||||||
|
public string Server { get; set; }
|
||||||
|
|
||||||
|
public int WarnResumableThreshold { get; set; }
|
||||||
|
|
||||||
|
public int CritNonResumableThreshold { get; set; }
|
||||||
|
|
||||||
|
public int QueryTimeoutSeconds { get; set; }
|
||||||
|
|
||||||
|
public int MaxApplicationDetails { get; set; }
|
||||||
|
|
||||||
|
public int MaxRuntimeApplicationDetails { get; set; }
|
||||||
|
|
||||||
|
public bool AlertOnStoppedHostInstances { get; set; }
|
||||||
|
|
||||||
|
public bool AlertOnArtifactRuntimeIssues { get; set; }
|
||||||
|
|
||||||
|
public bool EmitPerApplicationServices { get; set; }
|
||||||
|
|
||||||
|
public bool SelfTest { get; set; }
|
||||||
|
|
||||||
|
public static MonitoringOptions Load(string[] args)
|
||||||
|
{
|
||||||
|
var options = new MonitoringOptions
|
||||||
|
{
|
||||||
|
ServiceName = ReadString("ServiceName", "BizTalk Suspended Instances"),
|
||||||
|
HostInstancesServiceName = ReadString("HostInstancesServiceName", "BizTalk Host Instances"),
|
||||||
|
RuntimeArtifactsServiceName = ReadString("RuntimeArtifactsServiceName", "BizTalk Runtime Artifacts"),
|
||||||
|
Server = ReadString("Server", "."),
|
||||||
|
WarnResumableThreshold = ReadInt("WarnResumableThreshold", 1, 0, 1000000),
|
||||||
|
CritNonResumableThreshold = ReadInt("CritNonResumableThreshold", 1, 0, 1000000),
|
||||||
|
QueryTimeoutSeconds = ReadInt("QueryTimeoutSeconds", 25, 3, 300),
|
||||||
|
MaxApplicationDetails = ReadInt("MaxApplicationDetails", 12, 1, 100),
|
||||||
|
MaxRuntimeApplicationDetails = ReadInt("MaxRuntimeApplicationDetails", 10, 1, 100),
|
||||||
|
AlertOnStoppedHostInstances = ReadBool("AlertOnStoppedHostInstances", true),
|
||||||
|
AlertOnArtifactRuntimeIssues = ReadBool("AlertOnArtifactRuntimeIssues", false),
|
||||||
|
EmitPerApplicationServices = ReadBool("EmitPerApplicationServices", false)
|
||||||
|
};
|
||||||
|
|
||||||
|
ApplyArguments(options, args ?? new string[0]);
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplyArguments(MonitoringOptions options, string[] args)
|
||||||
|
{
|
||||||
|
var map = new Dictionary<string, Action<string>>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
{ "--service-name", value => options.ServiceName = RequireValue("--service-name", value) },
|
||||||
|
{ "--host-service-name", value => options.HostInstancesServiceName = RequireValue("--host-service-name", value) },
|
||||||
|
{ "--runtime-service-name", value => options.RuntimeArtifactsServiceName = RequireValue("--runtime-service-name", value) },
|
||||||
|
{ "--server", value => options.Server = RequireValue("--server", value) },
|
||||||
|
{ "--warn-resumable", value => options.WarnResumableThreshold = ParseInt("--warn-resumable", value, 0, 1000000) },
|
||||||
|
{ "--crit-nonresumable", value => options.CritNonResumableThreshold = ParseInt("--crit-nonresumable", value, 0, 1000000) },
|
||||||
|
{ "--timeout-seconds", value => options.QueryTimeoutSeconds = ParseInt("--timeout-seconds", value, 3, 300) },
|
||||||
|
{ "--max-application-details", value => options.MaxApplicationDetails = ParseInt("--max-application-details", value, 1, 100) },
|
||||||
|
{ "--max-runtime-application-details", value => options.MaxRuntimeApplicationDetails = ParseInt("--max-runtime-application-details", value, 1, 100) }
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var index = 0; index < args.Length; index++)
|
||||||
|
{
|
||||||
|
var arg = args[index];
|
||||||
|
if (string.Equals(arg, "--self-test", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
options.SelfTest = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(arg, "--emit-per-application-services", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
options.EmitPerApplicationServices = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(arg, "--no-host-alerts", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
options.AlertOnStoppedHostInstances = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(arg, "--alert-on-artifact-runtime-issues", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
options.AlertOnArtifactRuntimeIssues = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Action<string> setter;
|
||||||
|
if (!map.TryGetValue(arg, out setter))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Unknown argument: " + arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index + 1 >= args.Length)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Missing value for argument: " + arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
setter(args[++index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ReadString(string key, string fallback)
|
||||||
|
{
|
||||||
|
var value = ConfigurationManager.AppSettings[key];
|
||||||
|
return string.IsNullOrWhiteSpace(value) ? fallback : value.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ReadInt(string key, int fallback, int min, int max)
|
||||||
|
{
|
||||||
|
return ParseInt(key, ConfigurationManager.AppSettings[key], min, max, fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ReadBool(string key, bool fallback)
|
||||||
|
{
|
||||||
|
var value = ConfigurationManager.AppSettings[key];
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parsed;
|
||||||
|
return bool.TryParse(value, out parsed) ? parsed : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ParseInt(string name, string value, int min, int max)
|
||||||
|
{
|
||||||
|
return ParseInt(name, value, min, max, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ParseInt(string name, string value, int min, int max, int? fallback)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
if (fallback.HasValue)
|
||||||
|
{
|
||||||
|
return fallback.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ArgumentException("Missing integer value for " + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
int parsed;
|
||||||
|
if (!int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out parsed))
|
||||||
|
{
|
||||||
|
if (fallback.HasValue)
|
||||||
|
{
|
||||||
|
return fallback.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ArgumentException("Invalid integer value for " + name + ": " + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsed < min || parsed > max)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(name, "Value must be between " + min + " and " + max + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string RequireValue(string name, string value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Missing value for " + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.Trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace BizTalkSuspendedInstancesCheck
|
||||||
|
{
|
||||||
|
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 CheckmkFormatter(options);
|
||||||
|
|
||||||
|
if (options.SelfTest)
|
||||||
|
{
|
||||||
|
Console.WriteLine(formatter.FormatSelfTest());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var probe = new WmiBizTalkProbe(options);
|
||||||
|
var result = probe.Query();
|
||||||
|
foreach (var line in formatter.Format(result))
|
||||||
|
{
|
||||||
|
Console.WriteLine(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var fallbackOptions = options ?? new MonitoringOptions();
|
||||||
|
var formatter = new CheckmkFormatter(fallbackOptions);
|
||||||
|
Console.WriteLine(formatter.FormatUnknown("BizTalk suspended instance check failed: " + ex.GetType().Name + ": " + ex.Message));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,262 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Management;
|
||||||
|
|
||||||
|
namespace BizTalkSuspendedInstancesCheck
|
||||||
|
{
|
||||||
|
internal sealed class WmiBizTalkProbe
|
||||||
|
{
|
||||||
|
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 const int SuspendedNonResumable = 32;
|
||||||
|
private const string NamespacePath = "root\\MicrosoftBizTalkServer";
|
||||||
|
private const string UnknownApplication = "(Unknown Application)";
|
||||||
|
private readonly MonitoringOptions _options;
|
||||||
|
|
||||||
|
public WmiBizTalkProbe(MonitoringOptions options)
|
||||||
|
{
|
||||||
|
_options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProbeResult Query()
|
||||||
|
{
|
||||||
|
var result = new ProbeResult();
|
||||||
|
var server = string.IsNullOrWhiteSpace(_options.Server) || _options.Server == "."
|
||||||
|
? Environment.MachineName
|
||||||
|
: _options.Server.Trim();
|
||||||
|
|
||||||
|
var scope = new ManagementScope("\\\\" + server + "\\" + NamespacePath);
|
||||||
|
scope.Connect();
|
||||||
|
|
||||||
|
var index = BuildApplicationIndex(scope, result);
|
||||||
|
QueryHostInstances(scope, result);
|
||||||
|
QueryApplicationRuntime(scope, result);
|
||||||
|
ForEachObject(scope, "SELECT * FROM MSBTS_ServiceInstance WHERE ServiceStatus = 4 OR ServiceStatus = 32", item =>
|
||||||
|
{
|
||||||
|
result.SuspendedInstances.Add(ToSuspendedInstance(item, index));
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApplicationIndex BuildApplicationIndex(ManagementScope scope, ProbeResult result)
|
||||||
|
{
|
||||||
|
var index = new ApplicationIndex();
|
||||||
|
AddArtifacts(scope, result, index, "MSBTS_SendPort");
|
||||||
|
AddArtifacts(scope, result, index, "MSBTS_ReceivePort");
|
||||||
|
AddArtifacts(scope, result, index, "MSBTS_ReceiveLocation");
|
||||||
|
AddArtifacts(scope, result, index, "MSBTS_Orchestration");
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddArtifacts(ManagementScope scope, ProbeResult result, ApplicationIndex index, string className)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ForEachObject(scope, "SELECT * FROM " + className, item =>
|
||||||
|
{
|
||||||
|
index.Add(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (ManagementException ex)
|
||||||
|
{
|
||||||
|
result.Diagnostics.Add("Application mapping skipped for " + className + ": " + ex.Message);
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException ex)
|
||||||
|
{
|
||||||
|
result.Diagnostics.Add("Application mapping skipped for " + className + ": " + ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void QueryHostInstances(ManagementScope scope, ProbeResult result)
|
||||||
|
{
|
||||||
|
ForEachObject(scope, "SELECT * FROM MSBTS_HostInstance", item =>
|
||||||
|
{
|
||||||
|
result.HostInstances.Add(new HostInstanceState
|
||||||
|
{
|
||||||
|
InstanceName = FirstNonEmpty(
|
||||||
|
ApplicationIndex.SafeGetString(item, "InstanceName"),
|
||||||
|
ApplicationIndex.SafeGetString(item, "Name")),
|
||||||
|
HostName = ApplicationIndex.SafeGetString(item, "HostName"),
|
||||||
|
RunningServer = ApplicationIndex.SafeGetString(item, "RunningServer"),
|
||||||
|
ServiceState = SafeGetInt32(item, "ServiceState", 0)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void QueryApplicationRuntime(ManagementScope scope, ProbeResult result)
|
||||||
|
{
|
||||||
|
var applications = new Dictionary<string, ApplicationRuntimeState>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
ForEachObject(scope, "SELECT * FROM MSBTS_ReceiveLocation", item =>
|
||||||
|
{
|
||||||
|
var app = GetApplication(applications, GetApplicationName(item));
|
||||||
|
app.ReceiveLocationTotal++;
|
||||||
|
if (SafeGetBoolean(item, "IsDisabled", false))
|
||||||
|
{
|
||||||
|
app.ReceiveLocationDisabled++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ForEachObject(scope, "SELECT * FROM MSBTS_SendPort", item =>
|
||||||
|
{
|
||||||
|
var app = GetApplication(applications, GetApplicationName(item));
|
||||||
|
var status = SafeGetInt32(item, "Status", 0);
|
||||||
|
app.SendPortTotal++;
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
case SendPortStarted:
|
||||||
|
app.SendPortStarted++;
|
||||||
|
break;
|
||||||
|
case SendPortStopped:
|
||||||
|
app.SendPortStopped++;
|
||||||
|
break;
|
||||||
|
case SendPortBound:
|
||||||
|
app.SendPortBound++;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
app.SendPortUnknown++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ForEachObject(scope, "SELECT * FROM MSBTS_Orchestration", item =>
|
||||||
|
{
|
||||||
|
var app = GetApplication(applications, GetApplicationName(item));
|
||||||
|
var status = SafeGetInt32(item, "OrchestrationStatus", 0);
|
||||||
|
app.OrchestrationTotal++;
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
case OrchestrationStarted:
|
||||||
|
app.OrchestrationStarted++;
|
||||||
|
break;
|
||||||
|
case OrchestrationStopped:
|
||||||
|
app.OrchestrationStopped++;
|
||||||
|
break;
|
||||||
|
case OrchestrationBound:
|
||||||
|
app.OrchestrationBound++;
|
||||||
|
break;
|
||||||
|
case OrchestrationUnbound:
|
||||||
|
app.OrchestrationUnbound++;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
app.OrchestrationUnknown++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
result.Applications.AddRange(applications.Values.OrderBy(x => x.ApplicationName));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ForEachObject(ManagementScope scope, string queryText, Action<ManagementObject> action)
|
||||||
|
{
|
||||||
|
var queryOptions = new EnumerationOptions
|
||||||
|
{
|
||||||
|
ReturnImmediately = true,
|
||||||
|
Rewindable = false,
|
||||||
|
Timeout = TimeSpan.FromSeconds(_options.QueryTimeoutSeconds),
|
||||||
|
Context = new ManagementNamedValueCollection()
|
||||||
|
};
|
||||||
|
|
||||||
|
queryOptions.Context.Add("IterationDelayMS", 10);
|
||||||
|
|
||||||
|
var query = new ObjectQuery(queryText);
|
||||||
|
using (var searcher = new ManagementObjectSearcher(scope, query, queryOptions))
|
||||||
|
using (var collection = searcher.Get())
|
||||||
|
{
|
||||||
|
foreach (ManagementObject item in collection)
|
||||||
|
{
|
||||||
|
using (item)
|
||||||
|
{
|
||||||
|
action(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SuspendedInstance ToSuspendedInstance(ManagementBaseObject item, ApplicationIndex index)
|
||||||
|
{
|
||||||
|
var status = SafeGetInt32(item, "ServiceStatus", 0);
|
||||||
|
return new SuspendedInstance
|
||||||
|
{
|
||||||
|
ApplicationName = index.Resolve(item),
|
||||||
|
ServiceName = ApplicationIndex.SafeGetString(item, "ServiceName"),
|
||||||
|
HostName = ApplicationIndex.SafeGetString(item, "HostName"),
|
||||||
|
ServiceClass = ApplicationIndex.SafeGetString(item, "ServiceClass"),
|
||||||
|
InstanceId = ApplicationIndex.SafeGetString(item, "InstanceID"),
|
||||||
|
ErrorId = ApplicationIndex.SafeGetString(item, "ErrorID"),
|
||||||
|
ErrorCategory = ApplicationIndex.SafeGetString(item, "ErrorCategory"),
|
||||||
|
ErrorDescription = ApplicationIndex.SafeGetString(item, "ErrorDescription"),
|
||||||
|
SuspendTime = ApplicationIndex.SafeGetString(item, "SuspendTime"),
|
||||||
|
ServiceStatus = status,
|
||||||
|
Kind = status == SuspendedNonResumable ? SuspendedKind.NonResumable : SuspendedKind.Resumable
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int SafeGetInt32(ManagementBaseObject item, string propertyName, int fallback)
|
||||||
|
{
|
||||||
|
var value = ApplicationIndex.SafeGetString(item, propertyName);
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
int parsed;
|
||||||
|
return int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out parsed) ? parsed : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool SafeGetBoolean(ManagementBaseObject item, string propertyName, bool fallback)
|
||||||
|
{
|
||||||
|
var value = ApplicationIndex.SafeGetString(item, propertyName);
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parsed;
|
||||||
|
return bool.TryParse(value, out parsed) ? parsed : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetApplicationName(ManagementBaseObject item)
|
||||||
|
{
|
||||||
|
return FirstNonEmpty(
|
||||||
|
ApplicationIndex.SafeGetString(item, "ApplicationName"),
|
||||||
|
ApplicationIndex.SafeGetString(item, "Application"),
|
||||||
|
ApplicationIndex.SafeGetString(item, "BizTalkApplication"))
|
||||||
|
?? UnknownApplication;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ApplicationRuntimeState GetApplication(Dictionary<string, ApplicationRuntimeState> applications, string applicationName)
|
||||||
|
{
|
||||||
|
var normalized = string.IsNullOrWhiteSpace(applicationName) ? UnknownApplication : applicationName.Trim();
|
||||||
|
ApplicationRuntimeState app;
|
||||||
|
if (!applications.TryGetValue(normalized, out app))
|
||||||
|
{
|
||||||
|
app = new ApplicationRuntimeState { ApplicationName = normalized };
|
||||||
|
applications.Add(normalized, app);
|
||||||
|
}
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FirstNonEmpty(params string[] values)
|
||||||
|
{
|
||||||
|
foreach (var value in values)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
return value.Trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user