Make shutdown checkpoint dialogs unmistakable

This commit is contained in:
2026-08-27 20:17:16 +02:00
parent 61b5b7e452
commit 8b1c14b892
19 changed files with 385 additions and 48 deletions
+2 -1
View File
@@ -2,7 +2,7 @@
## Projekt und Sicherheitsziel ## 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 ## 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 ü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. - 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. - 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. - 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. - 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. - Datei-Logging und optionale GUI-Logweiterleitung sind Diagnosekanäle und dürfen niemals einen fachlichen Planschritt oder dessen Fehlerbehandlung unterbrechen.
+14
View File
@@ -1,6 +1,20 @@
# Changelog # 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 ## [2.3.3] - 2026-08-27
### Fixed ### 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. - 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.
+2 -2
View File
@@ -15,8 +15,8 @@ Das Release erzeugt folgende Dateien unter `artifacts`:
- `BizTalkPlatformManagementTool-Setup.zip.b64.txt`: Certutil-kompatible Base64-Übertragung - `BizTalkPlatformManagementTool-Setup.zip.b64.txt`: Certutil-kompatible Base64-Übertragung
- `BizTalkPlatformManagementTool-Setup.zip.sha256.txt`: SHA-256 des ZIP-Archivs - `BizTalkPlatformManagementTool-Setup.zip.sha256.txt`: SHA-256 des ZIP-Archivs
- `BizTalkPlatformManagementTool-Setup.zip`: direkt entpackbares Installationspaket - `BizTalkPlatformManagementTool-Setup.zip`: direkt entpackbares Installationspaket
- `BizTalkPlatformManagementTool-2.3.3-source.zip.b64.txt`: vollständiger Quellstand als Base64-TXT - `BizTalkPlatformManagementTool-2.3.4-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.sha256.txt`: SHA-256 des Quellarchivs
Auf dem Zielsystem wird die TXT-Datei so rekonstruiert und geprüft: Auf dem Zielsystem wird die TXT-Datei so rekonstruiert und geprüft:
@@ -1,6 +1,6 @@
PROD-RUNBOOK: SCHEDULEDTASK-STEUERUNG UND PERSISTENTES LAUFZEITLOGGING PROD-RUNBOOK: SCHEDULEDTASK-STEUERUNG UND PERSISTENTES LAUFZEITLOGGING
Stand: 2026-08-26 Stand: 2026-08-26
Zielversion: 2.3.3 Zielversion: 2.3.4
Adapter: BizTalk ScheduledTask Adapter 7.0.2 Adapter: BizTalk ScheduledTask Adapter 7.0.2
Adapterpfad: C:\Program Files (x86)\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 VORBEREITUNG
1. before.json, Reports und vorhandene Logs extern sichern. 1. before.json, Reports und vorhandene Logs extern sichern.
2. Setup 2.3.3 als Administrator installieren/aktualisieren. 2. Setup 2.3.4 als Administrator installieren/aktualisieren.
3. setup_version=2.3.3.0 und erfolgreichen Ziel-Self-Test pruefen. 3. setup_version=2.3.4.0 und erfolgreichen Ziel-Self-Test pruefen.
4. Tool als Administrator starten, gruenen Startup-Log-Pruefeintrag kontrollieren, 4. Tool als Administrator starten, gruenen Startup-Log-Pruefeintrag kontrollieren,
dann Log Folder oeffnen. Normalfall: %ProgramData%\BizTalkPlatformManagementTool\Logs. dann Log Folder oeffnen. Normalfall: %ProgramData%\BizTalkPlatformManagementTool\Logs.
5. Im lokalen BizTalk-Ordner Microsoft.BizTalk.Scheduler.dll pruefen. 5. Im lokalen BizTalk-Ordner Microsoft.BizTalk.Scheduler.dll pruefen.
@@ -74,7 +74,7 @@ ABNAHME
- Teilfehler bleibt Failed, spaetere unabhaengige Schritte laufen weiter. - Teilfehler bleibt Failed, spaetere unabhaengige Schritte laufen weiter.
- Historie erscheint nach Neustart im Grid. - Historie erscheint nach Neustart im Grid.
- Vortage komprimiert, 30 Kalendertage aufbewahrt. - 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: Detailfassung:
docs\PROD-ScheduledTask-und-Laufzeitlogging-2026-08-26.md docs\PROD-ScheduledTask-und-Laufzeitlogging-2026-08-26.md
@@ -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.
+3 -1
View File
@@ -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 - 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 - 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 - 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 - 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 - 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 - 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`. 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. 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 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 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 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) - [AI maintainer handoff](AI-README.md)
- [References](REFERENCES.md) - [References](REFERENCES.md)
@@ -2,7 +2,7 @@
**Stand:** 2026-08-26 **Stand:** 2026-08-26
**Zielversion:** 2.3.3 **Zielversion:** 2.3.4
**Betroffener Adapter:** BizTalk ScheduledTask Adapter 7.0.2 **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 ## Vorbereitung
1. Vorhandene `before.json`, Ergebnisdateien und Laufzeitlogs außerhalb des Installationsordners sichern. 1. Vorhandene `before.json`, Ergebnisdateien und Laufzeitlogs außerhalb des Installationsordners sichern.
2. Setup 2.3.3 als Administrator installieren beziehungsweise aktualisieren. 2. Setup 2.3.4 als Administrator installieren beziehungsweise aktualisieren.
3. Im Setup-Log `setup_version=2.3.3.0` und erfolgreichen Ziel-Self-Test prüfen. 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`. 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. 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. 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. - Ergebnisreport und Nachher-Snapshot werden auch bei einem Teilfehler soweit möglich geschrieben.
- Historisches Log erscheint nach Neustart wieder im Grid. - Historisches Log erscheint nach Neustart wieder im Grid.
- Vortagslogs werden komprimiert und exakt 30 Kalendertage aufbewahrt. - 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. 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.
@@ -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.
@@ -23,7 +23,7 @@ namespace BizTalkPlatformManagementTool.Setup
private const string ProductName = "BizTalk Platform Management Tool"; private const string ProductName = "BizTalk Platform Management Tool";
/// <summary>Aktuelle Produktversion des Installers und Uninstall-Eintrags.</summary> /// <summary>Aktuelle Produktversion des Installers und Uninstall-Eintrags.</summary>
private const string ProductVersion = "2.3.3"; private const string ProductVersion = "2.3.4";
/// <summary> /// <summary>
/// Wartezeiten zwischen Wiederholungen atomarer Verzeichnisverschiebungen. /// Wartezeiten zwischen Wiederholungen atomarer Verzeichnisverschiebungen.
@@ -65,7 +65,7 @@ namespace BizTalkPlatformManagementTool.Setup
{ {
AutoSize = true, AutoSize = true,
Font = new Font(Font.FontFamily, 14, FontStyle.Bold), 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 root.Controls.Add(new Label
{ {
@@ -8,6 +8,6 @@ using System.Runtime.InteropServices;
[assembly: AssemblyProduct("BizTalk Platform Management Tool")] [assembly: AssemblyProduct("BizTalk Platform Management Tool")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]
[assembly: Guid("675b68a9-bd80-46a5-b8c5-3b11b0b374e2")] [assembly: Guid("675b68a9-bd80-46a5-b8c5-3b11b0b374e2")]
[assembly: AssemblyVersion("2.3.3.0")] [assembly: AssemblyVersion("2.3.4.0")]
[assembly: AssemblyFileVersion("2.3.3.0")] [assembly: AssemblyFileVersion("2.3.4.0")]
[assembly: InternalsVisibleTo("BizTalkPlatformManagementTool.Tests")] [assembly: InternalsVisibleTo("BizTalkPlatformManagementTool.Tests")]
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"> <assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="2.3.3.0" name="BizTalkPlatformManagementTool.Setup" /> <assemblyIdentity version="2.3.4.0" name="BizTalkPlatformManagementTool.Setup" />
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security><requestedPrivileges><requestedExecutionLevel level="requireAdministrator" uiAccess="false" /></requestedPrivileges></security> <security><requestedPrivileges><requestedExecutionLevel level="requireAdministrator" uiAccess="false" /></requestedPrivileges></security>
</trustInfo> </trustInfo>
@@ -70,6 +70,7 @@
<Compile Include="Services\SnapshotValidator.cs" /> <Compile Include="Services\SnapshotValidator.cs" />
<Compile Include="Services\SnapshotStore.cs" /> <Compile Include="Services\SnapshotStore.cs" />
<Compile Include="Services\BizTalkOperationService.cs" /> <Compile Include="Services\BizTalkOperationService.cs" />
<Compile Include="Ui\ShutdownOperatorPrompts.cs" />
<Compile Include="Ui\MainForm.cs" /> <Compile Include="Ui\MainForm.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -9,6 +9,6 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]
[assembly: Guid("2c5b2c0a-f407-46c2-9e3b-1fa09fa8445a")] [assembly: Guid("2c5b2c0a-f407-46c2-9e3b-1fa09fa8445a")]
[assembly: AssemblyVersion("2.3.3.0")] [assembly: AssemblyVersion("2.3.4.0")]
[assembly: AssemblyFileVersion("2.3.3.0")] [assembly: AssemblyFileVersion("2.3.4.0")]
[assembly: InternalsVisibleTo("BizTalkPlatformManagementTool.Tests")] [assembly: InternalsVisibleTo("BizTalkPlatformManagementTool.Tests")]
@@ -16,7 +16,7 @@ namespace BizTalkPlatformManagementTool.Services
/// <summary> /// <summary>
/// Current tool version written into generated snapshots. /// Current tool version written into generated snapshots.
/// </summary> /// </summary>
public const string Version = "2.3.3-net461"; public const string Version = "2.3.4-net461";
/// <summary> /// <summary>
/// Fallback application name used when neither WMI nor the ExplorerOM catalog can resolve ownership. /// Fallback application name used when neither WMI nor the ExplorerOM catalog can resolve ownership.
@@ -238,6 +238,8 @@ namespace BizTalkPlatformManagementTool.Ui
_dryRunCheckBox.Dock = DockStyle.Fill; _dryRunCheckBox.Dock = DockStyle.Fill;
_dryRunCheckBox.Margin = new Padding(8, 7, 8, 7); _dryRunCheckBox.Margin = new Padding(8, 7, 8, 7);
_dryRunCheckBox.TextAlign = ContentAlignment.MiddleLeft; _dryRunCheckBox.TextAlign = ContentAlignment.MiddleLeft;
_dryRunCheckBox.CheckedChanged += DryRunCheckBoxCheckedChanged;
UpdateDryRunPresentation();
var browseButton = new Button { Text = "Browse...", Dock = DockStyle.Fill, Margin = new Padding(8, 5, 8, 5) }; var browseButton = new Button { Text = "Browse...", Dock = DockStyle.Fill, Margin = new Padding(8, 5, 8, 5) };
browseButton.Click += BrowseButtonClick; browseButton.Click += BrowseButtonClick;
@@ -404,7 +406,9 @@ namespace BizTalkPlatformManagementTool.Ui
private void ShutdownClick(object sender, EventArgs e) private void ShutdownClick(object sender, EventArgs e)
{ {
var options = GetOptions(); 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); var snapshot = _service.CreateSnapshot(options.Server);
_service.SaveSnapshot(options.OutputDirectory, "before.json", snapshot); _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. // Der exakte, frisch erzeugte Plan wird vor Bestätigung und jeder Laufzeitänderung gespeichert.
var planPath = _service.SavePlan(options.OutputDirectory, "shutdown-plan.json", plan); var planPath = _service.SavePlan(options.OutputDirectory, "shutdown-plan.json", plan);
ShowPlan(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."); _logger.Warning("Shutdown cancelled after plan review. No runtime state was changed.");
return; return;
@@ -706,13 +714,13 @@ namespace BizTalkPlatformManagementTool.Ui
var confirmed = false; var confirmed = false;
Action showConfirmation = () => Action showConfirmation = () =>
{ {
var executableSteps = 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.";
var checkpointCount = plan.Steps.Count(x => x.Execute && string.Equals(x.Kind, OperationStepKind.OperatorCheckpoint.ToString(), StringComparison.OrdinalIgnoreCase)); Activate();
BringToFront();
var result = MessageBox.Show( var result = MessageBox.Show(
actionName + " will execute " + executableSteps + " step(s) on server '" + server + "'.\n\n" this,
+ (checkpointCount == 0 ? string.Empty : "The saved plan contains an operator drain checkpoint after all receive locations.\n\n") ShutdownOperatorPrompts.BuildPreparedPlanConfirmation(actionName, plan, server, planPath),
+ "The exact plan was saved to:\n" + planPath + "\n\nContinue now?", ShutdownOperatorPrompts.PreparedPlanTitle,
"Confirm Prepared BizTalk Plan",
MessageBoxButtons.YesNo, MessageBoxButtons.YesNo,
MessageBoxIcon.Warning, MessageBoxIcon.Warning,
MessageBoxDefaultButton.Button2); MessageBoxDefaultButton.Button2);
@@ -730,6 +738,37 @@ namespace BizTalkPlatformManagementTool.Ui
return confirmed; return confirmed;
} }
/// <summary>
/// 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.
/// </summary>
/// <param name="plan">The prepared shutdown plan.</param>
/// <param name="planPath">The durable path of the prepared plan.</param>
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();
}
}
/// <summary> /// <summary>
/// Pauses a real shutdown after the receive-location phase until the operator /// Pauses a real shutdown after the receive-location phase until the operator
/// confirms that the enterprise environment has drained. /// confirms that the enterprise environment has drained.
@@ -742,25 +781,13 @@ namespace BizTalkPlatformManagementTool.Ui
var confirmed = false; var confirmed = false;
Action showConfirmation = () => Action showConfirmation = () =>
{ {
var receiveResults = progress.Steps _statusLabel.Text = ShutdownOperatorPrompts.WaitingForInboundDrainStatus;
.Where(x => string.Equals(x.Kind, OperationStepKind.ReceiveLocation.ToString(), StringComparison.OrdinalIgnoreCase)) Activate();
.ToList(); BringToFront();
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";
var result = MessageBox.Show( var result = MessageBox.Show(
"The receive-location shutdown phase has finished.\n\n" this,
+ "Disabled successfully: " + succeeded + "\n" ShutdownOperatorPrompts.BuildInboundDrainConfirmation(progress),
+ "Already disabled: " + alreadyDisabled + "\n" ShutdownOperatorPrompts.InboundDrainTitle,
+ "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",
MessageBoxButtons.YesNo, MessageBoxButtons.YesNo,
MessageBoxIcon.Warning, MessageBoxIcon.Warning,
MessageBoxDefaultButton.Button2); MessageBoxDefaultButton.Button2);
@@ -792,8 +819,12 @@ namespace BizTalkPlatformManagementTool.Ui
var confirmed = false; var confirmed = false;
Action showConfirmation = () => 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 executableSteps = plan.Steps.Count(x => x.Execute);
var result = MessageBox.Show( var result = MessageBox.Show(
this,
"EMERGENCY RESTORE will reconcile " + executableSteps + " step(s) on server '" + server + "'.\n\n" "EMERGENCY RESTORE will reconcile " + executableSteps + " step(s) on server '" + server + "'.\n\n"
+ "Source snapshot (will not be overwritten):\n" + sourcePath + "\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" + "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; _clearButton.Enabled = !busy;
_openLogsButton.Enabled = !busy; _openLogsButton.Enabled = !busy;
_closeButton.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;
}); });
} }
/// <summary>Updates the prominent execution-mode warning when Dry run changes.</summary>
/// <param name="sender">The checkbox that raised the event.</param>
/// <param name="e">The event arguments.</param>
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.";
}
}
/// <summary>Uses explicit text and color instead of relying on a small check mark.</summary>
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;
}
/// <summary> /// <summary>
/// Handles the output directory Browse button click. /// Handles the output directory Browse button click.
/// </summary> /// </summary>
@@ -0,0 +1,109 @@
using System;
using System.Linq;
using BizTalkPlatformManagementTool.Models;
namespace BizTalkPlatformManagementTool.Ui
{
/// <summary>
/// Builds stable, testable operator guidance for shutdown plan review, dry-run
/// simulation and the real inbound-drain checkpoint.
/// </summary>
internal static class ShutdownOperatorPrompts
{
/// <summary>Title of the non-mutating dry-run explanation.</summary>
public const string DryRunTitle = "Shutdown Dry Run - No Runtime Changes";
/// <summary>Title of the saved-plan confirmation.</summary>
public const string PreparedPlanTitle = "Confirm Prepared BizTalk Plan";
/// <summary>Title of the real inbound-drain decision.</summary>
public const string InboundDrainTitle = "ACTION REQUIRED - Confirm BizTalk Inbound Drain";
/// <summary>Status displayed while the real shutdown waits for drain verification.</summary>
public const string WaitingForInboundDrainStatus = "ACTION REQUIRED: Shutdown paused. Verify inbound drain and answer the owned confirmation dialog.";
/// <summary>Builds the explicit explanation shown for a shutdown dry-run.</summary>
/// <param name="plan">The prepared shutdown plan.</param>
/// <param name="planPath">The durable path of the prepared plan.</param>
/// <returns>Operator-facing dry-run guidance.</returns>
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;
}
/// <summary>Builds the confirmation for a fully prepared real operation.</summary>
/// <param name="actionName">The operation name.</param>
/// <param name="plan">The prepared operation plan.</param>
/// <param name="server">The selected target server.</param>
/// <param name="planPath">The durable path of the prepared plan.</param>
/// <returns>Operator-facing prepared-plan confirmation.</returns>
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?";
}
/// <summary>Builds the real inbound-drain decision from completed receive-location results.</summary>
/// <param name="progress">The execution results already reached.</param>
/// <returns>Operator-facing drain verification guidance.</returns>
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.";
}
/// <summary>Counts executable drain checkpoints in a plan.</summary>
private static int CountCheckpoints(OperationPlan plan)
{
return plan.Steps.Count(x => x.Execute && IsCheckpoint(x));
}
/// <summary>Checks whether a plan row is the stable operator checkpoint kind.</summary>
private static bool IsCheckpoint(OperationStep step)
{
return step != null && string.Equals(step.Kind, OperationStepKind.OperatorCheckpoint.ToString(), StringComparison.OrdinalIgnoreCase);
}
}
}
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"> <assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="2.3.3.0" name="BizTalkPlatformManagementTool" /> <assemblyIdentity version="2.3.4.0" name="BizTalkPlatformManagementTool" />
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security> <security>
<requestedPrivileges> <requestedPrivileges>
@@ -7,6 +7,7 @@ using System.Text;
using BizTalkPlatformManagementTool.Models; using BizTalkPlatformManagementTool.Models;
using BizTalkPlatformManagementTool.Services; using BizTalkPlatformManagementTool.Services;
using BizTalkPlatformManagementTool.Setup; using BizTalkPlatformManagementTool.Setup;
using BizTalkPlatformManagementTool.Ui;
namespace BizTalkPlatformManagementTool.Tests namespace BizTalkPlatformManagementTool.Tests
{ {
@@ -28,10 +29,12 @@ namespace BizTalkPlatformManagementTool.Tests
Run("Legacy213SnapshotIsAcceptedForRecovery", Legacy213SnapshotIsAcceptedForRecovery); Run("Legacy213SnapshotIsAcceptedForRecovery", Legacy213SnapshotIsAcceptedForRecovery);
Run("RestorePlanUsesSafeOrder", RestorePlanUsesSafeOrder); Run("RestorePlanUsesSafeOrder", RestorePlanUsesSafeOrder);
Run("ShutdownPlanUsesGlobalSafeOrder", ShutdownPlanUsesGlobalSafeOrder); Run("ShutdownPlanUsesGlobalSafeOrder", ShutdownPlanUsesGlobalSafeOrder);
Run("ShutdownPlanRequiresCheckpointBeforeLaterWorkWithoutReceiveLocations", ShutdownPlanRequiresCheckpointBeforeLaterWorkWithoutReceiveLocations);
Run("ShutdownContinuesAcrossArtifactCategoriesAfterReceiveLocationFailure", ShutdownContinuesAcrossArtifactCategoriesAfterReceiveLocationFailure); Run("ShutdownContinuesAcrossArtifactCategoriesAfterReceiveLocationFailure", ShutdownContinuesAcrossArtifactCategoriesAfterReceiveLocationFailure);
Run("ShutdownCheckpointDeclineStopsLaterPhases", ShutdownCheckpointDeclineStopsLaterPhases); Run("ShutdownCheckpointDeclineStopsLaterPhases", ShutdownCheckpointDeclineStopsLaterPhases);
Run("ShutdownCheckpointMissingHandlerFailsClosed", ShutdownCheckpointMissingHandlerFailsClosed); Run("ShutdownCheckpointMissingHandlerFailsClosed", ShutdownCheckpointMissingHandlerFailsClosed);
Run("ShutdownCheckpointDryRunDoesNotPrompt", ShutdownCheckpointDryRunDoesNotPrompt); Run("ShutdownCheckpointDryRunDoesNotPrompt", ShutdownCheckpointDryRunDoesNotPrompt);
Run("ShutdownOperatorPromptsDistinguishDryRunAndRealDecision", ShutdownOperatorPromptsDistinguishDryRunAndRealDecision);
Run("EmergencyRestorePlanStartsSsoFirst", EmergencyRestorePlanStartsSsoFirst); Run("EmergencyRestorePlanStartsSsoFirst", EmergencyRestorePlanStartsSsoFirst);
Run("HostInstancePlanAcceptsShortAndFqdnServer", HostInstancePlanAcceptsShortAndFqdnServer); Run("HostInstancePlanAcceptsShortAndFqdnServer", HostInstancePlanAcceptsShortAndFqdnServer);
Run("PlanExecutionContinuesAfterSchedulerFailure", PlanExecutionContinuesAfterSchedulerFailure); Run("PlanExecutionContinuesAfterSchedulerFailure", PlanExecutionContinuesAfterSchedulerFailure);
@@ -182,6 +185,18 @@ namespace BizTalkPlatformManagementTool.Tests
Assert(lastSendPort < firstHost, "host instances were not globally last"); Assert(lastSendPort < firstHost, "host instances were not globally last");
} }
/// <summary>Prüft den Checkpoint auch dann, wenn keine aktive Receive Location als Planzeile existiert.</summary>
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");
}
/// <summary>Prüft, dass Scheduler-Erkennung auch nach Planpersistenz möglich bleibt.</summary> /// <summary>Prüft, dass Scheduler-Erkennung auch nach Planpersistenz möglich bleibt.</summary>
private static void ScheduledReceivePlanCarriesAdapterMetadata() 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"); Assert(!report.OperatorStopped && string.IsNullOrWhiteSpace(report.CheckpointDecision), "dry-run persisted a real checkpoint decision");
} }
/// <summary>Prüft, dass Dry-run-Hinweis und echter Entscheidungsdialog nicht verwechselt werden können.</summary>
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");
}
/// <summary>Prüft ENTSSO als erste Voraussetzung des Emergency Restore.</summary> /// <summary>Prüft ENTSSO als erste Voraussetzung des Emergency Restore.</summary>
private static void EmergencyRestorePlanStartsSsoFirst() private static void EmergencyRestorePlanStartsSsoFirst()
{ {