Add recovery preflight and ACC restart runbook

This commit is contained in:
2026-08-19 18:13:03 +02:00
parent 9ce7e8d45a
commit 1228f71765
16 changed files with 296 additions and 24 deletions
+3 -1
View File
@@ -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
+10
View File
@@ -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.
+2 -1
View File
@@ -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.
+7 -6
View File
@@ -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
+3 -2
View File
@@ -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`
@@ -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-<Zeitstempel>.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-<Zeitstempel>.json` geschrieben wurde und keine Initialisierungsfehler enthält.
- [ ] Die timestamp-basierte Datei `emergency-source-before-<Zeitstempel>.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-<Zeitstempel>.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-<Zeitstempel>.json` muss nach einem echten Lauf vorhanden sein, sofern der Nachher-Snapshot gelang.
- [ ] `emergency-restore-diff-<Zeitstempel>.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.
@@ -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.
@@ -23,7 +23,7 @@ namespace BizTalkPlatformManagementTool.Setup
private const string ProductName = "BizTalk Platform Management Tool";
/// <summary>Aktuelle Produktversion des Installers und Uninstall-Eintrags.</summary>
private const string ProductVersion = "2.2.0";
private const string ProductVersion = "2.2.1";
/// <summary>
/// Wartezeiten zwischen Wiederholungen atomarer Verzeichnisverschiebungen.
@@ -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
{
@@ -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")]
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="2.2.0.0" name="BizTalkPlatformManagementTool.Setup" />
<assemblyIdentity version="2.2.1.0" name="BizTalkPlatformManagementTool.Setup" />
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security><requestedPrivileges><requestedExecutionLevel level="requireAdministrator" uiAccess="false" /></requestedPrivileges></security>
</trustInfo>
@@ -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")]
@@ -16,7 +16,7 @@ namespace BizTalkPlatformManagementTool.Services
/// <summary>
/// Current tool version written into generated snapshots.
/// </summary>
public const string Version = "2.2.0-net461";
public const string Version = "2.2.1-net461";
/// <summary>
/// Fallback application name used when WMI does not expose an application property.
@@ -110,6 +110,11 @@ namespace BizTalkPlatformManagementTool.Ui
/// </summary>
private Button _restoreButton;
/// <summary>
/// Button that validates the selected recovery snapshot without contacting WMI.
/// </summary>
private Button _validateStateButton;
/// <summary>
/// Button that performs a state-aware best-effort recovery from an existing snapshot.
/// </summary>
@@ -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
});
}
/// <summary>
/// Validates the selected state file and target server without opening a WMI connection
/// or changing any runtime state.
/// </summary>
/// <param name="sender">The control that raised the event.</param>
/// <param name="e">The event arguments.</param>
private void ValidateStateClick(object sender, EventArgs e)
{
var options = GetOptions();
RunAsync("Validating recovery state file...", () =>
{
var sourcePath = ResolveStateFile(options);
var snapshot = JsonFileStore.Load<BizTalkSnapshot>(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);
});
}
/// <summary>
/// Handles emergency recovery from the preserved state file without creating
/// or overwriting <c>before.json</c>.
@@ -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
/// <param name="options">The operation options.</param>
/// <param name="report">The report that receives a snapshot error.</param>
/// <param name="fileName">The snapshot file name.</param>
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
}
}
/// <summary>
/// Handles selection of an existing JSON recovery snapshot.
/// </summary>
/// <param name="sender">The control that raised the event.</param>
/// <param name="e">The event arguments.</param>
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;
}
}
}
/// <summary>
/// Shows the successful WMI-free state-file validation result to the operator.
/// </summary>
/// <param name="summary">The complete validation summary.</param>
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();
}
}
/// <summary>
/// Executes a UI update on the UI thread when required.
/// </summary>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="2.2.0.0" name="BizTalkPlatformManagementTool" />
<assemblyIdentity version="2.2.1.0" name="BizTalkPlatformManagementTool" />
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
@@ -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<InvalidOperationException>(() => SnapshotValidator.EnsureServerMatches(snapshot, "BIZTALK-B"));
}
/// <summary>Prüft, dass ein erhaltener 2.1.3-Snapshot ohne Versionssperre als Recovery-Quelle dient.</summary>
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<BizTalkSnapshot>(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");
});
}
/// <summary>Prüft die sichere Restore-Reihenfolge und den Schutz gebundener Orchestrierungen.</summary>
private static void RestorePlanUsesSafeOrder()
{