From 8b1c14b892fa5ab9496eea1ba836217f6d181d92 Mon Sep 17 00:00:00 2001 From: Johannes Rest Date: Thu, 27 Aug 2026 20:17:16 +0200 Subject: [PATCH] Make shutdown checkpoint dialogs unmistakable --- AI-README.md | 3 +- CHANGELOG.md | 14 +++ Installation.md | 4 +- ...ledTask-und-Laufzeitlogging-2026-08-26.txt | 8 +- PROD-Shutdown-Dialog-Sichtbarkeit-2.3.4.txt | 55 ++++++++ README.md | 4 +- ...uledTask-und-Laufzeitlogging-2026-08-26.md | 8 +- ...Shutdown-Dialog-Sichtbarkeit-2026-08-27.md | 57 +++++++++ .../InstallerEngine.cs | 2 +- .../MainForm.cs | 2 +- .../Properties/AssemblyInfo.cs | 4 +- .../app.manifest | 2 +- .../BizTalkPlatformManagementTool.csproj | 1 + .../Properties/AssemblyInfo.cs | 4 +- .../Services/BizTalkOperationService.cs | 2 +- .../Ui/MainForm.cs | 118 ++++++++++++++---- .../Ui/ShutdownOperatorPrompts.cs | 109 ++++++++++++++++ .../app.manifest | 2 +- .../Program.cs | 34 +++++ 19 files changed, 385 insertions(+), 48 deletions(-) create mode 100644 PROD-Shutdown-Dialog-Sichtbarkeit-2.3.4.txt create mode 100644 docs/PROD-Shutdown-Dialog-Sichtbarkeit-2026-08-27.md create mode 100644 src/BizTalkPlatformManagementTool/Ui/ShutdownOperatorPrompts.cs diff --git a/AI-README.md b/AI-README.md index 6b63880..37f581b 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.3.3. +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.3.4. ## Installerinvarianten @@ -37,6 +37,7 @@ Die zentrale Implementierung liegt in `src/BizTalkPlatformManagementTool.Setup/I - 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. +- Alle laufzeitrelevanten Bestätigungsdialoge müssen mit dem Hauptformular als Owner angezeigt werden. Vor dem echten Drain-Checkpoint muss der Status sichtbar `ACTION REQUIRED`/pausiert melden; Dry-run muss ausdrücklich erklären, dass keine Runtime-Änderung und keine echte Drain-Entscheidung erfolgt. - 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. - Datei-Logging und optionale GUI-Logweiterleitung sind Diagnosekanäle und dürfen niemals einen fachlichen Planschritt oder dessen Fehlerbehandlung unterbrechen. diff --git a/CHANGELOG.md b/CHANGELOG.md index 1aa76fe..dffe93c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,20 @@ # Changelog +## [2.3.4] - 2026-08-27 +### Fixed +- Shutdown plan review, inbound-drain and emergency-restore confirmations are now owned by the main window, preventing a modal decision from being hidden behind the application in RDP and multi-monitor sessions. +- The shutdown status now changes from a generic preparation message to an explicit `ACTION REQUIRED` / paused state before the real inbound-drain decision. + +### Changed +- Dry-run and real execution are distinguished with prominent mode text, color and persistent status guidance; execution settings cannot be changed while an operation is active. +- A shutdown Dry-run now displays an owned informational notice explaining that it changes no receive locations and therefore does not open the real drain decision dialog. +- The prepared real-shutdown confirmation explicitly announces the second decision after the receive-location phase; closing the real checkpoint dialog is explicitly documented as a safe stop. + +### Added +- Regression coverage for checkpoint creation when later shutdown work exists without enabled receive locations, and for distinct Dry-run, prepared-plan and paused-checkpoint operator guidance. +- A dedicated PROD root-cause analysis, validation runbook and plain-text handoff for shutdown-dialog visibility. + ## [2.3.3] - 2026-08-27 ### Fixed - Snapshot, shutdown and restore rows no longer show every Send Port, Receive Location and Orchestration under `(Unknown Application)` when the BizTalk runtime WMI classes omit application ownership. diff --git a/Installation.md b/Installation.md index 770be90..4ef08d6 100644 --- a/Installation.md +++ b/Installation.md @@ -15,8 +15,8 @@ Das Release erzeugt folgende Dateien unter `artifacts`: - `BizTalkPlatformManagementTool-Setup.zip.b64.txt`: Certutil-kompatible Base64-Übertragung - `BizTalkPlatformManagementTool-Setup.zip.sha256.txt`: SHA-256 des ZIP-Archivs - `BizTalkPlatformManagementTool-Setup.zip`: direkt entpackbares Installationspaket -- `BizTalkPlatformManagementTool-2.3.3-source.zip.b64.txt`: vollständiger Quellstand als Base64-TXT -- `BizTalkPlatformManagementTool-2.3.3-source.zip.sha256.txt`: SHA-256 des Quellarchivs +- `BizTalkPlatformManagementTool-2.3.4-source.zip.b64.txt`: vollständiger Quellstand als Base64-TXT +- `BizTalkPlatformManagementTool-2.3.4-source.zip.sha256.txt`: SHA-256 des Quellarchivs Auf dem Zielsystem wird die TXT-Datei so rekonstruiert und geprüft: diff --git a/PROD-ScheduledTask-und-Laufzeitlogging-2026-08-26.txt b/PROD-ScheduledTask-und-Laufzeitlogging-2026-08-26.txt index 434b1ad..c0a739e 100644 --- a/PROD-ScheduledTask-und-Laufzeitlogging-2026-08-26.txt +++ b/PROD-ScheduledTask-und-Laufzeitlogging-2026-08-26.txt @@ -1,6 +1,6 @@ PROD-RUNBOOK: SCHEDULEDTASK-STEUERUNG UND PERSISTENTES LAUFZEITLOGGING Stand: 2026-08-26 -Zielversion: 2.3.3 +Zielversion: 2.3.4 Adapter: BizTalk ScheduledTask Adapter 7.0.2 Adapterpfad: C:\Program Files (x86)\BizTalk ScheduledTask Adapter 7.0.2 @@ -13,8 +13,8 @@ laufenden Toolprozess. Das Tool kopiert keine DLL und aendert den GAC nicht. VORBEREITUNG 1. before.json, Reports und vorhandene Logs extern sichern. -2. Setup 2.3.3 als Administrator installieren/aktualisieren. -3. setup_version=2.3.3.0 und erfolgreichen Ziel-Self-Test pruefen. +2. Setup 2.3.4 als Administrator installieren/aktualisieren. +3. setup_version=2.3.4.0 und erfolgreichen Ziel-Self-Test pruefen. 4. Tool als Administrator starten, gruenen Startup-Log-Pruefeintrag kontrollieren, dann Log Folder oeffnen. Normalfall: %ProgramData%\BizTalkPlatformManagementTool\Logs. 5. Im lokalen BizTalk-Ordner Microsoft.BizTalk.Scheduler.dll pruefen. @@ -74,7 +74,7 @@ ABNAHME - Teilfehler bleibt Failed, spaetere unabhaengige Schritte laufen weiter. - Historie erscheint nach Neustart im Grid. - Vortage komprimiert, 30 Kalendertage aufbewahrt. -- Setup/Tool/Reports zeigen 2.3.3 bzw. 2.3.3-net461. +- Setup/Tool/Reports zeigen 2.3.4 bzw. 2.3.4-net461. Detailfassung: docs\PROD-ScheduledTask-und-Laufzeitlogging-2026-08-26.md diff --git a/PROD-Shutdown-Dialog-Sichtbarkeit-2.3.4.txt b/PROD-Shutdown-Dialog-Sichtbarkeit-2.3.4.txt new file mode 100644 index 0000000..e879cff --- /dev/null +++ b/PROD-Shutdown-Dialog-Sichtbarkeit-2.3.4.txt @@ -0,0 +1,55 @@ +PROD-HANDOFF: SHUTDOWN-DIALOG-SICHTBARKEIT - VERSION 2.3.4 +Datum: 2026-08-27 + +BEFUND +Nach der Receive-Location-Phase wurde kein sichtbarer Dialog zur Bestaetigung +des Leerlaufens wahrgenommen. + +URSACHEN +1. Dry run ist standardmaessig aktiv. Er deaktiviert keine Receive Location und + ruft deshalb bewusst keinen echten Drain-Entscheidungsdialog auf. Dieser + Unterschied war waehrend der Ausfuehrung nicht deutlich genug sichtbar. +2. Die bisherigen MessageBoxen hatten keinen Owner. In RDP-/Multi-Monitor- + Sitzungen konnte ein modaler Plan- oder Drain-Dialog hinter dem Hauptfenster + liegen, waehrend dort nur "Preparing shutdown..." sichtbar blieb. + +SICHERHEIT +Der persistierte OperatorCheckpoint war vorhanden. Ohne Dialog-Callback, bei +Fehler, Nein oder Schliessen stoppt der echte Shutdown fail-closed. Spaetere +Orchestrierungen, Send Ports und Host Instances bleiben NotExecuted. + +FIX 2.3.4 +- Alle runtime-relevanten Bestaetigungen sind an das Hauptfenster gebunden. +- Am echten Checkpoint steht sichtbar ACTION REQUIRED / Shutdown paused. +- Der erste Realbetrieb-Dialog kuendigt die zweite Drain-Entscheidung an. +- Dry run und REAL EXECUTION sind farblich und textlich eindeutig. +- Dry run zeigt einen eigenen Hinweis: keine Mutation, keine echte Drain- + Entscheidung. +- Einstellungen sind waehrend einer laufenden Operation gesperrt. + +PROD-ABNAHME +1. Setup 2.3.4 installieren; setup_version=2.3.4.0 und Ziel-Self-Test pruefen. +2. Dry run: Shutdown Dry Run - No Runtime Changes muss sichtbar sein; keine + BizTalk-Mutation. +3. shutdown-plan.json: OperatorCheckpoint muss vor jeder spaeteren Shutdown- + Phase stehen. +4. Freigegebenes Wartungsfenster: Dry run deaktivieren; REAL EXECUTION muss + sichtbar sein. +5. Ersten gespeicherten Plan bestaetigen. +6. Nach Receive Locations: ACTION REQUIRED und owner-gebundener Drain-Dialog. +7. Zuerst Nein testen: CheckpointDecision=Stop, alle spaeteren Zeilen + NotExecuted. +8. Danach kontrolliert wiederholen; Group Hub und Monitoring pruefen; erst bei + bestaetigtem Leerlauf Ja auswaehlen. +9. RDP minimieren/wiederherstellen und ggf. Monitorwechsel testen. + +ALTE LAEUFE UNTERSCHEIDEN +- DryRun=true + keine CheckpointDecision: erwartete Simulation. +- Kein Ergebnis + UI bleibt Preparing shutdown: moeglicherweise verdeckter + Dialog; Alt+Tab pruefen, Prozess nicht blind beenden. +- CheckpointDecision=Stop/Error: sicher vor spaeteren Phasen gestoppt. +- CheckpointDecision=Continue: positive Bedienerentscheidung wurde erfasst. + +Portable Verifikation: Release-Build aller vier Projekte, 43 Regressionstests, +Runtime-Self-Test und manifest-/hashgepruefte Paketierung. Die finale Dialog- +und BizTalk-Pruefung erfolgt auf Windows/PROD. diff --git a/README.md b/README.md index 0426264..fed53f1 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ WinForms tool for controlled Microsoft BizTalk Server 2020 platform operations d - Thirty-day runtime-log retention with GZip compression for completed daily logs - Process-local ScheduledTask adapter dependency resolution for `scheduler:` receive locations without modifying the GAC - Fail-closed operator drain checkpoint after all receive locations and before later shutdown phases +- Owner-bound shutdown confirmations with an explicit paused/action-required status and an unambiguous Dry-run notice - Best-effort plan execution: one isolated WMI failure is recorded while remaining independent steps continue - Idempotent execution that skips artifacts already in the requested target state - Durable per-step result reports even when a shutdown or restore completes only partially @@ -78,7 +79,7 @@ Restore: Orchestrations that were `Bound` are deliberately left unchanged during restore to avoid accidentally making them `Unbound`. -The drain checkpoint is part of `shutdown-plan.json`. In a real run, **Yes** records `CheckpointDecision=Continue`; **No**, a closed dialog or a callback error fails closed before orchestrations, send ports and host instances. All later rows are persisted as `NotExecuted`, followed by a partial-state snapshot and `shutdown-result.json`. Dry-run shows the checkpoint but never opens a decision dialog. +The drain checkpoint is part of `shutdown-plan.json`. In a real run, the status changes to **ACTION REQUIRED**, and the owned dialog remains in front of the main window. **Yes** records `CheckpointDecision=Continue`; **No**, a closed dialog or a callback error fails closed before orchestrations, send ports and host instances. All later rows are persisted as `NotExecuted`, followed by a partial-state snapshot and `shutdown-result.json`. Dry-run shows the checkpoint but never opens a decision dialog; it displays a separate notice explaining that no receive location was changed. BizTalk applications displaying `Partially Started` immediately after receive locations are disabled is expected. Microsoft defines “Partial Stop - Allow running instances to continue” as disabling only receive locations so in-flight instances can finish. The tool deliberately does not issue an additional application-level stop: its stronger variants can stop or unenlist artifacts, remove subscriptions, undeploy policies or terminate instances. The existing artifact-level plan preserves exact snapshot state and controlled global ordering. @@ -148,5 +149,6 @@ Targeted German inline comments explain non-obvious operational decisions such a - [PROD runtime-log storage validation and 2.3.1 fix](docs/PROD-Laufzeitlog-Ablage-2026-08-26.md) - [PROD shutdown drain checkpoint and application-status analysis](docs/PROD-Shutdown-Drain-Checkpoint-2026-08-26.md) - [PROD application-association analysis and 2.3.3 validation](docs/PROD-Application-Zuordnung-2026-08-27.md) +- [PROD shutdown-dialog visibility analysis and 2.3.4 validation](docs/PROD-Shutdown-Dialog-Sichtbarkeit-2026-08-27.md) - [AI maintainer handoff](AI-README.md) - [References](REFERENCES.md) diff --git a/docs/PROD-ScheduledTask-und-Laufzeitlogging-2026-08-26.md b/docs/PROD-ScheduledTask-und-Laufzeitlogging-2026-08-26.md index c864b3d..935910b 100644 --- a/docs/PROD-ScheduledTask-und-Laufzeitlogging-2026-08-26.md +++ b/docs/PROD-ScheduledTask-und-Laufzeitlogging-2026-08-26.md @@ -2,7 +2,7 @@ **Stand:** 2026-08-26 -**Zielversion:** 2.3.3 +**Zielversion:** 2.3.4 **Betroffener Adapter:** BizTalk ScheduledTask Adapter 7.0.2 @@ -17,8 +17,8 @@ Version 2.3.1 erkennt ScheduledTask-Schritte an Adaptername oder `scheduler:`-UR ## Vorbereitung 1. Vorhandene `before.json`, Ergebnisdateien und Laufzeitlogs außerhalb des Installationsordners sichern. -2. Setup 2.3.3 als Administrator installieren beziehungsweise aktualisieren. -3. Im Setup-Log `setup_version=2.3.3.0` und erfolgreichen Ziel-Self-Test prüfen. +2. Setup 2.3.4 als Administrator installieren beziehungsweise aktualisieren. +3. Im Setup-Log `setup_version=2.3.4.0` und erfolgreichen Ziel-Self-Test prüfen. 4. Tool als Administrator starten, den grünen Eintrag `Runtime log storage verified by startup append` prüfen und mit **Log Folder** den dort genannten aktiven Pfad öffnen. Normalfall ist `%ProgramData%\BizTalkPlatformManagementTool\Logs`. 5. Prüfen, dass der lokale BizTalk-Installationsordner `Microsoft.BizTalk.Scheduler.dll` enthält. 6. Nur wenn BizTalk oder der Adapter abweichend installiert wurde: `AdapterAssemblySearchPaths` in `BizTalkPlatformManagementTool.exe.config` um den vorhandenen lokalen Ordner ergänzen. Mehrere Pfade werden mit Semikolon getrennt. Keine DLL aus ACC, einer alten BizTalk-Version oder einem Downloadordner kopieren. @@ -78,6 +78,6 @@ Der Fehlerdatensatz enthält Exceptiontyp, HRESULT, innere Ausnahmen, vorhandene - Ergebnisreport und Nachher-Snapshot werden auch bei einem Teilfehler soweit möglich geschrieben. - Historisches Log erscheint nach Neustart wieder im Grid. - Vortagslogs werden komprimiert und exakt 30 Kalendertage aufbewahrt. -- Setup, Tool und Ergebnisdateien melden Version 2.3.3 beziehungsweise `2.3.3-net461`. +- Setup, Tool und Ergebnisdateien melden Version 2.3.4 beziehungsweise `2.3.4-net461`. Die lokale Mono-Toolchain prüft Resolverlogik, Identitätsgrenze, Persistenz, Kompression und Aufbewahrung ohne BizTalk. Die endgültige Freigabe erfordert diesen realen Windows-/BizTalk-/PROD-Test. diff --git a/docs/PROD-Shutdown-Dialog-Sichtbarkeit-2026-08-27.md b/docs/PROD-Shutdown-Dialog-Sichtbarkeit-2026-08-27.md new file mode 100644 index 0000000..4909ae3 --- /dev/null +++ b/docs/PROD-Shutdown-Dialog-Sichtbarkeit-2026-08-27.md @@ -0,0 +1,57 @@ +# PROD-Analyse: Shutdown-Dialog nicht sichtbar + +**Datum:** 27.08.2026 + +**Zielversion:** 2.3.4 + +**Befund:** Nach der Receive-Location-Phase wurde kein sichtbarer Dialog zur Bestätigung des Leerlaufens wahrgenommen. + +## Ergebnis der Ursachenanalyse + +Der fachliche Checkpoint war in Version 2.3.3 vorhanden: Jeder Shutdown-Plan mit nachgelagerter Arbeit enthält genau eine persistierte `OperatorCheckpoint`-Zeile vor Orchestrierungen, Send Ports und lokalen Host Instances. Ohne Callback, bei einem Fehler oder bei **Nein** stoppt der Executor fail-closed; keine geschützte spätere Phase wird ausgeführt. + +Die Oberfläche hatte aber zwei voneinander unabhängige Sichtbarkeitsprobleme: + +1. **Dry run ist standardmäßig aktiv.** In diesem Modus wird keine Receive Location deaktiviert. Deshalb wird der echte Entscheidungsdialog absichtlich nicht aufgerufen; nur die Checkpoint-Zeile wird simuliert. Der kleine Haken und der generische Status machten diesen Unterschied während der Ausführung nicht eindeutig. +2. **Die Bestätigungen hatten keinen Fenster-Owner.** `MessageBox.Show(...)` wurde aus dem UI-Thread aufgerufen, aber nicht an das Hauptformular gebunden. Unter RDP, nach Monitorwechseln oder bei abweichender Z-Order konnte der modale Dialog hinter dem Hauptfenster beziehungsweise außerhalb des erwarteten Vordergrunds liegen. Der Background-Worker wartete dann korrekt auf `Invoke`, während das Hauptfenster weiterhin nur `Preparing shutdown...` zeigte und deaktiviert wirkte. + +Der Screenshot allein unterscheidet die beiden Fälle nicht, weil der Einstellungsbereich mit **Dry run** nicht sichtbar und kein `shutdown-result.json` beigefügt ist. Der sichtbare Zustand `Preparing shutdown...` bei bereits dargestelltem Plan ist jedoch mit einem wartenden, owner-losen Plan- oder Drain-Dialog vereinbar. Ein abgeschlossenes `DryRun=true` mit leerer `CheckpointDecision` belegt dagegen die beabsichtigte Simulation. + +## Korrektur in 2.3.4 + +- Planfreigabe, Drain-Entscheidung und Emergency-Restore-Bestätigung verwenden das Hauptformular explizit als Owner und holen es vor dem Anzeigen nach vorn. +- Unmittelbar vor der echten Drain-Entscheidung meldet die Statuszeile: `ACTION REQUIRED: Shutdown paused`. +- Der Titel des Checkpoints beginnt mit `ACTION REQUIRED`; der Text erklärt, dass der Shutdown pausiert ist und **Nein oder Schließen** sicher stoppt. +- Die erste Freigabe eines echten Shutdowns kündigt den zweiten Dialog nach der Receive-Location-Phase ausdrücklich an. +- Dry-run und Realbetrieb haben gut sichtbare, farblich getrennte Texte. Während einer Operation sind diese Einstellungen gesperrt. +- Ein Shutdown-Dry-run zeigt einen eigenen owner-gebundenen Hinweis: keine Runtime-Änderung, keine deaktivierte Receive Location und daher keine echte Drain-Entscheidung. + +Die Sicherheitssemantik bleibt unverändert: Das Tool behauptet keinen automatisch erkannten Leerlauf. Nur der Operator kann nach Prüfung von Group Hub und Enterprise-Monitoring mit **Ja** fortsetzen. + +## Diagnose vorhandener 2.3.2/2.3.3-Läufe + +`shutdown-result.json` und `shutdown-plan.json` sichern und wie folgt bewerten: + +| Evidenz | Bedeutung | +|---|---| +| `DryRun=true`, Checkpoint-Outcome `DryRun`, keine `CheckpointDecision` | Erwartete Simulation; es gab keine Runtime-Änderung und bewusst keinen Entscheidungsdialog. | +| Kein Ergebnisreport, UI bleibt bei `Preparing shutdown...` | Lauf wartet möglicherweise auf einen verdeckten Plan- oder Drain-Dialog; Taskwechsel/Alt+Tab prüfen. Prozess nicht blind beenden. | +| `DryRun=false`, `CheckpointDecision=Stop` | Operator hat Nein gewählt oder sicher gestoppt; spätere Zeilen müssen `NotExecuted` sein. | +| `DryRun=false`, `CheckpointDecision=Error` | Dialogmechanismus fehlte/fehlerhaft; fail-closed, spätere Zeilen müssen `NotExecuted` sein. | +| `DryRun=false`, `CheckpointDecision=Continue` | Eine positive Entscheidung wurde erfasst; spätere Phasen durften laufen. | +| Plan ohne `OperatorCheckpoint` | Nur zulässig, wenn keine Orchestrierung, kein Send Port und keine lokale Host Instance nachgelagert herunterzufahren war. | + +## PROD-Abnahme 2.3.4 + +1. Setup 2.3.4 als Administrator installieren und im Setup-Log `setup_version=2.3.4.0` sowie den erfolgreichen Ziel-Self-Test prüfen. +2. Tooldateiversion und Self-Test-Ausgabe `2.3.4-net461` prüfen. +3. **Dry run** aktiviert lassen, **Shutdown** wählen und den Hinweis `Shutdown Dry Run - No Runtime Changes` prüfen. Es darf keine BizTalk-Mutation stattfinden. +4. `shutdown-plan.json` prüfen: Bei späterer Shutdown-Arbeit steht `OperatorCheckpoint` vor Orchestrierungen, Send Ports und Host Instances. +5. In einem freigegebenen Wartungsfenster **Dry run** deaktivieren. Der Modus muss `REAL EXECUTION - drain dialog required` anzeigen. +6. Den gespeicherten Plan im ersten owner-gebundenen Dialog bestätigen. +7. Nach den Receive Locations muss die Statuszeile `ACTION REQUIRED` zeigen und der Dialog `ACTION REQUIRED - Confirm BizTalk Inbound Drain` vor dem Hauptfenster stehen. +8. Zuerst **Nein** testen. `shutdown-result.json` muss `CheckpointDecision=Stop` und für alle späteren Zeilen `NotExecuted` enthalten. +9. Den kontrollierten Test wiederholen, Group Hub/Monitoring prüfen und erst nach bestätigtem Leerlauf **Ja** wählen. Danach dürfen Orchestrierungen, Send Ports und Host Instances folgen. +10. RDP minimieren/wiederherstellen und, falls im Betrieb üblich, Monitorwechsel testen. Der owner-gebundene Dialog muss dem Hauptfenster zugeordnet bleiben. + +Die portable Verifikation umfasst Release-Build aller vier Projekte, 43 Regressionstests, Runtime-Self-Test und die manifest-/hashgeprüfte Paketierung. Die abschließende Sichtbarkeits- und BizTalk-Abnahme bleibt ein Windows-/PROD-Test. diff --git a/src/BizTalkPlatformManagementTool.Setup/InstallerEngine.cs b/src/BizTalkPlatformManagementTool.Setup/InstallerEngine.cs index c2a3b84..1ad3e71 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.3.3"; + private const string ProductVersion = "2.3.4"; /// /// Wartezeiten zwischen Wiederholungen atomarer Verzeichnisverschiebungen. diff --git a/src/BizTalkPlatformManagementTool.Setup/MainForm.cs b/src/BizTalkPlatformManagementTool.Setup/MainForm.cs index 186deb5..6b0359e 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.3.3" + Text = "BizTalk Platform Management Tool 2.3.4" }); root.Controls.Add(new Label { diff --git a/src/BizTalkPlatformManagementTool.Setup/Properties/AssemblyInfo.cs b/src/BizTalkPlatformManagementTool.Setup/Properties/AssemblyInfo.cs index 9308875..c5340ca 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.3.3.0")] -[assembly: AssemblyFileVersion("2.3.3.0")] +[assembly: AssemblyVersion("2.3.4.0")] +[assembly: AssemblyFileVersion("2.3.4.0")] [assembly: InternalsVisibleTo("BizTalkPlatformManagementTool.Tests")] diff --git a/src/BizTalkPlatformManagementTool.Setup/app.manifest b/src/BizTalkPlatformManagementTool.Setup/app.manifest index 6fdf250..ae2fbf2 100644 --- a/src/BizTalkPlatformManagementTool.Setup/app.manifest +++ b/src/BizTalkPlatformManagementTool.Setup/app.manifest @@ -1,6 +1,6 @@ - + diff --git a/src/BizTalkPlatformManagementTool/BizTalkPlatformManagementTool.csproj b/src/BizTalkPlatformManagementTool/BizTalkPlatformManagementTool.csproj index 84dfe17..658663d 100644 --- a/src/BizTalkPlatformManagementTool/BizTalkPlatformManagementTool.csproj +++ b/src/BizTalkPlatformManagementTool/BizTalkPlatformManagementTool.csproj @@ -70,6 +70,7 @@ + diff --git a/src/BizTalkPlatformManagementTool/Properties/AssemblyInfo.cs b/src/BizTalkPlatformManagementTool/Properties/AssemblyInfo.cs index 5a10cdf..701164a 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.3.3.0")] -[assembly: AssemblyFileVersion("2.3.3.0")] +[assembly: AssemblyVersion("2.3.4.0")] +[assembly: AssemblyFileVersion("2.3.4.0")] [assembly: InternalsVisibleTo("BizTalkPlatformManagementTool.Tests")] diff --git a/src/BizTalkPlatformManagementTool/Services/BizTalkOperationService.cs b/src/BizTalkPlatformManagementTool/Services/BizTalkOperationService.cs index e3d7651..dcf758c 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.3.3-net461"; + public const string Version = "2.3.4-net461"; /// /// Fallback application name used when neither WMI nor the ExplorerOM catalog can resolve ownership. diff --git a/src/BizTalkPlatformManagementTool/Ui/MainForm.cs b/src/BizTalkPlatformManagementTool/Ui/MainForm.cs index dc2768d..7d172eb 100644 --- a/src/BizTalkPlatformManagementTool/Ui/MainForm.cs +++ b/src/BizTalkPlatformManagementTool/Ui/MainForm.cs @@ -238,6 +238,8 @@ namespace BizTalkPlatformManagementTool.Ui _dryRunCheckBox.Dock = DockStyle.Fill; _dryRunCheckBox.Margin = new Padding(8, 7, 8, 7); _dryRunCheckBox.TextAlign = ContentAlignment.MiddleLeft; + _dryRunCheckBox.CheckedChanged += DryRunCheckBoxCheckedChanged; + UpdateDryRunPresentation(); var browseButton = new Button { Text = "Browse...", Dock = DockStyle.Fill, Margin = new Padding(8, 5, 8, 5) }; browseButton.Click += BrowseButtonClick; @@ -404,7 +406,9 @@ namespace BizTalkPlatformManagementTool.Ui private void ShutdownClick(object sender, EventArgs e) { var options = GetOptions(); - RunAsync("Preparing shutdown...", () => + RunAsync(options.DryRun + ? "DRY RUN: Preparing shutdown plan; no runtime state will be changed." + : "REAL SHUTDOWN: Preparing snapshot and saved plan.", () => { var snapshot = _service.CreateSnapshot(options.Server); _service.SaveSnapshot(options.OutputDirectory, "before.json", snapshot); @@ -412,7 +416,11 @@ namespace BizTalkPlatformManagementTool.Ui // Der exakte, frisch erzeugte Plan wird vor Bestätigung und jeder Laufzeitänderung gespeichert. var planPath = _service.SavePlan(options.OutputDirectory, "shutdown-plan.json", plan); ShowPlan(plan); - if (!options.DryRun && !ConfirmPreparedPlan("Shutdown", plan, options.Server, planPath)) + if (options.DryRun) + { + ShowShutdownDryRunNotice(plan, planPath); + } + else if (!ConfirmPreparedPlan("Shutdown", plan, options.Server, planPath)) { _logger.Warning("Shutdown cancelled after plan review. No runtime state was changed."); return; @@ -706,13 +714,13 @@ namespace BizTalkPlatformManagementTool.Ui var confirmed = false; Action showConfirmation = () => { - var executableSteps = plan.Steps.Count(x => x.Execute && !string.Equals(x.Kind, OperationStepKind.OperatorCheckpoint.ToString(), StringComparison.OrdinalIgnoreCase)); - var checkpointCount = plan.Steps.Count(x => x.Execute && string.Equals(x.Kind, OperationStepKind.OperatorCheckpoint.ToString(), StringComparison.OrdinalIgnoreCase)); + _statusLabel.Text = "ACTION REQUIRED: Review the saved " + actionName.ToLowerInvariant() + " plan and answer the owned confirmation dialog."; + Activate(); + BringToFront(); var result = MessageBox.Show( - actionName + " will execute " + executableSteps + " step(s) on server '" + server + "'.\n\n" - + (checkpointCount == 0 ? string.Empty : "The saved plan contains an operator drain checkpoint after all receive locations.\n\n") - + "The exact plan was saved to:\n" + planPath + "\n\nContinue now?", - "Confirm Prepared BizTalk Plan", + this, + ShutdownOperatorPrompts.BuildPreparedPlanConfirmation(actionName, plan, server, planPath), + ShutdownOperatorPrompts.PreparedPlanTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2); @@ -730,6 +738,37 @@ namespace BizTalkPlatformManagementTool.Ui return confirmed; } + /// + /// Makes the dry-run boundary explicit: the prepared checkpoint is visible in + /// the plan, but no receive location changes and no real drain decision occurs. + /// + /// The prepared shutdown plan. + /// The durable path of the prepared plan. + private void ShowShutdownDryRunNotice(OperationPlan plan, string planPath) + { + Action showNotice = () => + { + _statusLabel.Text = "DRY RUN: Plan prepared. No runtime changes and no real drain decision dialog."; + Activate(); + BringToFront(); + MessageBox.Show( + this, + ShutdownOperatorPrompts.BuildDryRunNotice(plan, planPath), + ShutdownOperatorPrompts.DryRunTitle, + MessageBoxButtons.OK, + MessageBoxIcon.Information); + }; + + if (InvokeRequired) + { + Invoke(showNotice); + } + else + { + showNotice(); + } + } + /// /// Pauses a real shutdown after the receive-location phase until the operator /// confirms that the enterprise environment has drained. @@ -742,25 +781,13 @@ namespace BizTalkPlatformManagementTool.Ui var confirmed = false; Action showConfirmation = () => { - var receiveResults = progress.Steps - .Where(x => string.Equals(x.Kind, OperationStepKind.ReceiveLocation.ToString(), StringComparison.OrdinalIgnoreCase)) - .ToList(); - var succeeded = receiveResults.Count(x => x.Outcome == OperationStepOutcomes.Succeeded); - var alreadyDisabled = receiveResults.Count(x => x.Outcome == OperationStepOutcomes.AlreadySatisfied); - var failed = receiveResults.Count(x => x.Outcome == OperationStepOutcomes.Failed); - var failureWarning = failed == 0 - ? string.Empty - : "\nWARNING: " + failed + " receive location(s) failed. Review the red Operation Log entries before continuing.\n"; + _statusLabel.Text = ShutdownOperatorPrompts.WaitingForInboundDrainStatus; + Activate(); + BringToFront(); var result = MessageBox.Show( - "The receive-location shutdown phase has finished.\n\n" - + "Disabled successfully: " + succeeded + "\n" - + "Already disabled: " + alreadyDisabled + "\n" - + "Failed: " + failed + "\n" - + failureWarning + "\n" - + "Keep this dialog open while you verify in BizTalk Group Hub and your enterprise monitoring that no new inbound work arrives and all in-flight service instances/messages have drained.\n\n" - + "Continue with orchestrations, send ports and host instances?\n\n" - + "Yes = continue shutdown. No = stop safely and persist all remaining steps as NotExecuted.", - "Confirm BizTalk Inbound Drain", + this, + ShutdownOperatorPrompts.BuildInboundDrainConfirmation(progress), + ShutdownOperatorPrompts.InboundDrainTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2); @@ -792,8 +819,12 @@ namespace BizTalkPlatformManagementTool.Ui var confirmed = false; Action showConfirmation = () => { + _statusLabel.Text = "ACTION REQUIRED: Review the emergency restore plan and answer the owned confirmation dialog."; + Activate(); + BringToFront(); var executableSteps = plan.Steps.Count(x => x.Execute); var result = MessageBox.Show( + this, "EMERGENCY RESTORE will reconcile " + executableSteps + " step(s) on server '" + server + "'.\n\n" + "Source snapshot (will not be overwritten):\n" + sourcePath + "\n\n" + "Enterprise Single Sign-On will be ensured Running first. Already-correct states are skipped; isolated failures are recorded and later steps continue.\n\n" @@ -983,10 +1014,43 @@ namespace BizTalkPlatformManagementTool.Ui _clearButton.Enabled = !busy; _openLogsButton.Enabled = !busy; _closeButton.Enabled = !busy; - _statusLabel.Text = status; + _serverTextBox.Enabled = !busy; + _outputTextBox.Enabled = !busy; + _stateFileTextBox.Enabled = !busy; + _timeoutInput.Enabled = !busy; + _pollInput.Enabled = !busy; + _dryRunCheckBox.Enabled = !busy; + _statusLabel.Text = !busy && string.Equals(status, "Ready.", StringComparison.Ordinal) + ? "Ready. " + (_dryRunCheckBox.Checked + ? "DRY RUN is enabled; shutdown will not change runtime state or request a drain decision." + : "REAL EXECUTION is enabled; shutdown will pause for the owned drain decision.") + : status; }); } + /// Updates the prominent execution-mode warning when Dry run changes. + /// The checkbox that raised the event. + /// The event arguments. + private void DryRunCheckBoxCheckedChanged(object sender, EventArgs e) + { + UpdateDryRunPresentation(); + if (!_isBusy) + { + _statusLabel.Text = _dryRunCheckBox.Checked + ? "Ready. DRY RUN is enabled; shutdown will not change runtime state or request a drain decision." + : "Ready. REAL EXECUTION is enabled; shutdown will pause for the owned drain decision."; + } + } + + /// Uses explicit text and color instead of relying on a small check mark. + private void UpdateDryRunPresentation() + { + _dryRunCheckBox.Text = _dryRunCheckBox.Checked + ? "DRY RUN - no changes / no drain dialog" + : "REAL EXECUTION - drain dialog required"; + _dryRunCheckBox.BackColor = _dryRunCheckBox.Checked ? Color.LightGoldenrodYellow : Color.MistyRose; + } + /// /// Handles the output directory Browse button click. /// diff --git a/src/BizTalkPlatformManagementTool/Ui/ShutdownOperatorPrompts.cs b/src/BizTalkPlatformManagementTool/Ui/ShutdownOperatorPrompts.cs new file mode 100644 index 0000000..0386a64 --- /dev/null +++ b/src/BizTalkPlatformManagementTool/Ui/ShutdownOperatorPrompts.cs @@ -0,0 +1,109 @@ +using System; +using System.Linq; +using BizTalkPlatformManagementTool.Models; + +namespace BizTalkPlatformManagementTool.Ui +{ + /// + /// Builds stable, testable operator guidance for shutdown plan review, dry-run + /// simulation and the real inbound-drain checkpoint. + /// + internal static class ShutdownOperatorPrompts + { + /// Title of the non-mutating dry-run explanation. + public const string DryRunTitle = "Shutdown Dry Run - No Runtime Changes"; + + /// Title of the saved-plan confirmation. + public const string PreparedPlanTitle = "Confirm Prepared BizTalk Plan"; + + /// Title of the real inbound-drain decision. + public const string InboundDrainTitle = "ACTION REQUIRED - Confirm BizTalk Inbound Drain"; + + /// Status displayed while the real shutdown waits for drain verification. + public const string WaitingForInboundDrainStatus = "ACTION REQUIRED: Shutdown paused. Verify inbound drain and answer the owned confirmation dialog."; + + /// Builds the explicit explanation shown for a shutdown dry-run. + /// The prepared shutdown plan. + /// The durable path of the prepared plan. + /// Operator-facing dry-run guidance. + public static string BuildDryRunNotice(OperationPlan plan, string planPath) + { + if (plan == null) + { + throw new ArgumentNullException("plan"); + } + + var checkpointCount = CountCheckpoints(plan); + return "DRY RUN is enabled. This run only simulates the saved shutdown plan.\n\n" + + "No receive location is disabled and no runtime state is changed. Therefore the real inbound-drain decision dialog will not open during this run.\n\n" + + (checkpointCount == 0 + ? "This plan has no later shutdown phase and therefore needs no drain checkpoint.\n\n" + : "The plan contains the inbound-drain checkpoint. It will require an explicit Yes/No decision when the same plan is prepared with Dry run disabled.\n\n") + + "Prepared plan:\n" + planPath; + } + + /// Builds the confirmation for a fully prepared real operation. + /// The operation name. + /// The prepared operation plan. + /// The selected target server. + /// The durable path of the prepared plan. + /// Operator-facing prepared-plan confirmation. + public static string BuildPreparedPlanConfirmation(string actionName, OperationPlan plan, string server, string planPath) + { + if (plan == null) + { + throw new ArgumentNullException("plan"); + } + + var executableSteps = plan.Steps.Count(x => x.Execute && !IsCheckpoint(x)); + var checkpointCount = CountCheckpoints(plan); + return actionName + " will execute " + executableSteps + " step(s) on server '" + server + "'.\n\n" + + (checkpointCount == 0 + ? string.Empty + : "The saved plan contains an operator drain checkpoint after all receive locations. The real shutdown will visibly pause there for a second, explicit decision.\n\n") + + "The exact plan was saved to:\n" + planPath + "\n\nContinue now?"; + } + + /// Builds the real inbound-drain decision from completed receive-location results. + /// The execution results already reached. + /// Operator-facing drain verification guidance. + public static string BuildInboundDrainConfirmation(OperationExecutionReport progress) + { + if (progress == null) + { + throw new ArgumentNullException("progress"); + } + + var receiveResults = progress.Steps + .Where(x => string.Equals(x.Kind, OperationStepKind.ReceiveLocation.ToString(), StringComparison.OrdinalIgnoreCase)) + .ToList(); + var succeeded = receiveResults.Count(x => x.Outcome == OperationStepOutcomes.Succeeded); + var alreadyDisabled = receiveResults.Count(x => x.Outcome == OperationStepOutcomes.AlreadySatisfied); + var failed = receiveResults.Count(x => x.Outcome == OperationStepOutcomes.Failed); + var failureWarning = failed == 0 + ? string.Empty + : "\nWARNING: " + failed + " receive location(s) failed. Review the red Operation Log entries before continuing.\n"; + + return "The receive-location shutdown phase has finished. The shutdown is PAUSED and no later phase can run until you decide.\n\n" + + "Disabled successfully: " + succeeded + "\n" + + "Already disabled: " + alreadyDisabled + "\n" + + "Failed: " + failed + "\n" + + failureWarning + "\n" + + "Keep this dialog open while you verify in BizTalk Group Hub and your enterprise monitoring that no new inbound work arrives and all in-flight service instances/messages have drained.\n\n" + + "Continue with orchestrations, send ports and host instances?\n\n" + + "Yes = continue shutdown. No or closing this dialog = stop safely and persist all remaining steps as NotExecuted."; + } + + /// Counts executable drain checkpoints in a plan. + private static int CountCheckpoints(OperationPlan plan) + { + return plan.Steps.Count(x => x.Execute && IsCheckpoint(x)); + } + + /// Checks whether a plan row is the stable operator checkpoint kind. + private static bool IsCheckpoint(OperationStep step) + { + return step != null && string.Equals(step.Kind, OperationStepKind.OperatorCheckpoint.ToString(), StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/src/BizTalkPlatformManagementTool/app.manifest b/src/BizTalkPlatformManagementTool/app.manifest index 3d1cb63..58cc45b 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 69303cb..dee8c14 100644 --- a/tests/BizTalkPlatformManagementTool.Tests/Program.cs +++ b/tests/BizTalkPlatformManagementTool.Tests/Program.cs @@ -7,6 +7,7 @@ using System.Text; using BizTalkPlatformManagementTool.Models; using BizTalkPlatformManagementTool.Services; using BizTalkPlatformManagementTool.Setup; +using BizTalkPlatformManagementTool.Ui; namespace BizTalkPlatformManagementTool.Tests { @@ -28,10 +29,12 @@ namespace BizTalkPlatformManagementTool.Tests Run("Legacy213SnapshotIsAcceptedForRecovery", Legacy213SnapshotIsAcceptedForRecovery); Run("RestorePlanUsesSafeOrder", RestorePlanUsesSafeOrder); Run("ShutdownPlanUsesGlobalSafeOrder", ShutdownPlanUsesGlobalSafeOrder); + Run("ShutdownPlanRequiresCheckpointBeforeLaterWorkWithoutReceiveLocations", ShutdownPlanRequiresCheckpointBeforeLaterWorkWithoutReceiveLocations); Run("ShutdownContinuesAcrossArtifactCategoriesAfterReceiveLocationFailure", ShutdownContinuesAcrossArtifactCategoriesAfterReceiveLocationFailure); Run("ShutdownCheckpointDeclineStopsLaterPhases", ShutdownCheckpointDeclineStopsLaterPhases); Run("ShutdownCheckpointMissingHandlerFailsClosed", ShutdownCheckpointMissingHandlerFailsClosed); Run("ShutdownCheckpointDryRunDoesNotPrompt", ShutdownCheckpointDryRunDoesNotPrompt); + Run("ShutdownOperatorPromptsDistinguishDryRunAndRealDecision", ShutdownOperatorPromptsDistinguishDryRunAndRealDecision); Run("EmergencyRestorePlanStartsSsoFirst", EmergencyRestorePlanStartsSsoFirst); Run("HostInstancePlanAcceptsShortAndFqdnServer", HostInstancePlanAcceptsShortAndFqdnServer); Run("PlanExecutionContinuesAfterSchedulerFailure", PlanExecutionContinuesAfterSchedulerFailure); @@ -182,6 +185,18 @@ namespace BizTalkPlatformManagementTool.Tests Assert(lastSendPort < firstHost, "host instances were not globally last"); } + /// Prüft den Checkpoint auch dann, wenn keine aktive Receive Location als Planzeile existiert. + private static void ShutdownPlanRequiresCheckpointBeforeLaterWorkWithoutReceiveLocations() + { + var snapshot = Snapshot("APP", "SEND", ArtifactStates.SendPortStarted); + var plan = new BizTalkOperationService(null).CreateShutdownPlan(snapshot, snapshot.Server); + var checkpoint = plan.Steps.FindIndex(x => x.Kind == OperationStepKind.OperatorCheckpoint.ToString()); + var sendPort = plan.Steps.FindIndex(x => x.Kind == OperationStepKind.SendPort.ToString()); + + Assert(checkpoint >= 0, "later shutdown work did not create an inbound-drain checkpoint"); + Assert(checkpoint < sendPort, "checkpoint was not placed before later shutdown work"); + } + /// Prüft, dass Scheduler-Erkennung auch nach Planpersistenz möglich bleibt. private static void ScheduledReceivePlanCarriesAdapterMetadata() { @@ -430,6 +445,25 @@ namespace BizTalkPlatformManagementTool.Tests Assert(!report.OperatorStopped && string.IsNullOrWhiteSpace(report.CheckpointDecision), "dry-run persisted a real checkpoint decision"); } + /// Prüft, dass Dry-run-Hinweis und echter Entscheidungsdialog nicht verwechselt werden können. + private static void ShutdownOperatorPromptsDistinguishDryRunAndRealDecision() + { + var snapshot = Snapshot("APP", "SEND", ArtifactStates.SendPortStarted); + snapshot.Applications[0].ReceiveLocations.Add(new ReceiveLocationState { Application = "APP", Name = "RL", Enabled = true }); + var plan = new BizTalkOperationService(null).CreateShutdownPlan(snapshot, snapshot.Server); + var dryRun = ShutdownOperatorPrompts.BuildDryRunNotice(plan, @"C:\evidence\shutdown-plan.json"); + var prepared = ShutdownOperatorPrompts.BuildPreparedPlanConfirmation("Shutdown", plan, snapshot.Server, @"C:\evidence\shutdown-plan.json"); + var progress = new OperationExecutionReport(); + progress.Steps.Add(new OperationStepResult { Kind = OperationStepKind.ReceiveLocation.ToString(), Outcome = OperationStepOutcomes.Succeeded }); + progress.Steps.Add(new OperationStepResult { Kind = OperationStepKind.ReceiveLocation.ToString(), Outcome = OperationStepOutcomes.Failed }); + var checkpoint = ShutdownOperatorPrompts.BuildInboundDrainConfirmation(progress); + + Assert(dryRun.Contains("No receive location is disabled") && dryRun.Contains("will not open"), "dry-run notice does not explain the missing real dialog"); + Assert(prepared.Contains("second, explicit decision"), "real-plan confirmation does not announce the later checkpoint dialog"); + Assert(checkpoint.Contains("shutdown is PAUSED") && checkpoint.Contains("Disabled successfully: 1") && checkpoint.Contains("Failed: 1"), "checkpoint decision does not expose its blocking state and receive results"); + Assert(checkpoint.Contains("closing this dialog = stop safely"), "checkpoint close behavior is not explicit"); + } + /// Prüft ENTSSO als erste Voraussetzung des Emergency Restore. private static void EmergencyRestorePlanStartsSsoFirst() {