diff --git a/Dokumentation.md b/Dokumentation.md
index d047ecc..a4350bd 100644
--- a/Dokumentation.md
+++ b/Dokumentation.md
@@ -21,7 +21,7 @@ ACC und PROD besitzen jeweils einen lokalen BizTalk Server 2020. Ein Prozess auf
```text
ACC BizTalk Server PROD BizTalk Server
| |
- | lokales WMI | lokales WMI
+ | WMI + ExplorerOM | WMI + ExplorerOM
v v
ACC JSON + lokale XLSX PROD JSON + lokale XLSX
\ /
@@ -42,10 +42,9 @@ CommandLineOptions
+-- validiert lokalen Modus oder Merge-Modus
BizTalkWmiCollector
- +-- MSBTS_Application
- +-- MSBTS_ReceivePort
- +-- MSBTS_SendPort
- +-- MSBTS_ReceiveLocation
+ +-- lokales MSBTS_GroupSetting per WMI
+ +-- lädt Microsoft.BizTalk.ExplorerOM aus GAC/Installationspfad
+ +-- Anwendungen und Ports per BtsCatalogExplorer
SnapshotStore
+-- normalisiert je Anwendung Adapterzählungen
@@ -58,37 +57,83 @@ XlsxReportWriter
+-- Titel, Filter, Freeze Pane und Summenzeile
```
-## 4. WMI-Datenmodell
+## 4. Datenzugriff
-### 4.1 Anwendungen
+### 4.1 Ursache des Fehlers `Invalid class`
-`MSBTS_Application` ist die Pflichtquelle der Anwendungsliste. Verwendet wird nur `Name` beziehungsweise `ApplicationName`.
+Die ursprüngliche Implementierung führte als erste Abfrage
+`SELECT * FROM MSBTS_Application` im Namespace
+`root\MicrosoftBizTalkServer` aus. Das von BizTalk Server dokumentierte WMI-Schema
+enthält keine Klasse `MSBTS_Application`. WMI antwortet deshalb unabhängig von
+einer administrativ gestarteten Konsole mit `Invalid class`.
-Die Abfrage muss mindestens eine Anwendung liefern. Andernfalls ist der lokale Lauf unvollständig.
+Auch die WMI-Portklassen bilden die fachliche Anwendungshierarchie nicht so
+vollständig ab, dass damit Anwendungen ohne Ports zuverlässig erfasst werden
+könnten. Der Fehler wird daher nicht nur abgefangen; der ungeeignete Zugriffsweg
+wurde ersetzt.
-### 4.2 Receive-Endpunkte
+### 4.2 Lokale Gruppenermittlung per WMI
-Eine `MSBTS_ReceiveLocation` zählt als ein Endpunkt. Der Adapter wird defensiv aus `AdapterName`, `TransportType` oder `PTTransportType` gelesen.
+Verwendeter Namespace:
-Da eine Receive Location abhängig vom WMI-Provider keine direkte `ApplicationName` liefern kann, wird vorher `MSBTS_ReceivePort` gelesen. Über `ReceivePortName` wird die Anwendung aufgelöst. Ein Receive Port zählt selbst nicht als Endpunkt.
+```text
+\\.\root\MicrosoftBizTalkServer
+```
-### 4.3 Send-Endpunkte
+Verwendete Abfrage:
-Bei `MSBTS_SendPort` zählt die primäre Transportkonfiguration als ein Endpunkt. Ist eine sekundäre Transportkonfiguration vorhanden, zählt sie als zusätzlicher Endpunkt.
+```sql
+SELECT Name, MgmtDbServerName, MgmtDbName FROM MSBTS_GroupSetting
+```
-Primärer Adapter:
+Der Punkt im Namespace erzwingt den lokalen Computer. Die Abfrage liefert den
+BizTalk-Gruppennamen und das Ziel der Management-Datenbank. Der konfigurierte
+WMI-Timeout wird angewendet. Kennwörter werden weder benötigt noch protokolliert.
-- `PTTransportType`
-- Fallback `PrimaryTransportType`
-- Fallback `AdapterName`
-- Fallback `TransportType`
+### 4.3 Kataloglesung per Explorer Object Model
-Sekundärer Adapter:
+Die Assembly `Microsoft.BizTalk.ExplorerOM.dll` wird zur Laufzeit geladen. Dadurch
+bleibt das Quellprojekt auf einem Build-Rechner ohne BizTalk-Assembly kompilierbar;
+für den echten Inventarlauf müssen die BizTalk-Verwaltungskomponenten lokal
+installiert sein.
-- `STTransportType`
-- Fallback `SecondaryTransportType`
+Ladestrategie:
-Fehlt ein gelieferter Adaptername, wird `Unbekannt` verwendet. Ein Endpunkt ohne Anwendungszuordnung macht den Lauf unvollständig, weil er sonst in der Anwendungssicht verloren ginge.
+1. normale Typauflösung der bereits verfügbaren Assembly
+2. Laden aus dem Global Assembly Cache
+3. kompatible GAC-Auflösung ohne fest verdrahtete Assemblyversion
+4. bekannte BizTalk-Installationsverzeichnisse unter `Program Files`
+
+`BtsCatalogExplorer.ConnectionString` verwendet ausschließlich integrierte
+Windows-Authentifizierung. Die Anwendung ruft weder `SaveChanges` noch eine andere
+ändernde ExplorerOM-Methode auf.
+
+Die Eigenschaft `Applications` liefert auch Anwendungen ohne Ports. Pro Anwendung
+werden nur folgende Eigenschaften gelesen:
+
+- `Name`
+- `SendPorts`
+- `SendPort.PrimaryTransport.TransportType.Name`
+- `SendPort.SecondaryTransport.TransportType.Name`
+- `ReceivePorts`
+- `ReceivePort.ReceiveLocations`
+- `ReceiveLocation.TransportType.Name`
+
+### 4.4 Receive-Endpunkte
+
+Eine Receive Location zählt als ein Endpunkt. Der Adaptername stammt aus
+`ReceiveLocation.TransportType.Name`. Ein Receive Port ist nur ein Container und
+zählt selbst nicht als Endpunkt.
+
+### 4.5 Send-Endpunkte
+
+Bei einem Send Port zählt die primäre Transportkonfiguration als ein Endpunkt. Ist
+eine sekundäre Transportkonfiguration vorhanden, zählt sie als zusätzlicher
+Endpunkt.
+
+Fehlt ein Adaptername, wird `Unbekannt` verwendet. Die Anwendung ist durch die
+Iteration innerhalb von `Application.SendPorts` beziehungsweise
+`Application.ReceivePorts` eindeutig vorgegeben.
## 5. Snapshot-Format
@@ -170,7 +215,8 @@ Der Filter umfasst nur Kopf- und Datenzeilen, nicht die Summenzeile. Die ersten
## 8. Fehlerverhalten
-Alle vier WMI-Abfragen bilden einen Pflichtabschnitt. Ein Fehler führt zu:
+Die WMI-Gruppenermittlung und die vollständige ExplorerOM-Kataloglesung bilden
+gemeinsam einen Pflichtabschnitt. Ein Fehler führt zu:
- Exitcode `1`
- Logeintrag
@@ -181,11 +227,33 @@ Der Merge lehnt einen solchen JSON-Snapshot ab. Dadurch werden technische Lücke
Ungültige Parameter, fehlende Dateien, fehlerhaftes JSON und Schreibfehler liefern Exitcode `2`.
+### 8.1 Diagnosedaten
+
+Fortschrittsmeldungen werden gleichzeitig auf die Konsole und in eine
+UTF-8-Logdatei geschrieben. Die Detailprotokollierung umfasst:
+
+- Laufzeit-, Betriebssystem- und Prozessarchitektur
+- Windows-Identität und Administratorstatus
+- WMI-Namespace und Timeout
+- vollständige WQL-Anweisung, Laufzeit und Datensatzanzahl
+- BizTalk-Gruppe sowie Management-DB-Server und -Name
+- ExplorerOM-Assemblyname, Version und Dateipfad
+- jede gelesene Anwendung und jeden gezählten Endpunkt mit Adaptertyp
+- Gesamtzahlen für Anwendungen, Send Ports, sekundäre Transporte, Receive Ports
+ und Receive Locations
+
+Eine Ausnahme wird mit Typ, Meldung, HRESULT in Hexadezimalschreibweise,
+`ManagementException.ErrorCode`, inneren Ausnahmen und Stacktrace ausgegeben.
+`TargetInvocationException` wird dabei bis zur eigentlichen Provider- oder
+Datenbankausnahme aufgelöst.
+
## 9. Sicherheit
- ausschließlich lesende lokale WMI-Abfragen
-- keine Änderungen an BizTalk-Konfiguration oder Runtime
-- keine SQL-Abfragen
+- ausschließlich lesende Katalogabfragen über das lokale BizTalk Explorer Object Model
+- keine Aufrufe von `SaveChanges` oder ändernden BizTalk-Methoden
+- keine selbst formulierten SQL-Abfragen; ExplorerOM liest die Management-Datenbank
+ über integrierte Windows-Authentifizierung
- keine Endpunktadressen
- keine Binding-Exporte
- keine Credentials, Secrets oder Tokens
@@ -203,6 +271,7 @@ Baseline:
- C# 7.3
- keine NuGet-Pakete
- kein .NET SDK erforderlich
+- keine compile-time-Referenz auf `Microsoft.BizTalk.ExplorerOM.dll`
```cmd
scripts\build-release.cmd
@@ -227,10 +296,12 @@ Je Umgebung:
1. `--self-test` erfolgreich ausführen.
2. Lokalen Lauf in administrativer `cmd.exe` starten.
-3. Exitcode `0` und Log prüfen.
-4. Anwendungsgesamtzahl mit der BizTalk Administration Console vergleichen.
-5. Send Ports, sekundäre Transporte und Receive Locations stichprobenartig zählen.
-6. Nur die vollständige JSON-Datei für den Merge verwenden.
+3. Im Log die erfolgreiche WMI-Abfrage `MSBTS_GroupSetting` prüfen.
+4. Im Log Assemblyname, Version und Pfad von ExplorerOM prüfen.
+5. Exitcode `0` und `IsComplete: true` prüfen.
+6. Anwendungsgesamtzahl mit der BizTalk Administration Console vergleichen.
+7. Send Ports, sekundäre Transporte und Receive Locations stichprobenartig zählen.
+8. Nur die vollständige JSON-Datei für den Merge verwenden.
Nach dem Merge:
diff --git a/Readme.md b/Readme.md
index c6ecc83..a221cce 100644
--- a/Readme.md
+++ b/Readme.md
@@ -26,12 +26,18 @@ Jeder lokale Lauf erzeugt zusätzlich eine einblättrige Excel-Sicht für die je
## Erfasste Daten
-Das Tool liest ausschließlich:
+Das Tool verwendet zwei ausschließlich lesende, lokale BizTalk-Schnittstellen:
-- `MSBTS_Application` für die vollständige Anwendungsliste
-- `MSBTS_SendPort` für primäre und gegebenenfalls sekundäre Send-Endpunkte
-- `MSBTS_ReceivePort` nur zur Zuordnung einer Receive Location zur Anwendung
-- `MSBTS_ReceiveLocation` für Receive-Endpunkte
+- WMI-Klasse `MSBTS_GroupSetting` im Namespace
+ `root\MicrosoftBizTalkServer`, um Server und Name der BizTalk-Management-Datenbank
+ der lokalen BizTalk-Gruppe zu ermitteln;
+- das lokal installierte `Microsoft.BizTalk.ExplorerOM`, um Anwendungen, Send Ports,
+ sekundäre Send-Transporte, Receive Ports und Receive Locations vollständig zu lesen.
+
+Das Explorer Object Model ist notwendig, weil es im BizTalk-WMI-Schema keine Klasse
+`MSBTS_Application` gibt. Die frühere Abfrage dieser nicht vorhandenen Klasse führte
+unmittelbar zu `Invalid class` beziehungsweise WMI-Status `InvalidClass`. Adminrechte
+ändern nicht, welche Klassen ein WMI-Provider bereitstellt.
Ein Endpunkt ist:
@@ -48,8 +54,13 @@ Nicht erfasst werden insbesondere Adressen, Credentials, Beschreibungen, Status,
- BizTalk Server 2020 auf Windows Server 2019
- lokale Ausführung auf dem jeweiligen BizTalk-Server
- .NET Framework 4.7.2 oder höher
+- lokal installierte BizTalk-Verwaltungskomponenten einschließlich
+ `Microsoft.BizTalk.ExplorerOM.dll`
- Leserechte auf `root\MicrosoftBizTalkServer`
-- administrative `cmd.exe` empfohlen
+- Leserechte auf den BizTalk-Katalog, vorzugsweise über die konfigurierte Gruppe
+ `BizTalk Server Read Only Users` oder eine höher berechtigte BizTalk-Rolle
+- administrative `cmd.exe` ist für die Diagnose hilfreich, ersetzt aber keine
+ BizTalk- beziehungsweise SQL-Leseberechtigung
Microsoft Excel, Microsoft Office, PowerShell, Internetzugriff, NuGet und ein .NET SDK werden auf dem Zielserver nicht benötigt.
@@ -83,6 +94,52 @@ Phase1-BizTalk-Anwendungskatalog-ACC-SERVER-20260727-150000.log
Nur JSON-Dateien eines Laufs mit Exitcode `0` dürfen zusammengeführt werden. Ein unvollständiger Snapshot trägt `IsComplete: false` und wird vom Merge abgelehnt.
+## Konsolen- und Dateilogging
+
+Jede Logmeldung wird unmittelbar gleichzeitig auf der Konsole und in der `.log`-Datei
+ausgegeben. Der lokale Inventarlauf protokolliert unter anderem:
+
+- Computer, Betriebssystem, CLR- und Prozessarchitektur
+- Windows-Sicherheitskontext und erkannte Administratorrolle
+- WMI-Namespace, Timeout, WQL-Abfrage, Dauer und Datensatzanzahl
+- erkannte BizTalk-Gruppe sowie Management-Datenbankziel ohne Kennwort
+- geladene ExplorerOM-Assembly einschließlich Version und Pfad
+- jede Anwendung sowie Send Ports, sekundäre Transporte und Receive Locations
+- Adaptertyp und Summen je Erfassungsart
+- bei Fehlern: Ausnahmetyp, Meldung, HRESULT, WMI-Status, innere Ausnahmen und Stacktrace
+
+Die Logdatei enthält interne Namen und ist wie JSON und XLSX geschützt abzulegen.
+
+## Fehleranalyse
+
+### `Invalid class` direkt nach dem Start
+
+Dieser Fehler wurde in der früheren Version durch
+`SELECT * FROM MSBTS_Application` verursacht. `MSBTS_Application` ist keine
+BizTalk-WMI-Klasse. Die aktuelle Version verwendet diese Abfrage nicht mehr.
+
+Im neuen Log muss stattdessen zunächst diese Abfrage erscheinen:
+
+```text
+SELECT Name, MgmtDbServerName, MgmtDbName FROM MSBTS_GroupSetting
+```
+
+Tritt weiterhin `InvalidClass` auf, ist damit nicht mehr die Anwendungsklasse
+gemeint. Dann ist die lokale BizTalk-WMI-Registrierung zu prüfen. Entscheidend sind
+die protokollierten Felder `WQL`, `WMI-Status` und `HRESULT`.
+
+### ExplorerOM-Assembly fehlt
+
+Die BizTalk-Verwaltungskomponenten müssen auf dem ausführenden BizTalk-Server
+installiert sein. Das Log zeigt alle geprüften Ladewege und die gefundene
+Assemblyversion.
+
+### Zugriff auf den BizTalk-Katalog verweigert
+
+Eine administrativ gestartete `cmd.exe` allein garantiert keine Berechtigung auf den
+BizTalk-Katalog. Das ausführende Konto benötigt mindestens die passenden
+BizTalk-Leserechte. Nach einer Gruppenänderung ist eine neue Anmeldung erforderlich.
+
## ACC und PROD zusammenführen
Beide JSON-Dateien in einen gemeinsamen, geschützten Ordner kopieren und ausführen:
@@ -164,4 +221,7 @@ Dekodieren:
certutil -decode BizTalkApplicationCatalog-source.zip.txt BizTalkApplicationCatalog-source.zip
```
+Die `.txt`-Datei ist Base64 und enthält das vollständige Quellpaket einschließlich
+aktualisierter `Readme.md` und `Dokumentation.md`.
+
Technische Details stehen in [Dokumentation.md](Dokumentation.md).
diff --git a/src/BizTalkApplicationCatalog/Collectors/BizTalkWmiCollector.cs b/src/BizTalkApplicationCatalog/Collectors/BizTalkWmiCollector.cs
index 8eb5a90..fce7b73 100644
--- a/src/BizTalkApplicationCatalog/Collectors/BizTalkWmiCollector.cs
+++ b/src/BizTalkApplicationCatalog/Collectors/BizTalkWmiCollector.cs
@@ -1,24 +1,33 @@
using System;
+using System.Collections;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Globalization;
+using System.IO;
using System.Linq;
using System.Management;
+using System.Reflection;
+using System.Security.Principal;
using BizTalkApplicationCatalog.Infrastructure;
using BizTalkApplicationCatalog.Models;
namespace BizTalkApplicationCatalog.Collectors
{
///
- /// Liest nur die Daten, die für die Phase-1-Ansicht erforderlich sind.
+ /// Ermittelt die lokale BizTalk-Gruppe lesend über WMI und liest den
+ /// Anwendungskatalog anschließend über das BizTalk Explorer Object Model.
///
internal sealed class BizTalkWmiCollector
{
+ private const string WmiNamespace = @"\\.\root\MicrosoftBizTalkServer";
+ private const string ExplorerAssemblyName = "Microsoft.BizTalk.ExplorerOM";
+ private const string ExplorerTypeName =
+ "Microsoft.BizTalk.ExplorerOM.BtsCatalogExplorer";
+
private readonly InventoryDocument document;
private readonly ConsoleFileLogger logger;
private readonly ManagementScope scope;
private readonly TimeSpan timeout;
- private readonly Dictionary receivePortApplications =
- new Dictionary(StringComparer.OrdinalIgnoreCase);
public BizTalkWmiCollector(
InventoryDocument document,
@@ -28,18 +37,18 @@ namespace BizTalkApplicationCatalog.Collectors
this.document = document;
this.logger = logger;
timeout = TimeSpan.FromSeconds(Math.Max(5, timeoutSeconds));
- scope = new ManagementScope(
- @"\\" + Environment.MachineName + @"\root\MicrosoftBizTalkServer");
+ scope = new ManagementScope(WmiNamespace);
scope.Options.Timeout = timeout;
+ scope.Options.EnablePrivileges = false;
+ scope.Options.Impersonation = ImpersonationLevel.Impersonate;
}
public void Collect()
{
- scope.Connect();
- CollectApplications();
- CollectReceivePortMappings();
- CollectSendPortEndpoints();
- CollectReceiveLocationEndpoints();
+ LogRuntimeInformation();
+ ConnectWmi();
+ var group = ReadBizTalkGroup();
+ CollectWithExplorerObjectModel(group);
document.Applications.Sort((left, right) =>
string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase));
@@ -58,108 +67,345 @@ namespace BizTalkApplicationCatalog.Collectors
});
}
- private void CollectApplications()
+ private void LogRuntimeInformation()
{
- foreach (var row in Query("SELECT * FROM MSBTS_Application"))
+ logger.Info("Diagnose: Computer=" + Environment.MachineName
+ + "; Betriebssystem=" + Environment.OSVersion.VersionString
+ + "; CLR=" + Environment.Version
+ + "; Prozess=" + (Environment.Is64BitProcess ? "64-Bit" : "32-Bit")
+ + "; Betriebssystem="
+ + (Environment.Is64BitOperatingSystem ? "64-Bit" : "32-Bit") + ".");
+ logger.Detail("WMI-Namespace: " + WmiNamespace);
+ logger.Detail("WMI-Timeout: "
+ + timeout.TotalSeconds.ToString(CultureInfo.InvariantCulture)
+ + " Sekunden.");
+
+ try
{
- using (row)
+ using (var identity = WindowsIdentity.GetCurrent())
{
- var name = First(row, "Name", "ApplicationName");
- if (!string.IsNullOrWhiteSpace(name))
- {
- document.Applications.Add(new ApplicationRecord { Name = name.Trim() });
- }
+ var principal = new WindowsPrincipal(identity);
+ logger.Info("Sicherheitskontext: Benutzer=" + identity.Name
+ + "; administrativ="
+ + (principal.IsInRole(WindowsBuiltInRole.Administrator)
+ ? "Ja"
+ : "Nein")
+ + ".");
}
}
-
- if (document.Applications.Count == 0)
+ catch (Exception exception)
{
+ // Ein Diagnosefehler darf die eigentliche Inventarisierung nicht verhindern.
+ logger.Warning("Sicherheitskontext konnte nicht ermittelt werden: "
+ + exception.Message);
+ }
+ }
+
+ private void ConnectWmi()
+ {
+ var timer = Stopwatch.StartNew();
+ logger.Info("Verbinde lokal mit dem BizTalk-WMI-Namespace.");
+ scope.Connect();
+ timer.Stop();
+ logger.Info("BizTalk-WMI-Verbindung hergestellt ("
+ + timer.ElapsedMilliseconds + " ms).");
+ }
+
+ private BizTalkGroupLocation ReadBizTalkGroup()
+ {
+ const string query =
+ "SELECT Name, MgmtDbServerName, MgmtDbName FROM MSBTS_GroupSetting";
+ var rows = Query("BizTalk-Gruppenkonfiguration", query);
+ if (rows.Count != 1)
+ {
+ DisposeRows(rows);
throw new InvalidOperationException(
- "MSBTS_Application lieferte keine Anwendungen.");
+ "MSBTS_GroupSetting lieferte "
+ + rows.Count
+ + " Datensätze; exakt ein Datensatz wurde erwartet.");
}
- logger.Info(document.Applications.Count
- + " BizTalk-Anwendung(en) gefunden.");
+ using (var row = rows[0])
+ {
+ var groupName = RequiredWmiText(row, "Name");
+ var serverName = RequiredWmiText(row, "MgmtDbServerName");
+ var databaseName = RequiredWmiText(row, "MgmtDbName");
+ logger.Info("BizTalk-Gruppe gefunden: " + groupName + ".");
+ logger.Info("Management-Datenbankziel: Server="
+ + serverName + "; Datenbank=" + databaseName + ".");
+ return new BizTalkGroupLocation(
+ groupName,
+ serverName,
+ databaseName);
+ }
}
- private void CollectReceivePortMappings()
+ private void CollectWithExplorerObjectModel(BizTalkGroupLocation group)
{
- var count = 0;
- foreach (var row in Query("SELECT * FROM MSBTS_ReceivePort"))
+ var explorerType = ResolveExplorerType();
+ object explorer = null;
+ try
{
- using (row)
+ explorer = Activator.CreateInstance(explorerType);
+ logger.Info("BizTalk Explorer Object Model wird ausschließlich lesend geöffnet.");
+
+ // Die Verbindungszeichenfolge enthält nur Zielserver, Datenbankname
+ // und integrierte Windows-Authentifizierung. Sie wird nicht protokolliert.
+ SetRequiredProperty(
+ explorer,
+ "ConnectionString",
+ "Data Source=" + group.DatabaseServer
+ + ";Initial Catalog=" + group.DatabaseName
+ + ";Integrated Security=SSPI;Persist Security Info=False;");
+
+ var timer = Stopwatch.StartNew();
+ var applications = EnumerateRequiredProperty(
+ explorer,
+ "Applications",
+ "BizTalk-Anwendungen");
+ timer.Stop();
+ logger.Info(applications.Count + " BizTalk-Anwendung(en) geladen ("
+ + timer.ElapsedMilliseconds + " ms).");
+
+ if (applications.Count == 0)
{
- var name = First(row, "Name", "ReceivePortName");
- var application = First(row, "ApplicationName", "Application");
- if (string.IsNullOrWhiteSpace(name)) continue;
- receivePortApplications[name] = application;
- count++;
+ throw new InvalidOperationException(
+ "Das BizTalk Explorer Object Model lieferte keine Anwendungen.");
+ }
+
+ CollectApplicationsAndEndpoints(applications);
+ }
+ finally
+ {
+ var disposable = explorer as IDisposable;
+ if (disposable != null)
+ {
+ disposable.Dispose();
+ logger.Detail("BizTalk Explorer Object Model wurde geschlossen.");
}
}
- logger.Info(count + " Receive-Port-Zuordnung(en) gelesen.");
}
- private void CollectSendPortEndpoints()
+ private void CollectApplicationsAndEndpoints(IList