Added new functionality: checking HIP hosts for TCP connectivity and whether they can be reached via RDP.

This commit is contained in:
2026-05-20 09:32:27 +02:00
parent b8b702bda3
commit 105f5ac7d5
16 changed files with 707 additions and 17 deletions
+8 -5
View File
@@ -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/
+40 -2
View File
@@ -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://<address>` for ping/network connectivity
- `RDP`: `rdp://<address>: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.
---
+40 -2
View File
@@ -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://<adresse>` für Ping/Netzwerk-Konnektivität
- `RDP`: `rdp://<adresse>: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.
---
+37 -2
View File
@@ -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://<ip>` für Netzwerk-Konnektivität und `rdp://<ip>: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
@@ -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<TargetOptions>();
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<TargetOptions>()
};
@@ -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": []
}
}
@@ -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<HipVirtualMachine> VirtualMachines { get; set; } = new List<HipVirtualMachine>();
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<HipVirtualMachineConfiguration>(json);
if (config == null)
{
throw new InvalidOperationException("HIP-VM-Konfiguration konnte nicht deserialisiert werden.");
}
config.VirtualMachines = config.VirtualMachines ?? new List<HipVirtualMachine>();
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;
}
}
@@ -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<TargetOptions> Targets { get; set; } = new List<TargetOptions>();
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<TargetOptions>();
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
@@ -41,6 +41,7 @@
<Reference Include="System.Web.Extensions" />
</ItemGroup>
<ItemGroup>
<Compile Include="Configuration\HipVirtualMachineConfiguration.cs" />
<Compile Include="Configuration\MonitorConfiguration.cs" />
<Compile Include="Logging\FileLogger.cs" />
<Compile Include="Logging\WindowsEventLogger.cs" />
@@ -56,6 +57,9 @@
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="hip-vms.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
@@ -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);
@@ -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,
@@ -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; }
@@ -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<ProbeResult> 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<string> 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<ProbeResult> CurrentFailures { get; set; } = new List<ProbeResult>();
public List<ProbeResult> CurrentResults { get; set; } = new List<ProbeResult>();
public IDictionary<string, TargetState> CurrentState { get; set; } = new Dictionary<string, TargetState>(StringComparer.OrdinalIgnoreCase);
}
}
+130 -3
View File
@@ -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<string, TargetState>(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<TargetOptions> BuildEnabledTargets(MonitorConfiguration config, string configPath, string baseDirectory, FileLogger logger)
{
var targets = config.Targets == null
? new List<TargetOptions>()
: 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<TargetOptions> CreateHipVirtualMachineTargets(HipVirtualMachineConfiguration vmConfig, HipVirtualMachineOptions options, FileLogger logger)
{
var targets = new List<TargetOptions>();
var machines = vmConfig == null || vmConfig.VirtualMachines == null
? new List<HipVirtualMachine>()
: 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);
}
@@ -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",
+179
View File
@@ -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
}
]
}