From b32cc61e43f5f41ef9117ebbad21318b4d1d9f39 Mon Sep 17 00:00:00 2001 From: Johannes Rest Date: Tue, 11 Aug 2026 13:23:01 +0200 Subject: [PATCH] Document C# codebase and publish 2.1.2 --- CHANGELOG.md | 9 + Dokumentation.md | 16 ++ Installation.md | 2 + README.md | 6 + ...TalkPlatformManagementTool.Packager.csproj | 2 +- .../Program.cs | 13 ++ ...BizTalkPlatformManagementTool.Setup.csproj | 1 + .../InstallerEngine.cs | 178 +++++++++++++++++- .../MainForm.cs | 37 +++- .../PackageManifest.cs | 27 ++- .../Program.cs | 4 + .../Properties/AssemblyInfo.cs | 4 +- .../SetupOperationLog.cs | 70 ++++++- .../app.manifest | 2 +- .../BizTalkPlatformManagementTool.csproj | 1 + src/BizTalkPlatformManagementTool/Program.cs | 5 + .../Properties/AssemblyInfo.cs | 4 +- .../RuntimeSelfTest.cs | 11 +- .../Services/BizTalkOperationService.cs | 13 +- .../Services/BizTalkWmiClient.cs | 3 + .../Services/CsvWriter.cs | 1 + .../Services/HtmlReportWriter.cs | 1 + .../Services/JsonFileStore.cs | 16 +- .../Services/OperationLogger.cs | 8 +- .../Services/SnapshotComparer.cs | 1 + .../Services/SnapshotValidator.cs | 46 ++++- .../Ui/MainForm.cs | 9 +- .../app.manifest | 2 +- ...BizTalkPlatformManagementTool.Tests.csproj | 2 +- .../Program.cs | 45 +++++ 30 files changed, 505 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3215454..3cd59fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Changelog +## [2.1.2] - 2026-08-11 +### Added +- Complete XML documentation for types and methods across application, setup, packager and regression projects, including parameters, generic type parameters and return values. +- Focused German inline comments for non-obvious BizTalk ordering, WMI compatibility, persistence, security and installer transaction decisions. +- XML documentation output in every Release project configuration for compiler-side validation. + +### Changed +- README, technical documentation and installation/build guidance now describe the source-documentation standard and generated XML developer artifacts. + ## [2.1.1] - 2026-08-11 ### Added - Stable setup/uninstall phase codes, environment and file metadata, complete self-test output, exception chains with HRESULT/stacktrace, and per-step rollback diagnostics. diff --git a/Dokumentation.md b/Dokumentation.md index 0d2cc06..9714a57 100644 --- a/Dokumentation.md +++ b/Dokumentation.md @@ -27,6 +27,22 @@ Das BizTalk Platform Management Tool unterstützt kontrollierte Wartungsfenster - Regressionstests: `tests/BizTalkPlatformManagementTool.Tests` - PowerShell-Archiv: `archive/powershell/BizTalkPlatformManagementTool.ps1` +## Code-Dokumentationsstandard + +Die vollständige C#-Codebasis in Anwendung, Installer, Packager und Regressionstests ist auf Typ- und Methodenebene mit XML-Dokumentationskommentaren versehen. Methoden dokumentieren ihre Parameter mit ``, generische Typen mit `` und Rückgabewerte mit ``, soweit jeweils vorhanden. Die Release-Konfiguration jedes Projekts erzeugt zusätzlich eine XML-Dokumentationsdatei im jeweiligen `bin\Release`-Verzeichnis. Dadurch prüft der Compiler Syntax und Referenzen der öffentlichen Dokumentation bei jedem Release-Build. + +Deutsche Inline-Kommentare stehen gezielt an Stellen, deren Zweck nicht allein aus dem Code hervorgeht. Dazu zählen insbesondere: + +- sichere Shutdown-/Restore-Reihenfolge und Schutz gebundener Orchestrierungen, +- WMI-Auflösung über breite Abfrage mit clientseitigem Filter, +- atomare JSON-Ersetzung auf demselben Volume, +- Neutralisierung formelartiger CSV-Werte, +- Persistenz eines Operationsplans vor der Benutzerbestätigung, +- Staging-, Aktivierungs-, Quarantäne- und Rollbackgrenzen des Installers, +- begrenzte Self-Test-Prozess- und Streambehandlung. + +Selbsterklärende Zuweisungen und reine UI-Konstruktion werden nicht zeilenweise kommentiert. Kommentare sollen die fachliche Begründung, Sicherheitsgrenze oder Plattformbesonderheit festhalten und nicht lediglich den unmittelbar sichtbaren Code wiederholen. + ## UI Workflow 1. Anwendung mit Administratorrechten starten. diff --git a/Installation.md b/Installation.md index a1ac857..c20abe1 100644 --- a/Installation.md +++ b/Installation.md @@ -98,6 +98,8 @@ scripts\package-release.cmd `test-release.cmd` baut alle vier Projekte und führt die Regressionstests aus. `package-release.cmd` baut und testet erneut, erzeugt Paket, ZIP, Base64-TXT und SHA-256-Datei und validiert dabei das interne Payload-Manifest. +Die Release-Konfiguration erzeugt außerdem pro Assembly eine XML-Dokumentationsdatei im jeweiligen `bin\Release`-Verzeichnis. Damit werden XML-Kommentare und `cref`-Referenzen während des Builds compilerseitig geprüft; diese Entwicklerartefakte sind für den Betrieb nicht erforderlich und deshalb nicht Bestandteil der Installer-Payload. + Unter Mono kann der portable Anteil lokal geprüft werden: ```sh diff --git a/README.md b/README.md index 76ef1c6..106879d 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,12 @@ The app targets .NET Framework 4.6.1 for compatibility with customer environment Use `scripts\test-release.cmd` for the build and regression suite and `scripts\package-release.cmd` for the tested installer ZIP, Certutil-compatible Base64 TXT and SHA-256 file. See [Installation](Installation.md) for decoding and update/rollback details. +## Source Documentation + +All C# types and methods in the application, setup, packager and regression project use XML documentation comments. Method contracts include `param`, `typeparam` and `returns` elements where applicable. Release builds generate one XML documentation file per assembly, so malformed or missing public documentation becomes visible during compilation. + +Targeted German inline comments explain non-obvious operational decisions such as WMI client-side filtering, shutdown/restore order, atomic file replacement, CSV formula neutralization and installer transaction boundaries. Trivial statements are intentionally not paraphrased in comments; the comments record the reason or safety constraint behind the code. + ## Documentation - [Installation](Installation.md) diff --git a/src/BizTalkPlatformManagementTool.Packager/BizTalkPlatformManagementTool.Packager.csproj b/src/BizTalkPlatformManagementTool.Packager/BizTalkPlatformManagementTool.Packager.csproj index 84871e9..332df41 100644 --- a/src/BizTalkPlatformManagementTool.Packager/BizTalkPlatformManagementTool.Packager.csproj +++ b/src/BizTalkPlatformManagementTool.Packager/BizTalkPlatformManagementTool.Packager.csproj @@ -8,7 +8,7 @@ v4.6.1512true truefullfalsebin\Debug\DEBUG;TRACE4 - pdbonlytruebin\Release\TRACE4 + pdbonlytruebin\Release\TRACE4bin\Release\BizTalkPlatformManagementTool.Packager.xml {675B68A9-BD80-46A5-B8C5-3B11B0B374E2}BizTalkPlatformManagementTool.Setup diff --git a/src/BizTalkPlatformManagementTool.Packager/Program.cs b/src/BizTalkPlatformManagementTool.Packager/Program.cs index e71bfd3..3e5304a 100644 --- a/src/BizTalkPlatformManagementTool.Packager/Program.cs +++ b/src/BizTalkPlatformManagementTool.Packager/Program.cs @@ -6,8 +6,14 @@ using BizTalkPlatformManagementTool.Setup; namespace BizTalkPlatformManagementTool.Packager { + /// Erzeugt aus den Release-Binärdateien das übertragbare Setup-Paket. internal static class Program { + /// + /// Erstellt Payload, Manifest, ZIP, Certutil-kompatible Base64-TXT und SHA-256-Datei. + /// + /// Repository-Wurzel und Build-Konfiguration. + /// Null bei erfolgreicher Paketierung, andernfalls eins. private static int Main(string[] args) { try @@ -27,6 +33,7 @@ namespace BizTalkPlatformManagementTool.Packager Copy(Path.Combine(root, "src", "BizTalkPlatformManagementTool", "bin", configuration, "BizTalkPlatformManagementTool.exe.config"), Path.Combine(application, "BizTalkPlatformManagementTool.exe.config")); Copy(Path.Combine(root, "Installation.md"), Path.Combine(package, "INSTALLATION.md")); PackageManifest.Write(application, Path.Combine(package, "application.manifest")); + // Das frisch erzeugte Manifest wird vor dem äußeren ZIP sofort gegen die Payload geprüft. PackageManifest.ValidateAndRead(application, Path.Combine(package, "application.manifest")); if (File.Exists(zip)) File.Delete(zip); @@ -46,6 +53,9 @@ namespace BizTalkPlatformManagementTool.Packager } } + /// Kopiert eine erforderliche Release-Datei und legt ihr Zielverzeichnis an. + /// Der vorhandene Quelldateipfad. + /// Der Zieldateipfad innerhalb des Pakets. private static void Copy(string source, string target) { if (!File.Exists(source)) throw new FileNotFoundException("Required package file missing: " + source, source); @@ -53,6 +63,9 @@ namespace BizTalkPlatformManagementTool.Packager File.Copy(source, target, true); } + /// Schreibt eine Datei als Certutil-kompatible Base64-TXT mit 64 Zeichen pro Zeile. + /// Die binäre Quelldatei. + /// Die zu erzeugende Textdatei. private static void WriteBase64(string source, string target) { var encoded = Convert.ToBase64String(File.ReadAllBytes(source)); diff --git a/src/BizTalkPlatformManagementTool.Setup/BizTalkPlatformManagementTool.Setup.csproj b/src/BizTalkPlatformManagementTool.Setup/BizTalkPlatformManagementTool.Setup.csproj index 83fd386..e3a87c8 100644 --- a/src/BizTalkPlatformManagementTool.Setup/BizTalkPlatformManagementTool.Setup.csproj +++ b/src/BizTalkPlatformManagementTool.Setup/BizTalkPlatformManagementTool.Setup.csproj @@ -22,6 +22,7 @@ pdbonlytrue bin\Release\TRACE4 + bin\Release\BizTalkPlatformManagementTool.Setup.xml diff --git a/src/BizTalkPlatformManagementTool.Setup/InstallerEngine.cs b/src/BizTalkPlatformManagementTool.Setup/InstallerEngine.cs index e54698d..0930c90 100644 --- a/src/BizTalkPlatformManagementTool.Setup/InstallerEngine.cs +++ b/src/BizTalkPlatformManagementTool.Setup/InstallerEngine.cs @@ -11,28 +11,66 @@ using Microsoft.Win32; namespace BizTalkPlatformManagementTool.Setup { + /// + /// Führt Installation, Update und Deinstallation mit Staging, Validierung und Rollback aus. + /// internal sealed class InstallerEngine { + /// Dateiname der installierten Hauptanwendung. internal const string ApplicationExeName = "BizTalkPlatformManagementTool.exe"; + + /// Anzeigename für Verknüpfungen und Windows-Uninstall-Eintrag. private const string ProductName = "BizTalk Platform Management Tool"; - private const string ProductVersion = "2.1.1"; + + /// Aktuelle Produktversion des Installers und Uninstall-Eintrags. + private const string ProductVersion = "2.1.2"; + + /// Maschinenweiter Registrypfad des Windows-Uninstall-Eintrags. private const string UninstallKeyPath = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\BizTalkPlatformManagementTool"; + + /// Verzeichnis der entpackten Setup-Dateien. private readonly string packageDirectory; + + /// Aktives maschinenweites Programmverzeichnis. private readonly string installDirectory; + + /// Dauerhaftes Datenverzeichnis unter ProgramData. private readonly string dataDirectory; + + /// Legt fest, ob Registry, Verknüpfungen und Uninstaller verwaltet werden. private readonly bool registerWindowsIntegration; + + /// Optional injizierte Self-Test-Funktion für portable Regressionstests. private readonly Func selfTestRunner; + + /// Zuletzt tatsächlich verwendetes primäres oder temporäres Diagnoseverzeichnis. private string lastInstallerLogDirectory; + /// + /// Enthält den vor einer Mutation gesicherten Zustand der Windows-Integration. + /// private sealed class WindowsIntegrationSnapshot { + /// Ruft ab oder legt fest, ob der Uninstall-Schlüssel vorher existierte. public bool RegistryKeyExisted { get; set; } + + /// Ruft die vorherigen Registrywerte einschließlich ihres Typs ab oder legt sie fest. public Dictionary> RegistryValues { get; set; } + + /// Ruft den vorherigen Inhalt der Desktop-Verknüpfung ab oder legt ihn fest. public byte[] DesktopShortcut { get; set; } + + /// Ruft den vorherigen Inhalt der Startmenü-Verknüpfung ab oder legt ihn fest. public byte[] StartMenuShortcut { get; set; } + + /// Ruft den vorherigen Inhalt der Uninstaller-Datei ab oder legt ihn fest. public byte[] Uninstaller { get; set; } } + /// + /// Initialisiert den Installer mit den maschinenweiten Standardzielpfaden. + /// + /// Das Verzeichnis mit Setup-Payload und Manifest. public InstallerEngine(string packageDirectory) : this( packageDirectory, @@ -43,6 +81,14 @@ namespace BizTalkPlatformManagementTool.Setup { } + /// + /// Initialisiert den Installer mit expliziten Pfaden und austauschbarer Self-Test-Ausführung. + /// + /// Das Verzeichnis mit Setup-Payload und Manifest. + /// Das aktive Programmverzeichnis. + /// Das dauerhafte Daten- und Diagnoseverzeichnis. + /// true, wenn Registry und Verknüpfungen verwaltet werden sollen. + /// Optionale Testfunktion für Regressionstests; null startet die reale EXE. internal InstallerEngine(string packageDirectory, string installDirectory, string dataDirectory, bool registerWindowsIntegration, Func selfTestRunner) { this.packageDirectory = Path.GetFullPath(packageDirectory); @@ -52,19 +98,23 @@ namespace BizTalkPlatformManagementTool.Setup this.selfTestRunner = selfTestRunner; } - /// Gets the fixed machine-wide application installation directory. + /// Ruft das feste maschinenweite Programmverzeichnis ab. public string InstallDirectory { get { return installDirectory; } } - /// Gets the directory containing persistent setup diagnostic logs. + /// Ruft das zuletzt verwendete beziehungsweise reguläre Installer-Logverzeichnis ab. public string InstallerLogDirectory { get { return lastInstallerLogDirectory ?? Path.Combine(dataDirectory, "InstallerLogs"); } } - /// Gets whether this setup copy contains a complete install/update payload. + /// Ruft ab, ob diese Setup-Kopie Payload und Manifest für Installation oder Update enthält. public bool HasInstallPayload { get { return Directory.Exists(Path.Combine(packageDirectory, "application")) && File.Exists(Path.Combine(packageDirectory, "application.manifest")); } } - /// Gets whether the application executable is present at the install target. + /// Ruft ab, ob die Anwendungs-EXE im Installationsziel vorhanden ist. public bool IsInstalled { get { return File.Exists(Path.Combine(installDirectory, ApplicationExeName)); } } - /// Validates, stages and transactionally installs or updates the application. + /// + /// Validiert und staged die Payload und installiert oder aktualisiert die Anwendung transaktional. + /// + /// true, wenn eine Desktop-Verknüpfung angelegt werden soll. + /// Optionale Fortschrittsausgabe für die Setup-Oberfläche. public void Install(bool createDesktopShortcut, Action report) { var uiReport = report ?? delegate { }; @@ -80,6 +130,8 @@ namespace BizTalkPlatformManagementTool.Setup var sourceApplication = Path.Combine(packageDirectory, "application"); var manifestPath = Path.Combine(packageDirectory, "application.manifest"); + // Staging und Backup sind Geschwister des Zielverzeichnisses. Dadurch bleiben + // die späteren Directory.Move-Operationen auf demselben Volume atomar. var stagingDirectory = installDirectory + ".staging." + Guid.NewGuid().ToString("N"); var backupDirectory = installDirectory + ".backup." + Guid.NewGuid().ToString("N"); var hadExistingInstallation = Directory.Exists(installDirectory); @@ -108,6 +160,7 @@ namespace BizTalkPlatformManagementTool.Setup LogDriveSpace(log, installDirectory); 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; if (integrationSnapshot != null) log.Write("INFO", "event=integration_snapshot registry_key_existed=" + integrationSnapshot.RegistryKeyExisted @@ -138,6 +191,7 @@ namespace BizTalkPlatformManagementTool.Setup phaseCode = "SETUP-ACTIVATION"; phase = "Vorhandene Version sichern und Staging atomar aktivieren"; write("Phase 3/6: " + phase + ". Ab hier beginnt die Systemaenderung."); + // Erst nach Manifestprüfung und bestandenem Staging-Self-Test wird die aktive Version verändert. if (hadExistingInstallation) { log.Write("INFO", "event=directory_move role=backup source=\"" + installDirectory + "\" target=\"" + backupDirectory + "\""); @@ -197,6 +251,8 @@ namespace BizTalkPlatformManagementTool.Setup } else { + // Dateisystem und Windows-Integration werden unabhängig behandelt, damit + // ein Fehler in einem Teil den Diagnosezustand des anderen nicht verdeckt. log.Write("WARN", "event=rollback_started activated=" + activated + " backup_created=" + backupCreated + " integration_mutation_started=" + integrationMutationStarted); try { @@ -256,7 +312,10 @@ namespace BizTalkPlatformManagementTool.Setup } } - /// Removes the active program directory and registered Windows integration. + /// + /// Entfernt das aktive Programmverzeichnis und die registrierte Windows-Integration. + /// + /// Optionale Fortschrittsausgabe für die Setup-Oberfläche. public void Uninstall(Action report) { var uiReport = report ?? delegate { }; @@ -289,6 +348,8 @@ namespace BizTalkPlatformManagementTool.Setup phaseCode = "UNINSTALL-QUARANTINE"; phase = "Programmverzeichnis deaktivieren"; write("Phase 2/3: " + phase + "."); + // Die atomare Umbenennung deaktiviert die Anwendung, ohne die einzige + // wiederherstellbare Kopie vor Abschluss der Deinstallation zu löschen. if (Directory.Exists(installDirectory)) { log.Write("INFO", "event=directory_move role=uninstall_quarantine source=\"" + installDirectory + "\" target=\"" + removalDirectory + "\""); @@ -345,6 +406,11 @@ namespace BizTalkPlatformManagementTool.Setup } } + /// + /// Registriert Startmenü, optionale Desktop-Verknüpfung, Uninstaller und Uninstall-Schlüssel. + /// + /// Der vollständige Pfad der aktivierten Anwendung. + /// true, wenn eine Desktop-Verknüpfung gewünscht ist. private void RegisterWindowsIntegration(string targetExe, bool createDesktopShortcut) { var programsDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonPrograms), ProductName); @@ -373,6 +439,10 @@ namespace BizTalkPlatformManagementTool.Setup } } + /// + /// Entfernt und verifiziert die vom Setup verwaltete Windows-Integration. + /// + /// Das Diagnoseprotokoll des aktuellen Setup-Laufs. private void RemoveWindowsIntegration(SetupOperationLog log) { DeleteFileIfExists(DesktopShortcutPath); @@ -388,6 +458,13 @@ namespace BizTalkPlatformManagementTool.Setup log.Write("INFO", "event=windows_integration_removed registry_key=\"HKLM\\" + UninstallKeyPath + "\""); } + /// + /// Erstellt eine Windows-Verknüpfung über Windows Script Host und gibt COM-Objekte deterministisch frei. + /// + /// Der vollständige Pfad der Verknüpfung. + /// Der Zielpfad der Verknüpfung. + /// Das Arbeitsverzeichnis des Ziels. + /// Die in Windows sichtbare Beschreibung. private static void CreateShortcut(string shortcutPath, string targetPath, string workingDirectory, string description) { Directory.CreateDirectory(Path.GetDirectoryName(shortcutPath)); @@ -414,6 +491,10 @@ namespace BizTalkPlatformManagementTool.Setup } } + /// + /// Liest Registrywerte, Verknüpfungen und Uninstaller vor einer möglichen Mutation ein. + /// + /// Ein wiederherstellbarer Snapshot der Windows-Integration. private WindowsIntegrationSnapshot CaptureWindowsIntegration() { var snapshot = new WindowsIntegrationSnapshot @@ -437,6 +518,10 @@ namespace BizTalkPlatformManagementTool.Setup return snapshot; } + /// + /// Stellt einen zuvor erfassten Zustand der Windows-Integration wieder her. + /// + /// Der wiederherzustellende Integrationszustand. private void RestoreWindowsIntegration(WindowsIntegrationSnapshot snapshot) { if (snapshot == null) return; @@ -457,11 +542,21 @@ namespace BizTalkPlatformManagementTool.Setup RestoreFile(UninstallerPath, snapshot.Uninstaller); } + /// + /// Liest eine Datei vollständig oder bildet ihr Nichtvorhandensein als null ab. + /// + /// Der zu lesende Dateipfad. + /// Der Dateiinhalt oder null, wenn die Datei nicht existiert. private static byte[] ReadFileOrNull(string path) { return File.Exists(path) ? File.ReadAllBytes(path) : null; } + /// + /// Stellt eine Datei exakt wieder her oder entfernt sie, wenn sie vorher nicht vorhanden war. + /// + /// Der wiederherzustellende Dateipfad. + /// Der vorherige Inhalt oder null für „nicht vorhanden“. private static void RestoreFile(string path, byte[] content) { if (content == null) @@ -469,11 +564,17 @@ namespace BizTalkPlatformManagementTool.Setup DeleteFileIfExists(path); return; } + // Beim Rollback des laufenden Uninstallers kann dessen Datei bereits exakt dem + // Snapshot entsprechen; dann vermeiden wir einen unnötigen Schreibzugriff auf die aktive EXE. if (File.Exists(path) && File.ReadAllBytes(path).SequenceEqual(content)) return; Directory.CreateDirectory(Path.GetDirectoryName(path)); File.WriteAllBytes(path, content); } + /// + /// Verhindert Mutation, solange die installierte Anwendung noch ausgeführt wird. + /// + /// Das Diagnoseprotokoll für Prozessfund und Zugriffsfehler. private void EnsureApplicationNotRunning(SetupOperationLog log) { var target = Path.Combine(installDirectory, ApplicationExeName); @@ -517,6 +618,12 @@ namespace BizTalkPlatformManagementTool.Setup log.Write("INFO", "event=running_application_check target_exists=true candidate_count=" + candidates.ToString(CultureInfo.InvariantCulture) + " result=not_running"); } + /// + /// Führt den WMI-freien Self-Test aus und validiert Exitcode sowie Erfolgstoken. + /// + /// Die zu prüfende Anwendungs-EXE. + /// Die Phasenbezeichnung für Log und Fehlermeldung. + /// Das Diagnoseprotokoll für Metadaten und Prozessausgaben. private void RunAndValidateSelfTest(string executable, string label, SetupOperationLog log) { log.WriteFileDetails(label + "_self_test_executable", executable); @@ -554,10 +661,14 @@ namespace BizTalkPlatformManagementTool.Setup using (var process = Process.Start(startInfo)) { if (process == null) throw new InvalidOperationException("Self-Test '" + label + "' konnte nicht gestartet werden."); + // Beide Kanäle werden parallel geleert, damit ein voller stdout-/stderr-Puffer + // den Kindprozess nicht blockiert und dadurch einen künstlichen Timeout erzeugt. var outputRead = process.StandardOutput.ReadToEndAsync(); var errorRead = process.StandardError.ReadToEndAsync(); if (!process.WaitForExit(60000)) { + // Auch nach dem Timeout bleiben Prozessende und Stream-Erfassung begrenzt; + // ein nicht beendbarer Kindprozess darf das Setup nicht endlos festhalten. var killResult = "sent"; try { process.Kill(); } catch (Exception killException) @@ -610,6 +721,12 @@ namespace BizTalkPlatformManagementTool.Setup } } + /// + /// Liest die erzeugte Windows-Integration zurück und vergleicht sie mit dem Sollzustand. + /// + /// 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) { if (!File.Exists(StartMenuShortcutPath)) throw new FileNotFoundException("Start menu shortcut was not created.", StartMenuShortcutPath); @@ -633,6 +750,12 @@ namespace BizTalkPlatformManagementTool.Setup log.Write("INFO", "event=windows_integration_validated registry_key=\"HKLM\\" + UninstallKeyPath + "\" desktop_shortcut=" + desktopRequested); } + /// + /// Vergleicht einen Registrywert mit seinem erwarteten Zeichenfolgenwert. + /// + /// Der geöffnete Uninstall-Schlüssel. + /// Der Name des zu prüfenden Registrywerts. + /// Der erwartete Wert. private static void RequireRegistryValue(RegistryKey key, string name, string expected) { var actual = Convert.ToString(key.GetValue(name, null, RegistryValueOptions.DoNotExpandEnvironmentNames), CultureInfo.InvariantCulture); @@ -640,6 +763,13 @@ namespace BizTalkPlatformManagementTool.Setup throw new InvalidOperationException("Uninstall registry value '" + name + "' is invalid. Expected='" + expected + "', actual='" + actual + "'."); } + /// + /// Schreibt zuerst dauerhaft ins Log und isoliert anschließend Fehler des UI-Callbacks. + /// + /// Das dauerhafte Setup-Protokoll. + /// Der optionale UI-Callback. + /// Der Log-Level. + /// Die auszugebende Nachricht. private static void ReportSafely(SetupOperationLog log, Action report, string level, string message) { log.Write(level, message); @@ -653,6 +783,11 @@ namespace BizTalkPlatformManagementTool.Setup } } + /// + /// Protokolliert Dateisystemtyp und freien Speicher des Zielvolumes bestmöglich. + /// + /// Das Setup-Protokoll. + /// Ein Pfad auf dem zu untersuchenden Volume. private static void LogDriveSpace(SetupOperationLog log, string path) { try @@ -671,6 +806,11 @@ namespace BizTalkPlatformManagementTool.Setup } } + /// + /// Verdichtet Prozessausgabe für eine begrenzte Bedienermeldung; das Log bleibt vollständig. + /// + /// Die rohe Standard- oder Fehlerausgabe. + /// Eine einzeilige Ausgabe mit maximal 2.000 Zeichen. private static string CompactProcessText(string value) { var text = (value ?? string.Empty).Replace("\r", string.Empty).Replace("\n", " | ").Trim(); @@ -678,6 +818,12 @@ namespace BizTalkPlatformManagementTool.Setup return text.Length <= maxLength ? text : text.Substring(0, maxLength) + "...[truncated]"; } + /// + /// Kopiert deklarierte Payload-Dateien und prüft jede Zielkopie erneut per SHA-256. + /// + /// Das validierte Payload-Quellverzeichnis. + /// Das isolierte Staging-Zielverzeichnis. + /// Die validierten Manifesteinträge. private static void CopyPayload(string sourceRoot, string targetRoot, IEnumerable files) { Directory.CreateDirectory(targetRoot); @@ -692,12 +838,23 @@ namespace BizTalkPlatformManagementTool.Setup } } + /// + /// Stellt sicher, dass eine betriebsnotwendige Datei im Manifest enthalten ist. + /// + /// Die validierten Manifesteinträge. + /// Der erforderliche relative Dateipfad. private static void RequirePayload(IEnumerable files, string relativePath) { if (!files.Any(x => string.Equals(x.RelativePath, relativePath, StringComparison.OrdinalIgnoreCase))) throw new InvalidDataException("Required payload file is missing from the manifest: " + relativePath); } + /// + /// Löscht ein temporäres Verzeichnis bestmöglich und meldet eine verbleibende Kopie als Warnung. + /// + /// Das zu löschende Verzeichnis. + /// Die ausfallsichere Fortschrittsausgabe. + /// true, wenn das Verzeichnis anschließend nicht mehr existiert. private static bool TryDeleteDirectory(string path, Action report) { try @@ -712,21 +869,28 @@ namespace BizTalkPlatformManagementTool.Setup } } + /// + /// Löscht eine Datei, sofern sie vorhanden ist, und lässt echte Löschfehler sichtbar werden. + /// + /// Der zu löschende Dateipfad. private static void DeleteFileIfExists(string path) { if (File.Exists(path)) File.Delete(path); } + /// Ruft den maschinenweiten Pfad der Desktop-Verknüpfung ab. private static string DesktopShortcutPath { get { return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory), ProductName + ".lnk"); } } + /// Ruft den maschinenweiten Pfad der Startmenü-Verknüpfung ab. private static string StartMenuShortcutPath { get { return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonPrograms), ProductName, ProductName + ".lnk"); } } + /// Ruft den dauerhaften Pfad der Uninstaller-Kopie ab. private string UninstallerPath { get { return Path.Combine(dataDirectory, "Setup", "Uninstall.exe"); } diff --git a/src/BizTalkPlatformManagementTool.Setup/MainForm.cs b/src/BizTalkPlatformManagementTool.Setup/MainForm.cs index 424628e..9d87793 100644 --- a/src/BizTalkPlatformManagementTool.Setup/MainForm.cs +++ b/src/BizTalkPlatformManagementTool.Setup/MainForm.cs @@ -7,16 +7,37 @@ using System.Windows.Forms; namespace BizTalkPlatformManagementTool.Setup { + /// + /// Stellt die Bedienoberfläche für Installation, Update, Deinstallation und Diagnosezugriff bereit. + /// internal sealed class MainForm : Form { + /// Ausführende Installer-Engine. private readonly InstallerEngine engine; + + /// Gibt an, ob das Fenster direkt zur Deinstallation geöffnet wurde. private readonly bool uninstallMode; + + /// Sichtbare Fortschritts- und Diagnoseausgabe. private readonly TextBox output = new TextBox(); + + /// Auswahl für die optionale maschinenweite Desktop-Verknüpfung. private readonly CheckBox desktopShortcut = new CheckBox(); + + /// Schaltfläche für Installation oder Update. private readonly Button installButton = new Button(); + + /// Schaltfläche für die Deinstallation. private readonly Button uninstallButton = new Button(); + + /// Verhindert parallele Aktionen und Schließen während einer Operation. private bool busy; + /// + /// Initialisiert das Setup-Fenster für den normalen oder direkten Deinstallationsmodus. + /// + /// Die ausführende Installer-Engine. + /// true, wenn das Setup über den Uninstall-Eintrag gestartet wurde. public MainForm(InstallerEngine engine, bool uninstallMode) { this.engine = engine; @@ -30,6 +51,7 @@ namespace BizTalkPlatformManagementTool.Setup FormClosing += OnFormClosing; } + /// Erstellt und verdrahtet die vollständige Setup-Oberfläche. private void BuildUi() { var root = new TableLayoutPanel { Dock = DockStyle.Fill, Padding = new Padding(16), RowCount = 5, ColumnCount = 1 }; @@ -43,7 +65,7 @@ namespace BizTalkPlatformManagementTool.Setup { AutoSize = true, Font = new Font(Font.FontFamily, 14, FontStyle.Bold), - Text = "BizTalk Platform Management Tool 2.1.1" + Text = "BizTalk Platform Management Tool 2.1.2" }); root.Controls.Add(new Label { @@ -89,6 +111,9 @@ namespace BizTalkPlatformManagementTool.Setup else if (!engine.HasInstallPayload) Append("Kein Installationspayload neben Setup.exe gefunden. Dieser Aufruf erlaubt nur die Deinstallation."); } + /// Öffnet das zuletzt verwendete Diagnoseverzeichnis im Windows Explorer. + /// Die auslösende Schaltfläche. + /// Die Ereignisargumente. private void OpenLogs(object sender, EventArgs e) { try @@ -102,6 +127,8 @@ namespace BizTalkPlatformManagementTool.Setup } } + /// Startet Installation oder Deinstallation außerhalb des UI-Threads. + /// true für Deinstallation, false für Installation oder Update. private void Run(bool uninstall) { if (uninstall && MessageBox.Show(this, "BizTalk Platform Management Tool wirklich deinstallieren?", "Deinstallation bestaetigen", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) != DialogResult.Yes) @@ -109,6 +136,7 @@ namespace BizTalkPlatformManagementTool.Setup var createDesktopShortcut = desktopShortcut.Checked; SetBusy(true); + // Dateisystem-, Registry- und Self-Test-Operationen dürfen die WinForms-Nachrichtenpumpe nicht blockieren. Task.Run(() => { try @@ -134,6 +162,8 @@ namespace BizTalkPlatformManagementTool.Setup }); } + /// Fügt der sichtbaren Ausgabe threadsicher eine zeitgestempelte Nachricht hinzu. + /// Die anzuzeigende Nachricht. private void Append(string message) { if (IsDisposed || Disposing) return; @@ -145,6 +175,8 @@ namespace BizTalkPlatformManagementTool.Setup output.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "] " + message + Environment.NewLine); } + /// Schaltet Steuerelemente und Wartecursor threadsicher in oder aus dem Arbeitszustand. + /// true, solange eine Setup-Operation läuft. private void SetBusy(bool value) { if (InvokeRequired) @@ -159,6 +191,9 @@ namespace BizTalkPlatformManagementTool.Setup UseWaitCursor = value; } + /// Verhindert das Schließen des Fensters während einer laufenden Setup-Operation. + /// Das zu schließende Setup-Fenster. + /// Die abbrechbaren Argumente des Schließereignisses. private void OnFormClosing(object sender, FormClosingEventArgs e) { if (!busy) return; diff --git a/src/BizTalkPlatformManagementTool.Setup/PackageManifest.cs b/src/BizTalkPlatformManagementTool.Setup/PackageManifest.cs index eeb9a8d..a710504 100644 --- a/src/BizTalkPlatformManagementTool.Setup/PackageManifest.cs +++ b/src/BizTalkPlatformManagementTool.Setup/PackageManifest.cs @@ -8,6 +8,7 @@ using System.Text; namespace BizTalkPlatformManagementTool.Setup { + /// Beschreibt eine durch Länge und SHA-256 abgesicherte Payload-Datei. public sealed class PackageFile { /// Gets or sets the normalized payload-relative path. @@ -18,9 +19,13 @@ namespace BizTalkPlatformManagementTool.Setup public string Sha256 { get; set; } } + /// Erzeugt und validiert das vollständige kryptografische Payload-Manifest. public static class PackageManifest { - /// Reads and cryptographically validates a complete application payload manifest. + /// Liest das Manifest und validiert jede sowie ausschließlich jede Payload-Datei. + /// Das Wurzelverzeichnis der Anwendungs-Payload. + /// Der Pfad des zu prüfenden Manifests. + /// Die validierten und normalisierten Manifesteinträge. public static IList ValidateAndRead(string applicationDirectory, string manifestPath) { if (!Directory.Exists(applicationDirectory)) throw new DirectoryNotFoundException("Application payload missing: " + applicationDirectory); @@ -49,6 +54,8 @@ namespace BizTalkPlatformManagementTool.Setup } if (files.Count == 0) throw new InvalidDataException("The package manifest does not contain payload files."); + // Der Mengenvergleich verhindert, dass nicht deklarierte DLLs oder Konfigurationen + // unbemerkt mit administrativen Rechten installiert werden. var actualFiles = Directory.GetFiles(applicationDirectory, "*", SearchOption.AllDirectories) .Select(x => NormalizeRelativePath(x.Substring(Path.GetFullPath(applicationDirectory).TrimEnd(Path.DirectorySeparatorChar).Length + 1))) .OrderBy(x => x, StringComparer.OrdinalIgnoreCase).ToArray(); @@ -58,7 +65,9 @@ namespace BizTalkPlatformManagementTool.Setup return files; } - /// Creates a deterministic manifest covering every application payload file. + /// Erzeugt ein deterministisch sortiertes Manifest über die vollständige Payload. + /// Das Wurzelverzeichnis der Anwendungs-Payload. + /// Der Zielpfad des Manifests. public static void Write(string applicationDirectory, string manifestPath) { var root = Path.GetFullPath(applicationDirectory).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar; @@ -70,7 +79,9 @@ namespace BizTalkPlatformManagementTool.Setup File.WriteAllLines(manifestPath, lines, new UTF8Encoding(false)); } - /// Calculates the lowercase SHA-256 digest of a file. + /// Berechnet den kleingeschriebenen SHA-256-Hash einer Datei. + /// Der Pfad der zu prüfenden Datei. + /// Der SHA-256-Hash als 64-stellige Hexadezimalzeichenfolge. public static string Sha256(string path) { using (var stream = File.OpenRead(path)) @@ -83,7 +94,10 @@ namespace BizTalkPlatformManagementTool.Setup } } - /// Resolves a relative payload path and rejects directory traversal. + /// Löst einen relativen Payload-Pfad auf und weist Directory Traversal zurück. + /// Das erlaubte Payload-Wurzelverzeichnis. + /// Der relative Pfad aus dem Manifest. + /// Der vollständig aufgelöste, innerhalb von liegende Pfad. public static string ResolveContainedPath(string root, string relative) { var normalizedRoot = Path.GetFullPath(root).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar; @@ -92,6 +106,11 @@ namespace BizTalkPlatformManagementTool.Setup return result; } + /// + /// Normalisiert Pfadtrenner und weist absolute, leere oder ausbrechende Pfade zurück. + /// + /// Der zu normalisierende relative Pfad. + /// Der normalisierte Pfad mit Schrägstrichen. private static string NormalizeRelativePath(string path) { path = (path ?? string.Empty).Replace('\\', '/').Trim(); diff --git a/src/BizTalkPlatformManagementTool.Setup/Program.cs b/src/BizTalkPlatformManagementTool.Setup/Program.cs index 6e993f1..bb4288a 100644 --- a/src/BizTalkPlatformManagementTool.Setup/Program.cs +++ b/src/BizTalkPlatformManagementTool.Setup/Program.cs @@ -3,8 +3,12 @@ using System.Windows.Forms; namespace BizTalkPlatformManagementTool.Setup { + /// Enthält den Einstiegspunkt des administrativen Windows-Setups. internal static class Program { + /// Startet die Setup-Oberfläche im Installations- oder Deinstallationsmodus. + /// Befehlszeilenargumente; --uninstall aktiviert die Deinstallation. + /// Null nach regulärem Schließen der Oberfläche. [STAThread] private static int Main(string[] args) { diff --git a/src/BizTalkPlatformManagementTool.Setup/Properties/AssemblyInfo.cs b/src/BizTalkPlatformManagementTool.Setup/Properties/AssemblyInfo.cs index 2490d7c..706a6af 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.1.1.0")] -[assembly: AssemblyFileVersion("2.1.1.0")] +[assembly: AssemblyVersion("2.1.2.0")] +[assembly: AssemblyFileVersion("2.1.2.0")] [assembly: InternalsVisibleTo("BizTalkPlatformManagementTool.Tests")] diff --git a/src/BizTalkPlatformManagementTool.Setup/SetupOperationLog.cs b/src/BizTalkPlatformManagementTool.Setup/SetupOperationLog.cs index 275ca9e..c365511 100644 --- a/src/BizTalkPlatformManagementTool.Setup/SetupOperationLog.cs +++ b/src/BizTalkPlatformManagementTool.Setup/SetupOperationLog.cs @@ -8,22 +8,38 @@ using System.Text; namespace BizTalkPlatformManagementTool.Setup { /// - /// Writes a durable, single-line diagnostic trace for one setup operation. - /// No credential or other secret is accepted by this component. + /// Schreibt ein dauerhaftes, einzeiliges Diagnoseprotokoll für genau einen Setup-Lauf. + /// Die Komponente übernimmt keine Kennwörter oder andere Geheimnisse. /// internal sealed class SetupOperationLog { + /// Serialisiert konkurrierende Schreibzugriffe innerhalb des Setup-Prozesses. private readonly object sync = new object(); + /// + /// Initialisiert ein Protokoll für einen bereits festgelegten Dateipfad. + /// + /// Der Logpfad oder eine leere Zeichenfolge bei vollständig ausgefallenem Logging. private SetupOperationLog(string filePath) { FilePath = filePath; } + /// Ruft den tatsächlich verwendeten Logpfad ab. public string FilePath { get; private set; } + + /// Ruft die formatierte Ursache eines Fehlers beim primären Logaufbau ab. public string CreationError { get; private set; } + + /// Ruft ab, ob das Log im temporären Rückfallverzeichnis liegt. public bool IsFallback { get; private set; } + /// + /// Erstellt ein Setup-Protokoll unter ProgramData oder ersatzweise im Temp-Verzeichnis. + /// + /// Das bevorzugte dauerhafte Datenverzeichnis. + /// Die kurze Operationsbezeichnung für den Dateinamen und Kontextkopf. + /// Ein verwendbares Protokollobjekt, auch wenn keine Logdatei angelegt werden konnte. public static SetupOperationLog Create(string dataDirectory, string operation) { try @@ -34,6 +50,8 @@ namespace BizTalkPlatformManagementTool.Setup { try { + // Ohne primäres ProgramData-Log bleibt wenigstens im Benutzer-Temp ein + // Diagnosepfad erhalten; die ursprüngliche Ursache wird dort mitgeschrieben. var fallback = CreateInDirectory( Path.Combine(Path.GetTempPath(), "BizTalkPlatformManagementTool", "InstallerLogs"), operation); @@ -55,6 +73,12 @@ namespace BizTalkPlatformManagementTool.Setup } } + /// + /// Legt eine eindeutig benannte Logdatei an und schreibt den technischen Kontextkopf. + /// + /// Das Zielverzeichnis der Logdatei. + /// Die Bezeichnung des Setup-Laufs. + /// Das initialisierte Setup-Protokoll. private static SetupOperationLog CreateInDirectory(string directory, string operation) { Directory.CreateDirectory(directory); @@ -81,6 +105,11 @@ namespace BizTalkPlatformManagementTool.Setup return log; } + /// + /// Schreibt eine UTC-zeitgestempelte, einzeilige Nachricht ausfallsicher in die Logdatei. + /// + /// Der textuelle Log-Level. + /// Die zu protokollierende Nachricht. public void Write(string level, string message) { if (string.IsNullOrEmpty(FilePath)) return; @@ -97,15 +126,26 @@ namespace BizTalkPlatformManagementTool.Setup } catch { - // Diagnostic logging must never replace the actual setup outcome. + // Das Diagnose-Logging darf das eigentliche Setup-Ergebnis niemals ersetzen. } } + /// + /// Protokolliert einen Fehler mit stabiler Kennung, Phase und vollständiger Exception-Kette. + /// + /// Die stabile maschinenlesbare Fehlerkennung. + /// Die lesbare Setup-Phase. + /// Die zu protokollierende Ausnahme. public void WriteException(string errorCode, string phase, Exception exception) { Write("ERROR", "event=exception error_code=" + errorCode + " phase=\"" + phase + "\" " + FormatException(exception)); } + /// + /// Protokolliert Existenz, Größe, Zeitstempel, Dateiversion und SHA-256 einer Datei. + /// + /// Die fachliche Rolle der Datei im Setup. + /// Der zu untersuchende Dateipfad. public void WriteFileDetails(string label, string path) { try @@ -131,6 +171,11 @@ namespace BizTalkPlatformManagementTool.Setup } } + /// + /// Formatiert eine Exception-Kette mit Typ, HRESULT, Nachricht und Stacktrace. + /// + /// Die äußerste Ausnahme. + /// Eine einzeilige Diagnose mit höchstens zwölf Exception-Ebenen. internal static string FormatException(Exception exception) { var result = new StringBuilder(); @@ -149,6 +194,10 @@ namespace BizTalkPlatformManagementTool.Setup return SingleLine(result.ToString()); } + /// + /// Ermittelt die aktuelle Windows-Identität ohne einen Diagnosefehler weiterzureichen. + /// + /// Der Identitätsname oder (unknown). private static string CurrentIdentity() { try @@ -164,6 +213,10 @@ namespace BizTalkPlatformManagementTool.Setup } } + /// + /// Ermittelt, ob der aktuelle Prozess mit Administratorrechten läuft. + /// + /// true, false oder unknown. private static string IsElevated() { try @@ -179,11 +232,20 @@ namespace BizTalkPlatformManagementTool.Setup } } + /// + /// Maskiert Steuerzeichen, damit jeder Logeintrag genau eine physische Zeile belegt. + /// + /// Der zu normalisierende Text. + /// Der einzeilige Text. private static string SingleLine(string value) { return (value ?? string.Empty).Replace("\r", "\\r").Replace("\n", "\\n").Replace("\t", "\\t"); } + /// + /// Entfernt Setup-Protokolle, deren letzte Änderung mehr als 90 Tage zurückliegt. + /// + /// Das zu bereinigende Installer-Logverzeichnis. private static void CleanupOldLogs(string directory) { try @@ -196,7 +258,7 @@ namespace BizTalkPlatformManagementTool.Setup } catch { - // Retention cleanup is best-effort. + // Die Aufbewahrungsbereinigung ist bestmöglich und blockiert kein Setup. } } } diff --git a/src/BizTalkPlatformManagementTool.Setup/app.manifest b/src/BizTalkPlatformManagementTool.Setup/app.manifest index 03cd382..4b6fc83 100644 --- a/src/BizTalkPlatformManagementTool.Setup/app.manifest +++ b/src/BizTalkPlatformManagementTool.Setup/app.manifest @@ -1,6 +1,6 @@ - + diff --git a/src/BizTalkPlatformManagementTool/BizTalkPlatformManagementTool.csproj b/src/BizTalkPlatformManagementTool/BizTalkPlatformManagementTool.csproj index a86763d..1eb1248 100644 --- a/src/BizTalkPlatformManagementTool/BizTalkPlatformManagementTool.csproj +++ b/src/BizTalkPlatformManagementTool/BizTalkPlatformManagementTool.csproj @@ -34,6 +34,7 @@ prompt 4 false + bin\Release\BizTalkPlatformManagementTool.xml diff --git a/src/BizTalkPlatformManagementTool/Program.cs b/src/BizTalkPlatformManagementTool/Program.cs index f1da365..a5aeb80 100644 --- a/src/BizTalkPlatformManagementTool/Program.cs +++ b/src/BizTalkPlatformManagementTool/Program.cs @@ -14,9 +14,13 @@ namespace BizTalkPlatformManagementTool /// /// Starts the application after verifying that BizTalk WMI operations can run elevated. /// + /// Befehlszeilenargumente; --self-test startet die WMI-freie Installerprüfung. + /// Null bei erfolgreichem Abschluss, andernfalls ein prozessgeeigneter Fehlercode. [STAThread] private static int Main(string[] args) { + // Der Self-Test muss ohne Administratorprüfung und ohne WinForms-Oberfläche laufen, + // damit der Installer ihn bereits im isolierten Staging-Verzeichnis ausführen kann. if (args != null && args.Length == 1 && string.Equals(args[0], "--self-test", StringComparison.OrdinalIgnoreCase)) { return RuntimeSelfTest.Run(); @@ -38,6 +42,7 @@ namespace BizTalkPlatformManagementTool } bool createdNew; + // Der sitzungsbezogene Mutex verhindert konkurrierende Wartungsoperationen desselben Benutzers. using (var mutex = new Mutex(true, @"Local\BizTalkPlatformManagementTool", out createdNew)) { if (!createdNew) diff --git a/src/BizTalkPlatformManagementTool/Properties/AssemblyInfo.cs b/src/BizTalkPlatformManagementTool/Properties/AssemblyInfo.cs index 45c08ad..c45dfbc 100644 --- a/src/BizTalkPlatformManagementTool/Properties/AssemblyInfo.cs +++ b/src/BizTalkPlatformManagementTool/Properties/AssemblyInfo.cs @@ -8,5 +8,5 @@ using System.Runtime.InteropServices; [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: ComVisible(false)] [assembly: Guid("2c5b2c0a-f407-46c2-9e3b-1fa09fa8445a")] -[assembly: AssemblyVersion("2.1.1.0")] -[assembly: AssemblyFileVersion("2.1.1.0")] +[assembly: AssemblyVersion("2.1.2.0")] +[assembly: AssemblyFileVersion("2.1.2.0")] diff --git a/src/BizTalkPlatformManagementTool/RuntimeSelfTest.cs b/src/BizTalkPlatformManagementTool/RuntimeSelfTest.cs index 56e5bca..e65cc9f 100644 --- a/src/BizTalkPlatformManagementTool/RuntimeSelfTest.cs +++ b/src/BizTalkPlatformManagementTool/RuntimeSelfTest.cs @@ -10,6 +10,10 @@ namespace BizTalkPlatformManagementTool /// internal static class RuntimeSelfTest { + /// + /// Prüft Serialisierung, Validierung, Vergleich und Report-Persistenz ohne BizTalk-WMI-Zugriff. + /// + /// Null bei erfolgreicher Prüfung, andernfalls eins. public static int Run() { var directory = Path.Combine(Path.GetTempPath(), "BizTalkPlatformManagementTool.SelfTest." + Guid.NewGuid().ToString("N")); @@ -50,11 +54,16 @@ namespace BizTalkPlatformManagementTool } catch { - // The self-test result is more important than temporary cleanup. + // Ein Bereinigungsfehler darf das bereits feststehende Self-Test-Ergebnis nicht überschreiben. } } } + /// + /// Erstellt einen minimalen, aber vollständig validierbaren Snapshot für den Self-Test. + /// + /// Der rohe BizTalk-Status des enthaltenen Send Ports. + /// Ein Snapshot mit genau einer Anwendung und einem Send Port. private static BizTalkSnapshot SampleSnapshot(int sendPortState) { var snapshot = new BizTalkSnapshot diff --git a/src/BizTalkPlatformManagementTool/Services/BizTalkOperationService.cs b/src/BizTalkPlatformManagementTool/Services/BizTalkOperationService.cs index 09a09d4..a9683c5 100644 --- a/src/BizTalkPlatformManagementTool/Services/BizTalkOperationService.cs +++ b/src/BizTalkPlatformManagementTool/Services/BizTalkOperationService.cs @@ -15,7 +15,7 @@ namespace BizTalkPlatformManagementTool.Services /// /// Current tool version written into generated snapshots. /// - public const string Version = "2.1.1-net461"; + public const string Version = "2.1.2-net461"; /// /// Fallback application name used when WMI does not expose an application property. @@ -185,6 +185,8 @@ namespace BizTalkPlatformManagementTool.Services SnapshotValidator.EnsureServerMatches(snapshot, server); var plan = NewPlan(OperationMode.Shutdown, server); + // Eingehenden Verkehr zuerst stoppen, bevor abhängige Verarbeitungsartefakte + // und zuletzt die Host Instances heruntergefahren werden. foreach (var app in snapshot.Applications) { foreach (var item in app.ReceiveLocations.Where(x => x.Enabled)) @@ -228,6 +230,8 @@ namespace BizTalkPlatformManagementTool.Services SnapshotValidator.EnsureServerMatches(snapshot, server); var plan = NewPlan(OperationMode.Restore, server); + // Beim Restore gilt die umgekehrte Abhängigkeitsrichtung: zuerst Laufzeit-Hosts, + // danach ausgehende Verarbeitung und Receive Locations bewusst ganz zum Schluss. foreach (var item in snapshot.HostInstances.Where(x => x.RawState == ArtifactStates.HostStarted)) { var step = Step("HostInstance", string.Empty, item.InstanceName, item.Server, "Start host instance", "MSBTS_HostInstance", "InstanceName", item.InstanceName, "Start", null, ArtifactStates.HostStarted); @@ -269,6 +273,8 @@ namespace BizTalkPlatformManagementTool.Services } else if (item.OrchestrationStatus == ArtifactStates.OrchestrationBound) { + // Ein blindes Unenlist würde Bound nach Unbound verschieben und damit + // einen anderen Zustand als im Snapshot herstellen. plan.Steps.Add(new OperationStep { Kind = "Note", @@ -321,6 +327,7 @@ namespace BizTalkPlatformManagementTool.Services if (options.DryRun) { + // Dry-run löst das Objekt absichtlich nicht erneut per WMI auf und führt keine Methode aus. _logger.Info("DRY RUN: " + step.Action + " '" + step.Name + "'"); continue; } @@ -611,6 +618,10 @@ namespace BizTalkPlatformManagementTool.Services return UnknownApplication; } + /// + /// Gibt alle von einer WMI-Abfrage übernommenen Objekte deterministisch frei. + /// + /// Die freizugebenden WMI-Objekte oder null. private static void DisposeAll(IEnumerable items) { if (items == null) diff --git a/src/BizTalkPlatformManagementTool/Services/BizTalkWmiClient.cs b/src/BizTalkPlatformManagementTool/Services/BizTalkWmiClient.cs index ddbe628..3acbd72 100644 --- a/src/BizTalkPlatformManagementTool/Services/BizTalkWmiClient.cs +++ b/src/BizTalkPlatformManagementTool/Services/BizTalkWmiClient.cs @@ -151,6 +151,8 @@ namespace BizTalkPlatformManagementTool.Services try { + // Bewusst keine WQL-WHERE-Klausel: BizTalk-Namen können Zeichen enthalten, + // die sonst eine fehlerhafte oder anders interpretierte Query erzeugen. ManagementObject match = null; var items = Query(className, false); foreach (var item in items) @@ -256,6 +258,7 @@ namespace BizTalkPlatformManagementTool.Services { break; } + // Am Timeout-Ende nur noch die tatsächlich verbleibende Zeit schlafen. Thread.Sleep(remaining < TimeSpan.FromSeconds(delay) ? remaining : TimeSpan.FromSeconds(delay)); } diff --git a/src/BizTalkPlatformManagementTool/Services/CsvWriter.cs b/src/BizTalkPlatformManagementTool/Services/CsvWriter.cs index 22b07ff..15d9bb3 100644 --- a/src/BizTalkPlatformManagementTool/Services/CsvWriter.cs +++ b/src/BizTalkPlatformManagementTool/Services/CsvWriter.cs @@ -95,6 +95,7 @@ namespace BizTalkPlatformManagementTool.Services value = value ?? string.Empty; if (value.Length > 0 && (value[0] == '=' || value[0] == '+' || value[0] == '-' || value[0] == '@' || value[0] == '\t')) { + // Tabellenkalkulationen dürfen exportierte Namen nicht als Formel ausführen. value = "'" + value; } if (value.IndexOfAny(new[] { ',', '"', '\r', '\n' }) < 0) diff --git a/src/BizTalkPlatformManagementTool/Services/HtmlReportWriter.cs b/src/BizTalkPlatformManagementTool/Services/HtmlReportWriter.cs index 5a356fb..dfe4d96 100644 --- a/src/BizTalkPlatformManagementTool/Services/HtmlReportWriter.cs +++ b/src/BizTalkPlatformManagementTool/Services/HtmlReportWriter.cs @@ -130,6 +130,7 @@ namespace BizTalkPlatformManagementTool.Services /// An HTML-safe value. private static string Encode(string value) { + // Alle aus BizTalk gelesenen Werte werden vor der Aufnahme in HTML neutralisiert. return WebUtility.HtmlEncode(value ?? string.Empty); } } diff --git a/src/BizTalkPlatformManagementTool/Services/JsonFileStore.cs b/src/BizTalkPlatformManagementTool/Services/JsonFileStore.cs index 71028c1..9d35cab 100644 --- a/src/BizTalkPlatformManagementTool/Services/JsonFileStore.cs +++ b/src/BizTalkPlatformManagementTool/Services/JsonFileStore.cs @@ -123,8 +123,12 @@ namespace BizTalkPlatformManagementTool.Services /// Writes a file through a same-directory temporary file so an interrupted /// save cannot leave a truncated snapshot or operation plan behind. /// + /// Der endgültige Zielpfad. + /// Der vollständig serialisierte Dateiinhalt. private static void WriteAtomically(string path, string content) { + // Temporärdatei und Backup liegen absichtlich im Zielverzeichnis. Dadurch bleiben + // Umbenennung und Austausch auf demselben Volume und können atomar erfolgen. var temporaryPath = path + ".tmp." + Guid.NewGuid().ToString("N"); var backupPath = path + ".bak." + Guid.NewGuid().ToString("N"); try @@ -156,6 +160,12 @@ namespace BizTalkPlatformManagementTool.Services } } + /// + /// Ersetzt eine vorhandene Datei über Umbenennungen, wenn nicht unterstützt wird. + /// + /// Der endgültige Zielpfad. + /// Die vollständig geschriebene Temporärdatei. + /// Der temporäre Sicherungspfad der vorherigen Datei. private static void ReplaceWithRenameFallback(string path, string temporaryPath, string backupPath) { File.Move(path, backupPath); @@ -174,6 +184,10 @@ namespace BizTalkPlatformManagementTool.Services } } + /// + /// Löscht eine temporäre Datei bestmöglich, ohne das primäre Speicherergebnis zu verändern. + /// + /// Der zu löschende Dateipfad. private static void TryDelete(string path) { try @@ -185,7 +199,7 @@ namespace BizTalkPlatformManagementTool.Services } catch { - // Temporary cleanup is best-effort and must not hide the save result. + // Die Bereinigung ist nachrangig und darf einen erfolgreichen Schreibvorgang nicht verdecken. } } } diff --git a/src/BizTalkPlatformManagementTool/Services/OperationLogger.cs b/src/BizTalkPlatformManagementTool/Services/OperationLogger.cs index 374e35b..cab86b0 100644 --- a/src/BizTalkPlatformManagementTool/Services/OperationLogger.cs +++ b/src/BizTalkPlatformManagementTool/Services/OperationLogger.cs @@ -197,7 +197,7 @@ namespace BizTalkPlatformManagementTool.Services } catch { - // Logging must never interrupt BizTalk operations. + // Ein Logfehler darf niemals eine fachliche BizTalk-Operation abbrechen. } } @@ -225,10 +225,14 @@ namespace BizTalkPlatformManagementTool.Services } catch { - // Log retention cleanup is best-effort. + // Die Aufbewahrungsbereinigung ist bestmöglich und beeinflusst den aktuellen Lauf nicht. } } + /// + /// Ermittelt das bevorzugte maschinenweite Logverzeichnis mit Rückfall auf das EXE-Verzeichnis. + /// + /// Ein verwendbares Verzeichnis für die täglichen Laufzeitlogs. private static string ResolveLogDirectory() { var commonData = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData); diff --git a/src/BizTalkPlatformManagementTool/Services/SnapshotComparer.cs b/src/BizTalkPlatformManagementTool/Services/SnapshotComparer.cs index 4d71f29..2dd54ee 100644 --- a/src/BizTalkPlatformManagementTool/Services/SnapshotComparer.cs +++ b/src/BizTalkPlatformManagementTool/Services/SnapshotComparer.cs @@ -137,6 +137,7 @@ namespace BizTalkPlatformManagementTool.Services foreach (var item in before) { + // Hostname allein ist gruppenweit nicht eindeutig; der Server gehört zur Identität. beforeMap[SnapshotValidator.ArtifactKey(item.Server, item.InstanceName)] = item; } foreach (var item in after) diff --git a/src/BizTalkPlatformManagementTool/Services/SnapshotValidator.cs b/src/BizTalkPlatformManagementTool/Services/SnapshotValidator.cs index 60c6f5c..9f1d7d4 100644 --- a/src/BizTalkPlatformManagementTool/Services/SnapshotValidator.cs +++ b/src/BizTalkPlatformManagementTool/Services/SnapshotValidator.cs @@ -9,7 +9,10 @@ namespace BizTalkPlatformManagementTool.Services /// public static class SnapshotValidator { - /// Normalizes optional collections and rejects missing or duplicate artifact identities. + /// + /// Normalisiert optionale Sammlungen und weist fehlende oder doppelte Artefaktidentitäten zurück. + /// + /// Der zu normalisierende und zu validierende Snapshot. public static void Validate(BizTalkSnapshot snapshot) { if (snapshot == null) @@ -55,7 +58,11 @@ namespace BizTalkPlatformManagementTool.Services } } - /// Validates a snapshot and ensures it belongs to the requested operation server. + /// + /// Validiert einen Snapshot und stellt sicher, dass er zum angeforderten Zielserver gehört. + /// + /// Der als Operationsgrundlage verwendete Snapshot. + /// Der für die Operation ausgewählte BizTalk-Server. public static void EnsureServerMatches(BizTalkSnapshot snapshot, string targetServer) { Validate(snapshot); @@ -69,7 +76,12 @@ namespace BizTalkPlatformManagementTool.Services } } - /// Compares server names while accepting short-name/FQDN variants of the same host. + /// + /// Vergleicht Servernamen und akzeptiert Kurzname und FQDN desselben Hosts als identisch. + /// + /// Der erste Servername. + /// Der zweite Servername. + /// true, wenn beide Namen denselben Server bezeichnen; andernfalls false. public static bool ServerNamesEqual(string left, string right) { var normalizedLeft = NormalizeServer(left); @@ -82,12 +94,28 @@ namespace BizTalkPlatformManagementTool.Services return string.Equals(ShortName(normalizedLeft), ShortName(normalizedRight), StringComparison.OrdinalIgnoreCase); } - /// Builds the collision-safe identity used for application artifacts. + /// + /// Erstellt die kollisionsarme Identität für anwendungsbezogene BizTalk-Artefakte. + /// + /// Der Name der BizTalk-Anwendung. + /// Der Artefaktname. + /// Ein zusammengesetzter Schlüssel aus Anwendung und Artefaktname. public static string ArtifactKey(string application, string name) { + // Das nicht druckbare Trennzeichen kann in normalen BizTalk-Namen nicht mit der + // sichtbaren Verkettung von Anwendung und Artefakt verwechselt werden. return (application ?? string.Empty).Trim() + "\u001f" + (name ?? string.Empty).Trim(); } + /// + /// Prüft eine Artefaktsammlung auf leere Namen und doppelte Identitäten. + /// + /// Der Typ des zu prüfenden Snapshot-Artefakts. + /// Die besitzende BizTalk-Anwendung. + /// Die lesbare Artefaktbezeichnung für Fehlermeldungen. + /// Die zu prüfenden Artefakte. + /// Funktion zum Ermitteln des Artefaktnamens. + /// Die bereits bekannten Identitäten dieses Artefakttyps. private static void ValidateArtifacts(string application, string type, IEnumerable values, Func getName, HashSet keys) { foreach (var value in values) @@ -105,6 +133,11 @@ namespace BizTalkPlatformManagementTool.Services } } + /// + /// Normalisiert lokale Serveraliasnamen auf den tatsächlichen Rechnernamen. + /// + /// Der eingegebene Servername. + /// Der getrimmte und normalisierte Servername. private static string NormalizeServer(string value) { value = (value ?? string.Empty).Trim().TrimStart('\\'); @@ -115,6 +148,11 @@ namespace BizTalkPlatformManagementTool.Services return value; } + /// + /// Entfernt den DNS-Suffix eines Servernamens. + /// + /// Ein normalisierter Kurzname oder FQDN. + /// Der Hostanteil vor dem ersten Punkt. private static string ShortName(string value) { var index = value.IndexOf('.'); diff --git a/src/BizTalkPlatformManagementTool/Ui/MainForm.cs b/src/BizTalkPlatformManagementTool/Ui/MainForm.cs index 9971ceb..09a287d 100644 --- a/src/BizTalkPlatformManagementTool/Ui/MainForm.cs +++ b/src/BizTalkPlatformManagementTool/Ui/MainForm.cs @@ -373,6 +373,7 @@ namespace BizTalkPlatformManagementTool.Ui var snapshot = _service.CreateSnapshot(options.Server); _service.SaveSnapshot(options.OutputDirectory, "before.json", snapshot); var plan = _service.CreateShutdownPlan(snapshot, options.Server); + // Der exakte, frisch erzeugte Plan wird vor Bestätigung und jeder Laufzeitänderung gespeichert. var planPath = _service.SavePlan(options.OutputDirectory, "shutdown-plan.json", plan); ShowPlan(plan); if (!options.DryRun && !ConfirmPreparedPlan("Shutdown", plan, options.Server, planPath)) @@ -402,6 +403,7 @@ namespace BizTalkPlatformManagementTool.Ui { var snapshot = JsonFileStore.Load(ResolveStateFile(options)); var plan = _service.CreateRestorePlan(snapshot, options.Server); + // Auch beim Restore bestätigt der Bediener genau den bereits auditierbar gespeicherten Plan. var planPath = _service.SavePlan(options.OutputDirectory, "restore-plan.json", plan); ShowPlan(plan); if (!options.DryRun && !ConfirmPreparedPlan("Restore", plan, options.Server, planPath)) @@ -503,6 +505,9 @@ namespace BizTalkPlatformManagementTool.Ui /// Confirms a fully prepared runtime-changing plan immediately before execution. /// /// The action name displayed in the confirmation dialog. + /// Der vollständig vorbereitete und gespeicherte Operationsplan. + /// Der Zielserver der geplanten Änderung. + /// Der Pfad der bereits gespeicherten Plandatei. /// True when the action may continue; otherwise false. private bool ConfirmPreparedPlan(string actionName, OperationPlan plan, string server, string planPath) { @@ -683,7 +688,7 @@ namespace BizTalkPlatformManagementTool.Ui } catch (InvalidOperationException) { - // The form was closed between the state check and BeginInvoke. + // Das Formular wurde zwischen Zustandsprüfung und BeginInvoke geschlossen. } } else @@ -695,6 +700,8 @@ namespace BizTalkPlatformManagementTool.Ui /// /// Prevents the form from being disposed while a maintenance operation is active. /// + /// Das Formular, das geschlossen werden soll. + /// Die abbrechbaren Argumente des Schließereignisses. private void MainFormClosing(object sender, FormClosingEventArgs e) { if (!_isBusy) diff --git a/src/BizTalkPlatformManagementTool/app.manifest b/src/BizTalkPlatformManagementTool/app.manifest index 5fb5071..2de45e5 100644 --- a/src/BizTalkPlatformManagementTool/app.manifest +++ b/src/BizTalkPlatformManagementTool/app.manifest @@ -1,6 +1,6 @@ - + diff --git a/tests/BizTalkPlatformManagementTool.Tests/BizTalkPlatformManagementTool.Tests.csproj b/tests/BizTalkPlatformManagementTool.Tests/BizTalkPlatformManagementTool.Tests.csproj index ceae66d..e99c670 100644 --- a/tests/BizTalkPlatformManagementTool.Tests/BizTalkPlatformManagementTool.Tests.csproj +++ b/tests/BizTalkPlatformManagementTool.Tests/BizTalkPlatformManagementTool.Tests.csproj @@ -3,7 +3,7 @@ DebugAnyCPU{318F4307-F62C-47C9-9B90-F0C9BF2F812A}ExeBizTalkPlatformManagementTool.TestsBizTalkPlatformManagementTool.Testsv4.6.1512true truefullfalsebin\Debug\DEBUG;TRACE4 - pdbonlytruebin\Release\TRACE4 + pdbonlytruebin\Release\TRACE4bin\Release\BizTalkPlatformManagementTool.Tests.xml diff --git a/tests/BizTalkPlatformManagementTool.Tests/Program.cs b/tests/BizTalkPlatformManagementTool.Tests/Program.cs index 09b0fec..7f60288 100644 --- a/tests/BizTalkPlatformManagementTool.Tests/Program.cs +++ b/tests/BizTalkPlatformManagementTool.Tests/Program.cs @@ -9,10 +9,16 @@ using BizTalkPlatformManagementTool.Setup; namespace BizTalkPlatformManagementTool.Tests { + /// + /// Enthält die portable Regressionstestsuite ohne Abhängigkeit von einem externen Testframework. + /// internal static class Program { + /// Anzahl der im aktuellen Testlauf fehlgeschlagenen Prüfungen. private static int failures; + /// Führt alle Regressionstests aus und liefert einen CI-tauglichen Exitcode. + /// Null, wenn alle Tests bestanden wurden; andernfalls eins. private static int Main() { Run("JsonRoundTripIsBomTolerantAndAtomic", JsonRoundTripIsBomTolerantAndAtomic); @@ -33,12 +39,16 @@ namespace BizTalkPlatformManagementTool.Tests return failures == 0 ? 0 : 1; } + /// Führt einen einzelnen Test isoliert aus und protokolliert sein Ergebnis. + /// Der stabile Testname für die Konsolenausgabe. + /// Die auszuführende Testfunktion. private static void Run(string name, Action test) { try { test(); Console.WriteLine("PASS " + name); } catch (Exception ex) { failures++; Console.Error.WriteLine("FAIL " + name + ": " + ex); } } + /// Prüft BOM-tolerantes Lesen und rückstandsfreies atomisches JSON-Schreiben. private static void JsonRoundTripIsBomTolerantAndAtomic() { InTemp(directory => @@ -58,6 +68,7 @@ namespace BizTalkPlatformManagementTool.Tests }); } + /// Prüft, dass gleichnamige Artefakte verschiedener Anwendungen getrennt verglichen werden. private static void DiffUsesApplicationAndNameIdentity() { var before = Snapshot("APP-A", "SHARED", ArtifactStates.SendPortStarted); @@ -69,6 +80,7 @@ namespace BizTalkPlatformManagementTool.Tests Assert(diff.ArtifactDifferences[0].Application == "APP-A", "wrong application was compared"); } + /// Prüft Kurzname/FQDN-Kompatibilität und Ablehnung eines fremden Restore-Zielservers. private static void RestoreRejectsDifferentServer() { var snapshot = Snapshot("APP", "PORT", ArtifactStates.SendPortStarted); @@ -77,6 +89,7 @@ namespace BizTalkPlatformManagementTool.Tests Expect(() => SnapshotValidator.EnsureServerMatches(snapshot, "BIZTALK-B")); } + /// Prüft die sichere Restore-Reihenfolge und den Schutz gebundener Orchestrierungen. private static void RestorePlanUsesSafeOrder() { var snapshot = Snapshot("APP", "PORT", ArtifactStates.SendPortStarted); @@ -90,6 +103,7 @@ namespace BizTalkPlatformManagementTool.Tests Assert(!bound.Execute && bound.Kind == "Note", "bound orchestration must remain unchanged"); } + /// Prüft die Neutralisierung formelartiger CSV-Feldwerte. private static void CsvNeutralizesFormulaValues() { InTemp(directory => @@ -102,6 +116,7 @@ namespace BizTalkPlatformManagementTool.Tests }); } + /// Prüft, dass eine nach Manifestbildung veränderte Payload abgelehnt wird. private static void PackageManifestRejectsTampering() { InTemp(directory => @@ -117,6 +132,7 @@ namespace BizTalkPlatformManagementTool.Tests }); } + /// Prüft die Aktivierung einer vollständig validierten Neuinstallation. private static void InstallerActivatesValidatedPayload() { InTemp(directory => @@ -131,6 +147,7 @@ namespace BizTalkPlatformManagementTool.Tests }); } + /// Prüft die Ablehnung nicht deklarierter Dateien und ausbrechender Manifestpfade. private static void PackageManifestRejectsUndeclaredAndTraversalFiles() { InTemp(directory => @@ -149,6 +166,7 @@ namespace BizTalkPlatformManagementTool.Tests }); } + /// Prüft, dass ein Staging-Fehler die aktive Installation nicht mutiert. private static void InstallerDoesNotMutateOnStagingFailure() { InTemp(directory => @@ -172,6 +190,7 @@ namespace BizTalkPlatformManagementTool.Tests }); } + /// Prüft die Wiederherstellung der Vorversion nach fehlgeschlagenem aktiviertem Self-Test. private static void InstallerRollsBackFailedActivatedSelfTest() { InTemp(directory => @@ -191,6 +210,7 @@ namespace BizTalkPlatformManagementTool.Tests }); } + /// Prüft die Deinstallation über ein atomar umbenanntes Quarantäneverzeichnis. private static void InstallerUninstallRemovesProgramDirectory() { InTemp(directory => @@ -205,6 +225,7 @@ namespace BizTalkPlatformManagementTool.Tests }); } + /// Prüft technischen Kontext, Fehlercode, HRESULT und innere Ausnahme im Setup-Log. private static void InstallerDiagnosticLogContainsContextAndExceptionChain() { InTemp(directory => @@ -225,6 +246,7 @@ namespace BizTalkPlatformManagementTool.Tests }); } + /// Prüft, dass ein fehlerhafter UI-Callback die Installation nicht beeinflusst. private static void InstallerSurvivesUiReportFailure() { InTemp(directory => @@ -240,6 +262,7 @@ namespace BizTalkPlatformManagementTool.Tests }); } + /// Prüft den Diagnose-Log-Fallback bei einem nicht verwendbaren ProgramData-Pfad. private static void InstallerDiagnosticLogFallsBackToTemp() { InTemp(directory => @@ -261,6 +284,11 @@ namespace BizTalkPlatformManagementTool.Tests }); } + /// Erstellt einen minimalen Snapshot für Vergleiche und Planprüfungen. + /// Der Name der Testanwendung. + /// Der Name des Test-Send-Ports. + /// Der rohe Send-Port-Status. + /// Ein Snapshot mit einer Anwendung und einem Send Port. private static BizTalkSnapshot Snapshot(string application, string port, int state) { var result = new BizTalkSnapshot { ToolVersion = "test", CreatedAt = DateTimeOffset.Now.ToString("o"), Server = Environment.MachineName }; @@ -270,6 +298,10 @@ namespace BizTalkPlatformManagementTool.Tests return result; } + /// Erzeugt eine minimale, manifestierte Installer-Payload. + /// Das temporäre Testwurzelverzeichnis. + /// Der simulierte Inhalt der Anwendungs-EXE. + /// Das Verzeichnis des erzeugten Testpakets. private static string CreatePackage(string root, string payload) { var package = Path.Combine(root, "package"); @@ -280,6 +312,8 @@ namespace BizTalkPlatformManagementTool.Tests return package; } + /// Führt einen Test in einem eindeutigen temporären Verzeichnis mit garantierter Bereinigung aus. + /// Die Testfunktion, die den temporären Pfad erhält. private static void InTemp(Action action) { var directory = Path.Combine(Path.GetTempPath(), "BizTalkPlatformManagementTool.Tests." + Guid.NewGuid().ToString("N")); @@ -288,7 +322,14 @@ namespace BizTalkPlatformManagementTool.Tests finally { if (Directory.Exists(directory)) Directory.Delete(directory, true); } } + /// Bricht den Test ab, wenn eine erwartete Bedingung nicht erfüllt ist. + /// Die erwartete Bedingung. + /// Die Fehlermeldung bei nicht erfüllter Bedingung. private static void Assert(bool condition, string message) { if (!condition) throw new InvalidOperationException(message); } + + /// Prüft, dass eine Aktion eine bestimmte Ausnahme auslöst. + /// Der erwartete Ausnahmetyp. + /// Die auszuführende Aktion. private static void Expect(Action action) where T : Exception { try { action(); } @@ -296,6 +337,10 @@ namespace BizTalkPlatformManagementTool.Tests throw new InvalidOperationException("Expected exception " + typeof(T).Name); } + /// Führt eine Aktion aus und gibt die erwartete Ausnahme für weitere Prüfungen zurück. + /// Der erwartete Ausnahmetyp. + /// Die auszuführende Aktion. + /// Die von der Aktion ausgelöste Ausnahme. private static T Capture(Action action) where T : Exception { try { action(); }