diff --git a/AI-README.md b/AI-README.md index 2b05582..6b63880 100644 --- a/AI-README.md +++ b/AI-README.md @@ -2,7 +2,7 @@ ## Projekt und Sicherheitsziel -Das Repository enthält ein .NET-Framework-4.6.1-WinForms-Tool für kontrollierte BizTalk-2020-Wartungsoperationen. Änderungen müssen Dry-run, explizite Freigabe realer Aktionen, sichere Reihenfolgen und wiederherstellbare Installergrenzen erhalten. Die aktuelle Produktversion ist 2.3.2. +Das Repository enthält ein .NET-Framework-4.6.1-WinForms-Tool für kontrollierte BizTalk-2020-Wartungsoperationen. Änderungen müssen Dry-run, explizite Freigabe realer Aktionen, sichere Reihenfolgen und wiederherstellbare Installergrenzen erhalten. Die aktuelle Produktversion ist 2.3.3. ## Installerinvarianten @@ -27,6 +27,8 @@ Die zentrale Implementierung liegt in `src/BizTalkPlatformManagementTool.Setup/I - Teilfehler werden vollständig pro Schritt persistiert; der Gesamtlauf bleibt sichtbar fehlgeschlagen und darf nicht als Erfolg ausgegeben werden. - Vor jeder Mutation wird der aktuelle Zustand geprüft. Bereits erreichte Sollzustände werden ohne Methodenaufruf als `AlreadySatisfied` erfasst. - Der Nachher-Snapshot wird unabhängig von Einzelfehlern versucht; sein Fehler gehört in denselben Ergebnisreport. +- Die Runtime-WMI-Klassen liefern keine verlässliche Application-Zuordnung. `BizTalkApplicationResolver` liest deshalb die Management-DB-Position aus `MSBTS_GroupSetting` und indiziert die read-only ExplorerOM-Anwendungshierarchie. WMI bleibt Quelle für Zustand und Mutationen; ExplorerOM wird ohne Compile-time-Abhängigkeit und nur mit geprüfter Microsoft-Assemblyidentität geladen. +- Scheitert die Katalogauflösung, bleibt die WMI-Zustandserfassung verfügbar, aber alle betroffenen Artefakte werden sichtbar unter `(Unknown Application)` gruppiert und Diagnose, Grid sowie Laufzeitlog müssen eine verwertbare Warnung ausgeben. - Shutdown-Kategorien gelten global über alle Anwendungen: Receive Locations, Orchestrations, Send Ports, Host Instances. - Zwischen der globalen Receive-Location-Phase und allen späteren Shutdown-Kategorien liegt ein persistierter Operator-Checkpoint. Ein realer Lauf darf nur nach explizitem Continue fortsetzen; Stop oder Handlerfehler markiert alle späteren Zeilen als `NotExecuted` und arbeitet fail-closed. - Dry-run zeigt den Checkpoint ohne Callback. Der Ergebnisreport muss Entscheidung, Zeitpunkt, OperatorStopped und NotExecutedCount dauerhaft enthalten. diff --git a/CHANGELOG.md b/CHANGELOG.md index d578302..1aa76fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ # Changelog +## [2.3.3] - 2026-08-27 +### Fixed +- Snapshot, shutdown and restore rows no longer show every Send Port, Receive Location and Orchestration under `(Unknown Application)` when the BizTalk runtime WMI classes omit application ownership. + +### Changed +- Application ownership is now read from the official read-only ExplorerOM application hierarchy, while WMI remains authoritative for runtime state and mutations. +- **Diagnose** reports visible Send Ports and the exact number with a resolved application association; incomplete catalog access remains a visible warning instead of being hidden. +- ExplorerOM is loaded process-locally from the GAC or identity-checked BizTalk installation paths, preserving the portable build and WMI-free installer self-test. + +### Added +- Regression coverage for Send Port, Receive Port/Location and Orchestration mapping, case-insensitive lookup and rejection of ambiguous cross-application names. +- A dedicated PROD root-cause analysis, validation runbook and plain-text handoff for the application-association fix. + ## [2.3.2] - 2026-08-26 ### Added - A persisted, fail-closed operator drain checkpoint between the global receive-location phase and all later real shutdown phases. diff --git a/Dokumentation.md b/Dokumentation.md index 2f9e5ef..944b846 100644 --- a/Dokumentation.md +++ b/Dokumentation.md @@ -20,6 +20,7 @@ Das BizTalk Platform Management Tool unterstützt kontrollierte Wartungsfenster - Projekt: `src/BizTalkPlatformManagementTool/BizTalkPlatformManagementTool.csproj` - UI: `src/BizTalkPlatformManagementTool/Ui/MainForm.cs` - WMI-Zugriff: `src/BizTalkPlatformManagementTool/Services/BizTalkWmiClient.cs` +- Anwendungszuordnung: `src/BizTalkPlatformManagementTool/Services/BizTalkApplicationResolver.cs` - Adapter-Assemblyauflösung: `src/BizTalkPlatformManagementTool/Services/AdapterAssemblyResolver.cs` - Persistentes Laufzeitlogging: `src/BizTalkPlatformManagementTool/Services/OperationLogger.cs` - Operationslogik: `src/BizTalkPlatformManagementTool/Services/BizTalkOperationService.cs` @@ -37,6 +38,7 @@ Deutsche Inline-Kommentare stehen gezielt an Stellen, deren Zweck nicht allein a - sichere Shutdown-/Restore-Reihenfolge und Schutz gebundener Orchestrierungen, - WMI-Auflösung über breite Abfrage mit clientseitigem Filter, +- read-only Anwendungszuordnung über die ExplorerOM-Anwendungshierarchie, - atomare JSON-Ersetzung auf demselben Volume, - Neutralisierung formelartiger CSV-Werte, - Persistenz eines Operationsplans vor der Benutzerbestätigung, @@ -82,6 +84,7 @@ Die Statusanzeige rechts im Kopfbereich bewertet die Host-Instance-Zustaende des - Ein Teilfehler bleibt im GUI und im Ergebnisreport ausdrücklich fehlgeschlagen und wird nicht als Gesamterfolg ausgegeben. - Der Nachher-Snapshot wird auch nach Einzelfehlern separat versucht; ein Snapshotfehler wird im Ergebnisreport gesichert. - WMI-Methodenrückgaben werden geprüft. +- **Diagnose** meldet neben sichtbaren Send Ports auch die Zahl erfolgreich zugeordneter Anwendungen; eine Differenz erzeugt eine Warnung. - Wartezeiten nutzen konfigurierbare Timeout- und Polling-Werte. - Host Instances auf anderen Servern werden übersprungen und als Warnung protokolliert. - Echte Shutdown-/Restore-Schritte protokollieren WMI-Klasse, Schlüssel, Zielobjekt und Methode, damit Fehler wie WMI-Query- oder Methodenfehler eindeutig zugeordnet werden können. @@ -157,6 +160,10 @@ Die Standardkonfiguration enthält BizTalk Server 2020 und den im PROD-Screensho ## Fehleranalyse +`MSBTS_SendPort`, `MSBTS_ReceiveLocation` und `MSBTS_Orchestration` enthalten gemäß BizTalk-WMI-Schema keine verlässliche Application-Property. Die Anwendung liest deshalb zuerst über `MSBTS_GroupSetting` den BizTalk-Managementdatenbankserver und -namen. Danach wird `Microsoft.BizTalk.ExplorerOM.dll` ohne Compile-time-Referenz aus dem GAC oder einem identitätsgeprüften BizTalk-Installationspfad geladen. Der read-only Anwendungskatalog liefert die Hierarchie Application → Send Ports, Receive Ports/Locations und Orchestrations; die eigentlichen Zustände und alle Mutationen bleiben ausschließlich WMI-basiert. + +Kann ExplorerOM nicht geladen werden, fehlen Management-DB-Rechte oder ist ein Name nicht eindeutig, bleibt die Zustandserfassung best-effort verfügbar. Die betroffenen Zeilen stehen dann bewusst unter `(Unknown Application)`. Das Grid und die Laufzeitdatei enthalten die vollständige Lade-/DB-/Reflection-Diagnose; **Diagnose** zeigt eine kleinere Zahl zugeordneter als sichtbarer Send Ports. Ein Dry-run darf erst nach geklärter Zuordnung freigegeben werden. + Bei echten Shutdown- und Restore-Aktionen wird jeder Schritt vor der Ausführung mit Artefakttyp, WMI-Klasse, Schlüsselproperty, Schlüsselwert und Methodenname protokolliert. Die Objektauflösung verwendet eine breite `SELECT * FROM `-Abfrage und filtert danach im Prozess auf den Schlüsselwert. Dadurch können Host-Instance-Namen und andere BizTalk-Namen mit Sonderzeichen keine ungültige WMI-WQL-`WHERE`-Query mehr erzeugen. Bei `scheduler:` erscheinen zusätzlich `ScheduledTask adapter preflight`, alle tatsächlich verwendeten Suchverzeichnisse, Prozessbitness und bei Erfolg die vollständige geladene Assemblyidentität samt Pfad. Ein Ladefehler enthält die anfordernde Assembly, HRESULT, Fusion-Details und Stacktrace. Für eine Supportanalyse sind die Tagesdatei, relevante `.log.gz`, der passende `*-result.json`, Plan, Vorher-/Nachher-Snapshot und die unveränderte EXE-Konfiguration zu sichern. @@ -202,6 +209,7 @@ Das äußere ZIP erhält zusätzlich eine SHA-256-Datei und eine Certutil-kompat - Atomare JSON-Aktualisierung und BOM-Kompatibilität. - Anwendungsbezogene Diff-Identität bei gleichnamigen Artefakten. +- ExplorerOM-basierte Artefaktzuordnung einschließlich Receive-Port-Fallback und Ambiguitätsschutz. - Restore-Servergrenze und sichere Restore-Reihenfolge. - Neutralisierung formelartiger CSV-Werte. - Erkennung manipulierter Payload-Dateien. diff --git a/Installation.md b/Installation.md index 83418cf..770be90 100644 --- a/Installation.md +++ b/Installation.md @@ -15,8 +15,8 @@ Das Release erzeugt folgende Dateien unter `artifacts`: - `BizTalkPlatformManagementTool-Setup.zip.b64.txt`: Certutil-kompatible Base64-Übertragung - `BizTalkPlatformManagementTool-Setup.zip.sha256.txt`: SHA-256 des ZIP-Archivs - `BizTalkPlatformManagementTool-Setup.zip`: direkt entpackbares Installationspaket -- `BizTalkPlatformManagementTool-2.3.2-source.zip.b64.txt`: vollständiger Quellstand als Base64-TXT -- `BizTalkPlatformManagementTool-2.3.2-source.zip.sha256.txt`: SHA-256 des Quellarchivs +- `BizTalkPlatformManagementTool-2.3.3-source.zip.b64.txt`: vollständiger Quellstand als Base64-TXT +- `BizTalkPlatformManagementTool-2.3.3-source.zip.sha256.txt`: SHA-256 des Quellarchivs Auf dem Zielsystem wird die TXT-Datei so rekonstruiert und geprüft: @@ -60,6 +60,12 @@ Danach wird bei jedem Programmstart die Zeile `Runtime log storage verified by s Der WMI-freie Installer-Self-Test schreibt und liest zusätzlich einen eigenen temporären Runtime-Logmarker. Damit wird eine Version, deren Logger grundsätzlich keine Datei erzeugen kann, bereits vor der Aktivierung abgelehnt. Die PROD-Abnahme des realen Maschinenpfads bleibt nach dem Update erforderlich. +## Anwendungszuordnung ab 2.3.3 + +Die Runtime-WMI-Klassen für Send Ports, Receive Locations und Orchestrierungen liefern keine verlässliche BizTalk-Anwendungszuordnung. Version 2.3.3 liest deshalb `MgmtDbServerName` und `MgmtDbName` aus `MSBTS_GroupSetting` und verwendet die lokal installierte `Microsoft.BizTalk.ExplorerOM.dll` ausschließlich read-only zum Aufbau der Application-/Artefakthierarchie. Zustandsabfrage sowie Shutdown-/Restore-Mutationen bleiben WMI-basiert. + +Es wird keine BizTalk-DLL mit dem Paket ausgeliefert. Die Microsoft-Assembly wird aus dem GAC oder aus einem vorhandenen, identitätsgeprüften BizTalk-Installationspfad geladen. Das ausführende Konto benötigt neben WMI-Zugriff Leserechte auf den BizTalk-Anwendungskatalog in der Management-Datenbank. Nach dem Update muss **Diagnose** für PROD dieselbe Zahl bei `Send ports visible` und `application associations resolved` melden. Andernfalls zeigt das Log den Lade-, Berechtigungs- oder Zuordnungsfehler; ein realer Lauf darf erst nach Klärung freigegeben werden. + ## Shutdown-Drain-Checkpoint ab 2.3.2 Ein echter Shutdown pausiert nach allen Receive Locations und vor Orchestrierungen, Send Ports und Host Instances. Der Dialog zeigt erfolgreiche, bereits deaktivierte und fehlgeschlagene Receive Locations. Er darf geöffnet bleiben, während Group Hub, laufende Service Instances, MessageBox-/Queue-Metriken und das Enterprise-Monitoring geprüft werden. diff --git a/PROD-Application-Zuordnung-2.3.3.txt b/PROD-Application-Zuordnung-2.3.3.txt new file mode 100644 index 0000000..87a91d5 --- /dev/null +++ b/PROD-Application-Zuordnung-2.3.3.txt @@ -0,0 +1,43 @@ +PROD-HANDOFF: APPLICATION-ZUORDNUNG - VERSION 2.3.3 +Stand: 2026-08-27 + +FEHLER +Version 2.3.2 zeigte Send Ports, Receive Locations und Orchestrierungen unter +"(Unknown Application)". Die Objekte und Zustaende kamen korrekt aus WMI, +aber die erwarteten Application-Properties existieren in diesen Runtime-WMI- +Klassen nicht. + +FIX +Version 2.3.3 liest MgmtDbServerName/MgmtDbName aus MSBTS_GroupSetting und +baut ueber die lokal installierte, read-only verwendete ExplorerOM-Hierarchie +einen Application-/Artefaktindex. WMI bleibt alleinige Quelle fuer Zustaende +und Mutationen. Es wird keine BizTalk-DLL mitgeliefert oder in den GAC kopiert. + +PROD-ABNAHME +1. Setup 2.3.3 installieren; setup_version=2.3.3.0 und Ziel-Self-Test pruefen. +2. Tool als Administrator starten; PROD-Ziel und aktiviertes Dry run pruefen. +3. Diagnose ausfuehren. Erwartet: + Send ports visible: N; application associations resolved: N. + Beide Zahlen muessen identisch sein. +4. Im Runtime-Log "BizTalk application catalog loaded through ExplorerOM" + und plausible Application-/Artefaktzaehler pruefen. +5. Snapshot Before erzeugen. Keine Unknown-Application-Gruppe fuer regulaere + katalogisierte Artefakte; mehrere Ports, Receive Locations und + Orchestrierungen gegen die BizTalk Administration Console abgleichen. +6. Shutdown nur als Dry-run ausfuehren. Echte Application-Namen und korrekte + globale Reihenfolge in Grid und shutdown-plan.json pruefen. +7. Erst danach einen echten Wartungslauf freigeben. + +STOPPKRITERIUM +Sind sichtbare und zugeordnete Send-Port-Zahl verschieden oder bleibt Unknown +Application sichtbar, keinen echten Shutdown starten. Tageslog, Diagnose- +Screenshot, before.json, shutdown-plan.json, Setup-Log, Prozessidentitaet und +vollstaendige Exceptiondiagnose sichern. ExplorerOM-Installation, Management- +DB-Erreichbarkeit und Leserechte pruefen. + +LOKALE VERIFIKATION +Release-Rebuild erfolgreich; 41 Regressionstests erfolgreich; WMI-freier +Self-Test erfolgreich; Paket/Base64/SHA-256 ueber die Release-Chain geprueft. + +DETAILS +docs\PROD-Application-Zuordnung-2026-08-27.md diff --git a/PROD-ScheduledTask-und-Laufzeitlogging-2026-08-26.txt b/PROD-ScheduledTask-und-Laufzeitlogging-2026-08-26.txt index e1bc7ba..434b1ad 100644 --- a/PROD-ScheduledTask-und-Laufzeitlogging-2026-08-26.txt +++ b/PROD-ScheduledTask-und-Laufzeitlogging-2026-08-26.txt @@ -1,6 +1,6 @@ PROD-RUNBOOK: SCHEDULEDTASK-STEUERUNG UND PERSISTENTES LAUFZEITLOGGING Stand: 2026-08-26 -Zielversion: 2.3.2 +Zielversion: 2.3.3 Adapter: BizTalk ScheduledTask Adapter 7.0.2 Adapterpfad: C:\Program Files (x86)\BizTalk ScheduledTask Adapter 7.0.2 @@ -13,8 +13,8 @@ laufenden Toolprozess. Das Tool kopiert keine DLL und aendert den GAC nicht. VORBEREITUNG 1. before.json, Reports und vorhandene Logs extern sichern. -2. Setup 2.3.2 als Administrator installieren/aktualisieren. -3. setup_version=2.3.2.0 und erfolgreichen Ziel-Self-Test pruefen. +2. Setup 2.3.3 als Administrator installieren/aktualisieren. +3. setup_version=2.3.3.0 und erfolgreichen Ziel-Self-Test pruefen. 4. Tool als Administrator starten, gruenen Startup-Log-Pruefeintrag kontrollieren, dann Log Folder oeffnen. Normalfall: %ProgramData%\BizTalkPlatformManagementTool\Logs. 5. Im lokalen BizTalk-Ordner Microsoft.BizTalk.Scheduler.dll pruefen. @@ -74,7 +74,7 @@ ABNAHME - Teilfehler bleibt Failed, spaetere unabhaengige Schritte laufen weiter. - Historie erscheint nach Neustart im Grid. - Vortage komprimiert, 30 Kalendertage aufbewahrt. -- Setup/Tool/Reports zeigen 2.3.2 bzw. 2.3.2-net461. +- Setup/Tool/Reports zeigen 2.3.3 bzw. 2.3.3-net461. Detailfassung: docs\PROD-ScheduledTask-und-Laufzeitlogging-2026-08-26.md diff --git a/README.md b/README.md index 76477dd..0426264 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ WinForms tool for controlled Microsoft BizTalk Server 2020 platform operations d - Host instance handling for the selected BizTalk server - Dry-run mode enabled by default - WMI access through `root\MicrosoftBizTalkServer` +- Correct application ownership from the read-only ExplorerOM application hierarchy, with WMI retained for runtime state and mutations - Startup check for administrator rights - Detailed operation logging in the GUI and daily rolling log files in a write-verified local directory - Automatic operation-log restoration into the grid after an application restart @@ -33,7 +34,7 @@ WinForms tool for controlled Microsoft BizTalk Server 2020 platform operations d - Durable per-step result reports even when a shutdown or restore completes only partially - Environment status indicator based on host instance state - Clear and Close actions in the main toolbar -- No compile-time dependency on BizTalk ExplorerOM assemblies +- No compile-time dependency on BizTalk ExplorerOM assemblies; the installed Microsoft assembly is loaded process-locally with identity validation - Transactional Windows installer/updater with SHA-256 payload validation and rollback - Bounded activation retries plus a verified copy fallback for first installs and atomically backed-up updates when Windows/EDR blocks only the staging rename - Opt-in all-users desktop shortcut whose ACL, WSH, validation or rollback failures remain visible but cannot roll back the core installation @@ -100,6 +101,8 @@ At every start, the application performs an actual create/write/flush/delete pro ## Troubleshooting +The BizTalk runtime WMI classes for Send Ports, Receive Locations and Orchestrations do not expose a reliable application property. The tool therefore reads `MgmtDbServerName`/`MgmtDbName` from `MSBTS_GroupSetting`, opens the installed read-only ExplorerOM application catalog and builds an application/artifact index before collecting WMI state. **Diagnose** reports both visible and application-resolved Send Port counts. A mismatch is never silently presented as a valid association: affected rows stay under `(Unknown Application)` and the Operation Log records whether ExplorerOM loading, Management database access or catalog matching failed. + The Operation Log shows the WMI class, key property, key value and method for real shutdown and restore steps. WMI objects are resolved with a broad `SELECT * FROM ` query and a client-side key filter so names containing special characters do not break the WMI query parser. Execution is deliberately best-effort. A failure such as an adapter-specific validation exception is written as `Failed` in the result report, but later independent plan steps are still attempted. The GUI ends in a failed/operator-review state when any step failed; it never reports a partial execution as an unconditional success. The post-operation snapshot is attempted independently and its own failure is preserved in the same report. @@ -144,5 +147,6 @@ Targeted German inline comments explain non-obvious operational decisions such a - [PROD ScheduledTask control and runtime logging runbook](docs/PROD-ScheduledTask-und-Laufzeitlogging-2026-08-26.md) - [PROD runtime-log storage validation and 2.3.1 fix](docs/PROD-Laufzeitlog-Ablage-2026-08-26.md) - [PROD shutdown drain checkpoint and application-status analysis](docs/PROD-Shutdown-Drain-Checkpoint-2026-08-26.md) +- [PROD application-association analysis and 2.3.3 validation](docs/PROD-Application-Zuordnung-2026-08-27.md) - [AI maintainer handoff](AI-README.md) - [References](REFERENCES.md) diff --git a/REFERENCES.md b/REFERENCES.md index 8eb1497..2b73969 100644 --- a/REFERENCES.md +++ b/REFERENCES.md @@ -3,6 +3,11 @@ - MSBTS_SendPort.Status (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-sendport-status-property-wmi - MSBTS_SendPort (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-sendport-wmi +- MSBTS_GroupSetting (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-groupsetting-wmi +- BtsCatalogExplorer (ExplorerOM): https://learn.microsoft.com/en-us/dotnet/api/microsoft.biztalk.explorerom.btscatalogexplorer?view=bts-2020 +- Application (ExplorerOM): https://learn.microsoft.com/en-us/dotnet/api/microsoft.biztalk.explorerom.application?view=bts-2020 +- IBizTalkApplication (ExplorerOM): https://learn.microsoft.com/en-us/dotnet/api/microsoft.biztalk.explorerom.ibiztalkapplication?view=bts-dotnet +- BtsOrchestration (ExplorerOM): https://learn.microsoft.com/en-us/dotnet/api/microsoft.biztalk.explorerom.btsorchestration?view=bts-2020 - MSBTS_SendPort.Start (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-sendport-start-method-wmi - MSBTS_SendPort.Stop (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-sendport-stop-method-wmi - MSBTS_SendPort.Enlist (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-sendport-enlist-method-wmi diff --git a/docs/PROD-Application-Zuordnung-2026-08-27.md b/docs/PROD-Application-Zuordnung-2026-08-27.md new file mode 100644 index 0000000..83dd99a --- /dev/null +++ b/docs/PROD-Application-Zuordnung-2026-08-27.md @@ -0,0 +1,81 @@ +# PROD-Analyse: `(Unknown Application)` bei Runtime-Artefakten + +**Stand:** 2026-08-27 + +**Fehlerstand:** 2.3.2 + +**Zielversion:** 2.3.3 + +## Beobachtung und Bewertung + +Im PROD-Shutdown-Plan wurden die gefundenen Send Ports mit korrekten Namen und Zuständen angezeigt, die Spalte **Application** enthielt jedoch durchgehend `(Unknown Application)`. Damit war der WMI-Zugriff selbst erfolgreich; fehlerhaft war die nachgelagerte Zuordnung der Artefakte zu BizTalk-Anwendungen. + +Der Lauf wäre technisch weiterhin in der global sicheren Kategorie-Reihenfolge ausführbar gewesen, weil Send Ports über ihren gruppenweit eindeutigen WMI-Namen adressiert werden. Die Anzeige ist dennoch ein Produktfehler: Planreview und anwendungsbezogene Snapshot-/Diff-Auswertung benötigen die echte Ownership. Ein realer Lauf mit ungeklärter Zuordnung ist daher nicht freizugeben. + +## Root Cause + +Version 2.3.2 versuchte bei `MSBTS_SendPort`, `MSBTS_ReceiveLocation` und `MSBTS_Orchestration` Properties namens `ApplicationName`, `Application` oder `BizTalkApplication` auszulesen. Diese Properties gehören nicht zum dokumentierten Runtime-WMI-Vertrag der drei Klassen. Der Code fiel deshalb für jedes betroffene Objekt auf `(Unknown Application)` zurück. + +Die BizTalk-Anwendung ist eine Kataloghierarchie, keine verlässliche Property dieser Runtime-WMI-Objekte. Microsoft stellt sie über `Microsoft.BizTalk.ExplorerOM` bereit: Eine Application enthält ihre Send Ports, Receive Ports samt Receive Locations und Orchestrierungen. `MSBTS_GroupSetting` liefert den Server und Namen der BizTalk Management-Datenbank, mit der `BtsCatalogExplorer` verbunden wird. + +## Korrektur in 2.3.3 + +`BizTalkApplicationResolver` baut vor der WMI-Zustandserfassung einen read-only Index auf: + +1. `MSBTS_GroupSetting` lesen und genau einen Management-DB-Endpunkt verlangen. +2. Die lokal installierte `Microsoft.BizTalk.ExplorerOM.dll` über CLR/GAC oder vorhandene BizTalk-Verzeichnisse laden. +3. Vor einem dateibasierten Fallback Simple Name und Microsoft-Public-Key-Token prüfen. +4. Applications und deren Send Ports, Receive Ports/Locations sowie Orchestrierungen enumerieren. +5. WMI-Artefakte case-insensitiv gegen diesen Index auflösen; bei Receive Locations dient der Parent-Receive-Port als zusätzlicher Fallback. +6. Namen, die widersprüchlich unter mehreren Anwendungen auftauchen, nicht raten, sondern weiter als unbekannt markieren. + +Es gibt weiterhin keine Compile-time-Referenz und keine mitgelieferte Kopie der BizTalk-DLL. Der Installer-Self-Test bleibt WMI- und BizTalk-frei. ExplorerOM wird ausschließlich gelesen; Zustände, Wait-Logik und sämtliche Shutdown-/Restore-Mutationen bleiben beim bestehenden WMI-Adapter. + +Wenn Assembly, Management-Datenbank oder Berechtigung nicht verfügbar sind, bricht die Zustandserfassung nicht verdeckt ab. Die betroffenen Zeilen bleiben sichtbar unter `(Unknown Application)`, und Grid sowie Laufzeitlog enthalten die vollständige Ursache. **Diagnose** meldet künftig sowohl die Zahl sichtbarer Send Ports als auch die Zahl erfolgreicher Application-Zuordnungen. + +## Lokale Verifikation + +- Release-Rebuild aller vier .NET-Framework-4.6.1-Projekte erfolgreich. +- 41 portable Regressionstests erfolgreich. +- Neuer Test deckt Send Port, Receive Location, Receive-Port-Fallback, Orchestration, case-insensitive Namen und mehrdeutige Cross-Application-Namen ab. +- WMI-freier Anwendungsselftest erfolgreich mit `2.3.3-net461`. +- Paketmanifest, Installer-ZIP, Certutil-Base64 und SHA-256 werden durch die Release-Chain erzeugt und separat gegengeprüft. + +Die lokale Suite kann den echten BizTalk-2020-ExplorerOM-Katalog nicht simulieren. Die abschließende Freigabe benötigt deshalb die folgende PROD-Abnahme. + +## PROD-Abnahme nach dem Update + +1. Setup 2.3.3 als Administrator ausführen und im Setup-Log `setup_version=2.3.3.0` sowie erfolgreichen Ziel-Self-Test prüfen. +2. Tool starten, PROD-Zielserver kontrollieren und **Dry run** aktiviert lassen. +3. **Diagnose** ausführen. Erwartung: `Send ports visible: N; application associations resolved: N.` mit identischen Zahlen und ohne nachfolgende Association-Warnung. +4. Laufzeitlog über **Log Folder** öffnen und den Eintrag `BizTalk application catalog loaded through ExplorerOM` prüfen. Er muss plausible Zähler für Applications, Send Ports, Receive Ports/Locations und Orchestrations enthalten. +5. **Snapshot Before** erzeugen und `before.json` prüfen: + - keine `(Unknown Application)`-Gruppe für regulär katalogisierte Runtime-Artefakte, + - stichprobenartig mehrere Send Ports aus unterschiedlichen Anwendungen gegen die BizTalk Administration Console abgleichen, + - Receive Locations und Orchestrierungen ebenfalls anwendungsrichtig gruppiert. +6. **Shutdown** nur als Dry-run ausführen. Im Grid und in `shutdown-plan.json` müssen die echten Application-Namen erscheinen; Reihenfolge und Schrittzahl gegen den Snapshot prüfen. +7. Erst nach erfolgreichem Katalog-, Snapshot- und Dry-run-Abgleich über einen echten Wartungslauf entscheiden. + +## Fehlerpfad und Supportdaten + +Sind die Diagnosezahlen unterschiedlich oder erscheinen weiterhin unbekannte Anwendungen, keinen echten Shutdown starten. Sichern: + +- aktuelle Tageslogdatei, +- Diagnose-Screenshot, +- `before.json` und Sidecars, +- `shutdown-plan.json` aus dem Dry-run, +- Setup-Log, +- genaue Windows-Identität des Toolprozesses, +- WMI-/ExplorerOM-/SQL-Exception einschließlich HRESULT und Inner Exceptions. + +Dann insbesondere prüfen: lokale BizTalk-Administrationskomponenten/ExplorerOM-Installation, Erreichbarkeit der in `MSBTS_GroupSetting` gemeldeten Management-Datenbank und Leseberechtigung des ausführenden Kontos. + +## Microsoft-Quellen + +- [MSBTS_SendPort (WMI)](https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-sendport-wmi) +- [MSBTS_ReceiveLocation (WMI)](https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-receivelocation-wmi) +- [MSBTS_Orchestration (WMI)](https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-orchestration-wmi) +- [MSBTS_GroupSetting (WMI)](https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-groupsetting-wmi) +- [BtsCatalogExplorer (ExplorerOM)](https://learn.microsoft.com/en-us/dotnet/api/microsoft.biztalk.explorerom.btscatalogexplorer?view=bts-2020) +- [Application (ExplorerOM)](https://learn.microsoft.com/en-us/dotnet/api/microsoft.biztalk.explorerom.application?view=bts-2020) +- [BtsOrchestration (ExplorerOM)](https://learn.microsoft.com/en-us/dotnet/api/microsoft.biztalk.explorerom.btsorchestration?view=bts-2020) diff --git a/docs/PROD-ScheduledTask-und-Laufzeitlogging-2026-08-26.md b/docs/PROD-ScheduledTask-und-Laufzeitlogging-2026-08-26.md index 6ac9fb9..c864b3d 100644 --- a/docs/PROD-ScheduledTask-und-Laufzeitlogging-2026-08-26.md +++ b/docs/PROD-ScheduledTask-und-Laufzeitlogging-2026-08-26.md @@ -2,7 +2,7 @@ **Stand:** 2026-08-26 -**Zielversion:** 2.3.2 +**Zielversion:** 2.3.3 **Betroffener Adapter:** BizTalk ScheduledTask Adapter 7.0.2 @@ -17,8 +17,8 @@ Version 2.3.1 erkennt ScheduledTask-Schritte an Adaptername oder `scheduler:`-UR ## Vorbereitung 1. Vorhandene `before.json`, Ergebnisdateien und Laufzeitlogs außerhalb des Installationsordners sichern. -2. Setup 2.3.2 als Administrator installieren beziehungsweise aktualisieren. -3. Im Setup-Log `setup_version=2.3.2.0` und erfolgreichen Ziel-Self-Test prüfen. +2. Setup 2.3.3 als Administrator installieren beziehungsweise aktualisieren. +3. Im Setup-Log `setup_version=2.3.3.0` und erfolgreichen Ziel-Self-Test prüfen. 4. Tool als Administrator starten, den grünen Eintrag `Runtime log storage verified by startup append` prüfen und mit **Log Folder** den dort genannten aktiven Pfad öffnen. Normalfall ist `%ProgramData%\BizTalkPlatformManagementTool\Logs`. 5. Prüfen, dass der lokale BizTalk-Installationsordner `Microsoft.BizTalk.Scheduler.dll` enthält. 6. Nur wenn BizTalk oder der Adapter abweichend installiert wurde: `AdapterAssemblySearchPaths` in `BizTalkPlatformManagementTool.exe.config` um den vorhandenen lokalen Ordner ergänzen. Mehrere Pfade werden mit Semikolon getrennt. Keine DLL aus ACC, einer alten BizTalk-Version oder einem Downloadordner kopieren. @@ -78,6 +78,6 @@ Der Fehlerdatensatz enthält Exceptiontyp, HRESULT, innere Ausnahmen, vorhandene - Ergebnisreport und Nachher-Snapshot werden auch bei einem Teilfehler soweit möglich geschrieben. - Historisches Log erscheint nach Neustart wieder im Grid. - Vortagslogs werden komprimiert und exakt 30 Kalendertage aufbewahrt. -- Setup, Tool und Ergebnisdateien melden Version 2.3.2 beziehungsweise `2.3.2-net461`. +- Setup, Tool und Ergebnisdateien melden Version 2.3.3 beziehungsweise `2.3.3-net461`. Die lokale Mono-Toolchain prüft Resolverlogik, Identitätsgrenze, Persistenz, Kompression und Aufbewahrung ohne BizTalk. Die endgültige Freigabe erfordert diesen realen Windows-/BizTalk-/PROD-Test. diff --git a/src/BizTalkPlatformManagementTool.Setup/InstallerEngine.cs b/src/BizTalkPlatformManagementTool.Setup/InstallerEngine.cs index aa7cb72..c2a3b84 100644 --- a/src/BizTalkPlatformManagementTool.Setup/InstallerEngine.cs +++ b/src/BizTalkPlatformManagementTool.Setup/InstallerEngine.cs @@ -23,7 +23,7 @@ namespace BizTalkPlatformManagementTool.Setup private const string ProductName = "BizTalk Platform Management Tool"; /// Aktuelle Produktversion des Installers und Uninstall-Eintrags. - private const string ProductVersion = "2.3.2"; + private const string ProductVersion = "2.3.3"; /// /// Wartezeiten zwischen Wiederholungen atomarer Verzeichnisverschiebungen. diff --git a/src/BizTalkPlatformManagementTool.Setup/MainForm.cs b/src/BizTalkPlatformManagementTool.Setup/MainForm.cs index 534ab45..186deb5 100644 --- a/src/BizTalkPlatformManagementTool.Setup/MainForm.cs +++ b/src/BizTalkPlatformManagementTool.Setup/MainForm.cs @@ -65,7 +65,7 @@ namespace BizTalkPlatformManagementTool.Setup { AutoSize = true, Font = new Font(Font.FontFamily, 14, FontStyle.Bold), - Text = "BizTalk Platform Management Tool 2.3.2" + Text = "BizTalk Platform Management Tool 2.3.3" }); root.Controls.Add(new Label { diff --git a/src/BizTalkPlatformManagementTool.Setup/Properties/AssemblyInfo.cs b/src/BizTalkPlatformManagementTool.Setup/Properties/AssemblyInfo.cs index fec2322..9308875 100644 --- a/src/BizTalkPlatformManagementTool.Setup/Properties/AssemblyInfo.cs +++ b/src/BizTalkPlatformManagementTool.Setup/Properties/AssemblyInfo.cs @@ -8,6 +8,6 @@ using System.Runtime.InteropServices; [assembly: AssemblyProduct("BizTalk Platform Management Tool")] [assembly: ComVisible(false)] [assembly: Guid("675b68a9-bd80-46a5-b8c5-3b11b0b374e2")] -[assembly: AssemblyVersion("2.3.2.0")] -[assembly: AssemblyFileVersion("2.3.2.0")] +[assembly: AssemblyVersion("2.3.3.0")] +[assembly: AssemblyFileVersion("2.3.3.0")] [assembly: InternalsVisibleTo("BizTalkPlatformManagementTool.Tests")] diff --git a/src/BizTalkPlatformManagementTool.Setup/app.manifest b/src/BizTalkPlatformManagementTool.Setup/app.manifest index 83a87d7..6fdf250 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 28fa7c9..84dfe17 100644 --- a/src/BizTalkPlatformManagementTool/BizTalkPlatformManagementTool.csproj +++ b/src/BizTalkPlatformManagementTool/BizTalkPlatformManagementTool.csproj @@ -58,6 +58,7 @@ + diff --git a/src/BizTalkPlatformManagementTool/Properties/AssemblyInfo.cs b/src/BizTalkPlatformManagementTool/Properties/AssemblyInfo.cs index ff5a59b..5a10cdf 100644 --- a/src/BizTalkPlatformManagementTool/Properties/AssemblyInfo.cs +++ b/src/BizTalkPlatformManagementTool/Properties/AssemblyInfo.cs @@ -9,6 +9,6 @@ using System.Runtime.InteropServices; [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: ComVisible(false)] [assembly: Guid("2c5b2c0a-f407-46c2-9e3b-1fa09fa8445a")] -[assembly: AssemblyVersion("2.3.2.0")] -[assembly: AssemblyFileVersion("2.3.2.0")] +[assembly: AssemblyVersion("2.3.3.0")] +[assembly: AssemblyFileVersion("2.3.3.0")] [assembly: InternalsVisibleTo("BizTalkPlatformManagementTool.Tests")] diff --git a/src/BizTalkPlatformManagementTool/Services/AdapterAssemblyResolver.cs b/src/BizTalkPlatformManagementTool/Services/AdapterAssemblyResolver.cs index 343605d..f893c78 100644 --- a/src/BizTalkPlatformManagementTool/Services/AdapterAssemblyResolver.cs +++ b/src/BizTalkPlatformManagementTool/Services/AdapterAssemblyResolver.cs @@ -241,7 +241,7 @@ namespace BizTalkPlatformManagementTool.Services } /// Discovers BizTalk, ScheduledTask and explicitly configured directories. - private static IEnumerable DiscoverSearchDirectories() + internal static IEnumerable DiscoverSearchDirectories() { var result = new List(); AddConfiguredDirectories(result); diff --git a/src/BizTalkPlatformManagementTool/Services/BizTalkApplicationResolver.cs b/src/BizTalkPlatformManagementTool/Services/BizTalkApplicationResolver.cs new file mode 100644 index 0000000..fc7a970 --- /dev/null +++ b/src/BizTalkPlatformManagementTool/Services/BizTalkApplicationResolver.cs @@ -0,0 +1,443 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.IO; +using System.Linq; +using System.Management; +using System.Reflection; + +namespace BizTalkPlatformManagementTool.Services +{ + /// + /// Resolves runtime artifacts to their owning BizTalk applications by reading the + /// ExplorerOM application hierarchy without a compile-time BizTalk dependency. + /// + internal sealed class BizTalkApplicationResolver + { + /// The strong-name token used by Microsoft BizTalk product assemblies. + private static readonly byte[] BizTalkPublicKeyToken = { 0x31, 0xbf, 0x38, 0x56, 0xad, 0x36, 0x4e, 0x35 }; + + /// Maps group-unique send-port names to application names. + private readonly ArtifactApplicationMap _sendPorts = new ArtifactApplicationMap(); + + /// Maps receive-port names to application names. + private readonly ArtifactApplicationMap _receivePorts = new ArtifactApplicationMap(); + + /// Maps receive-location names to application names. + private readonly ArtifactApplicationMap _receiveLocations = new ArtifactApplicationMap(); + + /// Maps orchestration names and supported aliases to application names. + private readonly ArtifactApplicationMap _orchestrations = new ArtifactApplicationMap(); + + /// Initializes an empty resolver, primarily for deterministic regression tests. + internal BizTalkApplicationResolver() + { + } + + /// + /// Reads the BizTalk Management database location through WMI and builds an + /// application/artifact index through the locally installed ExplorerOM assembly. + /// + /// Connected BizTalk WMI client. + /// Optional operation logger for support diagnostics. + /// A resolver populated from the BizTalk application catalog. + internal static BizTalkApplicationResolver Load(BizTalkWmiClient client, OperationLogger logger) + { + if (client == null) + { + throw new ArgumentNullException("client"); + } + + string databaseServer; + string databaseName; + ReadManagementDatabase(client, out databaseServer, out databaseName); + + var assembly = LoadExplorerOmAssembly(); + var catalogType = assembly.GetType("Microsoft.BizTalk.ExplorerOM.BtsCatalogExplorer", true, false); + var catalog = Activator.CreateInstance(catalogType); + try + { + var connection = new SqlConnectionStringBuilder + { + DataSource = databaseServer, + InitialCatalog = databaseName, + IntegratedSecurity = true, + ConnectTimeout = 15, + ApplicationName = "BizTalkPlatformManagementTool" + }; + SetProperty(catalog, "ConnectionString", connection.ConnectionString); + + var resolver = new BizTalkApplicationResolver(); + resolver.IndexApplications(GetProperty(catalog, "Applications")); + Info(logger, "BizTalk application catalog loaded through ExplorerOM. " + + "Applications=" + resolver.ApplicationCount + + "; SendPorts=" + resolver._sendPorts.Count + + "; ReceivePorts=" + resolver._receivePorts.Count + + "; ReceiveLocations=" + resolver._receiveLocations.Count + + "; Orchestrations=" + resolver._orchestrations.Count + + "; ManagementDatabase=" + databaseServer + "\\" + databaseName + "."); + return resolver; + } + catch (TargetInvocationException ex) + { + throw new InvalidOperationException( + "BizTalk ExplorerOM could not read the application catalog from " + + databaseServer + "\\" + databaseName + ". " + + ExceptionDiagnostics.Format(ex.InnerException ?? ex), ex.InnerException ?? ex); + } + finally + { + var disposable = catalog as IDisposable; + if (disposable != null) + { + disposable.Dispose(); + } + } + } + + /// Gets the number of distinct application names represented in the index. + internal int ApplicationCount + { + get + { + return _sendPorts.ApplicationNames + .Concat(_receivePorts.ApplicationNames) + .Concat(_receiveLocations.ApplicationNames) + .Concat(_orchestrations.ApplicationNames) + .Distinct(StringComparer.OrdinalIgnoreCase) + .Count(); + } + } + + /// Adds a send-port ownership record to the index. + /// Owning BizTalk application. + /// Send-port name. + internal void AddSendPort(string application, string name) + { + _sendPorts.Add(application, name); + } + + /// Adds a receive-port ownership record to the index. + /// Owning BizTalk application. + /// Receive-port name. + internal void AddReceivePort(string application, string name) + { + _receivePorts.Add(application, name); + } + + /// Adds a receive-location ownership record to the index. + /// Owning BizTalk application. + /// Receive-location name. + internal void AddReceiveLocation(string application, string name) + { + _receiveLocations.Add(application, name); + } + + /// Adds an orchestration ownership record to the index. + /// Owning BizTalk application. + /// Orchestration name or alias. + internal void AddOrchestration(string application, string name) + { + _orchestrations.Add(application, name); + } + + /// Resolves a send port to its owning application. + /// Send-port name. + /// The application name, or null when absent or ambiguous. + internal string ResolveSendPort(string name) + { + return _sendPorts.Resolve(name); + } + + /// Resolves a receive location, with its receive port as a stable fallback. + /// Receive-location name. + /// Parent receive-port name. + /// The application name, or null when absent or ambiguous. + internal string ResolveReceiveLocation(string locationName, string receivePortName) + { + return _receiveLocations.Resolve(locationName) ?? _receivePorts.Resolve(receivePortName); + } + + /// Resolves an orchestration to its owning application. + /// WMI orchestration name. + /// The application name, or null when absent or ambiguous. + internal string ResolveOrchestration(string name) + { + return _orchestrations.Resolve(name); + } + + /// Enumerates all applications and their runtime artifact collections. + /// ExplorerOM application collection. + private void IndexApplications(object applications) + { + foreach (var application in Enumerate(applications, "Applications")) + { + var applicationName = ReadString(application, "Name"); + if (string.IsNullOrWhiteSpace(applicationName)) + { + continue; + } + + foreach (var sendPort in Enumerate(GetProperty(application, "SendPorts"), "Application.SendPorts")) + { + AddSendPort(applicationName, ReadString(sendPort, "Name")); + } + + foreach (var receivePort in Enumerate(GetProperty(application, "ReceivePorts"), "Application.ReceivePorts")) + { + AddReceivePort(applicationName, ReadString(receivePort, "Name")); + foreach (var location in Enumerate(GetProperty(receivePort, "ReceiveLocations"), "ReceivePort.ReceiveLocations")) + { + AddReceiveLocation(applicationName, ReadString(location, "Name")); + } + } + + foreach (var orchestration in Enumerate(GetProperty(application, "Orchestrations"), "Application.Orchestrations")) + { + AddOrchestration(applicationName, ReadString(orchestration, "FullName")); + AddOrchestration(applicationName, ReadString(orchestration, "AssemblyQualifiedName")); + } + } + } + + /// Reads the BizTalk Management database endpoint from MSBTS_GroupSetting. + /// Connected WMI client. + /// Resolved SQL Server name. + /// Resolved management database name. + private static void ReadManagementDatabase(BizTalkWmiClient client, out string server, out string database) + { + var settings = client.Query("MSBTS_GroupSetting"); + try + { + if (settings.Count != 1) + { + throw new InvalidOperationException( + "Expected exactly one MSBTS_GroupSetting instance but found " + settings.Count + "."); + } + server = BizTalkWmiClient.SafeGetString(settings[0], "MgmtDbServerName", null); + database = BizTalkWmiClient.SafeGetString(settings[0], "MgmtDbName", null); + if (string.IsNullOrWhiteSpace(server) || string.IsNullOrWhiteSpace(database)) + { + throw new InvalidOperationException( + "MSBTS_GroupSetting did not expose MgmtDbServerName and MgmtDbName."); + } + } + finally + { + foreach (ManagementObject setting in settings) + { + setting.Dispose(); + } + } + } + + /// Loads an identity-verified ExplorerOM assembly from the CLR/GAC or installed BizTalk folders. + /// The loaded Microsoft.BizTalk.ExplorerOM assembly. + private static Assembly LoadExplorerOmAssembly() + { + var requested = new AssemblyName("Microsoft.BizTalk.ExplorerOM"); + requested.SetPublicKeyToken(BizTalkPublicKeyToken); + + var loaded = AppDomain.CurrentDomain.GetAssemblies() + .FirstOrDefault(x => string.Equals(x.GetName().Name, requested.Name, StringComparison.OrdinalIgnoreCase)); + if (loaded != null) + { + RequireMicrosoftIdentity(loaded); + return loaded; + } + + var explorerType = Type.GetType( + "Microsoft.BizTalk.ExplorerOM.BtsCatalogExplorer, Microsoft.BizTalk.ExplorerOM", + false); + if (explorerType != null) + { + RequireMicrosoftIdentity(explorerType.Assembly); + return explorerType.Assembly; + } + + try + { + return Assembly.Load(requested); + } + catch (FileNotFoundException) + { + // The installed-directory fallback below keeps non-GAC BizTalk admin installations usable. + } + catch (FileLoadException) + { + // The fallback still enforces the Microsoft strong-name identity before loading a file. + } + +#pragma warning disable 618 + // ExplorerOM versions vary across supported BizTalk installations. This GAC-only + // compatibility API is followed by an explicit Microsoft public-key-token check. + var partial = Assembly.LoadWithPartialName(requested.Name); +#pragma warning restore 618 + if (partial != null) + { + RequireMicrosoftIdentity(partial); + return partial; + } + + var path = AdapterAssemblyResolver.FindCandidateFile( + requested.Name, + requested, + AdapterAssemblyResolver.DiscoverSearchDirectories()); + if (path == null) + { + throw new FileNotFoundException( + "Microsoft.BizTalk.ExplorerOM.dll was not found through CLR/GAC resolution or in the configured/installed BizTalk directories. " + + "The application association cannot be derived from the runtime WMI classes alone."); + } + var fromPath = Assembly.LoadFrom(path); + RequireMicrosoftIdentity(fromPath); + return fromPath; + } + + /// Rejects a same-named assembly that is not signed with the BizTalk product token. + /// Loaded ExplorerOM candidate. + private static void RequireMicrosoftIdentity(Assembly assembly) + { + var actual = assembly == null ? null : assembly.GetName().GetPublicKeyToken(); + if (actual == null || actual.Length != BizTalkPublicKeyToken.Length) + { + throw new FileLoadException("Microsoft.BizTalk.ExplorerOM has no valid BizTalk product public key token."); + } + for (var index = 0; index < actual.Length; index++) + { + if (actual[index] != BizTalkPublicKeyToken[index]) + { + throw new FileLoadException("Microsoft.BizTalk.ExplorerOM has an unexpected public key token."); + } + } + } + + /// Gets a required public instance property through reflection. + /// Object that owns the property. + /// Property name. + /// The property value. + private static object GetProperty(object instance, string name) + { + if (instance == null) + { + throw new InvalidOperationException("ExplorerOM object was null while reading property '" + name + "'."); + } + var property = instance.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public); + if (property == null) + { + throw new MissingMemberException(instance.GetType().FullName, name); + } + return property.GetValue(instance, null); + } + + /// Sets a required public instance property through reflection. + /// Object that owns the property. + /// Property name. + /// Value to assign. + private static void SetProperty(object instance, string name, object value) + { + var property = instance.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public); + if (property == null || !property.CanWrite) + { + throw new MissingMemberException(instance.GetType().FullName, name); + } + property.SetValue(instance, value, null); + } + + /// Reads an optional string property from one ExplorerOM object. + /// ExplorerOM object. + /// Property name. + /// The trimmed string value, or null. + private static string ReadString(object instance, string name) + { + var property = instance.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public); + if (property == null) + { + return null; + } + var value = property.GetValue(instance, null) as string; + return string.IsNullOrWhiteSpace(value) ? null : value.Trim(); + } + + /// Validates and enumerates one ExplorerOM collection. + /// Collection object. + /// Property path used in diagnostics. + /// The collection as an enumerable sequence. + private static IEnumerable Enumerate(object value, string description) + { + var enumerable = value as IEnumerable; + if (enumerable == null) + { + throw new InvalidOperationException("ExplorerOM property '" + description + "' is not enumerable."); + } + return enumerable; + } + + /// Writes an informational message when logging is available. + /// Optional logger. + /// Message text. + private static void Info(OperationLogger logger, string message) + { + if (logger != null) + { + logger.Info(message); + } + } + + /// Stores one artifact-type mapping and rejects cross-application ambiguity. + private sealed class ArtifactApplicationMap + { + /// Unambiguous name/application mappings. + private readonly Dictionary _applications = new Dictionary(StringComparer.OrdinalIgnoreCase); + + /// Names observed under more than one application. + private readonly HashSet _ambiguous = new HashSet(StringComparer.OrdinalIgnoreCase); + + /// Gets the number of unambiguous artifact names. + internal int Count { get { return _applications.Count; } } + + /// Gets the represented application names. + internal IEnumerable ApplicationNames { get { return _applications.Values; } } + + /// Adds one mapping unless the artifact name is blank or becomes ambiguous. + /// Application name. + /// Artifact name. + internal void Add(string application, string artifact) + { + if (string.IsNullOrWhiteSpace(application) || string.IsNullOrWhiteSpace(artifact)) + { + return; + } + application = application.Trim(); + artifact = artifact.Trim(); + if (_ambiguous.Contains(artifact)) + { + return; + } + string existing; + if (_applications.TryGetValue(artifact, out existing) + && !string.Equals(existing, application, StringComparison.OrdinalIgnoreCase)) + { + _applications.Remove(artifact); + _ambiguous.Add(artifact); + return; + } + _applications[artifact] = application; + } + + /// Resolves one unambiguous artifact name. + /// Artifact name. + /// The application name, or null. + internal string Resolve(string artifact) + { + if (string.IsNullOrWhiteSpace(artifact) || _ambiguous.Contains(artifact.Trim())) + { + return null; + } + string application; + return _applications.TryGetValue(artifact.Trim(), out application) ? application : null; + } + } + } +} diff --git a/src/BizTalkPlatformManagementTool/Services/BizTalkOperationService.cs b/src/BizTalkPlatformManagementTool/Services/BizTalkOperationService.cs index 3539da0..e3d7651 100644 --- a/src/BizTalkPlatformManagementTool/Services/BizTalkOperationService.cs +++ b/src/BizTalkPlatformManagementTool/Services/BizTalkOperationService.cs @@ -16,10 +16,10 @@ namespace BizTalkPlatformManagementTool.Services /// /// Current tool version written into generated snapshots. /// - public const string Version = "2.3.2-net461"; + public const string Version = "2.3.3-net461"; /// - /// Fallback application name used when WMI does not expose an application property. + /// Fallback application name used when neither WMI nor the ExplorerOM catalog can resolve ownership. /// private const string UnknownApplication = "(Unknown Application)"; @@ -48,7 +48,17 @@ namespace BizTalkPlatformManagementTool.Services var ports = client.Query("MSBTS_SendPort"); try { - _logger.Success("WMI/CIM diagnostic succeeded. Send ports visible: " + ports.Count); + var applications = TryLoadApplicationResolver(client); + var resolved = applications == null + ? 0 + : ports.Count(x => !string.IsNullOrWhiteSpace(applications.ResolveSendPort(BizTalkWmiClient.SafeGetString(x, "Name", null)))); + _logger.Success("WMI/CIM diagnostic succeeded. Send ports visible: " + ports.Count + + "; application associations resolved: " + resolved + "."); + if (ports.Count > 0 && resolved != ports.Count) + { + _logger.Warning("Application association is incomplete. Unresolved send ports: " + + (ports.Count - resolved) + ". Check the preceding ExplorerOM and BizTalk Management database diagnostics."); + } } finally { @@ -74,18 +84,21 @@ namespace BizTalkPlatformManagementTool.Services }; var apps = new Dictionary(StringComparer.OrdinalIgnoreCase); + var applicationResolver = TryLoadApplicationResolver(client); var receiveLocations = client.Query("MSBTS_ReceiveLocation"); try { foreach (var item in receiveLocations) { - var appName = GetApplicationName(item); + var name = BizTalkWmiClient.SafeGetString(item, "Name", string.Empty); + var receivePortName = BizTalkWmiClient.SafeGetString(item, "ReceivePortName", string.Empty); + var appName = GetApplicationName(item, applicationResolver, "ReceiveLocation", name, receivePortName); var app = GetApplication(apps, appName); app.ReceiveLocations.Add(new ReceiveLocationState { Application = app.Application, - Name = BizTalkWmiClient.SafeGetString(item, "Name", string.Empty), - ReceivePortName = BizTalkWmiClient.SafeGetString(item, "ReceivePortName", string.Empty), + Name = name, + ReceivePortName = receivePortName, Enabled = !BizTalkWmiClient.SafeGetBoolean(item, "IsDisabled", true), AdapterName = BizTalkWmiClient.SafeGetString(item, "AdapterName", string.Empty), Address = BizTalkWmiClient.SafeGetString(item, "InboundTransportURL", string.Empty) @@ -102,12 +115,13 @@ namespace BizTalkPlatformManagementTool.Services { foreach (var item in sendPorts) { - var appName = GetApplicationName(item); + var name = BizTalkWmiClient.SafeGetString(item, "Name", string.Empty); + var appName = GetApplicationName(item, applicationResolver, "SendPort", name, null); var app = GetApplication(apps, appName); app.SendPorts.Add(new SendPortState { Application = app.Application, - Name = BizTalkWmiClient.SafeGetString(item, "Name", string.Empty), + Name = name, Status = BizTalkWmiClient.SafeGetInt32(item, "Status", 0), PrimaryTransportType = BizTalkWmiClient.SafeGetString(item, "PTTransportType", string.Empty), PrimaryTransportAddress = BizTalkWmiClient.SafeGetString(item, "PTAddress", string.Empty) @@ -124,12 +138,13 @@ namespace BizTalkPlatformManagementTool.Services { foreach (var item in orchestrations) { - var appName = GetApplicationName(item); + var name = BizTalkWmiClient.SafeGetString(item, "Name", string.Empty); + var appName = GetApplicationName(item, applicationResolver, "Orchestration", name, null); var app = GetApplication(apps, appName); app.Orchestrations.Add(new OrchestrationState { Application = app.Application, - Name = BizTalkWmiClient.SafeGetString(item, "Name", string.Empty), + Name = name, OrchestrationStatus = BizTalkWmiClient.SafeGetInt32(item, "OrchestrationStatus", 0) }); } @@ -170,6 +185,14 @@ namespace BizTalkPlatformManagementTool.Services snapshot.HostInstances = snapshot.HostInstances.OrderBy(x => x.Server).ThenBy(x => x.InstanceName).ToList(); SnapshotValidator.Validate(snapshot); + ApplicationSnapshot unresolved; + if (apps.TryGetValue(UnknownApplication, out unresolved) && _logger != null) + { + _logger.Warning("Snapshot contains unresolved BizTalk application associations. ReceiveLocations=" + + unresolved.ReceiveLocations.Count + "; SendPorts=" + unresolved.SendPorts.Count + + "; Orchestrations=" + unresolved.Orchestrations.Count + + ". Review ExplorerOM and BizTalk Management database diagnostics before plan approval."); + } _logger.Success("Snapshot created. Applications: " + snapshot.Applications.Count + ", host instances: " + snapshot.HostInstances.Count); return snapshot; } @@ -873,24 +896,75 @@ namespace BizTalkPlatformManagementTool.Services } /// - /// Reads the application name from the available WMI properties. + /// Reads a directly exposed application name or resolves it through the + /// ExplorerOM application hierarchy because the runtime WMI classes do not + /// define a reliable application property. /// /// The WMI object being mapped into a snapshot item. + /// Optional ExplorerOM application resolver. + /// Artifact kind used to select the catalog index. + /// Artifact name. + /// Optional parent receive-port name. /// The discovered application name or a stable fallback. - private static string GetApplicationName(ManagementObject item) + private static string GetApplicationName(ManagementObject item, BizTalkApplicationResolver resolver, string kind, string name, string parentName) { var names = new[] { "ApplicationName", "Application", "BizTalkApplication" }; - foreach (var name in names) + foreach (var propertyName in names) { - var value = BizTalkWmiClient.SafeGetString(item, name, null); + var value = BizTalkWmiClient.SafeGetString(item, propertyName, null); if (!string.IsNullOrWhiteSpace(value)) { return value; } } + + if (resolver != null) + { + string application = null; + if (string.Equals(kind, "SendPort", StringComparison.Ordinal)) + { + application = resolver.ResolveSendPort(name); + } + else if (string.Equals(kind, "ReceiveLocation", StringComparison.Ordinal)) + { + application = resolver.ResolveReceiveLocation(name, parentName); + } + else if (string.Equals(kind, "Orchestration", StringComparison.Ordinal)) + { + application = resolver.ResolveOrchestration(name); + } + if (!string.IsNullOrWhiteSpace(application)) + { + return application; + } + } return UnknownApplication; } + /// + /// Loads the optional ExplorerOM application index while preserving WMI-only + /// snapshot availability and emitting an actionable warning on failure. + /// + /// Connected BizTalk WMI client. + /// The populated resolver, or null when the catalog could not be read. + private BizTalkApplicationResolver TryLoadApplicationResolver(BizTalkWmiClient client) + { + try + { + return BizTalkApplicationResolver.Load(client, _logger); + } + catch (Exception ex) + { + if (_logger != null) + { + _logger.Warning("BizTalk application association could not be loaded. Runtime state collection remains available, " + + "but affected artifacts will be grouped under " + UnknownApplication + ". " + + ExceptionDiagnostics.Format(ex)); + } + return null; + } + } + /// /// Gibt alle von einer WMI-Abfrage übernommenen Objekte deterministisch frei. /// diff --git a/src/BizTalkPlatformManagementTool/app.manifest b/src/BizTalkPlatformManagementTool/app.manifest index 39a45d1..3d1cb63 100644 --- a/src/BizTalkPlatformManagementTool/app.manifest +++ b/src/BizTalkPlatformManagementTool/app.manifest @@ -1,6 +1,6 @@ - + diff --git a/tests/BizTalkPlatformManagementTool.Tests/Program.cs b/tests/BizTalkPlatformManagementTool.Tests/Program.cs index 0535075..69303cb 100644 --- a/tests/BizTalkPlatformManagementTool.Tests/Program.cs +++ b/tests/BizTalkPlatformManagementTool.Tests/Program.cs @@ -40,6 +40,7 @@ namespace BizTalkPlatformManagementTool.Tests Run("PlanExecutionSkipsAlreadySatisfiedState", PlanExecutionSkipsAlreadySatisfiedState); Run("ExecutionReportRoundTripPreservesFailure", ExecutionReportRoundTripPreservesFailure); Run("ScheduledReceivePlanCarriesAdapterMetadata", ScheduledReceivePlanCarriesAdapterMetadata); + Run("ApplicationResolverMapsArtifactsAndRejectsAmbiguity", ApplicationResolverMapsArtifactsAndRejectsAmbiguity); Run("AdapterAssemblyResolverRequiresMatchingIdentity", AdapterAssemblyResolverRequiresMatchingIdentity); Run("OperationLogPersistsCompressesAndRetainsThirtyDays", OperationLogPersistsCompressesAndRetainsThirtyDays); Run("OperationLogUsesVerifiedStartupFallback", OperationLogUsesVerifiedStartupFallback); @@ -208,6 +209,25 @@ namespace BizTalkPlatformManagementTool.Tests }); } + /// Prüft die ExplorerOM-basierte Zuordnung aller Laufzeitartefakte und den Ambiguitätsschutz. + private static void ApplicationResolverMapsArtifactsAndRejectsAmbiguity() + { + var resolver = new BizTalkApplicationResolver(); + resolver.AddSendPort("APP-A", "SP-A"); + resolver.AddReceivePort("APP-B", "RP-B"); + resolver.AddReceiveLocation("APP-B", "RL-B"); + resolver.AddOrchestration("APP-C", "Company.Process.Order"); + + Assert(resolver.ApplicationCount == 3, "application count did not de-duplicate artifact owners"); + Assert(resolver.ResolveSendPort("sp-a") == "APP-A", "send port was not resolved case-insensitively"); + Assert(resolver.ResolveReceiveLocation("RL-B", null) == "APP-B", "receive location was not resolved"); + Assert(resolver.ResolveReceiveLocation("missing", "rp-b") == "APP-B", "receive-port fallback was not used"); + Assert(resolver.ResolveOrchestration("company.process.order") == "APP-C", "orchestration was not resolved"); + + resolver.AddSendPort("APP-D", "SP-A"); + Assert(resolver.ResolveSendPort("SP-A") == null, "cross-application duplicate was not rejected as ambiguous"); + } + /// Prüft Dateinamen- und vollständige Assemblyidentität vor prozesslokalem Laden. private static void AdapterAssemblyResolverRequiresMatchingIdentity() {