diff --git a/AI-README.md b/AI-README.md index 46cd44a..b0d2a11 100644 --- a/AI-README.md +++ b/AI-README.md @@ -2,7 +2,7 @@ ## Projekt und Sicherheitsziel -Das Repository enthält ein .NET-Framework-4.6.1-WinForms-Tool für kontrollierte BizTalk-2020-Wartungsoperationen. Änderungen müssen Dry-run, explizite Freigabe realer Aktionen, sichere Reihenfolgen und wiederherstellbare Installergrenzen erhalten. Die aktuelle Produktversion ist 2.2.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.2.4. ## Installerinvarianten @@ -15,6 +15,8 @@ Das Repository enthält ein .NET-Framework-4.6.1-WinForms-Tool für kontrolliert - Sobald ein Zielverzeichnis teilweise angelegt sein kann, muss `activated=true` gesetzt sein, damit der Catch-Pfad es entfernt. - Scheitert der Ziel-Self-Test nach einem Update-Kopierfallback, muss die teilweise neue Version entfernt und das atomare Backup wieder als aktives Verzeichnis eingesetzt werden. - Windows-Integration wird erst nach bestandenem Ziel-Self-Test verändert und bei Folgefehlern aus dem Snapshot restauriert. +- Startmenü-Link, Uninstaller und Uninstall-Registry sind verpflichtend; Fehler müssen weiterhin den vollständigen Rollback auslösen. +- Der gemeinsame Desktop-Link ist optional und standardmäßig abgewählt. Snapshot, Erstellen/Entfernen, Validierung, Rollback und Uninstall müssen jeden Fehler als sichtbare Warnung isolieren und dürfen keine Kernoperation abbrechen. - Diagnose-Logging darf das eigentliche Setup-Ergebnis nie ersetzen. Die zentrale Implementierung liegt in `src/BizTalkPlatformManagementTool.Setup/InstallerEngine.cs`. Move-Retries sind auf acht Versuche und 19,75 Sekunden Wartezeit begrenzt. Die injizierbaren `directoryMover`- und `retryDelay`-Delegates existieren ausschließlich, damit Sperrpfade ohne echte Wartezeit portabel getestet werden können. diff --git a/CHANGELOG.md b/CHANGELOG.md index 08ac5a1..6220518 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ # Changelog +## [2.2.4] - 2026-08-24 +### Fixed +- Permission, Windows Script Host, validation and rollback errors affecting the optional all-users desktop shortcut no longer abort installation, update or uninstallation. +- Failure to capture or restore the optional desktop shortcut can no longer prevent restoration of mandatory registry, Start menu and uninstaller state. + +### Changed +- The all-users desktop shortcut is now opt-in and unchecked by default. +- Optional desktop warnings are visible in the setup UI and durable log, while completion remains successful with an explicit warning result. + +### Added +- Failure-injection coverage proving that both an optional shortcut exception and a failing warning sink remain non-fatal. +- Dedicated operator and maintainer documentation for the optional Windows-integration boundary. + ## [2.2.3] - 2026-08-24 ### Fixed - A persistent Windows/EDR denial of the final staging-directory rename no longer aborts activation after an existing installation has already been atomically moved to its backup. diff --git a/Dokumentation.md b/Dokumentation.md index d3ab78b..009b0b5 100644 --- a/Dokumentation.md +++ b/Dokumentation.md @@ -163,6 +163,8 @@ Fehler vor der ersten Mutation werden als **Kein Rollback erforderlich** ausgewi Wenn ausschließlich die atomare Staging-Umbenennung dauerhaft gesperrt bleibt, ist ein eng begrenzter Kopierfallback zulässig: Bei einer Neuinstallation muss das Ziel fehlen; bei einem Update muss die vollständige Vorversion bereits atomar ins Backup verschoben worden sein und das aktive Ziel fehlen. Jede Zieldatei wird erneut anhand des Manifests per SHA-256 geprüft, und der zweite Self-Test läuft weiterhin aus dem endgültigen Ziel. Der Fallback ist nie ein In-place-Update. Ein Teilfehler entfernt die Zielkopie und stellt beim Update das Backup wieder her. Jeder Retry, der Geltungsbereich und die verwendete Aktivierungsmethode stehen im Setup-Log. +Die Windows-Integration besitzt ab Version 2.2.4 eine explizite Kritikalitätsgrenze. Startmenü-Link, Uninstaller und Registry sind für einen wartbaren Maschinenzustand verpflichtend und bleiben Teil der Transaktion. Der gemeinsame Desktop-Link ist dagegen eine standardmäßig abgewählte Komfortfunktion. Sämtliche Zugriffe darauf – Eingangssnapshot, Erstellen/Entfernen, Rücklesevalidierung, Rollback und Uninstall – laufen innerhalb einer ausfallsicheren optionalen Exception-Grenze. Ein Fehler erzeugt UI- und Logwarnung sowie einen erfolgreichen Abschluss mit Warnstatus, aber keinen Anwendungsrollback. Auch eine fehlerhafte GUI-Warnungsweiterleitung ist isoliert. + Das dauerhafte Setup-Log enthält pro Lauf einen Kontextkopf mit Version, Betriebssystem, 32-/64-Bit-Kontext, CLR, Rechner, Identität und Elevation. Hinzu kommen Pfade, freier Speicher, Dateiversion/-größe/-SHA-256, vollständige Self-Test-Ausgaben mit Exitcode, Exception-Ketten mit HRESULT und Stacktrace sowie jeder Rollbackschritt. Stabile `SETUP-*`- und `UNINSTALL-*`-Fehlercodes ermöglichen die Zuordnung unabhängig vom lokalisierten Meldungstext. Die Aufbewahrung beträgt 90 Tage. Wenn ProgramData für die Diagnose nicht verfügbar ist, wird ein Temp-Fallback verwendet und sichtbar gemeldet; die Schaltfläche **Diagnoselogs öffnen** öffnet den regulären Ablageort. Der Self-Test ist eine technische Installationsabnahme ohne BizTalk-WMI. Ein fachlicher Umgebungszustand wie fehlender WMI-Zugriff oder `Unknown` führt deshalb nicht zum Installer-Rollback. Nach erfolgreicher Installation bleiben **Diagnose** und ein Dry-run gegen das Zielsystem verpflichtende Abnahmeschritte. @@ -184,6 +186,7 @@ Das äußere ZIP erhält zusätzlich eine SHA-256-Datei und eine Certutil-kompat - Erholung nach einer vorübergehenden Rename-Sperre mit korrektem Backoff und Diagnoseereignissen. - Verifizierter Kopierfallback einer dauerhaft move-gesperrten Neuinstallation und eines bereits atomar gesicherten Updates. - Vollständiger Update-Rollback, wenn der Ziel-Self-Test nach dem Kopierfallback fehlschlägt. +- Nichtfataler gemeinsamer Desktop-Link bei simuliertem ACL-Fehler, Zustandsabweichung und ausfallender Warnungsweiterleitung. - Begrenzter Abbruch eines dauerhaft move-gesperrten Updates ohne Veränderung der Vorversion. - Wiederherstellung der Vorversion, wenn der Self-Test nach Aktivierung fehlschlägt. - Deinstallation durch atomare Umbenennung des Programmverzeichnisses vor der bestmöglichen Bereinigung. diff --git a/Installation.md b/Installation.md index bca5ea2..a725fe8 100644 --- a/Installation.md +++ b/Installation.md @@ -30,7 +30,7 @@ Der Hash aus `certutil` muss exakt dem Wert in der SHA-256-Datei entsprechen. Da 1. ZIP vollständig entpacken; `Setup.exe`, `application.manifest` und der Ordner `application` müssen nebeneinander liegen. 2. `Setup.exe` starten und die UAC-Abfrage bestätigen. -3. Optional die Desktop-Verknüpfung abwählen. +3. Die standardmäßig abgewählte, optionale Desktop-Verknüpfung nur bei Bedarf für alle Benutzer aktivieren. 4. **Installieren** wählen. 5. Den Abschluss und den Pfad des Diagnoselogs prüfen. 6. Die Anwendung starten, **Dry run** aktiviert lassen und zuerst **Diagnose** ausführen. @@ -61,6 +61,12 @@ Scheitern Kopie, Hashprüfung oder Ziel-Self-Test, entfernt das Setup das teilwe Schlägt ein Schritt nach Beginn der Umschaltung fehl, entfernt das Setup die neue Version und stellt das Backup wieder her. Tritt der Fehler bereits bei Paketprüfung oder Staging auf, meldet das Setup ausdrücklich **Kein Rollback erforderlich**, weil die aktive Installation unverändert blieb. Staging und Backup werden anschließend bestmöglich bereinigt. Kann ein nicht mehr benötigtes temporäres Verzeichnis wegen einer Dateisperre nicht gelöscht werden, bleibt die erfolgreiche Installation aktiv und das Log kennzeichnet den Abschluss als `success_with_temporary_cleanup_warning`. +### Optionale Desktop-Verknüpfung für alle Benutzer + +Der gemeinsame Desktop-Link unter `%PUBLIC%\Desktop` ist eine Komfortfunktion und ab Version 2.2.4 standardmäßig abgewählt. Fehlende Rechte, eine WSH-/COM-Sperre, eine abweichende Linkvalidierung oder ein Fehler beim Sichern, Entfernen beziehungsweise Wiederherstellen dieses optionalen Links dürfen Installation, Update, Rollback und Deinstallation nicht abbrechen. Das Setup zeigt eine Warnung, schreibt die vollständige Exception ins Diagnoselog und setzt die Kernoperation fort. + +Startmenü-Link, Uninstaller unter ProgramData und Uninstall-Registry bleiben verpflichtende Windows-Integration. Scheitert einer dieser Bestandteile, wird die Installation weiterhin als fehlgeschlagen behandelt und die vorherige Version vollständig wiederhergestellt. Der Installer verändert keine ACLs und umgeht keine Sicherheitsrichtlinie; bei einem Desktop-Link-Fehler bleibt der Link lediglich im tatsächlich erreichbaren Zustand. + ## Installer-Diagnose Jeder Installations-, Update- und Deinstallationslauf erzeugt ein eigenes UTF-8-Log. Über **Diagnoselogs öffnen** kann der Ordner direkt aus dem Setup geöffnet werden. Die Logs werden 90 Tage aufbewahrt. Ist ProgramData nicht beschreibbar, versucht das Setup ein Fallback unter `%TEMP%\BizTalkPlatformManagementTool\InstallerLogs` und zeigt dessen Pfad an. Kann auch dort kein Log erzeugt werden, bleibt der eigentliche Fehler in der Setup-Meldung sichtbar. @@ -81,6 +87,8 @@ Das Log enthält Betriebssystem, Prozess-/OS-Bitness, .NET-Version, Rechner, Win Bei einer gesperrten Verzeichnisverschiebung erscheinen `event=directory_move_retry` und nach einer Erholung `event=directory_move_recovered`. Ein Kopierfallback protokolliert `event=activation_fallback_started`, `event=activation_fallback_complete`, den Geltungsbereich `scope=new_install` oder `scope=update_after_backup` und im Abschluss `activation_method=verified_copy_fallback`. Kann bei einem Update bereits das Backup nicht erzeugt werden, endet es weiterhin mit `SETUP-ACTIVATION`, bevor die aktive Version verändert wurde. +Ein Fehler des gemeinsamen Desktop-Links erscheint als `event=optional_windows_integration_warning` mit einer Rolle wie `desktop_shortcut_snapshot`, `desktop_shortcut_create`, `desktop_shortcut_remove`, `desktop_shortcut_validate`, `desktop_shortcut_rollback_restore` oder `desktop_shortcut_uninstall_remove`. Der erfolgreiche Gesamtabschluss enthält dann `optional_desktop_shortcut_warning=True` und beispielsweise `result=success_with_optional_desktop_shortcut_warning`; er ist kein Rollback und kein fehlgeschlagenes Setup. + Für eine Supportanalyse bitte sichern: 1. Vollständige Fehlermeldung einschließlich Fehlercode und Phase. @@ -91,7 +99,7 @@ Ein erfolgreicher Installer-Self-Test bestätigt Paket, Programmstart und lokale ## Emergency Restore nach einem Teilabbruch -Version 2.2.3 kann einen Wiederanlauf allein aus einer erhaltenen `before.json` vorbereiten und ausführen. Eine mit Version 2.1.3 erzeugte Datei ist kompatibel; zusätzliche Plan- oder Nachher-Dateien des fehlgeschlagenen Laufs sind nicht erforderlich. Auch ein Fehler der GUI-Logweiterleitung ist vom fachlichen Executor isoliert und kann die späteren Planschritte nicht abbrechen. +Version 2.2.4 kann einen Wiederanlauf allein aus einer erhaltenen `before.json` vorbereiten und ausführen. Eine mit Version 2.1.3 erzeugte Datei ist kompatibel; zusätzliche Plan- oder Nachher-Dateien des fehlgeschlagenen Laufs sind nicht erforderlich. Auch ein Fehler der GUI-Logweiterleitung ist vom fachlichen Executor isoliert und kann die späteren Planschritte nicht abbrechen. 1. Die erhaltene `before.json` außerhalb des Arbeitsverzeichnisses zusätzlich sichern. 2. Anwendung als Administrator starten und denselben Zielserver wählen, der im Snapshot gespeichert ist. diff --git a/README.md b/README.md index 2db7c40..2bb71fc 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ WinForms tool for controlled Microsoft BizTalk Server 2020 platform operations d - No compile-time dependency on BizTalk ExplorerOM assemblies - Transactional Windows installer/updater with SHA-256 payload validation and rollback - Bounded activation retries plus a verified copy fallback for first installs and atomically backed-up updates when Windows/EDR blocks only the staging rename +- Opt-in all-users desktop shortcut whose ACL, WSH, validation or rollback failures remain visible but cannot roll back the core installation - Installer diagnostics with stable phase codes, complete child-process output, exception chains and a ProgramData-to-Temp log fallback - WMI-free runtime self-test plus automated regression test executable @@ -116,6 +117,7 @@ Targeted German inline comments explain non-obvious operational decisions such a - [Installer stability analysis](docs/Installer-Stabilitaetsanalyse-2026-08-11.md) - [ACC activation incident analysis](docs/ACC-Installer-Aktivierungsfehler-2026-08-11.md) - [PROD activation incident analysis and 2.2.3 fix](docs/PROD-Installer-Aktivierungsfehler-2026-08-24.md) +- [Optional all-users desktop shortcut stability](docs/Installer-Optionale-Desktopverknuepfung-2026-08-24.md) - [ACC runtime shutdown incident and recovery fix](docs/ACC-Runtime-Shutdown-Exception-2026-08-19.md) - [AI maintainer handoff](AI-README.md) - [References](REFERENCES.md) diff --git a/docs/Installer-Optionale-Desktopverknuepfung-2026-08-24.md b/docs/Installer-Optionale-Desktopverknuepfung-2026-08-24.md new file mode 100644 index 0000000..7a6dbc9 --- /dev/null +++ b/docs/Installer-Optionale-Desktopverknuepfung-2026-08-24.md @@ -0,0 +1,56 @@ +# Optionale Desktop-Verknüpfung für alle Benutzer + +## Befund + +Bis Version 2.2.3 war die Option **Desktop-Verknüpfung für alle Benutzer erstellen** standardmäßig aktiviert. Der Installer verwaltet den Link über Windows Script Host im von `Environment.SpecialFolder.CommonDesktopDirectory` gelieferten gemeinsamen Desktop-Verzeichnis, üblicherweise `%PUBLIC%\Desktop`. + +Obwohl der Link fachlich optional war, lagen alle Zugriffe innerhalb der transaktionskritischen Windows-Integrationsphase. Eine `UnauthorizedAccessException`, COM-/WSH-Exception oder Zustandsabweichung konnte daher nach bereits erfolgreicher Payload-Aktivierung einen `SETUP-WINDOWS-INTEGRATION`-Fehler und den vollständigen Rollback auslösen. Betroffen waren nicht nur das Erstellen, sondern fünf Pfade: + +1. Lesen des vorhandenen Links für den Rollback-Snapshot. +2. Erstellen oder Entfernen entsprechend der Setup-Auswahl. +3. Rücklesende Validierung des gewünschten Zustands. +4. Wiederherstellung des vorherigen Links während eines Rollbacks. +5. Entfernen und Prüfen bei der Deinstallation. + +Der konkrete Berechtigungsgeber kann erst anhand des vollständigen `setup-*.log`, der effektiven ACL und gegebenenfalls EDR-Ereignisse bestimmt werden. Unabhängig davon war die bisherige Kritikalität für eine reine Komfortfunktion zu hoch. + +## Fix in Version 2.2.4 + +- Die Desktop-Option ist standardmäßig abgewählt und muss bewusst aktiviert werden. +- Alle fünf Desktop-Pfade laufen über eine gemeinsame optionale Exception-Grenze. +- Jeder Fehler wird mit Exceptiontyp, HRESULT, Stacktrace, Rolle und Pfad als `optional_windows_integration_warning` protokolliert. +- Die Setup-Oberfläche zeigt eine Warnung und erklärt ausdrücklich, dass die Kerninstallation fortgesetzt wird. +- Auch eine Exception der Warnungsweiterleitung wird isoliert und als `optional_windows_integration_report_failed` protokolliert. +- Ein Desktop-Snapshot, der wegen fehlender Rechte nicht gelesen werden konnte, wird beim späteren Rollback nicht erfunden oder überschrieben. +- Ein fehlgeschlagener Desktop-Zugriff bei der Deinstallation kann einen alten Link zurücklassen, verhindert aber nicht die Entfernung der Anwendung. + +Der Installer ändert keine ACLs, übernimmt keinen Besitz und umgeht keine Sicherheitsrichtlinie. Die Sicherheitsentscheidung des Systems bleibt bestehen; lediglich die optionale Verknüpfung wird aus der Kerntransaktion entfernt. + +## Weiterhin verpflichtende Integration + +Diese Bestandteile bleiben transaktionskritisch: + +- Startmenü-Verknüpfung für alle Benutzer, +- Uninstaller unter `%ProgramData%\BizTalkPlatformManagementTool\Setup`, +- Uninstall-Eintrag unter `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall`. + +Ein Fehler dieser Elemente verhindert weiterhin einen inkonsistenten Erfolg und löst bei Installation oder Update den Rollback aus. + +## Diagnose und Abnahme + +Bei nicht angeforderter oder erfolgreicher Desktop-Operation enthält das Log `event=optional_windows_integration_completed`. Bei einem tolerierten Fehler enthält es: + +```text +event=optional_windows_integration_warning role=desktop_shortcut_... path="..." exception_type=... hresult=... +event=setup_completed result=success_with_optional_desktop_shortcut_warning optional_desktop_shortcut_warning=True +``` + +Für die Windows-Abnahme: + +1. Setup 2.2.4 zunächst mit abgewählter Desktop-Option installieren oder aktualisieren. +2. `setup_version=2.2.4.0`, erfolgreichen Kernabschluss, Startmenü-Link, Uninstaller und Registry prüfen. +3. Die Option in einem weiteren Update gezielt aktivieren. Wird der gemeinsame Desktop blockiert, muss das Setup mit Warnung erfolgreich bleiben und die Anwendung startbar sein. +4. Danach **Diagnose** und einen Dry-run ausführen. +5. Deinstallation separat prüfen; ein nicht löschbarer optionaler Desktop-Link darf höchstens als Warnung verbleiben. + +Die portable Regressionstestsuite injiziert einen ACL-Fehler, eine Validierungsabweichung und einen fehlerhaften Warnungs-Sink. Reale `%PUBLIC%\Desktop`-ACLs, WSH/COM, Endpoint-Schutz und Gruppenrichtlinien bleiben auf Windows zu validieren. diff --git a/docs/Installer-Stabilitaetsanalyse-2026-08-11.md b/docs/Installer-Stabilitaetsanalyse-2026-08-11.md index 8d979be..7691488 100644 --- a/docs/Installer-Stabilitaetsanalyse-2026-08-11.md +++ b/docs/Installer-Stabilitaetsanalyse-2026-08-11.md @@ -20,13 +20,14 @@ Vor Version 2.1.0 enthielt das Repository keinen Installer für die C#-Anwendung - Stabile Fehlercodes, vollständige Self-Test-Ausgaben, Exception-Ketten, HRESULTs und einzeln protokollierte Rollbackschritte. - Explizite Unterscheidung zwischen Fehlern vor der ersten Mutation und tatsächlich ausgeführtem Rollback. - Rücklesende Verifikation von Verknüpfungen, Uninstaller und zentralen Registrywerten. +- Explizite optionale Exception-Grenze für sämtliche Zugriffe auf den gemeinsamen Desktop-Link; Startmenü, Uninstaller und Registry bleiben transaktionskritisch. - Deinstallation über Windows-Uninstall-Eintrag; Diagnoseprotokolle bleiben erhalten. - Äußere ZIP-Prüfsumme und Certutil-kompatible Base64-TXT für kontrollierte Übertragung. ## Lokal verifiziert - Release-Build aller Projekte mit Mono MSBuild. -- Neunundzwanzig Regressionstests einschließlich manipulierter/zusätzlicher/ausbrechender Payload-Pfade, Staging-Abbruch ohne Mutation, transienter Move-Erholung, verifiziertem Neuinstallations- und Updatefallback, begrenztem Updateabbruch, erzwungenem Fehler des zweiten Self-Tests mit Wiederherstellung der Vorversion, Deinstallation über ein Quarantäneverzeichnis, Diagnosekontext, Log-Fallback und Fehlerisolierung der UI-Ausgabe. +- Dreißig Regressionstests einschließlich manipulierter/zusätzlicher/ausbrechender Payload-Pfade, Staging-Abbruch ohne Mutation, transienter Move-Erholung, verifiziertem Neuinstallations- und Updatefallback, begrenztem Updateabbruch, erzwungenem Fehler des zweiten Self-Tests mit Wiederherstellung der Vorversion, Deinstallation über ein Quarantäneverzeichnis, Diagnosekontext, Log-Fallback sowie Fehlerisolierung der UI- und optionalen Desktop-Ausgabe. - WMI-freier Self-Test der produktiven EXE. - Erstellung des Installationsordners, ZIPs, Base64-TXTs und der SHA-256-Datei. - Rückdekodierung der Base64-TXT und Bytevergleich mit dem ZIP. diff --git a/docs/PROD-Installer-Aktivierungsfehler-2026-08-24.md b/docs/PROD-Installer-Aktivierungsfehler-2026-08-24.md index 04efb80..acd893f 100644 --- a/docs/PROD-Installer-Aktivierungsfehler-2026-08-24.md +++ b/docs/PROD-Installer-Aktivierungsfehler-2026-08-24.md @@ -43,10 +43,10 @@ Zwei neue Failure-Injection-Regressionstests belegen den erfolgreichen Updatefal 1. Die 2.1.2-Datei nicht erneut starten. 2. Screenshot und vollständiges `setup-*.log` aus `%ProgramData%\BizTalkPlatformManagementTool\InstallerLogs` als Vorfallevidenz sichern. -3. `artifacts\BizTalkPlatformManagementTool-Setup.zip.b64.txt` aus Version 2.2.3 mit `certutil -decode` rekonstruieren. +3. `artifacts\BizTalkPlatformManagementTool-Setup.zip.b64.txt` aus Version 2.2.4 mit `certutil -decode` rekonstruieren. 4. Den SHA-256 des ZIPs mit `BizTalkPlatformManagementTool-Setup.zip.sha256.txt` vergleichen. -5. Das ZIP in einen neuen Ordner entpacken und die dortige `Setup.exe` starten. Der Fenstertitel muss `2.2.3` zeigen. -6. Im Erfolgslog `setup_version=2.2.3.0` und `activation_method=atomic_move` oder `activation_method=verified_copy_fallback` prüfen. Beim Fallback muss zusätzlich `scope=new_install` oder `scope=update_after_backup` protokolliert sein. +5. Das ZIP in einen neuen Ordner entpacken und die dortige `Setup.exe` starten. Der Fenstertitel muss `2.2.4` zeigen. +6. Im Erfolgslog `setup_version=2.2.4.0` und `activation_method=atomic_move` oder `activation_method=verified_copy_fallback` prüfen. Beim Fallback muss zusätzlich `scope=new_install` oder `scope=update_after_backup` protokolliert sein. 7. Danach in der Anwendung zuerst **Diagnose** und anschließend mit aktiviertem **Dry run** den vorgesehenen Betriebsablauf prüfen. Eine Windows-/BizTalk-/EDR-Abnahme in PROD bleibt erforderlich; die lokale Mono-Suite kann reale ACLs, Endpoint-Schutz, Registry und Verknüpfungen nicht simulieren. diff --git a/src/BizTalkPlatformManagementTool.Setup/InstallerEngine.cs b/src/BizTalkPlatformManagementTool.Setup/InstallerEngine.cs index f063df7..44323dc 100644 --- a/src/BizTalkPlatformManagementTool.Setup/InstallerEngine.cs +++ b/src/BizTalkPlatformManagementTool.Setup/InstallerEngine.cs @@ -23,7 +23,7 @@ namespace BizTalkPlatformManagementTool.Setup private const string ProductName = "BizTalk Platform Management Tool"; /// Aktuelle Produktversion des Installers und Uninstall-Eintrags. - private const string ProductVersion = "2.2.3"; + private const string ProductVersion = "2.2.4"; /// /// Wartezeiten zwischen Wiederholungen atomarer Verzeichnisverschiebungen. @@ -71,6 +71,9 @@ namespace BizTalkPlatformManagementTool.Setup /// Ruft den vorherigen Inhalt der Desktop-Verknüpfung ab oder legt ihn fest. public byte[] DesktopShortcut { get; set; } + /// Ruft ab oder legt fest, ob der optionale Desktop-Zustand sicher gelesen wurde. + public bool DesktopShortcutCaptured { get; set; } + /// Ruft den vorherigen Inhalt der Startmenü-Verknüpfung ab oder legt ihn fest. public byte[] StartMenuShortcut { get; set; } @@ -155,6 +158,7 @@ namespace BizTalkPlatformManagementTool.Setup var uiReport = report ?? delegate { }; var log = SetupOperationLog.Create(dataDirectory, "install-update"); Action write = message => ReportSafely(log, uiReport, "INFO", message); + Action warn = message => ReportSafely(log, uiReport, "WARN", message); if (!string.IsNullOrEmpty(log.CreationError)) write((log.IsFallback ? "WARNUNG: Primaeres Diagnoselog nicht verfuegbar; Temp-Fallback wird verwendet: " : "WARNUNG: Diagnoselog konnte nicht angelegt werden: ") + log.CreationError); if (log.FilePath.Length != 0) @@ -175,6 +179,7 @@ namespace BizTalkPlatformManagementTool.Setup var activated = false; var activationUsedCopyFallback = false; var integrationMutationStarted = false; + var optionalDesktopShortcutWarning = false; var phaseCode = "SETUP-INITIALIZATION"; var phase = "Initialisierung"; @@ -197,13 +202,17 @@ namespace BizTalkPlatformManagementTool.Setup log.WriteFileDetails("setup_executable", Assembly.GetExecutingAssembly().Location); log.WriteFileDetails("existing_application", Path.Combine(installDirectory, ApplicationExeName)); // Der vollständige Integrationszustand wird vor der ersten Mutation gesichert. - integrationSnapshot = registerWindowsIntegration ? CaptureWindowsIntegration() : null; + integrationSnapshot = registerWindowsIntegration ? CaptureWindowsIntegration(log, warn) : null; if (integrationSnapshot != null) + { + optionalDesktopShortcutWarning = !integrationSnapshot.DesktopShortcutCaptured; log.Write("INFO", "event=integration_snapshot registry_key_existed=" + integrationSnapshot.RegistryKeyExisted + " registry_value_count=" + integrationSnapshot.RegistryValues.Count + + " desktop_shortcut_captured=" + integrationSnapshot.DesktopShortcutCaptured + " desktop_shortcut=" + (integrationSnapshot.DesktopShortcut != null) + " start_menu_shortcut=" + (integrationSnapshot.StartMenuShortcut != null) + " uninstaller=" + (integrationSnapshot.Uninstaller != null)); + } phaseCode = "SETUP-PACKAGE-VALIDATION"; phase = "Paketmanifest und SHA-256 pruefen"; @@ -277,8 +286,9 @@ namespace BizTalkPlatformManagementTool.Setup if (registerWindowsIntegration) { integrationMutationStarted = true; - RegisterWindowsIntegration(targetExe, createDesktopShortcut); - ValidateWindowsIntegration(targetExe, createDesktopShortcut, log); + var desktopConfigured = RegisterWindowsIntegration(targetExe, createDesktopShortcut, log, warn); + var desktopValidated = ValidateWindowsIntegration(targetExe, createDesktopShortcut, log, warn); + optionalDesktopShortcutWarning = optionalDesktopShortcutWarning || !desktopConfigured || !desktopValidated; } phaseCode = "SETUP-CLEANUP"; @@ -287,11 +297,15 @@ namespace BizTalkPlatformManagementTool.Setup var backupCleanupSucceeded = TryDeleteDirectory(backupDirectory, write); var stagingCleanupSucceeded = TryDeleteDirectory(stagingDirectory, write); var cleanupSucceeded = backupCleanupSucceeded && stagingCleanupSucceeded; + var completionResult = cleanupSucceeded + ? (optionalDesktopShortcutWarning ? "success_with_optional_desktop_shortcut_warning" : "success") + : (optionalDesktopShortcutWarning ? "success_with_multiple_warnings" : "success_with_temporary_cleanup_warning"); log.Write( - cleanupSucceeded ? "INFO" : "WARN", - "event=setup_completed result=" + (cleanupSucceeded ? "success" : "success_with_temporary_cleanup_warning") + cleanupSucceeded && !optionalDesktopShortcutWarning ? "INFO" : "WARN", + "event=setup_completed result=" + completionResult + " backup_directory_preserved=" + Directory.Exists(backupDirectory) + " staging_directory_preserved=" + Directory.Exists(stagingDirectory) + + " optional_desktop_shortcut_warning=" + optionalDesktopShortcutWarning + " activation_method=" + (activationUsedCopyFallback ? "verified_copy_fallback" : "atomic_move") + " install_directory=\"" + installDirectory + "\""); write("Installation/Update erfolgreich abgeschlossen: " + installDirectory); @@ -340,7 +354,7 @@ namespace BizTalkPlatformManagementTool.Setup if (registerWindowsIntegration && integrationSnapshot != null) { log.Write("WARN", "event=rollback_step step=restore_windows_integration"); - RestoreWindowsIntegration(integrationSnapshot); + RestoreWindowsIntegration(integrationSnapshot, log, warn); log.Write("WARN", "event=rollback_step_complete step=windows_integration result=success"); } } @@ -380,6 +394,7 @@ namespace BizTalkPlatformManagementTool.Setup var uiReport = report ?? delegate { }; var log = SetupOperationLog.Create(dataDirectory, "uninstall"); Action write = message => ReportSafely(log, uiReport, "INFO", message); + Action warn = message => ReportSafely(log, uiReport, "WARN", message); if (!string.IsNullOrEmpty(log.CreationError)) write((log.IsFallback ? "WARNUNG: Primaeres Diagnoselog nicht verfuegbar; Temp-Fallback wird verwendet: " : "WARNUNG: Diagnoselog konnte nicht angelegt werden: ") + log.CreationError); if (log.FilePath.Length != 0) @@ -388,6 +403,7 @@ namespace BizTalkPlatformManagementTool.Setup write("Diagnoselog: " + log.FilePath); } WindowsIntegrationSnapshot integrationSnapshot = null; + var optionalDesktopShortcutWarning = false; var removalDirectory = installDirectory + ".removed." + Guid.NewGuid().ToString("N"); var filesMoved = false; var phaseCode = "UNINSTALL-INITIALIZATION"; @@ -397,7 +413,9 @@ namespace BizTalkPlatformManagementTool.Setup Directory.CreateDirectory(dataDirectory); log.Write("INFO", "event=uninstall_context install_directory=\"" + installDirectory + "\" removal_directory=\"" + removalDirectory + "\" data_directory=\"" + dataDirectory + "\""); log.WriteFileDetails("installed_application", Path.Combine(installDirectory, ApplicationExeName)); - integrationSnapshot = registerWindowsIntegration ? CaptureWindowsIntegration() : null; + integrationSnapshot = registerWindowsIntegration ? CaptureWindowsIntegration(log, warn) : null; + if (integrationSnapshot != null) + optionalDesktopShortcutWarning = !integrationSnapshot.DesktopShortcutCaptured; phaseCode = "UNINSTALL-RUNNING-APPLICATION"; phase = "Laufende Anwendung ausschliessen"; @@ -420,10 +438,14 @@ namespace BizTalkPlatformManagementTool.Setup phaseCode = "UNINSTALL-WINDOWS-INTEGRATION"; phase = "Windows-Integration entfernen"; write("Phase 3/3: " + phase + "."); - if (registerWindowsIntegration) RemoveWindowsIntegration(log); + optionalDesktopShortcutWarning = optionalDesktopShortcutWarning || (registerWindowsIntegration && !RemoveWindowsIntegration(log, warn)); var cleanupSucceeded = TryDeleteDirectory(removalDirectory, write); - log.Write("INFO", "event=uninstall_completed result=" + (cleanupSucceeded ? "success" : "success_with_quarantine_cleanup_warning") - + " removal_directory_preserved=" + Directory.Exists(removalDirectory)); + var completionResult = cleanupSucceeded + ? (optionalDesktopShortcutWarning ? "success_with_optional_desktop_shortcut_warning" : "success") + : (optionalDesktopShortcutWarning ? "success_with_multiple_warnings" : "success_with_quarantine_cleanup_warning"); + log.Write(cleanupSucceeded && !optionalDesktopShortcutWarning ? "INFO" : "WARN", "event=uninstall_completed result=" + completionResult + + " removal_directory_preserved=" + Directory.Exists(removalDirectory) + + " optional_desktop_shortcut_warning=" + optionalDesktopShortcutWarning); write("Deinstallation erfolgreich. Installer-Logs bleiben erhalten: " + Path.Combine(dataDirectory, "InstallerLogs")); } catch (Exception ex) @@ -447,7 +469,7 @@ namespace BizTalkPlatformManagementTool.Setup { try { - RestoreWindowsIntegration(integrationSnapshot); + RestoreWindowsIntegration(integrationSnapshot, log, warn); log.Write("WARN", "event=uninstall_rollback_step step=restore_windows_integration result=success"); } catch (Exception rollbackException) @@ -470,14 +492,26 @@ namespace BizTalkPlatformManagementTool.Setup /// /// Der vollständige Pfad der aktivierten Anwendung. /// true, wenn eine Desktop-Verknüpfung gewünscht ist. - private void RegisterWindowsIntegration(string targetExe, bool createDesktopShortcut) + /// Das Diagnoseprotokoll des aktuellen Setup-Laufs. + /// Die ausfallsichere Bedienerwarnung. + /// true, wenn der optionale Desktop-Zustand ohne Fehler gesetzt wurde. + private bool RegisterWindowsIntegration(string targetExe, bool createDesktopShortcut, SetupOperationLog log, Action warn) { var programsDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonPrograms), ProductName); Directory.CreateDirectory(programsDirectory); CreateShortcut(Path.Combine(programsDirectory, ProductName + ".lnk"), targetExe, installDirectory, ProductName); - if (createDesktopShortcut) CreateShortcut(DesktopShortcutPath, targetExe, installDirectory, ProductName); - else if (File.Exists(DesktopShortcutPath)) File.Delete(DesktopShortcutPath); + var desktopConfigured = TryOptionalWindowsIntegrationStep( + createDesktopShortcut ? "desktop_shortcut_create" : "desktop_shortcut_remove", + "Desktop-Verknuepfung fuer alle Benutzer " + (createDesktopShortcut ? "erstellen" : "entfernen"), + DesktopShortcutPath, + () => + { + if (createDesktopShortcut) CreateShortcut(DesktopShortcutPath, targetExe, installDirectory, ProductName); + else DeleteFileIfExists(DesktopShortcutPath); + }, + log, + warn); var setupDirectory = Path.Combine(dataDirectory, "Setup"); Directory.CreateDirectory(setupDirectory); @@ -496,25 +530,40 @@ namespace BizTalkPlatformManagementTool.Setup key.SetValue("NoModify", 1, RegistryValueKind.DWord); key.SetValue("NoRepair", 1, RegistryValueKind.DWord); } + return desktopConfigured; } /// /// Entfernt und verifiziert die vom Setup verwaltete Windows-Integration. /// /// Das Diagnoseprotokoll des aktuellen Setup-Laufs. - private void RemoveWindowsIntegration(SetupOperationLog log) + /// Die ausfallsichere Bedienerwarnung. + /// true, wenn der optionale Desktop-Link entfernt oder bereits nicht vorhanden war. + private bool RemoveWindowsIntegration(SetupOperationLog log, Action warn) { - DeleteFileIfExists(DesktopShortcutPath); + var desktopRemoved = TryOptionalWindowsIntegrationStep( + "desktop_shortcut_uninstall_remove", + "Desktop-Verknuepfung fuer alle Benutzer bei der Deinstallation entfernen", + DesktopShortcutPath, + () => + { + DeleteFileIfExists(DesktopShortcutPath); + if (File.Exists(DesktopShortcutPath)) + throw new InvalidOperationException("Desktop shortcut still exists after removal."); + }, + log, + warn); var programsDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonPrograms), ProductName); DeleteFileIfExists(Path.Combine(programsDirectory, ProductName + ".lnk")); if (Directory.Exists(programsDirectory) && Directory.GetFileSystemEntries(programsDirectory).Length == 0) Directory.Delete(programsDirectory); Registry.LocalMachine.DeleteSubKeyTree(UninstallKeyPath, false); using (var remainingKey = Registry.LocalMachine.OpenSubKey(UninstallKeyPath, false)) { - if (File.Exists(DesktopShortcutPath) || File.Exists(StartMenuShortcutPath) || remainingKey != null) + if (File.Exists(StartMenuShortcutPath) || remainingKey != null) throw new InvalidOperationException("Windows integration removal verification failed."); } - log.Write("INFO", "event=windows_integration_removed registry_key=\"HKLM\\" + UninstallKeyPath + "\""); + log.Write("INFO", "event=windows_integration_removed registry_key=\"HKLM\\" + UninstallKeyPath + "\" optional_desktop_shortcut_removed=" + desktopRemoved); + return desktopRemoved; } /// @@ -553,16 +602,24 @@ namespace BizTalkPlatformManagementTool.Setup /// /// Liest Registrywerte, Verknüpfungen und Uninstaller vor einer möglichen Mutation ein. /// + /// Das Diagnoseprotokoll des aktuellen Setup-Laufs. + /// Die ausfallsichere Bedienerwarnung. /// Ein wiederherstellbarer Snapshot der Windows-Integration. - private WindowsIntegrationSnapshot CaptureWindowsIntegration() + private WindowsIntegrationSnapshot CaptureWindowsIntegration(SetupOperationLog log, Action warn) { var snapshot = new WindowsIntegrationSnapshot { RegistryValues = new Dictionary>(StringComparer.OrdinalIgnoreCase), - DesktopShortcut = ReadFileOrNull(DesktopShortcutPath), StartMenuShortcut = ReadFileOrNull(StartMenuShortcutPath), Uninstaller = ReadFileOrNull(UninstallerPath) }; + snapshot.DesktopShortcutCaptured = TryOptionalWindowsIntegrationStep( + "desktop_shortcut_snapshot", + "vorhandene Desktop-Verknuepfung fuer alle Benutzer sichern", + DesktopShortcutPath, + () => snapshot.DesktopShortcut = ReadFileOrNull(DesktopShortcutPath), + log, + warn); using (var key = Registry.LocalMachine.OpenSubKey(UninstallKeyPath, false)) { snapshot.RegistryKeyExisted = key != null; @@ -581,7 +638,9 @@ namespace BizTalkPlatformManagementTool.Setup /// Stellt einen zuvor erfassten Zustand der Windows-Integration wieder her. /// /// Der wiederherzustellende Integrationszustand. - private void RestoreWindowsIntegration(WindowsIntegrationSnapshot snapshot) + /// Das Diagnoseprotokoll des aktuellen Setup-Laufs. + /// Die ausfallsichere Bedienerwarnung. + private void RestoreWindowsIntegration(WindowsIntegrationSnapshot snapshot, SetupOperationLog log, Action warn) { if (snapshot == null) return; Registry.LocalMachine.DeleteSubKeyTree(UninstallKeyPath, false); @@ -596,9 +655,22 @@ namespace BizTalkPlatformManagementTool.Setup } } } - RestoreFile(DesktopShortcutPath, snapshot.DesktopShortcut); RestoreFile(StartMenuShortcutPath, snapshot.StartMenuShortcut); RestoreFile(UninstallerPath, snapshot.Uninstaller); + if (snapshot.DesktopShortcutCaptured) + { + TryOptionalWindowsIntegrationStep( + "desktop_shortcut_rollback_restore", + "Desktop-Verknuepfung fuer alle Benutzer beim Rollback wiederherstellen", + DesktopShortcutPath, + () => RestoreFile(DesktopShortcutPath, snapshot.DesktopShortcut), + log, + warn); + } + else + { + log.Write("WARN", "event=optional_windows_integration_skipped role=desktop_shortcut_rollback_restore reason=snapshot_unavailable path=\"" + DesktopShortcutPath + "\""); + } } /// @@ -786,11 +858,11 @@ namespace BizTalkPlatformManagementTool.Setup /// Der erwartete Zielpfad der Anwendung. /// Der gewünschte Zustand der Desktop-Verknüpfung. /// Das Diagnoseprotokoll für die validierten Dateien. - private void ValidateWindowsIntegration(string targetExe, bool desktopRequested, SetupOperationLog log) + /// Die ausfallsichere Bedienerwarnung. + /// true, wenn auch der optionale Desktop-Zustand dem Wunsch entspricht. + private bool ValidateWindowsIntegration(string targetExe, bool desktopRequested, SetupOperationLog log, Action warn) { if (!File.Exists(StartMenuShortcutPath)) throw new FileNotFoundException("Start menu shortcut was not created.", StartMenuShortcutPath); - if (desktopRequested != File.Exists(DesktopShortcutPath)) - throw new InvalidOperationException("Desktop shortcut state does not match the setup selection. Requested=" + desktopRequested + "."); if (!File.Exists(UninstallerPath)) throw new FileNotFoundException("Uninstaller was not installed.", UninstallerPath); using (var key = Registry.LocalMachine.OpenSubKey(UninstallKeyPath, false)) @@ -805,8 +877,66 @@ namespace BizTalkPlatformManagementTool.Setup log.WriteFileDetails("registered_uninstaller", UninstallerPath); log.WriteFileDetails("start_menu_shortcut", StartMenuShortcutPath); - if (desktopRequested) log.WriteFileDetails("desktop_shortcut", DesktopShortcutPath); - log.Write("INFO", "event=windows_integration_validated registry_key=\"HKLM\\" + UninstallKeyPath + "\" desktop_shortcut=" + desktopRequested); + var desktopValidated = TryOptionalWindowsIntegrationStep( + "desktop_shortcut_validate", + "Desktop-Verknuepfung fuer alle Benutzer validieren", + DesktopShortcutPath, + () => + { + var exists = File.Exists(DesktopShortcutPath); + if (desktopRequested != exists) + throw new InvalidOperationException("Desktop shortcut state does not match the setup selection. Requested=" + desktopRequested + ", exists=" + exists + "."); + if (exists) log.WriteFileDetails("desktop_shortcut", DesktopShortcutPath); + }, + log, + warn); + log.Write("INFO", "event=windows_integration_validated registry_key=\"HKLM\\" + UninstallKeyPath + + "\" desktop_shortcut_requested=" + desktopRequested + + " desktop_shortcut_validated=" + desktopValidated); + return desktopValidated; + } + + /// + /// Führt einen optionalen Windows-Integrationsschritt so aus, dass weder dessen Fehler + /// noch eine fehlerhafte Bedienerwarnung Installation, Rollback oder Deinstallation abbrechen. + /// + /// Die maschinenlesbare Rolle für das Diagnoselog. + /// Die lesbare Beschreibung des optionalen Schritts. + /// Der betroffene Pfad. + /// Die auszuführende optionale Operation. + /// Das Diagnoseprotokoll des aktuellen Setup-Laufs. + /// Die optionale Bedienerwarnung. + /// true nach Erfolg, andernfalls false. + internal static bool TryOptionalWindowsIntegrationStep( + string role, + string description, + string path, + Action operation, + SetupOperationLog log, + Action warn) + { + if (operation == null) throw new ArgumentNullException("operation"); + try + { + operation(); + log.Write("INFO", "event=optional_windows_integration_completed role=" + role + " path=\"" + path + "\""); + return true; + } + catch (Exception ex) + { + log.Write("WARN", "event=optional_windows_integration_warning role=" + role + " path=\"" + path + "\" " + SetupOperationLog.FormatException(ex)); + var message = "WARNUNG: Optionaler Schritt '" + description + "' ist fehlgeschlagen: " + ex.Message + + " Die Kernoperation wird fortgesetzt; Details stehen im Diagnoselog."; + try + { + if (warn != null) warn(message); + } + catch (Exception warningException) + { + log.Write("WARN", "event=optional_windows_integration_report_failed role=" + role + " " + SetupOperationLog.FormatException(warningException)); + } + return false; + } } /// diff --git a/src/BizTalkPlatformManagementTool.Setup/MainForm.cs b/src/BizTalkPlatformManagementTool.Setup/MainForm.cs index 1ebc6b1..e905b6b 100644 --- a/src/BizTalkPlatformManagementTool.Setup/MainForm.cs +++ b/src/BizTalkPlatformManagementTool.Setup/MainForm.cs @@ -65,7 +65,7 @@ namespace BizTalkPlatformManagementTool.Setup { AutoSize = true, Font = new Font(Font.FontFamily, 14, FontStyle.Bold), - Text = "BizTalk Platform Management Tool 2.2.3" + Text = "BizTalk Platform Management Tool 2.2.4" }); root.Controls.Add(new Label { @@ -74,8 +74,8 @@ namespace BizTalkPlatformManagementTool.Setup Text = "Transaktionaler Installer mit SHA-256-Pruefung, Staging-Self-Test und automatischem Rollback.\r\nZiel: " + engine.InstallDirectory }); - desktopShortcut.Text = "Desktop-Verknuepfung fuer alle Benutzer erstellen"; - desktopShortcut.Checked = true; + desktopShortcut.Text = "Optionale Desktop-Verknuepfung fuer alle Benutzer erstellen"; + desktopShortcut.Checked = false; desktopShortcut.AutoSize = true; desktopShortcut.Enabled = !uninstallMode; root.Controls.Add(desktopShortcut); diff --git a/src/BizTalkPlatformManagementTool.Setup/Properties/AssemblyInfo.cs b/src/BizTalkPlatformManagementTool.Setup/Properties/AssemblyInfo.cs index 7ad24ae..1b071a4 100644 --- a/src/BizTalkPlatformManagementTool.Setup/Properties/AssemblyInfo.cs +++ b/src/BizTalkPlatformManagementTool.Setup/Properties/AssemblyInfo.cs @@ -8,6 +8,6 @@ using System.Runtime.InteropServices; [assembly: AssemblyProduct("BizTalk Platform Management Tool")] [assembly: ComVisible(false)] [assembly: Guid("675b68a9-bd80-46a5-b8c5-3b11b0b374e2")] -[assembly: AssemblyVersion("2.2.3.0")] -[assembly: AssemblyFileVersion("2.2.3.0")] +[assembly: AssemblyVersion("2.2.4.0")] +[assembly: AssemblyFileVersion("2.2.4.0")] [assembly: InternalsVisibleTo("BizTalkPlatformManagementTool.Tests")] diff --git a/src/BizTalkPlatformManagementTool.Setup/app.manifest b/src/BizTalkPlatformManagementTool.Setup/app.manifest index 8561008..1eb4aa1 100644 --- a/src/BizTalkPlatformManagementTool.Setup/app.manifest +++ b/src/BizTalkPlatformManagementTool.Setup/app.manifest @@ -1,6 +1,6 @@ - + diff --git a/src/BizTalkPlatformManagementTool/Properties/AssemblyInfo.cs b/src/BizTalkPlatformManagementTool/Properties/AssemblyInfo.cs index 041e8ab..f6f2647 100644 --- a/src/BizTalkPlatformManagementTool/Properties/AssemblyInfo.cs +++ b/src/BizTalkPlatformManagementTool/Properties/AssemblyInfo.cs @@ -9,6 +9,6 @@ using System.Runtime.InteropServices; [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: ComVisible(false)] [assembly: Guid("2c5b2c0a-f407-46c2-9e3b-1fa09fa8445a")] -[assembly: AssemblyVersion("2.2.3.0")] -[assembly: AssemblyFileVersion("2.2.3.0")] +[assembly: AssemblyVersion("2.2.4.0")] +[assembly: AssemblyFileVersion("2.2.4.0")] [assembly: InternalsVisibleTo("BizTalkPlatformManagementTool.Tests")] diff --git a/src/BizTalkPlatformManagementTool/Services/BizTalkOperationService.cs b/src/BizTalkPlatformManagementTool/Services/BizTalkOperationService.cs index b93ccac..f0b6d23 100644 --- a/src/BizTalkPlatformManagementTool/Services/BizTalkOperationService.cs +++ b/src/BizTalkPlatformManagementTool/Services/BizTalkOperationService.cs @@ -16,7 +16,7 @@ namespace BizTalkPlatformManagementTool.Services /// /// Current tool version written into generated snapshots. /// - public const string Version = "2.2.3-net461"; + public const string Version = "2.2.4-net461"; /// /// Fallback application name used when WMI does not expose an application property. diff --git a/src/BizTalkPlatformManagementTool/app.manifest b/src/BizTalkPlatformManagementTool/app.manifest index dc22ddd..d6d82a6 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 0648802..aab72da 100644 --- a/tests/BizTalkPlatformManagementTool.Tests/Program.cs +++ b/tests/BizTalkPlatformManagementTool.Tests/Program.cs @@ -50,6 +50,7 @@ namespace BizTalkPlatformManagementTool.Tests Run("InstallerDiagnosticLogContainsContextAndExceptionChain", InstallerDiagnosticLogContainsContextAndExceptionChain); Run("InstallerDiagnosticLogFallsBackToTemp", InstallerDiagnosticLogFallsBackToTemp); Run("InstallerSurvivesUiReportFailure", InstallerSurvivesUiReportFailure); + Run("OptionalWindowsIntegrationFailuresAreNonFatal", OptionalWindowsIntegrationFailuresAreNonFatal); Console.WriteLine(failures == 0 ? "ALL TESTS PASSED" : failures + " TEST(S) FAILED"); return failures == 0 ? 0 : 1; } @@ -618,6 +619,56 @@ namespace BizTalkPlatformManagementTool.Tests }); } + /// Prüft, dass optionale Desktop- und Warnungsfehler niemals eine Kernoperation abbrechen. + private static void OptionalWindowsIntegrationFailuresAreNonFatal() + { + InTemp(directory => + { + var log = SetupOperationLog.Create(directory, "optional-integration-test"); + var warnings = new List(); + var failed = InstallerEngine.TryOptionalWindowsIntegrationStep( + "desktop_shortcut_create", + "Desktop-Verknuepfung erstellen", + Path.Combine(directory, "Public Desktop", "Tool.lnk"), + () => { throw new UnauthorizedAccessException("simulated public desktop ACL denial"); }, + log, + warnings.Add); + Assert(!failed, "optional ACL failure was reported as success"); + Assert(warnings.Count == 1 && warnings[0].Contains("Kernoperation wird fortgesetzt"), "operator continuation warning missing"); + + var warningFailureEscaped = false; + try + { + InstallerEngine.TryOptionalWindowsIntegrationStep( + "desktop_shortcut_validate", + "Desktop-Verknuepfung validieren", + Path.Combine(directory, "Public Desktop", "Tool.lnk"), + () => { throw new InvalidOperationException("simulated state mismatch"); }, + log, + message => { throw new InvalidOperationException("simulated warning sink failure"); }); + } + catch + { + warningFailureEscaped = true; + } + Assert(!warningFailureEscaped, "optional warning sink failure escaped the isolation boundary"); + + var succeeded = InstallerEngine.TryOptionalWindowsIntegrationStep( + "desktop_shortcut_remove", + "Desktop-Verknuepfung entfernen", + Path.Combine(directory, "Public Desktop", "Tool.lnk"), + () => { }, + log, + warnings.Add); + Assert(succeeded, "successful optional operation was reported as failure"); + + var content = File.ReadAllText(log.FilePath); + Assert(content.Contains("event=optional_windows_integration_warning role=desktop_shortcut_create"), "optional ACL failure missing from diagnostics"); + Assert(content.Contains("event=optional_windows_integration_report_failed role=desktop_shortcut_validate"), "warning sink failure missing from diagnostics"); + Assert(content.Contains("event=optional_windows_integration_completed role=desktop_shortcut_remove"), "optional success missing from diagnostics"); + }); + } + /// Prüft den Diagnose-Log-Fallback bei einem nicht verwendbaren ProgramData-Pfad. private static void InstallerDiagnosticLogFallsBackToTemp() {