From 1228f717657147de545a7f8a73522f9502aaaa70 Mon Sep 17 00:00:00 2001 From: Johannes Rest Date: Wed, 19 Aug 2026 18:13:03 +0200 Subject: [PATCH] Add recovery preflight and ACC restart runbook --- AI-README.md | 4 +- CHANGELOG.md | 10 ++ Dokumentation.md | 3 +- Installation.md | 13 +- README.md | 5 +- ...C-Emergency-Restore-before-json-Runbook.md | 119 ++++++++++++++++++ ...C-Runtime-Shutdown-Exception-2026-08-19.md | 11 +- .../InstallerEngine.cs | 2 +- .../MainForm.cs | 2 +- .../Properties/AssemblyInfo.cs | 4 +- .../app.manifest | 2 +- .../Properties/AssemblyInfo.cs | 4 +- .../Services/BizTalkOperationService.cs | 2 +- .../Ui/MainForm.cs | 117 ++++++++++++++++- .../app.manifest | 2 +- .../Program.cs | 20 +++ 16 files changed, 296 insertions(+), 24 deletions(-) create mode 100644 docs/ACC-Emergency-Restore-before-json-Runbook.md diff --git a/AI-README.md b/AI-README.md index e910126..4a2d519 100644 --- a/AI-README.md +++ b/AI-README.md @@ -2,7 +2,7 @@ ## Projekt und Sicherheitsziel -Das Repository enthält ein .NET-Framework-4.6.1-WinForms-Tool für kontrollierte BizTalk-2020-Wartungsoperationen. Änderungen müssen Dry-run, explizite Freigabe realer Aktionen, sichere Reihenfolgen und wiederherstellbare Installergrenzen erhalten. Die aktuelle Produktversion ist 2.2.0. +Das Repository enthält ein .NET-Framework-4.6.1-WinForms-Tool für kontrollierte BizTalk-2020-Wartungsoperationen. Änderungen müssen Dry-run, explizite Freigabe realer Aktionen, sichere Reihenfolgen und wiederherstellbare Installergrenzen erhalten. Die aktuelle Produktversion ist 2.2.1. ## Installerinvarianten @@ -28,6 +28,8 @@ Die zentrale Implementierung liegt in `src/BizTalkPlatformManagementTool.Setup/I - Restore-Kategorien gelten global über alle Anwendungen: Host Instances, Send Ports, Orchestrations, Receive Locations. - Emergency Restore überschreibt niemals die Eingabe-`before.json`, erzeugt eine timestamp-basierte Kopie und stellt `ENTSSO` vor Host Instances sicher. - Emergency Restore muss mit genau einer validen `before.json` funktionieren; Dateien eines vorherigen fehlgeschlagenen Laufs dürfen keine Voraussetzung sein. +- Ein älterer kompatibler Snapshot, insbesondere aus 2.1.3, darf nicht allein anhand seines `ToolVersion`-Werts abgelehnt werden. +- Der echte Emergency Restore erzeugt nach Möglichkeit automatisch einen timestamp-basierten Soll/Ist-Diff aus Recovery-Quelle und Nachher-Snapshot. - Die zentrale best-effort Orchestrierung liegt in `OperationPlanExecutor`; die WMI-/Service-Zustandsprüfung bleibt im produktiven Runtime-Adapter. ## Versionierung diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a574c0..2670f76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ # Changelog +## [2.2.1] - 2026-08-19 +### Added +- WMI-free **Validate State** preflight with snapshot metadata, server matching and artifact counts. +- Direct **State...** file selection for a preserved recovery snapshot. +- Automatic timestamped JSON/CSV/HTML target/actual diff after a real Emergency Restore. +- Regression coverage proving that a `before.json` created by version 2.1.3 remains a valid Emergency Restore source. + +### Changed +- Emergency-recovery documentation now provides a dedicated operator runbook for the ACC restart and explicitly distinguishes dry-run evidence from real execution evidence. + ## [2.2.0] - 2026-08-19 ### Added - State-aware **Emergency Restore** from an existing `before.json` without overwriting the recovery source. diff --git a/Dokumentation.md b/Dokumentation.md index 499388e..f30537a 100644 --- a/Dokumentation.md +++ b/Dokumentation.md @@ -56,7 +56,7 @@ Selbsterklärende Zuweisungen und reine UI-Konstruktion werden nicht zeilenweise 9. Nach der Wartung mit **Restore** aus `before.json` wiederherstellen. 10. Mit **Snapshot After** und **Compare** die Umgebung validieren. -Wenn ein echter Shutdown oder Restore nur teilweise ausgeführt wurde und lediglich die ursprüngliche `before.json` verfügbar ist, wird **Emergency Restore** verwendet. Der Modus kopiert die Quelle unter einem timestamp-basierten Namen, überschreibt `before.json` nicht, stellt zuerst `ENTSSO` sicher und gleicht danach den gespeicherten Sollzustand zustandsbewusst ab. Der erste Lauf muss als Dry-run erfolgen. +Wenn ein echter Shutdown oder Restore nur teilweise ausgeführt wurde und lediglich die ursprüngliche `before.json` verfügbar ist, wird **Emergency Restore** verwendet. Die Quelle kann über **State...** ausgewählt und vorab über **Validate State** ohne WMI-Zugriff geprüft werden. Der Modus kopiert die Quelle unter einem timestamp-basierten Namen, überschreibt `before.json` nicht, stellt zuerst `ENTSSO` sicher und gleicht danach den gespeicherten Sollzustand zustandsbewusst ab. Der erste Lauf muss als Dry-run erfolgen. Die Statusanzeige rechts im Kopfbereich bewertet die Host-Instance-Zustaende des letzten Snapshots und zeigt `Started`, `Stopped`, `Partial` oder `Unknown`. **Clear** leert die sichtbaren Ergebnis- und Log-Grids, loescht aber keine Dateien. **Close** beendet die Anwendung. @@ -109,6 +109,7 @@ Der Emergency Restore benötigt nur eine valide `before.json` und denselben Ziel 5. Bereits korrekte Zustände als `AlreadySatisfied` überspringen. 6. Isolierte WMI-/Adapterfehler als `Failed` erfassen und mit dem nächsten unabhängigen Schritt fortfahren. 7. Nachher-Snapshot unabhängig versuchen und vollständigen Ergebnisreport schreiben. +8. Bei erfolgreichem Nachher-Snapshot automatisch einen timestamp-basierten Soll/Ist-Diff als JSON, CSV und HTML erzeugen. Der Best-effort-Ansatz bedeutet nicht, dass Fehler ignoriert werden: Jeder Teilfehler erzeugt einen roten/fehlgeschlagenen Abschluss und erfordert die Prüfung des Ergebnisreports. Er verhindert lediglich, dass beispielsweise eine einzelne nicht validierbare Receive Location alle späteren Host-, Port- oder Receive-Location-Schritte blockiert. diff --git a/Installation.md b/Installation.md index 4d9849d..a5a4315 100644 --- a/Installation.md +++ b/Installation.md @@ -89,18 +89,19 @@ Ein erfolgreicher Installer-Self-Test bestätigt Paket, Programmstart und lokale ## Emergency Restore nach einem Teilabbruch -Version 2.2.0 kann einen Wiederanlauf allein aus einer erhaltenen `before.json` vorbereiten und ausführen. Zusätzliche Plan- oder Nachher-Dateien des fehlgeschlagenen Laufs sind nicht erforderlich. +Version 2.2.1 kann einen Wiederanlauf allein aus einer erhaltenen `before.json` vorbereiten und ausführen. Eine mit Version 2.1.3 erzeugte Datei ist kompatibel; zusätzliche Plan- oder Nachher-Dateien des fehlgeschlagenen Laufs sind nicht erforderlich. 1. Die erhaltene `before.json` außerhalb des Arbeitsverzeichnisses zusätzlich sichern. 2. Anwendung als Administrator starten und denselben Zielserver wählen, der im Snapshot gespeichert ist. -3. Im Feld **State** die erhaltene Datei auswählen oder ihren vollständigen Pfad eintragen. -4. **Dry run** aktiviert lassen und **Emergency Restore** wählen. -5. Den timestamp-basierten `emergency-restore-plan-*` prüfen. Die bestehende `before.json` wird dabei nicht überschrieben. -6. Dry run deaktivieren, **Emergency Restore** erneut wählen und den expliziten Dialog bestätigen. +3. Über **State...** die erhaltene Datei auswählen oder im Feld **State** ihren vollständigen Pfad eintragen. +4. **Validate State** ausführen. Diese Prüfung öffnet keine WMI-Verbindung und verändert keinen Laufzeitzustand. +5. **Dry run** aktiviert lassen und **Emergency Restore** wählen. +6. Den timestamp-basierten `emergency-restore-plan-*` prüfen. Die bestehende `before.json` wird dabei nicht überschrieben. +7. Dry run deaktivieren, **Emergency Restore** erneut wählen und den expliziten Dialog bestätigen. Der Emergency Restore stellt zuerst sicher, dass der Windows-Dienst `ENTSSO` läuft. Danach folgen Host Instances, Send Ports, Orchestrations und zuletzt Receive Locations. Vor jeder Mutation wird der aktuelle Zustand geprüft; bereits korrekte Zustände werden als `AlreadySatisfied` protokolliert. Ein isolierter Fehler wird als `Failed` festgehalten, während alle späteren unabhängigen Schritte weiter versucht werden. -Jeder Lauf schreibt eine unveränderte Snapshot-Kopie sowie timestamp-basierte Plan-, Ergebnis- und Nachher-Dateien. Ein Ergebnis mit mindestens einem fehlgeschlagenen Schritt bleibt im GUI ausdrücklich `Failed` und verlangt Operator-Review, auch wenn alle anderen Schritte erfolgreich waren. +Jeder Lauf schreibt eine unveränderte Snapshot-Kopie sowie timestamp-basierte Plan- und Ergebnisdateien. Der echte Lauf versucht zusätzlich Nachher-Snapshot und `emergency-restore-diff-*` als JSON, CSV und HTML. Ein Ergebnis mit mindestens einem fehlgeschlagenen Schritt bleibt im GUI ausdrücklich `Failed` und verlangt Operator-Review, auch wenn alle anderen Schritte erfolgreich waren. ## Deinstallation diff --git a/README.md b/README.md index ef739e3..16d65d0 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ WinForms tool for controlled Microsoft BizTalk Server 2020 platform operations d - Controlled shutdown from the current runtime state - Controlled restore from `before.json` - State-aware emergency restore from a preserved `before.json`, including Enterprise SSO startup +- WMI-free validation and file selection for preserved recovery snapshots, including 2.1.3 snapshots - Host instance handling for the selected BizTalk server - Dry-run mode enabled by default - WMI access through `root\MicrosoftBizTalkServer` @@ -45,7 +46,7 @@ WinForms tool for controlled Microsoft BizTalk Server 2020 platform operations d 7. After maintenance, click **Restore** using the saved `before.json`. 8. Click **Snapshot After** and **Compare**. -If a shutdown was interrupted and only the original `before.json` remains, select that file, keep **Dry run** enabled and click **Emergency Restore**. The recovery plan never overwrites the source snapshot, ensures the `ENTSSO` service is running first, skips already-correct runtime states and continues after isolated step failures. Disable Dry run only after reviewing the timestamped emergency plan. +If a shutdown was interrupted and only the original `before.json` remains, select that file with **State...**, run **Validate State**, keep **Dry run** enabled and click **Emergency Restore**. The recovery plan never overwrites the source snapshot, ensures the `ENTSSO` service is running first, skips already-correct runtime states and continues after isolated step failures. Disable Dry run only after reviewing the timestamped emergency plan. A real run automatically writes a timestamped target/actual diff when the post-operation snapshot succeeds. The environment indicator shows `Started`, `Stopped`, `Partial` or `Unknown` from the most recent snapshot. `Clear` removes the visible status and operation log grids; it does not delete files. @@ -75,7 +76,7 @@ Orchestrations that were `Bound` are deliberately left unchanged during restore - `shutdown-plan.json`, `restore-plan.json` - `shutdown-after.json`, `restore-after.json` - `shutdown-result.json`, `restore-result.json` -- Timestamped `emergency-source-before-*`, `emergency-restore-plan-*`, `emergency-restore-result-*` and `emergency-restore-after-*` files +- Timestamped `emergency-source-before-*`, `emergency-restore-plan-*`, `emergency-restore-result-*`, `emergency-restore-after-*` and `emergency-restore-diff-*` files - `diff.json`, `diff.csv`, `diff.html` - Snapshot sidecars: `*.csv`, `*.hosts.csv`, `*.html` - Runtime logs under `%ProgramData%\BizTalkPlatformManagementTool\Logs` diff --git a/docs/ACC-Emergency-Restore-before-json-Runbook.md b/docs/ACC-Emergency-Restore-before-json-Runbook.md new file mode 100644 index 0000000..718118a --- /dev/null +++ b/docs/ACC-Emergency-Restore-before-json-Runbook.md @@ -0,0 +1,119 @@ +# ACC Emergency Restore aus `before.json` + +Stand: 19.08.2026 +Ausführung: Donnerstag, 20.08.2026, oder Montag, 24.08.2026 +Zielserver: `AV23AGPWBI01` +Benötigte Toolversion: `2.2.1` + +## Kurzantwort + +Ja. Die vorhandene, mit Version 2.1.3 erzeugte `before.json` kann von Version 2.2.1 direkt als Recovery-Quelle verwendet werden. Zusätzliche Dateien des abgebrochenen Shutdowns sind nicht erforderlich. Die Kompatibilität wird durch einen Regressionstest abgedeckt; die echte BizTalk-/ACC-Ausführung muss noch vor Ort bestätigt werden. + +Für diesen Teilzustand ausschließlich **Emergency Restore** verwenden. Dieser Modus startet zuerst `ENTSSO`, arbeitet danach den gespeicherten Sollzustand idempotent ab, setzt nach Einzelfehlern fort und überschreibt die Quell-`before.json` nicht. + +## 1. Dateien vorbereiten + +- [ ] Originale `before.json` an einem zweiten Ort unverändert sichern. +- [ ] Optional den Hash der Originaldatei protokollieren: + +```bat +certutil -hashfile before.json SHA256 +``` + +- [ ] Die Release-Datei `BizTalkPlatformManagementTool-Setup.zip.b64.txt` und die zugehörige SHA-256-Datei auf ACC übertragen. +- [ ] Die TXT-Datei rekonstruieren und das ZIP prüfen: + +```bat +certutil -decode BizTalkPlatformManagementTool-Setup.zip.b64.txt BizTalkPlatformManagementTool-Setup.zip +certutil -hashfile BizTalkPlatformManagementTool-Setup.zip SHA256 +type BizTalkPlatformManagementTool-Setup.zip.sha256.txt +``` + +- [ ] Nur fortfahren, wenn beide ZIP-Hashes exakt gleich sind. +- [ ] ZIP in einen neuen Ordner entpacken, die laufende Toolinstanz schließen und `Setup.exe` als Administrator starten. +- [ ] Installation/Update auf Version `2.2.1` vollständig abschließen. +- [ ] Ein neues, leeres Ausgabeverzeichnis anlegen, zum Beispiel `C:\BizTalk-Recovery\2026-08-20` oder `C:\BizTalk-Recovery\2026-08-24`. + +Wichtig: Die Recovery-Quelle außerhalb dieses Ausgabeverzeichnisses lassen. Während der Recovery nicht **Snapshot Before** anklicken, damit kein neuer Teilzustand als vermeintlicher Sollzustand gespeichert wird. + +## 2. `before.json` ohne WMI prüfen + +- [ ] Anwendung als Administrator starten. +- [ ] Im Feld **Server** `AV23AGPWBI01` eintragen. Kurzname und FQDN desselben Hosts werden akzeptiert. +- [ ] Unter **Output** das neue Recovery-Ausgabeverzeichnis auswählen. +- [ ] Über **State...** die gesicherte `before.json` auswählen. +- [ ] **Dry run** aktiviert lassen. +- [ ] **Validate State** anklicken. +- [ ] Im Erfolgsdialog prüfen: + - Snapshotversion ist erwartungsgemäß `2.1.3-net461` oder `2.1.3`. + - Snapshotserver ist `AV23AGPWBI01` beziehungsweise dessen FQDN. + - Erstellzeit und Anzahl von Applications, Host Instances, Receive Locations, Send Ports und Orchestrations wirken plausibel. + +**Validate State** öffnet keine WMI-Verbindung und verändert weder BizTalk noch Windows-Dienste. Bei einer roten Fehlermeldung nicht real ausführen, sondern zuerst Pfad, JSON-Datei und Serverauswahl korrigieren. + +## 3. Emergency-Dry-run prüfen + +- [ ] **Dry run** bleibt aktiviert. +- [ ] **Emergency Restore** anklicken. +- [ ] Den erzeugten `emergency-restore-plan-.json` öffnen. +- [ ] Prüfen, dass der Plan zu `AV23AGPWBI01` gehört. +- [ ] Reihenfolge prüfen: + 1. Windows-Dienst `ENTSSO`, + 2. alle Host Instances, + 3. alle Send Ports, + 4. alle Orchestrations, + 5. alle Receive Locations. +- [ ] Prüfen, dass der Dry-run-Report `emergency-restore-result-.json` geschrieben wurde und keine Initialisierungsfehler enthält. +- [ ] Die timestamp-basierte Datei `emergency-source-before-.json` als zusätzliche Recovery-Evidenz sichern. + +Der Dry-run führt keine Zustandsänderung aus. Er bestätigt aber, dass genau die erhaltene Datei gelesen, validiert und in einen ausführbaren Emergency-Plan übersetzt werden kann. + +## 4. Echten Wiederanlauf ausführen + +- [ ] Sicherstellen, dass SQL Server, Netzwerk und die übrigen BizTalk-Abhängigkeiten erreichbar sind. +- [ ] Noch einmal kontrollieren: Server, Output-Pfad und State-Datei sind korrekt. +- [ ] **Dry run** deaktivieren. +- [ ] **Emergency Restore** anklicken. +- [ ] Im Bestätigungsdialog Quellpfad, Zielserver, Plandatei und Schrittzahl lesen. +- [ ] Erst danach mit **Yes** bestätigen. +- [ ] Anwendung bis zum Abschluss geöffnet lassen. + +Die reale Reihenfolge ist: + +1. `ENTSSO` auf `Running` bringen. +2. Host Instances gemäß `before.json` abgleichen. +3. Send Ports abgleichen. +4. Orchestrations abgleichen; im Snapshot nur `Bound` gebliebene Orchestrations werden bewusst zur manuellen Kontrolle markiert. +5. Receive Locations zuletzt abgleichen. + +Vor jeder Mutation liest Version 2.2.1 den Istzustand. Ein bereits korrekter Zustand wird als `AlreadySatisfied` protokolliert und nicht erneut per WMI geändert. Ein einzelner Fehler wird als `Failed` gespeichert; alle späteren unabhängigen Schritte werden trotzdem versucht. + +## 5. Ergebnis abnehmen + +- [ ] `emergency-restore-result-.json` prüfen. +- [ ] Idealfall: `FailedCount` ist `0`, `InitializationError` und `PostSnapshotError` sind leer. +- [ ] `AlreadySatisfied` ist normal und bedeutet, dass keine unnötige Mutation ausgeführt wurde. +- [ ] `emergency-restore-after-.json` muss nach einem echten Lauf vorhanden sein, sofern der Nachher-Snapshot gelang. +- [ ] `emergency-restore-diff-.json`, `.csv` und `.html` prüfen. Im vollständigen Sollzustand sind keine fachlichen Unterschiede enthalten. +- [ ] Danach **Diagnose** ausführen und den BizTalk-Zustand zusätzlich in der BizTalk Administration Console kontrollieren. +- [ ] Empfang, Verarbeitung und Versand mit dem für ACC vorgesehenen fachlichen Smoke-Test prüfen; Suspended Instances kontrollieren. +- [ ] Recovery-Quelle, Plan, Ergebnis, Nachher-Snapshot, Diff und Laufzeitlog gemeinsam sichern. + +## Sonderfall `RV_PMP_Trigger_Schedule` + +Wenn die Receive Location noch aktiviert ist und `before.json` ebenfalls `Enabled=true` vorgibt, wird sie als `AlreadySatisfied` übersprungen. Damit wird der fehlerhafte Scheduler-WMI-Aufruf nicht erneut ausgelöst. + +Wenn sie deaktiviert ist, obwohl `before.json` sie aktiviert erwartet, versucht das Tool die Aktivierung. Tritt der bekannte Fehler zu `Microsoft.BizTalk.Scheduler, Version=3.13.0.0` erneut auf, wird genau dieser Schritt als `Failed` protokolliert; die übrigen Receive Locations werden weiterbearbeitet. Dann: + +- [ ] Ergebnisreport und vollständige Exception sichern. +- [ ] Aktuellen Zustand der Receive Location in der BizTalk Administration Console prüfen. +- [ ] Scheduler-Assembly/Abhängigkeiten auf dem Administrations-/BizTalk-Host reparieren oder den Schritt nach fachlicher Freigabe manuell ausführen. +- [ ] PROD erst freigeben, wenn der verbleibende Einzelfehler behoben oder ausdrücklich akzeptiert und getestet ist. + +## Wenn der Lauf erneut unterbrochen wird + +Die ursprüngliche `before.json` bleibt unverändert. Version 2.2.1 kann mit derselben Recovery-Quelle erneut gestartet werden, weil bereits erreichte Zustände als `AlreadySatisfied` übersprungen werden. Vor dem Wiederholen immer den jüngsten Ergebnisreport sichern und prüfen, welcher Schritt tatsächlich fehlgeschlagen ist. + +## Go/No-Go für PROD + +ACC ist erst ein belastbarer PROD-Nachweis, wenn der echte Emergency Restore abgeschlossen wurde, der Ergebnisreport vollständig ist, der automatische Diff geprüft wurde und jeder verbliebene `Failed`-Schritt fachlich behandelt wurde. Die lokalen 24 Regressionstests und der Self-Test ersetzen diesen realen BizTalk-/Windows-Nachweis nicht. diff --git a/docs/ACC-Runtime-Shutdown-Exception-2026-08-19.md b/docs/ACC-Runtime-Shutdown-Exception-2026-08-19.md index 900f4f1..ff691bd 100644 --- a/docs/ACC-Runtime-Shutdown-Exception-2026-08-19.md +++ b/docs/ACC-Runtime-Shutdown-Exception-2026-08-19.md @@ -48,6 +48,13 @@ Der neue Button **Emergency Restore** benötigt außer der erhaltenen `before.js 6. arbeitet nach isolierten Fehlern weiter, 7. versucht einen Nachher-Snapshot und schreibt immer den vollständigen Ergebnisreport, soweit das Ausgabeverzeichnis verfügbar ist. +## Recovery-Ergänzungen in Version 2.2.1 + +- **State...** wählt die erhaltene `before.json` direkt aus. +- **Validate State** prüft JSON-Struktur und Zielserver ohne WMI-Verbindung oder Laufzeitänderung und zeigt Snapshotversion, Erstellzeit sowie Artefaktzahlen an. +- Ein 2.1.3-Snapshot wird ausdrücklich als kompatible Recovery-Quelle regressionsgetestet; `ToolVersion` ist keine Versionssperre. +- Nach einem echten Emergency Restore wird bei erfolgreichem Nachher-Snapshot automatisch `emergency-restore-diff-*` als JSON, CSV und HTML geschrieben. + ### Globale Reihenfolge Shutdown: @@ -79,11 +86,11 @@ Die portable Regressionstestsuite enthält ab Version 2.2.0 unter anderem folgen - globale Shutdown-/Restore-Reihenfolge mit mehreren Anwendungen, - Kurzname/FQDN-Gleichheit für Host-Instance-Schritte. -Die lokale Suite umfasst 23 bestandene Tests. Die abschließende Freigabe für PROD benötigt weiterhin einen Windows-/BizTalk-Test auf ACC: echter Scheduler-Fehlerpfad, vollständige Fortsetzung, Ergebnisreport, Emergency-Dry-run und realer Wiederanlauf aus einer Kopie der vorhandenen `before.json`. +Die lokale Suite umfasst ab Version 2.2.1 insgesamt 24 Tests einschließlich der expliziten 2.1.3-Snapshot-Kompatibilität. Die abschließende Freigabe für PROD benötigt weiterhin einen Windows-/BizTalk-Test auf ACC: echter Scheduler-Fehlerpfad, vollständige Fortsetzung, Ergebnisreport, Emergency-Dry-run und realer Wiederanlauf aus einer Kopie der vorhandenen `before.json`. ## PROD-Freigabekriterien -- Version 2.2.0 installieren und Installer-Self-Test bestätigen. +- Version 2.2.1 installieren und Installer-Self-Test bestätigen. - `Diagnose` und einen vollständigen Shutdown-Dry-run ausführen. - Kontrolliert nachweisen, dass eine isolierte Scheduler-Exception spätere Schritte nicht blockiert. - `shutdown-result.json` und Nachher-Snapshot prüfen. diff --git a/src/BizTalkPlatformManagementTool.Setup/InstallerEngine.cs b/src/BizTalkPlatformManagementTool.Setup/InstallerEngine.cs index c5f543f..fd577a6 100644 --- a/src/BizTalkPlatformManagementTool.Setup/InstallerEngine.cs +++ b/src/BizTalkPlatformManagementTool.Setup/InstallerEngine.cs @@ -23,7 +23,7 @@ namespace BizTalkPlatformManagementTool.Setup private const string ProductName = "BizTalk Platform Management Tool"; /// Aktuelle Produktversion des Installers und Uninstall-Eintrags. - private const string ProductVersion = "2.2.0"; + private const string ProductVersion = "2.2.1"; /// /// Wartezeiten zwischen Wiederholungen atomarer Verzeichnisverschiebungen. diff --git a/src/BizTalkPlatformManagementTool.Setup/MainForm.cs b/src/BizTalkPlatformManagementTool.Setup/MainForm.cs index 78ba9d7..e97d547 100644 --- a/src/BizTalkPlatformManagementTool.Setup/MainForm.cs +++ b/src/BizTalkPlatformManagementTool.Setup/MainForm.cs @@ -65,7 +65,7 @@ namespace BizTalkPlatformManagementTool.Setup { AutoSize = true, Font = new Font(Font.FontFamily, 14, FontStyle.Bold), - Text = "BizTalk Platform Management Tool 2.2.0" + Text = "BizTalk Platform Management Tool 2.2.1" }); root.Controls.Add(new Label { diff --git a/src/BizTalkPlatformManagementTool.Setup/Properties/AssemblyInfo.cs b/src/BizTalkPlatformManagementTool.Setup/Properties/AssemblyInfo.cs index 4fc329d..873a0e8 100644 --- a/src/BizTalkPlatformManagementTool.Setup/Properties/AssemblyInfo.cs +++ b/src/BizTalkPlatformManagementTool.Setup/Properties/AssemblyInfo.cs @@ -8,6 +8,6 @@ using System.Runtime.InteropServices; [assembly: AssemblyProduct("BizTalk Platform Management Tool")] [assembly: ComVisible(false)] [assembly: Guid("675b68a9-bd80-46a5-b8c5-3b11b0b374e2")] -[assembly: AssemblyVersion("2.2.0.0")] -[assembly: AssemblyFileVersion("2.2.0.0")] +[assembly: AssemblyVersion("2.2.1.0")] +[assembly: AssemblyFileVersion("2.2.1.0")] [assembly: InternalsVisibleTo("BizTalkPlatformManagementTool.Tests")] diff --git a/src/BizTalkPlatformManagementTool.Setup/app.manifest b/src/BizTalkPlatformManagementTool.Setup/app.manifest index 6d5ab7a..ef6a3f0 100644 --- a/src/BizTalkPlatformManagementTool.Setup/app.manifest +++ b/src/BizTalkPlatformManagementTool.Setup/app.manifest @@ -1,6 +1,6 @@ - + diff --git a/src/BizTalkPlatformManagementTool/Properties/AssemblyInfo.cs b/src/BizTalkPlatformManagementTool/Properties/AssemblyInfo.cs index b715322..bbe9b00 100644 --- a/src/BizTalkPlatformManagementTool/Properties/AssemblyInfo.cs +++ b/src/BizTalkPlatformManagementTool/Properties/AssemblyInfo.cs @@ -9,6 +9,6 @@ using System.Runtime.InteropServices; [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: ComVisible(false)] [assembly: Guid("2c5b2c0a-f407-46c2-9e3b-1fa09fa8445a")] -[assembly: AssemblyVersion("2.2.0.0")] -[assembly: AssemblyFileVersion("2.2.0.0")] +[assembly: AssemblyVersion("2.2.1.0")] +[assembly: AssemblyFileVersion("2.2.1.0")] [assembly: InternalsVisibleTo("BizTalkPlatformManagementTool.Tests")] diff --git a/src/BizTalkPlatformManagementTool/Services/BizTalkOperationService.cs b/src/BizTalkPlatformManagementTool/Services/BizTalkOperationService.cs index 3ad018b..1f09073 100644 --- a/src/BizTalkPlatformManagementTool/Services/BizTalkOperationService.cs +++ b/src/BizTalkPlatformManagementTool/Services/BizTalkOperationService.cs @@ -16,7 +16,7 @@ namespace BizTalkPlatformManagementTool.Services /// /// Current tool version written into generated snapshots. /// - public const string Version = "2.2.0-net461"; + public const string Version = "2.2.1-net461"; /// /// Fallback application name used when WMI does not expose an application property. diff --git a/src/BizTalkPlatformManagementTool/Ui/MainForm.cs b/src/BizTalkPlatformManagementTool/Ui/MainForm.cs index d5a9338..171b828 100644 --- a/src/BizTalkPlatformManagementTool/Ui/MainForm.cs +++ b/src/BizTalkPlatformManagementTool/Ui/MainForm.cs @@ -110,6 +110,11 @@ namespace BizTalkPlatformManagementTool.Ui /// private Button _restoreButton; + /// + /// Button that validates the selected recovery snapshot without contacting WMI. + /// + private Button _validateStateButton; + /// /// Button that performs a state-aware best-effort recovery from an existing snapshot. /// @@ -220,6 +225,8 @@ namespace BizTalkPlatformManagementTool.Ui var browseButton = new Button { Text = "Browse...", Dock = DockStyle.Fill, Margin = new Padding(8, 5, 8, 5) }; browseButton.Click += BrowseButtonClick; + var stateBrowseButton = new Button { Text = "State...", Dock = DockStyle.Fill, Margin = new Padding(8, 5, 8, 5) }; + stateBrowseButton.Click += StateBrowseButtonClick; _environmentStatusLabel.Dock = DockStyle.Fill; _environmentStatusLabel.Margin = new Padding(12, 5, 0, 5); @@ -239,6 +246,7 @@ namespace BizTalkPlatformManagementTool.Ui panel.Controls.Add(_stateFileTextBox, 1, 1); panel.SetColumnSpan(_stateFileTextBox, 2); panel.Controls.Add(_dryRunCheckBox, 3, 1); + panel.Controls.Add(stateBrowseButton, 4, 1); panel.Controls.Add(Label("Poll sec."), 7, 1); panel.Controls.Add(_pollInput, 8, 1); panel.Controls.Add(_environmentStatusLabel, 9, 0); @@ -260,6 +268,7 @@ namespace BizTalkPlatformManagementTool.Ui _compareButton = ActionButton("Compare", CompareClick); _shutdownButton = ActionButton("Shutdown", ShutdownClick); _restoreButton = ActionButton("Restore", RestoreClick); + _validateStateButton = ActionButton("Validate State", ValidateStateClick); _emergencyRestoreButton = ActionButton("Emergency Restore", EmergencyRestoreClick); _emergencyRestoreButton.Width = 142; _clearButton = ActionButton("Clear", ClearClick); @@ -271,6 +280,7 @@ namespace BizTalkPlatformManagementTool.Ui panel.Controls.Add(_compareButton); panel.Controls.Add(_shutdownButton); panel.Controls.Add(_restoreButton); + panel.Controls.Add(_validateStateButton); panel.Controls.Add(_emergencyRestoreButton); panel.Controls.Add(_clearButton); panel.Controls.Add(_closeButton); @@ -425,6 +435,39 @@ namespace BizTalkPlatformManagementTool.Ui }); } + /// + /// Validates the selected state file and target server without opening a WMI connection + /// or changing any runtime state. + /// + /// The control that raised the event. + /// The event arguments. + private void ValidateStateClick(object sender, EventArgs e) + { + var options = GetOptions(); + RunAsync("Validating recovery state file...", () => + { + var sourcePath = ResolveStateFile(options); + var snapshot = JsonFileStore.Load(sourcePath); + SnapshotValidator.EnsureServerMatches(snapshot, options.Server); + ShowSnapshot(snapshot); + + var receiveLocationCount = snapshot.Applications.Sum(x => x.ReceiveLocations.Count); + var sendPortCount = snapshot.Applications.Sum(x => x.SendPorts.Count); + var orchestrationCount = snapshot.Applications.Sum(x => x.Orchestrations.Count); + var summary = "Recovery state is valid. Source=" + sourcePath + + "; SnapshotVersion=" + (snapshot.ToolVersion ?? "(not recorded)") + + "; CreatedAt=" + (snapshot.CreatedAt ?? "(not recorded)") + + "; Server=" + snapshot.Server + + "; Applications=" + snapshot.Applications.Count + + "; HostInstances=" + snapshot.HostInstances.Count + + "; ReceiveLocations=" + receiveLocationCount + + "; SendPorts=" + sendPortCount + + "; Orchestrations=" + orchestrationCount + "."; + _logger.Success(summary); + ShowStateValidationSummary(summary); + }); + } + /// /// Handles emergency recovery from the preserved state file without creating /// or overwriting before.json. @@ -454,7 +497,24 @@ namespace BizTalkPlatformManagementTool.Ui } var report = _service.ExecutePlan(plan, options); - CapturePostOperationSnapshot(options, report, "emergency-restore-after-" + stamp + ".json"); + var after = CapturePostOperationSnapshot(options, report, "emergency-restore-after-" + stamp + ".json"); + if (after != null) + { + try + { + var diff = SnapshotComparer.Compare(snapshot, after); + var diffPath = _service.SaveDiff(options.OutputDirectory, "emergency-restore-diff-" + stamp + ".json", diff); + _logger.Success("Saved emergency recovery target/actual comparison: " + diffPath); + } + catch (Exception ex) + { + var error = "Emergency recovery comparison failed: " + FormatException(ex); + report.PostSnapshotError = string.IsNullOrWhiteSpace(report.PostSnapshotError) + ? error + : report.PostSnapshotError + " | " + error; + _logger.Error(error + " The execution report will still be saved."); + } + } var reportPath = _service.SaveExecutionReport(options.OutputDirectory, "emergency-restore-result-" + stamp + ".json", report); ShowExecutionReport(report); ThrowWhenOperatorReviewIsRequired(report, reportPath); @@ -467,22 +527,24 @@ namespace BizTalkPlatformManagementTool.Ui /// The operation options. /// The report that receives a snapshot error. /// The snapshot file name. - private void CapturePostOperationSnapshot(OperationOptions options, OperationExecutionReport report, string fileName) + private BizTalkSnapshot CapturePostOperationSnapshot(OperationOptions options, OperationExecutionReport report, string fileName) { if (options.DryRun) { - return; + return null; } try { var after = _service.CreateSnapshot(options.Server); _service.SaveSnapshot(options.OutputDirectory, fileName, after); ShowSnapshot(after); + return after; } catch (Exception ex) { report.PostSnapshotError = FormatException(ex); _logger.Error("Post-operation snapshot failed, but the execution report will still be saved. Error: " + report.PostSnapshotError); + return null; } } @@ -788,6 +850,7 @@ namespace BizTalkPlatformManagementTool.Ui _compareButton.Enabled = !busy; _shutdownButton.Enabled = !busy; _restoreButton.Enabled = !busy; + _validateStateButton.Enabled = !busy; _emergencyRestoreButton.Enabled = !busy; _clearButton.Enabled = !busy; _closeButton.Enabled = !busy; @@ -812,6 +875,54 @@ namespace BizTalkPlatformManagementTool.Ui } } + /// + /// Handles selection of an existing JSON recovery snapshot. + /// + /// The control that raised the event. + /// The event arguments. + private void StateBrowseButtonClick(object sender, EventArgs e) + { + using (var dialog = new OpenFileDialog()) + { + dialog.Title = "Select preserved BizTalk state snapshot"; + dialog.Filter = "BizTalk snapshot (*.json)|*.json|All files (*.*)|*.*"; + dialog.CheckFileExists = true; + dialog.Multiselect = false; + var currentPath = ResolveStateFile(GetOptions()); + if (File.Exists(currentPath)) + { + dialog.InitialDirectory = Path.GetDirectoryName(currentPath); + dialog.FileName = Path.GetFileName(currentPath); + } + if (dialog.ShowDialog(this) == DialogResult.OK) + { + _stateFileTextBox.Text = dialog.FileName; + } + } + } + + /// + /// Shows the successful WMI-free state-file validation result to the operator. + /// + /// The complete validation summary. + private void ShowStateValidationSummary(string summary) + { + Action show = () => MessageBox.Show( + this, + summary + "\n\nNo runtime state was changed and no WMI connection was opened.", + "Recovery State Valid", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + if (InvokeRequired) + { + Invoke(show); + } + else + { + show(); + } + } + /// /// Executes a UI update on the UI thread when required. /// diff --git a/src/BizTalkPlatformManagementTool/app.manifest b/src/BizTalkPlatformManagementTool/app.manifest index e224801..80e6843 100644 --- a/src/BizTalkPlatformManagementTool/app.manifest +++ b/src/BizTalkPlatformManagementTool/app.manifest @@ -1,6 +1,6 @@ - + diff --git a/tests/BizTalkPlatformManagementTool.Tests/Program.cs b/tests/BizTalkPlatformManagementTool.Tests/Program.cs index fe9465a..f2a43c4 100644 --- a/tests/BizTalkPlatformManagementTool.Tests/Program.cs +++ b/tests/BizTalkPlatformManagementTool.Tests/Program.cs @@ -24,6 +24,7 @@ namespace BizTalkPlatformManagementTool.Tests Run("JsonRoundTripIsBomTolerantAndAtomic", JsonRoundTripIsBomTolerantAndAtomic); Run("DiffUsesApplicationAndNameIdentity", DiffUsesApplicationAndNameIdentity); Run("RestoreRejectsDifferentServer", RestoreRejectsDifferentServer); + Run("Legacy213SnapshotIsAcceptedForRecovery", Legacy213SnapshotIsAcceptedForRecovery); Run("RestorePlanUsesSafeOrder", RestorePlanUsesSafeOrder); Run("ShutdownPlanUsesGlobalSafeOrder", ShutdownPlanUsesGlobalSafeOrder); Run("EmergencyRestorePlanStartsSsoFirst", EmergencyRestorePlanStartsSsoFirst); @@ -98,6 +99,25 @@ namespace BizTalkPlatformManagementTool.Tests Expect(() => SnapshotValidator.EnsureServerMatches(snapshot, "BIZTALK-B")); } + /// Prüft, dass ein erhaltener 2.1.3-Snapshot ohne Versionssperre als Recovery-Quelle dient. + private static void Legacy213SnapshotIsAcceptedForRecovery() + { + InTemp(directory => + { + var original = Snapshot("APP", "PORT", ArtifactStates.SendPortStarted); + original.ToolVersion = "2.1.3-net461"; + original.Server = "BIZTALK-A.example.local"; + var path = Path.Combine(directory, "before.json"); + JsonFileStore.Save(path, original); + + var loaded = JsonFileStore.Load(path); + SnapshotValidator.EnsureServerMatches(loaded, "BIZTALK-A"); + var plan = new BizTalkOperationService(null).CreateEmergencyRestorePlan(loaded, "BIZTALK-A"); + Assert(plan.Steps.First().Kind == "WindowsService", "legacy snapshot did not create an emergency plan"); + Assert(loaded.ToolVersion == "2.1.3-net461", "legacy tool version was not preserved"); + }); + } + /// Prüft die sichere Restore-Reihenfolge und den Schutz gebundener Orchestrierungen. private static void RestorePlanUsesSafeOrder() {