diff --git a/.gitignore b/.gitignore
index ec9f101..3798f23 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,11 +22,14 @@ _ReSharper*/
[Bb]in/
[Oo]bj/
[TestResult*/
-artifacts/
-publish/
-
-logs/
-state/
+artifacts/
+publish/
+
+HIP-VMs.txt
+hostcheck.ps
+
+logs/
+state/
packages/
.nuget/
diff --git a/Documentation.en.md b/Documentation.en.md
index f6d4744..4ab167a 100644
--- a/Documentation.en.md
+++ b/Documentation.en.md
@@ -12,6 +12,7 @@ The monitor checks technical reachability and writes:
- Windows Application Event Log
- email notifications for failures, recoveries, and a daily status summary
- a state file for transition detection, cooldown handling, and once-per-day summary tracking
+- optional static HIP VM checks from `hip-vms.json` for network/ping and RDP/TCP 3389
### 2. BizTalkMonitorConfigGenerator
@@ -60,7 +61,8 @@ The goal is to avoid manually maintaining the target list and instead derive it
├── App.config
├── HostAvailabilityMonitor.csproj
├── Program.cs
- └── appsettings.json
+ ├── appsettings.json
+ └── hip-vms.json
```
This layout is intentionally simple so it can be committed to **Gitea**, built on Windows, and deployed to BizTalk or application servers with minimal friction.
@@ -95,6 +97,7 @@ The generator tries to transform BizTalk addresses into monitor-compatible endpo
- `sftp://...`
- `tcp://...`
- `icmp://...`
+- `rdp://...`
- plain host / host:port values on a best-effort basis
### Special handling
@@ -139,6 +142,7 @@ File: `src\HostAvailabilityMonitor\appsettings.json`
- `Logging`: file logging configuration
- `EventLog`: Windows Event Log configuration
- `Email`: SMTP and notification configuration
+- `HipVirtualMachines`: optional static VM checks
- `Targets`: endpoints to probe
### Email section
@@ -233,6 +237,7 @@ The daily status email contains:
- status
- detail
- host/port/HTTP status when available
+- a HIP VM snapshot with network and RDP status per VM when `HipVirtualMachines.Enabled=true`
Note:
@@ -240,6 +245,39 @@ The daily counters are derived from the **current day's rolling log file**. If t
---
+## HIP VM Monitoring
+
+The HIP VMs are kept in a dedicated file:
+
+```text
+src\HostAvailabilityMonitor\hip-vms.json
+```
+
+This keeps them independent from the `Targets` regenerated by the BizTalk generator. Changes to BizTalk send ports or receive locations do not overwrite the static VM list.
+
+Enable the feature in `appsettings.json`:
+
+```json
+"HipVirtualMachines": {
+ "Enabled": true,
+ "ConfigPath": "hip-vms.json",
+ "ApplicationName": "HIP Virtual Machines",
+ "TimeoutSeconds": 5,
+ "RdpPort": 3389,
+ "CheckNetwork": true,
+ "CheckRdp": true
+}
+```
+
+The `hip-vms.json` file contains group, name, address, description, and enabled state for each VM. At startup, the monitor turns every enabled VM into normal monitor targets:
+
+- `Network`: `icmp://
` for ping/network connectivity
+- `RDP`: `rdp://:3389` for TCP connectivity to the RDP service
+
+These targets use the same state file as all other checks. Lost network or RDP reachability and later recoveries are therefore reported through the normal failure/recovery emails. File logs and Windows Event Log entries include source, group, machine, and check type.
+
+---
+
## Generator configuration details
File: `generator-settings.json`
@@ -297,7 +335,7 @@ It contains:
Important:
-When the generator writes the monitor JSON, the daily summary options are also copied into the generated target file. That means `GeneratedMonitorConfig.Email.SendDailySummary`, `DailySummaryHourLocal`, and `DailySummaryMinuteLocal` flow directly into the output monitor configuration.
+When the generator writes the monitor JSON, the daily summary options and static HIP VM settings are also copied into the generated target file. That means `GeneratedMonitorConfig.Email.SendDailySummary`, `DailySummaryHourLocal`, `DailySummaryMinuteLocal`, and `GeneratedMonitorConfig.HipVirtualMachines` flow directly into the output monitor configuration.
---
diff --git a/Dokumentation.md b/Dokumentation.md
index 044c621..5f78240 100644
--- a/Dokumentation.md
+++ b/Dokumentation.md
@@ -12,6 +12,7 @@ Der Monitor prüft technische Erreichbarkeit und schreibt:
- Windows Application Event Log
- E-Mail-Benachrichtigungen bei Ausfall, Recovery und täglichem Status
- Statusdatei für Zustandswechsel, Cooldown-Logik und die tägliche Summary-Mail
+- optional statische HIP-VM-Prüfungen aus `hip-vms.json` für Netzwerk/Ping und RDP/TCP 3389
### 2. BizTalkMonitorConfigGenerator
@@ -60,7 +61,8 @@ Ziel ist, dass die Ziel-Liste nicht manuell gepflegt werden muss, sondern direkt
├── App.config
├── HostAvailabilityMonitor.csproj
├── Program.cs
- └── appsettings.json
+ ├── appsettings.json
+ └── hip-vms.json
```
Diese Struktur ist für ein normales Gitea-Repository ausgelegt. Build und Deployment können aus der Solution oder projektweise erfolgen.
@@ -95,6 +97,7 @@ Der Generator versucht, BizTalk-Adressen in Monitor-kompatible Endpunkte zu übe
- `sftp://...`
- `tcp://...`
- `icmp://...`
+- `rdp://...`
- Plain Host / Host:Port (best effort)
### Spezielle Behandlung
@@ -139,6 +142,7 @@ Datei: `src\HostAvailabilityMonitor\appsettings.json`
- `Logging`: Dateilog-Konfiguration
- `EventLog`: Windows Event Log Konfiguration
- `Email`: SMTP- und Benachrichtigungskonfiguration
+- `HipVirtualMachines`: optionale statische VM-Prüfungen
- `Targets`: die zu prüfenden Endpunkte
### Email
@@ -233,6 +237,7 @@ Die Tagesstatus-Mail enthält:
- Status
- Detail
- Host/Port/HTTP-Status, soweit vorhanden
+- HIP-VM-Snapshot mit Netzwerk- und RDP-Status pro VM, wenn `HipVirtualMachines.Enabled=true`
Hinweis:
@@ -240,6 +245,39 @@ Die Tageszähler werden aus dem **heutigen Rolling-Log** ausgewertet. Sollte die
---
+## HIP-VM-Monitoring
+
+Die HIP-VMs werden bewusst in einer eigenen Datei gepflegt:
+
+```text
+src\HostAvailabilityMonitor\hip-vms.json
+```
+
+Damit bleiben sie unabhängig von den durch den BizTalk-Generator neu erzeugten `Targets`. Änderungen an BizTalk-Sendports oder Receive Locations überschreiben diese statische VM-Liste nicht.
+
+Aktiviert wird die Erweiterung in `appsettings.json`:
+
+```json
+"HipVirtualMachines": {
+ "Enabled": true,
+ "ConfigPath": "hip-vms.json",
+ "ApplicationName": "HIP Virtual Machines",
+ "TimeoutSeconds": 5,
+ "RdpPort": 3389,
+ "CheckNetwork": true,
+ "CheckRdp": true
+}
+```
+
+Die Datei `hip-vms.json` enthält pro VM Gruppe, Namen, Adresse, Beschreibung und Aktivstatus. Beim Start erzeugt der Monitor daraus normale Targets:
+
+- `Network`: `icmp://` für Ping/Netzwerk-Konnektivität
+- `RDP`: `rdp://:3389` für TCP-Konnektivität zum RDP-Dienst
+
+Diese Targets nutzen dieselbe State-Datei wie alle anderen Checks. Dadurch werden verlorene Netzwerk- oder RDP-Erreichbarkeit und spätere Recoveries automatisch in den normalen Failure-/Recovery-Mails gemeldet. Im Dateilog und Event Log werden zusätzlich Quelle, Gruppe, Maschine und Check-Typ geschrieben.
+
+---
+
## Generator-Konfiguration im Detail
Datei: `generator-settings.json`
@@ -297,7 +335,7 @@ Hier werden gepflegt:
Wichtig:
-Wenn der Generator die Monitor-JSON erzeugt, werden auch die Felder für die Tagesstatus-Mail mit erzeugt. Das heißt: `GeneratedMonitorConfig.Email.SendDailySummary`, `DailySummaryHourLocal` und `DailySummaryMinuteLocal` werden direkt in die Zielkonfiguration übernommen.
+Wenn der Generator die Monitor-JSON erzeugt, werden auch die Felder für die Tagesstatus-Mail und die statische HIP-VM-Prüfung mit erzeugt. Das heißt: `GeneratedMonitorConfig.Email.SendDailySummary`, `DailySummaryHourLocal`, `DailySummaryMinuteLocal` und `GeneratedMonitorConfig.HipVirtualMachines` werden direkt in die Zielkonfiguration übernommen.
---
diff --git a/README.md b/README.md
index 55b95fb..6017b42 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,8 @@
Dieses Repository ist für eine **Gitea-kompatible Repo-Struktur** aufgebaut und enthält zwei Konsolenanwendungen für **Windows Server 2019 / BizTalk Server 2019** auf **.NET Framework 4.7.2**:
1. **HostAvailabilityMonitor**
- - prüft Endpunkte wie UNC/SMB, `file://`, HTTP/HTTPS, SFTP, FTP, TCP und ICMP
+ - prüft Endpunkte wie UNC/SMB, `file://`, HTTP/HTTPS, SFTP, FTP, TCP, ICMP und RDP
+ - lädt optional eine statische HIP-VM-Liste aus `hip-vms.json` und prüft pro VM Netzwerk/Ping sowie RDP/TCP 3389
- schreibt Rolling-Logs, Windows Application Event Log und E-Mails bei DOWN, Recovery und täglichem Status
2. **BizTalkMonitorConfigGenerator**
@@ -61,7 +62,8 @@ Der typische Betrieb auf einer BizTalk-Maschine ist:
├── App.config
├── HostAvailabilityMonitor.csproj
├── Program.cs
- └── appsettings.json
+ ├── appsettings.json
+ └── hip-vms.json
```
Diese Struktur ist bewusst einfach gehalten, damit das Repository sauber in **Gitea** eingecheckt, gebaut und auf Windows-Servern ausgerollt werden kann.
@@ -88,6 +90,8 @@ Diese Struktur ist bewusst einfach gehalten, damit das Repository sauber in **Gi
- Windows Application Event Log
- SMTP-Mails bei DOWN, Recovery (`DOWN -> UP`) und täglichem Status
- SMTP-Sendports bzw. reine E-Mail-Empfaengerlisten werden vom Generator uebersprungen und vom Monitor defensiv nur als `Skipped` protokolliert, nicht alarmiert
+- statische HIP-VM-Prüfung über separate `hip-vms.json`, damit durch BizTalk neu erzeugte Endpunktlisten nicht mit der VM-Liste kollidieren
+- pro HIP-VM werden zwei normale Monitor-Targets erzeugt: `icmp://` für Netzwerk-Konnektivität und `rdp://:3389` für RDP
- tägliche Status-Mail auf Basis des **ersten Laufs ab der konfigurierten Uhrzeit**
- State-Datei gegen Mail-Spam und für die Einmal-pro-Tag-Logik der Status-Mail
- `ApplicationName` pro Target
@@ -257,6 +261,36 @@ Inhalt der Tagesstatus-Mail:
- Anzahl heutiger erfolgreicher und fehlgeschlagener Checks
- aktueller Snapshot des letzten Laufs
- Liste der aktuell DOWN befindlichen Endpunkte mit Anwendung und Umgebung
+- HIP-VM-Snapshot mit Netzwerk- und RDP-Status pro VM
+
+## HIP-VM-Monitoring
+
+Die statischen HIP-VMs liegen getrennt von den BizTalk-Endpunkten in:
+
+```text
+src\HostAvailabilityMonitor\hip-vms.json
+```
+
+Aktiviert wird die Erweiterung in `appsettings.json`:
+
+```json
+"HipVirtualMachines": {
+ "Enabled": true,
+ "ConfigPath": "hip-vms.json",
+ "ApplicationName": "HIP Virtual Machines",
+ "TimeoutSeconds": 5,
+ "RdpPort": 3389,
+ "CheckNetwork": true,
+ "CheckRdp": true
+}
+```
+
+Pro VM erzeugt der Monitor beim Start zwei normale Checks:
+
+- `Network`: Ping/ICMP gegen die konfigurierte Adresse
+- `RDP`: TCP-Verbindung auf den konfigurierten RDP-Port, standardmäßig `3389`
+
+Ausfälle und Recoveries laufen über dieselbe State-Datei und dieselbe Mail-Logik wie alle anderen Targets. Die tägliche Status-Mail enthält zusätzlich eine kompakte HIP-VM-Übersicht je Gruppe, Maschine und Beschreibung.
## Mehrere Mail-Empfaenger konfigurieren
@@ -307,6 +341,7 @@ Wichtige Felder:
- `BizTalk.ApplicationMappings`: Mapping von BizTalk-Anwendungsnamen auf fachliche App-Namen
- `Output.Path`: Pfad der zu erzeugenden Monitor-JSON
- `GeneratedMonitorConfig`: Vorlage für Logging, Runtime, Event Log und Mail des Monitors
+- `GeneratedMonitorConfig.HipVirtualMachines`: statische HIP-VM-Prüfung in der generierten Monitor-JSON einschalten
- `GeneratedMonitorConfig.Email.SendDailySummary`: tägliche Status-Mail in der generierten Monitor-JSON einschalten
- `GeneratedMonitorConfig.Email.DailySummaryHourLocal`: lokale Uhrzeit der Tageszusammenfassung
- `GeneratedMonitorConfig.Email.DailySummaryMinuteLocal`: lokale Minute der Tageszusammenfassung
diff --git a/src/BizTalkMonitorConfigGenerator/Configuration/GeneratorConfiguration.cs b/src/BizTalkMonitorConfigGenerator/Configuration/GeneratorConfiguration.cs
index 3bc9982..9c55b5c 100644
--- a/src/BizTalkMonitorConfigGenerator/Configuration/GeneratorConfiguration.cs
+++ b/src/BizTalkMonitorConfigGenerator/Configuration/GeneratorConfiguration.cs
@@ -41,6 +41,7 @@ namespace BizTalkMonitorConfigGenerator.Configuration
config.GeneratedMonitorConfig.Logging = config.GeneratedMonitorConfig.Logging ?? new LoggingOptions();
config.GeneratedMonitorConfig.EventLog = config.GeneratedMonitorConfig.EventLog ?? new EventLogOptions();
config.GeneratedMonitorConfig.Email = config.GeneratedMonitorConfig.Email ?? new EmailOptions();
+ config.GeneratedMonitorConfig.HipVirtualMachines = config.GeneratedMonitorConfig.HipVirtualMachines ?? new HipVirtualMachineOptions();
config.GeneratedMonitorConfig.Email.To = RecipientListHelper.SanitizeRecipientList(config.GeneratedMonitorConfig.Email.To);
config.GeneratedMonitorConfig.Email.Cc = RecipientListHelper.SanitizeRecipientList(config.GeneratedMonitorConfig.Email.Cc);
config.GeneratedMonitorConfig.Email.Bcc = RecipientListHelper.SanitizeRecipientList(config.GeneratedMonitorConfig.Email.Bcc);
@@ -213,6 +214,7 @@ namespace BizTalkMonitorConfigGenerator.Configuration
configuration.Logging = configuration.Logging ?? new LoggingOptions();
configuration.EventLog = configuration.EventLog ?? new EventLogOptions();
configuration.Email = configuration.Email ?? new EmailOptions();
+ configuration.HipVirtualMachines = configuration.HipVirtualMachines ?? new HipVirtualMachineOptions();
configuration.Targets = configuration.Targets ?? new List();
if (configuration.Runtime.MaxConcurrency <= 0)
@@ -225,6 +227,24 @@ namespace BizTalkMonitorConfigGenerator.Configuration
throw new InvalidOperationException("GeneratedMonitorConfig.Logging.RetentionDays darf nicht negativ sein.");
}
+ if (configuration.HipVirtualMachines.Enabled)
+ {
+ if (string.IsNullOrWhiteSpace(configuration.HipVirtualMachines.ConfigPath))
+ {
+ throw new InvalidOperationException("GeneratedMonitorConfig.HipVirtualMachines.Enabled=true, aber ConfigPath ist leer.");
+ }
+
+ if (configuration.HipVirtualMachines.TimeoutSeconds <= 0)
+ {
+ configuration.HipVirtualMachines.TimeoutSeconds = 5;
+ }
+
+ if (configuration.HipVirtualMachines.RdpPort <= 0 || configuration.HipVirtualMachines.RdpPort > 65535)
+ {
+ throw new InvalidOperationException("GeneratedMonitorConfig.HipVirtualMachines.RdpPort muss zwischen 1 und 65535 liegen.");
+ }
+ }
+
if (configuration.Email.Enabled)
{
if (string.IsNullOrWhiteSpace(configuration.Email.SmtpHost))
@@ -306,6 +326,16 @@ namespace BizTalkMonitorConfigGenerator.Configuration
CooldownMinutes = configuration.Email.CooldownMinutes,
TimeoutSeconds = configuration.Email.TimeoutSeconds
},
+ HipVirtualMachines = new HipVirtualMachineOptions
+ {
+ Enabled = configuration.HipVirtualMachines.Enabled,
+ ConfigPath = configuration.HipVirtualMachines.ConfigPath,
+ ApplicationName = configuration.HipVirtualMachines.ApplicationName,
+ TimeoutSeconds = configuration.HipVirtualMachines.TimeoutSeconds,
+ RdpPort = configuration.HipVirtualMachines.RdpPort,
+ CheckNetwork = configuration.HipVirtualMachines.CheckNetwork,
+ CheckRdp = configuration.HipVirtualMachines.CheckRdp
+ },
Targets = new List()
};
diff --git a/src/BizTalkMonitorConfigGenerator/generator-settings.json b/src/BizTalkMonitorConfigGenerator/generator-settings.json
index c014519..0d1e383 100644
--- a/src/BizTalkMonitorConfigGenerator/generator-settings.json
+++ b/src/BizTalkMonitorConfigGenerator/generator-settings.json
@@ -94,6 +94,15 @@
"CooldownMinutes": 0,
"TimeoutSeconds": 30
},
+ "HipVirtualMachines": {
+ "Enabled": true,
+ "ConfigPath": "hip-vms.json",
+ "ApplicationName": "HIP Virtual Machines",
+ "TimeoutSeconds": 5,
+ "RdpPort": 3389,
+ "CheckNetwork": true,
+ "CheckRdp": true
+ },
"Targets": []
}
}
diff --git a/src/HostAvailabilityMonitor/Configuration/HipVirtualMachineConfiguration.cs b/src/HostAvailabilityMonitor/Configuration/HipVirtualMachineConfiguration.cs
new file mode 100644
index 0000000..458c92c
--- /dev/null
+++ b/src/HostAvailabilityMonitor/Configuration/HipVirtualMachineConfiguration.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Web.Script.Serialization;
+
+namespace HostAvailabilityMonitor.Configuration
+{
+ public sealed class HipVirtualMachineConfiguration
+ {
+ public List VirtualMachines { get; set; } = new List();
+
+ public static HipVirtualMachineConfiguration Load(string path)
+ {
+ if (string.IsNullOrWhiteSpace(path))
+ {
+ throw new ArgumentException("Der Pfad zur HIP-VM-Konfiguration ist leer.", nameof(path));
+ }
+
+ if (!File.Exists(path))
+ {
+ throw new FileNotFoundException("HIP-VM-Konfiguration wurde nicht gefunden: " + path, path);
+ }
+
+ var json = File.ReadAllText(path);
+ var serializer = new JavaScriptSerializer();
+ var config = serializer.Deserialize(json);
+ if (config == null)
+ {
+ throw new InvalidOperationException("HIP-VM-Konfiguration konnte nicht deserialisiert werden.");
+ }
+
+ config.VirtualMachines = config.VirtualMachines ?? new List();
+ return config;
+ }
+ }
+
+ public sealed class HipVirtualMachine
+ {
+ public string Group { get; set; } = string.Empty;
+ public string Name { get; set; } = string.Empty;
+ public string Address { get; set; } = string.Empty;
+ public string Description { get; set; } = string.Empty;
+ public bool Enabled { get; set; } = true;
+ }
+}
diff --git a/src/HostAvailabilityMonitor/Configuration/MonitorConfiguration.cs b/src/HostAvailabilityMonitor/Configuration/MonitorConfiguration.cs
index 680ef2d..37e87fc 100644
--- a/src/HostAvailabilityMonitor/Configuration/MonitorConfiguration.cs
+++ b/src/HostAvailabilityMonitor/Configuration/MonitorConfiguration.cs
@@ -14,6 +14,7 @@ namespace HostAvailabilityMonitor.Configuration
public LoggingOptions Logging { get; set; } = new LoggingOptions();
public EventLogOptions EventLog { get; set; } = new EventLogOptions();
public EmailOptions Email { get; set; } = new EmailOptions();
+ public HipVirtualMachineOptions HipVirtualMachines { get; set; } = new HipVirtualMachineOptions();
public List Targets { get; set; } = new List();
public static MonitorConfiguration Load(string path)
@@ -36,6 +37,7 @@ namespace HostAvailabilityMonitor.Configuration
config.Logging = config.Logging ?? new LoggingOptions();
config.EventLog = config.EventLog ?? new EventLogOptions();
config.Email = config.Email ?? new EmailOptions();
+ config.HipVirtualMachines = config.HipVirtualMachines ?? new HipVirtualMachineOptions();
config.Email.To = SanitizeRecipientList(config.Email.To);
config.Email.Cc = SanitizeRecipientList(config.Email.Cc);
config.Email.Bcc = SanitizeRecipientList(config.Email.Bcc);
@@ -84,7 +86,9 @@ namespace HostAvailabilityMonitor.Configuration
public void Validate()
{
- if (Targets == null || Targets.Count == 0)
+ Targets = Targets ?? new List();
+
+ if ((Targets == null || Targets.Count == 0) && (HipVirtualMachines == null || !HipVirtualMachines.Enabled))
{
throw new InvalidOperationException("Es wurde kein Ziel in der Konfiguration definiert.");
}
@@ -112,6 +116,29 @@ namespace HostAvailabilityMonitor.Configuration
Runtime.MaxConcurrency = 1;
}
+ if (HipVirtualMachines == null)
+ {
+ HipVirtualMachines = new HipVirtualMachineOptions();
+ }
+
+ if (HipVirtualMachines.Enabled)
+ {
+ if (string.IsNullOrWhiteSpace(HipVirtualMachines.ConfigPath))
+ {
+ throw new InvalidOperationException("HipVirtualMachines.Enabled=true, aber ConfigPath ist leer.");
+ }
+
+ if (HipVirtualMachines.TimeoutSeconds <= 0)
+ {
+ HipVirtualMachines.TimeoutSeconds = 5;
+ }
+
+ if (HipVirtualMachines.RdpPort <= 0 || HipVirtualMachines.RdpPort > 65535)
+ {
+ throw new InvalidOperationException("HipVirtualMachines.RdpPort muss zwischen 1 und 65535 liegen.");
+ }
+ }
+
if (Logging.RetentionDays < 0)
{
throw new InvalidOperationException("Logging.RetentionDays darf nicht negativ sein.");
@@ -227,6 +254,17 @@ namespace HostAvailabilityMonitor.Configuration
public int TimeoutSeconds { get; set; } = 30;
}
+ public sealed class HipVirtualMachineOptions
+ {
+ public bool Enabled { get; set; } = false;
+ public string ConfigPath { get; set; } = "hip-vms.json";
+ public string ApplicationName { get; set; } = "HIP Virtual Machines";
+ public int TimeoutSeconds { get; set; } = 5;
+ public int RdpPort { get; set; } = 3389;
+ public bool CheckNetwork { get; set; } = true;
+ public bool CheckRdp { get; set; } = true;
+ }
+
public sealed class TargetOptions
{
public string Name { get; set; } = string.Empty;
@@ -236,6 +274,11 @@ namespace HostAvailabilityMonitor.Configuration
public int? Port { get; set; }
public bool? ValidateUncPathAccess { get; set; }
public string HttpMethod { get; set; }
+ public string Source { get; set; }
+ public string Group { get; set; }
+ public string Description { get; set; }
+ public string MachineName { get; set; }
+ public string CheckName { get; set; }
public bool Enabled { get; set; } = true;
public string DisplayName
diff --git a/src/HostAvailabilityMonitor/HostAvailabilityMonitor.csproj b/src/HostAvailabilityMonitor/HostAvailabilityMonitor.csproj
index fac127b..1c74ffc 100644
--- a/src/HostAvailabilityMonitor/HostAvailabilityMonitor.csproj
+++ b/src/HostAvailabilityMonitor/HostAvailabilityMonitor.csproj
@@ -41,6 +41,7 @@
+
@@ -56,6 +57,9 @@
PreserveNewest
+
+ PreserveNewest
+
diff --git a/src/HostAvailabilityMonitor/Logging/FileLogger.cs b/src/HostAvailabilityMonitor/Logging/FileLogger.cs
index 31123bd..a78df20 100644
--- a/src/HostAvailabilityMonitor/Logging/FileLogger.cs
+++ b/src/HostAvailabilityMonitor/Logging/FileLogger.cs
@@ -104,6 +104,26 @@ namespace HostAvailabilityMonitor.Logging
parts.Add("Port=" + result.Port.Value);
}
+ if (!string.IsNullOrWhiteSpace(result.Source))
+ {
+ parts.Add("Source=" + Escape(result.Source));
+ }
+
+ if (!string.IsNullOrWhiteSpace(result.Group))
+ {
+ parts.Add("Group=" + Escape(result.Group));
+ }
+
+ if (!string.IsNullOrWhiteSpace(result.MachineName))
+ {
+ parts.Add("Machine=" + Escape(result.MachineName));
+ }
+
+ if (!string.IsNullOrWhiteSpace(result.CheckName))
+ {
+ parts.Add("Check=" + Escape(result.CheckName));
+ }
+
if (result.HttpStatusCode.HasValue)
{
parts.Add("HttpStatus=" + result.HttpStatusCode.Value);
diff --git a/src/HostAvailabilityMonitor/Monitoring/EndpointProbeService.cs b/src/HostAvailabilityMonitor/Monitoring/EndpointProbeService.cs
index 8c11157..8d2d9b1 100644
--- a/src/HostAvailabilityMonitor/Monitoring/EndpointProbeService.cs
+++ b/src/HostAvailabilityMonitor/Monitoring/EndpointProbeService.cs
@@ -65,12 +65,14 @@ namespace HostAvailabilityMonitor.Monitoring
return await CheckTcpAsync(target, uri.Host, target.Port ?? ResolvePort(uri, null), startedAt, TargetKind.Tcp, endpoint, cancellationToken).ConfigureAwait(false);
case "icmp":
return await CheckIcmpAsync(target, uri.Host, startedAt, endpoint, cancellationToken).ConfigureAwait(false);
+ case "rdp":
+ return await CheckTcpAsync(target, uri.Host, target.Port ?? ResolvePort(uri, 3389), startedAt, TargetKind.Rdp, endpoint, cancellationToken).ConfigureAwait(false);
case "ftp":
return await CheckTcpAsync(target, uri.Host, target.Port ?? ResolvePort(uri, 21), startedAt, TargetKind.Ftp, endpoint, cancellationToken).ConfigureAwait(false);
case "file":
return await CheckFileUriAsync(target, uri, startedAt, cancellationToken).ConfigureAwait(false);
default:
- return Fail(target, TargetKind.Unknown, endpoint, startedAt, "Nicht unterstütztes Schema '" + uri.Scheme + "'. Unterstützt: UNC, file, http, https, sftp, ftp, tcp, icmp.", null, uri.Host, uri.IsDefaultPort ? (int?)null : uri.Port);
+ return Fail(target, TargetKind.Unknown, endpoint, startedAt, "Nicht unterstütztes Schema '" + uri.Scheme + "'. Unterstützt: UNC, file, http, https, sftp, ftp, tcp, icmp, rdp.", null, uri.Host, uri.IsDefaultPort ? (int?)null : uri.Port);
}
}
@@ -395,6 +397,11 @@ namespace HostAvailabilityMonitor.Monitoring
Detail = detail,
Host = host,
Port = port,
+ Source = target.Source,
+ Group = target.Group,
+ Description = target.Description,
+ MachineName = target.MachineName,
+ CheckName = target.CheckName,
HttpStatusCode = httpStatusCode,
ErrorType = errorType,
StartedAtLocal = startedAt,
diff --git a/src/HostAvailabilityMonitor/Monitoring/ProbeResult.cs b/src/HostAvailabilityMonitor/Monitoring/ProbeResult.cs
index 48ccc60..c5fc745 100644
--- a/src/HostAvailabilityMonitor/Monitoring/ProbeResult.cs
+++ b/src/HostAvailabilityMonitor/Monitoring/ProbeResult.cs
@@ -13,7 +13,8 @@ namespace HostAvailabilityMonitor.Monitoring
Icmp = 6,
Host = 7,
Ftp = 8,
- File = 9
+ File = 9,
+ Rdp = 10
}
public sealed class ProbeResult
@@ -29,6 +30,11 @@ namespace HostAvailabilityMonitor.Monitoring
public string Detail { get; set; }
public string Host { get; set; }
public int? Port { get; set; }
+ public string Source { get; set; }
+ public string Group { get; set; }
+ public string Description { get; set; }
+ public string MachineName { get; set; }
+ public string CheckName { get; set; }
public int? HttpStatusCode { get; set; }
public string ErrorType { get; set; }
public DateTimeOffset StartedAtLocal { get; set; }
diff --git a/src/HostAvailabilityMonitor/Notifications/EmailNotifier.cs b/src/HostAvailabilityMonitor/Notifications/EmailNotifier.cs
index f1e7e12..a249601 100644
--- a/src/HostAvailabilityMonitor/Notifications/EmailNotifier.cs
+++ b/src/HostAvailabilityMonitor/Notifications/EmailNotifier.cs
@@ -276,6 +276,8 @@ namespace HostAvailabilityMonitor.Notifications
sb.AppendLine("Aktuell sind keine Endpunkte down / There are currently no unavailable endpoints.");
}
+ AppendHipVirtualMachineSnapshot(sb, summary.CurrentResults);
+
return sb.ToString();
}
@@ -296,6 +298,22 @@ namespace HostAvailabilityMonitor.Notifications
{
sb.AppendLine(" Port: " + result.Port.Value);
}
+ if (!string.IsNullOrWhiteSpace(result.Group))
+ {
+ sb.AppendLine(" Gruppe / Group: " + result.Group);
+ }
+ if (!string.IsNullOrWhiteSpace(result.MachineName))
+ {
+ sb.AppendLine(" Maschine / Machine: " + result.MachineName);
+ }
+ if (!string.IsNullOrWhiteSpace(result.CheckName))
+ {
+ sb.AppendLine(" Check: " + result.CheckName);
+ }
+ if (!string.IsNullOrWhiteSpace(result.Description))
+ {
+ sb.AppendLine(" Beschreibung / Description: " + result.Description);
+ }
if (result.HttpStatusCode.HasValue)
{
sb.AppendLine(" HTTP Status: " + result.HttpStatusCode.Value);
@@ -310,6 +328,84 @@ namespace HostAvailabilityMonitor.Notifications
}
}
+ private static void AppendHipVirtualMachineSnapshot(StringBuilder sb, IReadOnlyCollection results)
+ {
+ if (results == null)
+ {
+ return;
+ }
+
+ var vmResults = results
+ .Where(r => r != null && string.Equals(r.Source, "HipVirtualMachines", StringComparison.OrdinalIgnoreCase))
+ .ToList();
+ if (vmResults.Count == 0)
+ {
+ return;
+ }
+
+ sb.AppendLine();
+ sb.AppendLine("HIP-VM Snapshot / HIP VM snapshot:");
+
+ var networkUp = vmResults.Count(r => string.Equals(r.CheckName, "Network", StringComparison.OrdinalIgnoreCase) && r.IsSuccess);
+ var networkDown = vmResults.Count(r => string.Equals(r.CheckName, "Network", StringComparison.OrdinalIgnoreCase) && !r.IsSkipped && !r.IsSuccess);
+ var rdpUp = vmResults.Count(r => string.Equals(r.CheckName, "RDP", StringComparison.OrdinalIgnoreCase) && r.IsSuccess);
+ var rdpDown = vmResults.Count(r => string.Equals(r.CheckName, "RDP", StringComparison.OrdinalIgnoreCase) && !r.IsSkipped && !r.IsSuccess);
+ sb.AppendLine("- Netzwerk / Network UP: " + networkUp + ", DOWN: " + networkDown);
+ sb.AppendLine("- RDP UP: " + rdpUp + ", DOWN: " + rdpDown);
+
+ var groups = vmResults
+ .GroupBy(r => BuildVmSnapshotKey(r), StringComparer.OrdinalIgnoreCase)
+ .OrderBy(g => FirstNonEmpty(g.Select(r => r.Group)), StringComparer.OrdinalIgnoreCase)
+ .ThenBy(g => FirstNonEmpty(g.Select(r => r.MachineName)), StringComparer.OrdinalIgnoreCase);
+
+ foreach (var group in groups)
+ {
+ var entries = group.ToList();
+ var first = entries[0];
+ var network = entries.FirstOrDefault(r => string.Equals(r.CheckName, "Network", StringComparison.OrdinalIgnoreCase));
+ var rdp = entries.FirstOrDefault(r => string.Equals(r.CheckName, "RDP", StringComparison.OrdinalIgnoreCase));
+
+ sb.AppendLine("- " + Safe(first.Group) + " | " + Safe(first.MachineName) + " | " + Safe(first.Description));
+ sb.AppendLine(" Netzwerk / Network: " + FormatCompactStatus(network));
+ sb.AppendLine(" RDP: " + FormatCompactStatus(rdp));
+ }
+ }
+
+ private static string BuildVmSnapshotKey(ProbeResult result)
+ {
+ return string.Join("|", new[]
+ {
+ Safe(result.Group),
+ Safe(result.MachineName),
+ Safe(result.Description)
+ });
+ }
+
+ private static string FormatCompactStatus(ProbeResult result)
+ {
+ if (result == null)
+ {
+ return "n/a";
+ }
+
+ if (result.IsSkipped)
+ {
+ return "SKIPPED - " + Safe(result.Detail);
+ }
+
+ return (result.IsSuccess ? "UP" : "DOWN") + " - " + Safe(result.Detail);
+ }
+
+ private static string FirstNonEmpty(IEnumerable values)
+ {
+ if (values == null)
+ {
+ return string.Empty;
+ }
+
+ return values.FirstOrDefault(v => !string.IsNullOrWhiteSpace(v)) ?? string.Empty;
+ }
+
private static string Safe(string value)
{
return string.IsNullOrWhiteSpace(value) ? "n/a" : value;
@@ -329,6 +425,7 @@ namespace HostAvailabilityMonitor.Notifications
public int CurrentTargetsDown { get; set; }
public int CurrentTargetsSkipped { get; set; }
public List CurrentFailures { get; set; } = new List();
+ public List CurrentResults { get; set; } = new List();
public IDictionary CurrentState { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase);
}
}
diff --git a/src/HostAvailabilityMonitor/Program.cs b/src/HostAvailabilityMonitor/Program.cs
index 0b35041..e7b70aa 100644
--- a/src/HostAvailabilityMonitor/Program.cs
+++ b/src/HostAvailabilityMonitor/Program.cs
@@ -31,6 +31,7 @@ namespace HostAvailabilityMonitor
var configPath = args != null && args.Length > 0 && !string.IsNullOrWhiteSpace(args[0])
? args[0]
: Path.Combine(baseDirectory, "appsettings.json");
+ var fullConfigPath = Path.GetFullPath(configPath);
var config = MonitorConfiguration.Load(configPath);
config.Validate();
@@ -43,7 +44,7 @@ namespace HostAvailabilityMonitor
logger = new FileLogger(logDirectory, config.Logging.FilePrefix, config.Logging.RetentionDays, config.ApplicationName, config.EnvironmentName);
logger.CleanupOldFiles();
logger.Info(config.ApplicationName + " gestartet. Umgebung=" + config.EnvironmentName + ", Server=" + Environment.MachineName + ".");
- logger.Info("Konfiguration geladen: " + Path.GetFullPath(configPath));
+ logger.Info("Konfiguration geladen: " + fullConfigPath);
eventLogger = new WindowsEventLogger(config.EventLog, logger);
eventLogger.TryInitialize();
@@ -67,7 +68,7 @@ namespace HostAvailabilityMonitor
var currentState = new Dictionary(previousState, StringComparer.OrdinalIgnoreCase);
var probeService = new EndpointProbeService(config.Runtime);
- var enabledTargets = config.Targets.Where(t => t != null && t.Enabled).ToList();
+ var enabledTargets = BuildEnabledTargets(config, fullConfigPath, baseDirectory, logger);
if (enabledTargets.Count == 0)
{
@@ -219,6 +220,126 @@ namespace HostAvailabilityMonitor
return results;
}
+ private static List BuildEnabledTargets(MonitorConfiguration config, string configPath, string baseDirectory, FileLogger logger)
+ {
+ var targets = config.Targets == null
+ ? new List()
+ : config.Targets.Where(t => t != null && t.Enabled).ToList();
+
+ if (config.HipVirtualMachines == null || !config.HipVirtualMachines.Enabled)
+ {
+ return targets;
+ }
+
+ var vmConfigPath = ResolveConfigurationPath(configPath, baseDirectory, config.HipVirtualMachines.ConfigPath);
+ logger.Info("HIP-VM-Konfiguration wird geladen: " + vmConfigPath);
+
+ var vmConfig = HipVirtualMachineConfiguration.Load(vmConfigPath);
+ var vmTargets = CreateHipVirtualMachineTargets(vmConfig, config.HipVirtualMachines, logger);
+ targets.AddRange(vmTargets);
+
+ logger.Info("HIP-VM-Konfiguration geladen. VMs=" + vmConfig.VirtualMachines.Count + ", generierte Targets=" + vmTargets.Count + ".");
+ return targets;
+ }
+
+ private static List CreateHipVirtualMachineTargets(HipVirtualMachineConfiguration vmConfig, HipVirtualMachineOptions options, FileLogger logger)
+ {
+ var targets = new List();
+ var machines = vmConfig == null || vmConfig.VirtualMachines == null
+ ? new List()
+ : vmConfig.VirtualMachines;
+
+ foreach (var vm in machines)
+ {
+ if (vm == null || !vm.Enabled)
+ {
+ continue;
+ }
+
+ var address = (vm.Address ?? string.Empty).Trim();
+ if (string.IsNullOrWhiteSpace(address))
+ {
+ logger.Warn("HIP-VM ohne Adresse wurde uebersprungen. Name=" + (vm.Name ?? string.Empty) + ".");
+ continue;
+ }
+
+ var machineName = string.IsNullOrWhiteSpace(vm.Name) ? address : vm.Name.Trim();
+ var applicationName = string.IsNullOrWhiteSpace(options.ApplicationName) ? "HIP Virtual Machines" : options.ApplicationName.Trim();
+ var timeoutSeconds = Math.Max(1, options.TimeoutSeconds);
+
+ if (options.CheckNetwork)
+ {
+ targets.Add(CreateHipVirtualMachineTarget(
+ vm,
+ machineName,
+ applicationName,
+ "Network",
+ "Netzwerk",
+ "icmp://" + address,
+ null,
+ timeoutSeconds));
+ }
+
+ if (options.CheckRdp)
+ {
+ targets.Add(CreateHipVirtualMachineTarget(
+ vm,
+ machineName,
+ applicationName,
+ "RDP",
+ "RDP",
+ "rdp://" + address + ":" + Math.Max(1, options.RdpPort),
+ Math.Max(1, options.RdpPort),
+ timeoutSeconds));
+ }
+ }
+
+ return targets;
+ }
+
+ private static TargetOptions CreateHipVirtualMachineTarget(
+ HipVirtualMachine vm,
+ string machineName,
+ string applicationName,
+ string checkName,
+ string displayCheckName,
+ string endpoint,
+ int? port,
+ int timeoutSeconds)
+ {
+ return new TargetOptions
+ {
+ Name = machineName + " - " + displayCheckName,
+ ApplicationName = applicationName,
+ Endpoint = endpoint,
+ Port = port,
+ TimeoutSeconds = timeoutSeconds,
+ Enabled = true,
+ Source = "HipVirtualMachines",
+ Group = vm.Group,
+ Description = vm.Description,
+ MachineName = machineName,
+ CheckName = checkName
+ };
+ }
+
+ private static string ResolveConfigurationPath(string configPath, string baseDirectory, string configuredPath)
+ {
+ if (Path.IsPathRooted(configuredPath))
+ {
+ return configuredPath;
+ }
+
+ var configDirectory = Path.GetDirectoryName(Path.GetFullPath(configPath));
+ var configRelativePath = Path.GetFullPath(Path.Combine(configDirectory, configuredPath));
+ if (File.Exists(configRelativePath))
+ {
+ return configRelativePath;
+ }
+
+ return Path.GetFullPath(Path.Combine(baseDirectory, configuredPath));
+ }
+
private static async Task TrySendDailySummaryAsync(
MonitorConfiguration config,
EmailNotifier notifier,
@@ -258,6 +379,7 @@ namespace HostAvailabilityMonitor
CurrentTargetsDown = latestResults.Count(r => !r.IsSkipped && !r.IsSuccess),
CurrentTargetsSkipped = latestResults.Count(r => r.IsSkipped),
CurrentFailures = currentFailures,
+ CurrentResults = latestResults,
CurrentState = currentState
};
@@ -368,11 +490,16 @@ namespace HostAvailabilityMonitor
private static string BuildEventMessage(MonitorConfiguration config, ProbeResult result)
{
return string.Format(
- "Umgebung={0} | Anwendung={1} | Name={2} | Endpoint={3} | Status={4} | Detail={5}",
+ "Umgebung={0} | Anwendung={1} | Name={2} | Endpoint={3} | Typ={4} | Quelle={5} | Gruppe={6} | Maschine={7} | Check={8} | Status={9} | Detail={10}",
config.EnvironmentName,
result.ApplicationName,
result.Name,
result.Endpoint,
+ result.Kind,
+ result.Source,
+ result.Group,
+ result.MachineName,
+ result.CheckName,
result.StatusText,
result.Detail);
}
diff --git a/src/HostAvailabilityMonitor/appsettings.json b/src/HostAvailabilityMonitor/appsettings.json
index 4f70f53..1340d8d 100644
--- a/src/HostAvailabilityMonitor/appsettings.json
+++ b/src/HostAvailabilityMonitor/appsettings.json
@@ -48,6 +48,15 @@
"CooldownMinutes": 0,
"TimeoutSeconds": 30
},
+ "HipVirtualMachines": {
+ "Enabled": true,
+ "ConfigPath": "hip-vms.json",
+ "ApplicationName": "HIP Virtual Machines",
+ "TimeoutSeconds": 5,
+ "RdpPort": 3389,
+ "CheckNetwork": true,
+ "CheckRdp": true
+ },
"Targets": [
{
"Name": "Internes Fileshare 1",
diff --git a/src/HostAvailabilityMonitor/hip-vms.json b/src/HostAvailabilityMonitor/hip-vms.json
new file mode 100644
index 0000000..ea9df17
--- /dev/null
+++ b/src/HostAvailabilityMonitor/hip-vms.json
@@ -0,0 +1,179 @@
+{
+ "VirtualMachines": [
+ {
+ "Group": "Entwickermaschinen",
+ "Name": "dv23aasw0001",
+ "Address": "10.121.157.21",
+ "Description": "Developer Machine (Steffen R.)",
+ "Enabled": true
+ },
+ {
+ "Group": "Entwickermaschinen",
+ "Name": "dv23aasw0006",
+ "Address": "10.121.157.27",
+ "Description": "Developer Machine (Johannes Rest)",
+ "Enabled": true
+ },
+ {
+ "Group": "Entwickermaschinen",
+ "Name": "dv23aasw0007",
+ "Address": "10.121.157.28",
+ "Description": "Developer Machine (Reza Takhsha)",
+ "Enabled": true
+ },
+ {
+ "Group": "Entwickermaschinen",
+ "Name": "dv23aasw0002",
+ "Address": "10.121.157.22",
+ "Description": "Developer Machine (Chris Schild)",
+ "Enabled": true
+ },
+ {
+ "Group": "Entwickermaschinen",
+ "Name": "dv23agpwbi01",
+ "Address": "10.121.157.8",
+ "Description": "Developer Machine (Ronny)",
+ "Enabled": true
+ },
+ {
+ "Group": "Entwickermaschinen",
+ "Name": "dv23agpwbi02",
+ "Address": "10.121.157.9",
+ "Description": "Developer Machine (Lars)",
+ "Enabled": true
+ },
+ {
+ "Group": "Entwickermaschinen",
+ "Name": "dv23agpwbi03",
+ "Address": "10.121.157.10",
+ "Description": "BizTalk Server",
+ "Enabled": true
+ },
+ {
+ "Group": "Entwickermaschinen",
+ "Name": "dv23agpwbi04",
+ "Address": "10.121.157.7",
+ "Description": "Monitoring/Administration Server",
+ "Enabled": true
+ },
+ {
+ "Group": "Entwickermaschinen",
+ "Name": "dv23adbwbi01",
+ "Address": "10.121.157.6",
+ "Description": "BizTalk DB Server",
+ "Enabled": true
+ },
+ {
+ "Group": "Entwickermaschinen",
+ "Name": "dv23wgpwbi01",
+ "Address": "10.123.49.100",
+ "Description": "IIS Server",
+ "Enabled": true
+ },
+ {
+ "Group": "TST",
+ "Name": "tv23agpwbi01",
+ "Address": "10.121.157.11",
+ "Description": "BizTalk Server",
+ "Enabled": true
+ },
+ {
+ "Group": "TST",
+ "Name": "tv23agpwbi02",
+ "Address": "10.121.157.12",
+ "Description": "Monitoring/Administration Server",
+ "Enabled": true
+ },
+ {
+ "Group": "TST",
+ "Name": "tv23adbwbi01",
+ "Address": "10.121.157.13",
+ "Description": "BizTalk DB Server",
+ "Enabled": true
+ },
+ {
+ "Group": "TST",
+ "Name": "tv23wgpwbi01",
+ "Address": "10.123.49.228",
+ "Description": "IIS Server",
+ "Enabled": true
+ },
+ {
+ "Group": "ACC",
+ "Name": "av23agpwbi01",
+ "Address": "10.121.145.6",
+ "Description": "BizTalk Server",
+ "Enabled": true
+ },
+ {
+ "Group": "ACC",
+ "Name": "av23agpwbi02",
+ "Address": "10.121.145.7",
+ "Description": "Monitoring/Administration Server",
+ "Enabled": true
+ },
+ {
+ "Group": "ACC",
+ "Name": "av23adbwbi01",
+ "Address": "10.121.145.8",
+ "Description": "BizTalk DB Server",
+ "Enabled": true
+ },
+ {
+ "Group": "ACC",
+ "Name": "acc-lbi-23-agw-bewintegration0001",
+ "Address": "10.123.47.198",
+ "Description": "Load Balancer",
+ "Enabled": true
+ },
+ {
+ "Group": "ACC",
+ "Name": "av23wgpwbi01",
+ "Address": "10.123.47.196",
+ "Description": "IIS Server",
+ "Enabled": true
+ },
+ {
+ "Group": "ACC",
+ "Name": "av23wgpwbi02",
+ "Address": "10.123.47.197",
+ "Description": "IIS Server",
+ "Enabled": true
+ },
+ {
+ "Group": "Produktion",
+ "Name": "pv23agpwbi01",
+ "Address": "10.121.145.9",
+ "Description": "BizTalk Server",
+ "Enabled": true
+ },
+ {
+ "Group": "Produktion",
+ "Name": "pv23agpwbi02",
+ "Address": "10.121.145.10",
+ "Description": "Monitoring/Administration Server",
+ "Enabled": true
+ },
+ {
+ "Group": "Produktion",
+ "Name": "pv23adbwbi01",
+ "Address": "10.121.145.11",
+ "Description": "BizTalk DB Server",
+ "Enabled": true
+ },
+ {
+ "Group": "Produktion",
+ "Name": "pv23wgpwbi01",
+ "Address": "10.123.48.196",
+ "Description": "IIS Server",
+ "Enabled": true
+ },
+ {
+ "Group": "Produktion",
+ "Name": "pv23wgpwbi02",
+ "Address": "10.123.48.197",
+ "Description": "IIS Server",
+ "Enabled": true
+ }
+ ]
+}