diff --git a/Dokumentation.md b/Dokumentation.md
index 072a4f7..387c657 100644
--- a/Dokumentation.md
+++ b/Dokumentation.md
@@ -41,9 +41,10 @@ Administrative cmd.exe
| +-- MSBTS_GroupSetting für Management SQL/DB
|
+-- BizTalkWmiCollector
- | +-- ExplorerOM: vollständige Anwendungsliste
+ | +-- ExplorerOM: Anwendungen, Assemblies, Schemas,
+ | | Maps und Pipelines
| +-- root\MicrosoftBizTalkServer
- | +-- optionale Artefakte, Adapter, Hosts und Handler
+ | +-- Orchestrierungen, Ports, Adapter, Hosts und Handler
| +-- optionaler SAP-Endpunkt-Fallback
|
+-- BindingExportCollector
@@ -104,8 +105,9 @@ SELECT Name,MgmtDbServerName,MgmtDbName FROM MSBTS_GroupSetting
Die vollständige Anwendungsliste wird anschließend über
`Microsoft.BizTalk.ExplorerOM.BtsCatalogExplorer` und integrierte
Windows-Authentifizierung aus der BizTalk Management Database gelesen. Es werden
-keine ändernden ExplorerOM-Methoden aufgerufen. Die Connection String wird nicht
-protokolliert.
+keine ändernden ExplorerOM-Methoden aufgerufen. Die bereitgestellten Assemblies
+und deren Schemas, Transforms/Maps und Pipelines werden aus denselben
+Anwendungsobjekten gelesen. Die Connection String wird nicht protokolliert.
Dieser Aufbau vermeidet die nicht verlässlich vorhandene WMI-Klasse
`MSBTS_Application`, die je nach Installation mit `Invalid class` abbrechen kann.
@@ -114,7 +116,7 @@ ExplorerOM-Assemblyname, Version und Dateipfad werden protokolliert. Wenn
`--management-server`/`--management-database` ermittelte Datenbankziel weiterhin
für ExplorerOM verwendet werden.
-### 3.3 Optionale BizTalk-WMI-Daten
+### 3.3 BizTalk-WMI-Laufzeitdaten
Primärer Namespace:
@@ -122,31 +124,31 @@ Primärer Namespace:
root\MicrosoftBizTalkServer
```
-Optionale beziehungsweise versionsabhängige Artefaktklassen:
+Verwendete BizTalk-WMI-Klassen:
- `MSBTS_Orchestration`
- `MSBTS_SendPort`
- `MSBTS_SendPortGroup`
- `MSBTS_ReceivePort`
- `MSBTS_ReceiveLocation`
-- `MSBTS_Assembly`
-- `MSBTS_Schema`
-- `MSBTS_Map`
-- `MSBTS_Pipeline`
- `MSBTS_AdapterSetting`
- `MSBTS_ReceiveHandler`
- `MSBTS_SendHandler2` bzw. `MSBTS_SendHandler`
- `MSBTS_HostSetting`
- `MSBTS_HostInstance`
-Eine fehlende optionale Klasse erzeugt einen Hinweis, verhindert aber nicht die
-ExplorerOM-Anwendungsliste oder den Word-Bericht. Ist der gesamte BizTalk-WMI-
-Namespace für diese Zusatzdaten nicht erreichbar, wird der WMI-Teil übersprungen;
-die zuvor geladene Anwendungsliste bleibt erhalten.
+`MSBTS_Assembly`, `MSBTS_Schema`, `MSBTS_Map` und `MSBTS_Pipeline` werden bewusst
+nicht abgefragt: Sie waren auf ACC nicht registriert und gehören nicht zu den
+dokumentierten WMI-Kernklassen. Die entsprechenden Daten stammen aus ExplorerOM.
-Zum Schutz vor ungewöhnlich großen Artefaktmengen begrenzt `MaxArtifactDetailsPerType`
-die aufgenommenen Detaildatensätze pro WMI-Klasse auf standardmäßig 5000. Die
-Anwendungsliste selbst wird nicht begrenzt.
+Eine fehlende optionale Laufzeitklasse erzeugt einen Hinweis, verhindert aber
+nicht die ExplorerOM-Anwendungsliste oder den Word-Bericht. Ist der gesamte
+BizTalk-WMI-Namespace für diese Zusatzdaten nicht erreichbar, wird der WMI-Teil
+übersprungen; die zuvor geladene Anwendungsliste bleibt erhalten.
+
+Zum Schutz vor ungewöhnlich großen Artefaktmengen begrenzt
+`MaxArtifactDetailsPerType` die aufgenommenen Detaildatensätze je Artefakttyp auf
+standardmäßig 5000. Die Anwendungsliste selbst wird nicht begrenzt.
### 3.4 BTSTask-Gruppenbinding
@@ -337,13 +339,16 @@ Das DR-Runbook muss mindestens enthalten:
- WMI-Timeout, WQL-, Laufzeit- und Datensatzprotokollierung
- vollständige Anwendungsliste über read-only ExplorerOM statt
`MSBTS_Application`
+- Assemblies, Schemas, Maps und Pipelines über ExplorerOM statt nicht
+ registrierter WMI-Klassen
+- Reflection unterstützt auch explizit implementierte ExplorerOM-Interfaces
- dynamische ExplorerOM-Auflösung aus GAC und BizTalk-Installationspfad
- WMI-Zusatzdaten dürfen nach erfolgreicher ExplorerOM-Erfassung ausfallen
- Prozess-Timeout für `BTSTask`
- live gespiegelte, secret-bereinigte BTSTask-Ausgabe
-- WMI-Fallback für SAP-Endpunkte
+- WMI-Fallback für SAP-Endpunkte ohne doppelte Port-Abfragen
- Offline-Bindingmodus
-- optionale WMI-Klassen stoppen den Lauf nicht
+- optionale Zugriffsfehler werden als Warnung protokolliert und stoppen den Lauf nicht
- begrenzte Dateisystemsuche
- Fehlerisolation bei Registry, Zertifikaten und Dateimetadaten
- XML-Parser ohne DTD/External Entities
@@ -420,6 +425,7 @@ Tests prüfen:
- SAP-Client, Host und `UseSnc`
- Anwendungszuordnung
- Diagnose-Logging mit HRESULT und innerer Reflection-Ausnahme
+- ExplorerOM-Artefakte einschließlich expliziter Interface-Eigenschaften
- DOCX-Paketstruktur
- XML-Gültigkeit aller DOCX-Parts
- Ausschluss von Testkennwörtern
diff --git a/Readme.md b/Readme.md
index 5f37797..d3c0a7d 100644
--- a/Readme.md
+++ b/Readme.md
@@ -22,7 +22,7 @@ BizTalk-Installationspfad geladen.
- Anzahl und Zuordnung von Orchestrierungen
- Send Ports und Send Port Groups
- Receive Ports und Receive Locations
-- Assemblies, Schemas, Maps und Pipelines, soweit die jeweilige WMI-Klasse verfügbar ist
+- Assemblies, Schemas, Maps und Pipelines aus ExplorerOM
- SAP-/IDoc-Indikatoren aus bereitgestellten Schemas, Actions und Artefaktnamen
Die Liste ist nicht auf eine bisher bekannte 15-App-Liste begrenzt. Damit werden auch weitere Anwendungen wie beispielsweise `MasterData_SAP_WebGIS`, `MasterDataHeat`, `WoWHeatInvoice` oder `MeterChangeInfo/Order` sichtbar, sofern sie tatsächlich in der jeweiligen BizTalk-Gruppe installiert sind.
@@ -139,6 +139,7 @@ Das Tool zeigt jeden Abschnitt und die erzeugten Dateien direkt auf der Konsole
[INFO] Suche die lokale Assembly Microsoft.BizTalk.ExplorerOM.dll.
[INFO] ExplorerOM-Assembly geladen: Microsoft.BizTalk.ExplorerOM, Version=...
[INFO] 34 BizTalk-Anwendung(en) geladen (412 ms).
+[INFO] ExplorerOM Schema: 87 Artefakt(e).
[INFO] Verbinde lokal mit dem BizTalk-WMI-Namespace für optionale Zusatzdaten.
[INFO] WMI-Abfrage abgeschlossen: SendPort; Datensätze=91; Dauer=173 ms.
[INFO] Exportiere BizTalk-Gruppenbindings read-only mit BTSTask.
@@ -147,11 +148,12 @@ Das Tool zeigt jeden Abschnitt und die erzeugten Dateien direkt auf der Konsole
[INFO] Ergebnisübersicht: Anwendungen=34; SAP-Endpunkte=12; Findings=4.
```
-Konsole und Log erhalten dieselben Fortschritts- und Diagnosemeldungen. Fehler
-enthalten Ausnahmetyp, HRESULT, WMI-Status, innere Ausnahmen und Stacktrace.
-Fehler eines Collectors stoppen die übrigen Abschnitte nicht. Fehlende optionale
-WMI-Klassen verwerfen insbesondere nicht die über ExplorerOM geladene
-Anwendungsliste.
+Konsole und Log erhalten dieselben Fortschritts- und Diagnosemeldungen.
+Pflichtfehler erscheinen als `[FEHLER]`, durch einen Fallback abgedeckte oder
+optionale Zugriffsfehler als `[WARNUNG]`. Ausnahmedetails enthalten Typ, HRESULT,
+WMI-Status, innere Ausnahmen und Stacktrace. Fehler eines Collectors stoppen die
+übrigen Abschnitte nicht; Fehler optionaler Zusatzdaten verwerfen insbesondere
+nicht die über ExplorerOM geladene Anwendungsliste.
## Ausgabedateien
@@ -226,10 +228,12 @@ Die Gitea-Workflowdatei `.gitea/workflows/build.yml` baut, testet und veröffent
### WMI meldet `Invalid class`
Die Anwendungsliste verwendet nicht mehr die nicht verlässlich vorhandene Klasse
-`MSBTS_Application`, sondern ExplorerOM. Ein `Invalid class` für eine optionale
-Artefaktklasse erscheint mit WQL, HRESULT und WMI-Status im Log, wird als Finding
-übernommen und der Lauf wird fortgesetzt. Schlägt bereits
-`MSBTS_GroupSetting` fehl, kann die Management-Datenbank über die beiden
+`MSBTS_Application`, sondern ExplorerOM. Auch Assemblies, Schemas, Maps und
+Pipelines werden über ExplorerOM gelesen, weil `MSBTS_Assembly`, `MSBTS_Schema`,
+`MSBTS_Map` und `MSBTS_Pipeline` nicht zu den verlässlich registrierten
+BizTalk-WMI-Kernklassen gehören. Verbleibende optionale WMI-Fehler erscheinen mit
+WQL, HRESULT und WMI-Status als `[WARNUNG]`; der Lauf wird fortgesetzt. Schlägt
+bereits `MSBTS_GroupSetting` fehl, kann die Management-Datenbank über die beiden
`--management-*`-Optionen vorgegeben werden.
### ExplorerOM-Assembly fehlt
diff --git a/src/BizTalkSapEnvironmentInventory/Collectors/BizTalkWmiCollector.cs b/src/BizTalkSapEnvironmentInventory/Collectors/BizTalkWmiCollector.cs
index 20b378d..13840b5 100644
--- a/src/BizTalkSapEnvironmentInventory/Collectors/BizTalkWmiCollector.cs
+++ b/src/BizTalkSapEnvironmentInventory/Collectors/BizTalkWmiCollector.cs
@@ -15,8 +15,8 @@ using BizTalkSapEnvironmentInventory.Models;
namespace BizTalkSapEnvironmentInventory.Collectors
{
///
- /// Liest die vollständige Anwendungsliste über ExplorerOM und ergänzt sie um
- /// optionale Artefakt-, Adapter- und Hostdaten aus dem BizTalk-WMI-Provider.
+ /// Liest Anwendungen sowie bereitgestellte Assembly-Artefakte über ExplorerOM
+ /// und ergänzt sie um die vom BizTalk-WMI-Provider angebotenen Laufzeitdaten.
///
internal sealed class BizTalkWmiCollector
{
@@ -36,11 +36,31 @@ namespace BizTalkSapEnvironmentInventory.Collectors
ConsoleFileLogger logger,
int timeoutSeconds,
int maxArtifactDetailsPerType)
+ : this(
+ document,
+ logger,
+ timeoutSeconds,
+ maxArtifactDetailsPerType,
+ true)
+ {
+ }
+
+ private BizTalkWmiCollector(
+ InventoryDocument document,
+ ConsoleFileLogger logger,
+ int timeoutSeconds,
+ int maxArtifactDetailsPerType,
+ bool initializeWmi)
{
this.document = document;
this.logger = logger;
timeout = TimeSpan.FromSeconds(Math.Max(5, timeoutSeconds));
this.maxArtifactDetailsPerType = Math.Max(100, maxArtifactDetailsPerType);
+ if (!initializeWmi)
+ {
+ return;
+ }
+
scope = new ManagementScope(WmiNamespace);
scope.Options.Timeout = timeout;
scope.Options.EnablePrivileges = false;
@@ -110,6 +130,8 @@ namespace BizTalkSapEnvironmentInventory.Collectors
logger.Info(applications.Count + " BizTalk-Anwendung(en) geladen ("
+ timer.ElapsedMilliseconds + " ms).");
+ var explorerArtifactCounts =
+ new Dictionary(StringComparer.OrdinalIgnoreCase);
foreach (var item in applications)
{
var name = RequiredText(item, "Name");
@@ -118,6 +140,10 @@ namespace BizTalkSapEnvironmentInventory.Collectors
application.Status = FormatStatus(OptionalText(item, "Status"));
logger.Detail("ExplorerOM-Anwendung: " + name
+ "; Status=" + application.Status + ".");
+ CollectExplorerArtifacts(
+ item,
+ application,
+ explorerArtifactCounts);
}
if (document.Applications.Count == 0)
@@ -125,6 +151,14 @@ namespace BizTalkSapEnvironmentInventory.Collectors
throw new InvalidOperationException(
"Das BizTalk Explorer Object Model lieferte keine Anwendungen.");
}
+
+ foreach (var artifactType in new[] { "Assembly", "Schema", "Map", "Pipeline" })
+ {
+ int count;
+ explorerArtifactCounts.TryGetValue(artifactType, out count);
+ logger.Info("ExplorerOM " + artifactType + ": "
+ + count + " Artefakt(e).");
+ }
}
finally
{
@@ -145,11 +179,7 @@ namespace BizTalkSapEnvironmentInventory.Collectors
new ClassDescriptor("MSBTS_SendPort", "SendPort"),
new ClassDescriptor("MSBTS_SendPortGroup", "SendPortGroup"),
new ClassDescriptor("MSBTS_ReceivePort", "ReceivePort"),
- new ClassDescriptor("MSBTS_ReceiveLocation", "ReceiveLocation"),
- new ClassDescriptor("MSBTS_Assembly", "Assembly"),
- new ClassDescriptor("MSBTS_Schema", "Schema"),
- new ClassDescriptor("MSBTS_Map", "Map"),
- new ClassDescriptor("MSBTS_Pipeline", "Pipeline")
+ new ClassDescriptor("MSBTS_ReceiveLocation", "ReceiveLocation")
};
foreach (var descriptor in artifactClasses)
@@ -165,9 +195,6 @@ namespace BizTalkSapEnvironmentInventory.Collectors
}
TryCollectComponents("MSBTS_HostSetting", "Host", document.Hosts, false);
TryCollectComponents("MSBTS_HostInstance", "HostInstance", document.Hosts, false);
-
- TryCollectWmiSapEndpoints("MSBTS_SendPort", "Senden");
- TryCollectWmiSapEndpoints("MSBTS_ReceiveLocation", "Empfangen");
}
private void LogRuntimeInformation()
@@ -233,7 +260,9 @@ namespace BizTalkSapEnvironmentInventory.Collectors
+ "Die über ExplorerOM geladene Anwendungsliste bleibt verwendbar.",
Owner = "BizTalk-Betrieb"
});
- logger.Exception("BizTalk-WMI-Verbindung fehlgeschlagen", exception);
+ logger.WarningException(
+ "BizTalk-WMI-Verbindung fehlgeschlagen",
+ exception);
logger.Warning(
"Optionale WMI-Zusatzdaten werden übersprungen; "
+ "die ExplorerOM-Anwendungsliste bleibt erhalten.");
@@ -424,6 +453,24 @@ namespace BizTalkSapEnvironmentInventory.Collectors
}
}
+ // SendPort und ReceiveLocation werden bereits hier gelesen.
+ // Der SAP-Fallback nutzt dieselben Zeilen und belastet den
+ // WMI-Provider daher nicht mit einer zweiten Vollabfrage.
+ if (string.Equals(
+ descriptor.ClassName,
+ "MSBTS_SendPort",
+ StringComparison.OrdinalIgnoreCase))
+ {
+ TryAddWmiSapEndpoint(row, "Senden");
+ }
+ else if (string.Equals(
+ descriptor.ClassName,
+ "MSBTS_ReceiveLocation",
+ StringComparison.OrdinalIgnoreCase))
+ {
+ TryAddWmiSapEndpoint(row, "Empfangen");
+ }
+
count++;
}
@@ -481,61 +528,44 @@ namespace BizTalkSapEnvironmentInventory.Collectors
}
}
- private void TryCollectWmiSapEndpoints(string className, string direction)
+ private void TryAddWmiSapEndpoint(ManagementObject row, string direction)
{
- List rows = null;
- try
+ if (!LooksLikeSap(row))
{
- rows = Query(
- className + " SAP-Fallback",
- "SELECT * FROM " + className);
- foreach (var row in rows)
- {
- if (!LooksLikeSap(row))
- {
- continue;
- }
+ return;
+ }
- var endpointName = First(row, "Name");
- if (document.SapEndpoints.Any(item =>
- string.Equals(item.Direction, direction, StringComparison.OrdinalIgnoreCase)
- && string.Equals(item.Name, endpointName, StringComparison.OrdinalIgnoreCase)))
- {
- continue;
- }
+ var endpointName = First(row, "Name");
+ if (string.IsNullOrWhiteSpace(endpointName)
+ || document.SapEndpoints.Any(item =>
+ string.Equals(item.Direction, direction, StringComparison.OrdinalIgnoreCase)
+ && string.Equals(item.Name, endpointName, StringComparison.OrdinalIgnoreCase)))
+ {
+ return;
+ }
- var endpoint = new SapEndpointRecord
- {
- ApplicationName = First(row, "ApplicationName"),
- Direction = direction,
- Name = endpointName,
- ParentPortName = First(row, "ReceivePortName"),
- AdapterName = First(
- row,
- "PTTransportType",
- "AdapterName",
- "TransportType"),
- HostName = First(row, "HostName", "SendHandler", "ReceiveHandler"),
- Status = FormatStatus(First(row, "Status", "IsDisabled")),
- Address = SensitiveDataSanitizer.SanitizeText(First(
- row,
- "PTAddress",
- "InboundTransportURL",
- "Address")),
- Source = "WMI-Fallback"
- };
- AddSelectedProperties(row, endpoint.Properties);
- document.SapEndpoints.Add(endpoint);
- }
- }
- catch (Exception exception) when (IsRecoverableWmiException(exception))
+ var endpoint = new SapEndpointRecord
{
- AddOptionalWmiFinding(className + " SAP-Fallback", exception);
- }
- finally
- {
- DisposeRows(rows);
- }
+ ApplicationName = First(row, "ApplicationName"),
+ Direction = direction,
+ Name = endpointName,
+ ParentPortName = First(row, "ReceivePortName"),
+ AdapterName = First(
+ row,
+ "PTTransportType",
+ "AdapterName",
+ "TransportType"),
+ HostName = First(row, "HostName", "SendHandler", "ReceiveHandler"),
+ Status = FormatStatus(First(row, "Status", "IsDisabled")),
+ Address = SensitiveDataSanitizer.SanitizeText(First(
+ row,
+ "PTAddress",
+ "InboundTransportURL",
+ "Address")),
+ Source = "WMI-Fallback"
+ };
+ AddSelectedProperties(row, endpoint.Properties);
+ document.SapEndpoints.Add(endpoint);
}
private List Query(string name, string query)
@@ -591,6 +621,247 @@ namespace BizTalkSapEnvironmentInventory.Collectors
return result;
}
+ ///
+ /// Liest Assemblies und deren Schemas, Maps und Pipelines über ExplorerOM.
+ /// Diese Objekte sind keine verlässlich verfügbaren BizTalk-WMI-Klassen.
+ /// Fehler in den Zusatzsammlungen dürfen die Anwendungsliste nicht verwerfen.
+ ///
+ private void CollectExplorerArtifacts(
+ object applicationObject,
+ ApplicationRecord application,
+ IDictionary counts)
+ {
+ foreach (var assembly in EnumerateOptionalProperty(
+ applicationObject,
+ "Assemblies",
+ "Assemblies der Anwendung " + application.Name))
+ {
+ var assemblyName = FirstOptionalText(assembly, "DisplayName", "Name");
+ TryAddExplorerArtifact(
+ application,
+ counts,
+ "Assembly",
+ assemblyName,
+ string.Empty);
+
+ CollectExplorerAssemblyArtifacts(
+ assembly,
+ application,
+ counts,
+ "Schemas",
+ "Schema",
+ assemblyName);
+ CollectExplorerAssemblyArtifacts(
+ assembly,
+ application,
+ counts,
+ "Transforms",
+ "Map",
+ assemblyName);
+ CollectExplorerAssemblyArtifacts(
+ assembly,
+ application,
+ counts,
+ "Pipelines",
+ "Pipeline",
+ assemblyName);
+ }
+ }
+
+ private void CollectExplorerAssemblyArtifacts(
+ object assembly,
+ ApplicationRecord application,
+ IDictionary counts,
+ string collectionProperty,
+ string artifactType,
+ string assemblyName)
+ {
+ foreach (var item in EnumerateOptionalProperty(
+ assembly,
+ collectionProperty,
+ collectionProperty + " aus Assembly " + assemblyName))
+ {
+ var name = FirstOptionalText(
+ item,
+ "FullName",
+ "AssemblyQualifiedName",
+ "Name");
+ TryAddExplorerArtifact(
+ application,
+ counts,
+ artifactType,
+ name,
+ assemblyName);
+ }
+ }
+
+ private void TryAddExplorerArtifact(
+ ApplicationRecord application,
+ IDictionary counts,
+ string artifactType,
+ string name,
+ string assemblyName)
+ {
+ if (string.IsNullOrWhiteSpace(name))
+ {
+ logger.Detail("ExplorerOM-" + artifactType
+ + " ohne auswertbaren Namen wurde übersprungen.");
+ return;
+ }
+
+ int count;
+ counts.TryGetValue(artifactType, out count);
+ if (count >= maxArtifactDetailsPerType)
+ {
+ AddArtifactLimitFindingOnce(artifactType);
+ return;
+ }
+
+ var artifact = new ArtifactRecord
+ {
+ Type = artifactType,
+ Name = name.Trim(),
+ ApplicationName = application.Name,
+ Status = "Bereitgestellt"
+ };
+ artifact.Properties.Add(new NameValueRecord(
+ "Quelle",
+ "BizTalk Explorer Object Model",
+ "ExplorerOM"));
+ if (!string.IsNullOrWhiteSpace(assemblyName)
+ && !string.Equals(name, assemblyName, StringComparison.OrdinalIgnoreCase))
+ {
+ artifact.Properties.Add(new NameValueRecord(
+ "Assembly",
+ assemblyName,
+ "ExplorerOM"));
+ }
+
+ application.Artifacts.Add(artifact);
+ counts[artifactType] = count + 1;
+ }
+
+ private void AddArtifactLimitFindingOnce(string artifactType)
+ {
+ if (document.Findings.Any(item =>
+ string.Equals(item.Area, artifactType, StringComparison.OrdinalIgnoreCase)
+ && item.Message.IndexOf(
+ "Artefaktdetails wurden bei",
+ StringComparison.OrdinalIgnoreCase) >= 0))
+ {
+ return;
+ }
+
+ document.Findings.Add(new Finding
+ {
+ Severity = "Warnung",
+ Area = artifactType,
+ Message = "Artefaktdetails wurden bei "
+ + maxArtifactDetailsPerType
+ + " Einträgen begrenzt.",
+ RecommendedAction =
+ "MaxArtifactDetailsPerType bei Bedarf kontrolliert erhöhen.",
+ Owner = "BizTalk-Betrieb"
+ });
+ }
+
+ private IList