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(); }