diff --git a/Dokumentation.md b/Dokumentation.md
index 9ace5f7..072a4f7 100644
--- a/Dokumentation.md
+++ b/Dokumentation.md
@@ -38,13 +38,13 @@ Administrative cmd.exe
+-- SystemCollector
| +-- Windows WMI
| +-- BizTalk Registry
- | +-- Management SQL/DB
+ | +-- MSBTS_GroupSetting für Management SQL/DB
|
+-- BizTalkWmiCollector
+ | +-- ExplorerOM: vollständige Anwendungsliste
| +-- root\MicrosoftBizTalkServer
- | +-- Anwendungen und Artefakte
- | +-- Adapter, Hosts und Handler
- | +-- SAP-Endpunkt-Fallback
+ | +-- optionale Artefakte, Adapter, Hosts und Handler
+ | +-- optionaler SAP-Endpunkt-Fallback
|
+-- BindingExportCollector
| +-- BTSTask ExportBindings /GroupLevel
@@ -62,7 +62,13 @@ Administrative cmd.exe
+-- atomare Ausgabe
```
-Das Programm referenziert keine `Microsoft.BizTalk.*`-Assembly. Dadurch besteht das Deployment nur aus EXE, Konfiguration, Startskript und Dokumentation. Die BizTalk-Integration erfolgt über die offiziell vorhandenen Verwaltungsoberflächen WMI und `BTSTask.exe`.
+Das Programm besitzt keine compile-time-Referenz auf eine
+`Microsoft.BizTalk.*`-Assembly. Dadurch besteht das Deployment nur aus EXE,
+Konfiguration, Startskript und Dokumentation. Die lokal mit den
+BizTalk-Verwaltungskomponenten installierte
+`Microsoft.BizTalk.ExplorerOM.dll` wird zur Laufzeit aus GAC beziehungsweise
+Installationspfad geladen. Die übrige BizTalk-Integration erfolgt über WMI und
+`BTSTask.exe`.
## 3. Datenquellen
@@ -85,7 +91,30 @@ Ermittelt werden unter anderem:
Kommandozeilenparameter überschreiben automatisch ermittelte Management-DB-Werte.
-### 3.2 BizTalk-WMI
+### 3.2 BizTalk-Gruppe und ExplorerOM
+
+Die Management-Datenbank wird zunächst aus Registry und lokalem
+`MSBTS_GroupSetting` ermittelt. Kommandozeilenparameter haben Vorrang. Die
+verwendete WQL-Abfrage wird mit Laufzeit und Datensatzanzahl protokolliert:
+
+```sql
+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.
+
+Dieser Aufbau vermeidet die nicht verlässlich vorhandene WMI-Klasse
+`MSBTS_Application`, die je nach Installation mit `Invalid class` abbrechen kann.
+ExplorerOM-Assemblyname, Version und Dateipfad werden protokolliert. Wenn
+`MSBTS_GroupSetting` nicht gelesen werden kann, darf das automatisch oder per
+`--management-server`/`--management-database` ermittelte Datenbankziel weiterhin
+für ExplorerOM verwendet werden.
+
+### 3.3 Optionale BizTalk-WMI-Daten
Primärer Namespace:
@@ -93,11 +122,6 @@ Primärer Namespace:
root\MicrosoftBizTalkServer
```
-Pflichtquelle:
-
-- `MSBTS_Application`
-- `MSBTS_GroupSetting` wird zusätzlich zur sicheren Erkennung von Management SQL Server und Datenbank verwendet
-
Optionale beziehungsweise versionsabhängige Artefaktklassen:
- `MSBTS_Orchestration`
@@ -115,13 +139,16 @@ Optionale beziehungsweise versionsabhängige Artefaktklassen:
- `MSBTS_HostSetting`
- `MSBTS_HostInstance`
-Eine fehlende optionale Klasse erzeugt einen Hinweis, verhindert aber nicht die Anwendungsliste oder den Word-Bericht.
+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.
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.
-### 3.3 BTSTask-Gruppenbinding
+### 3.4 BTSTask-Gruppenbinding
Das Tool verwendet:
@@ -144,7 +171,7 @@ Die temporäre XML-Datei wird:
Microsoft dokumentiert, dass Kennwörter beim Binding-Export entfernt werden. Zusätzlich redigiert das Tool sensitive Element-, Attribut- und URI-Werte.
-### 3.4 Offline-Binding
+### 3.5 Offline-Binding
Für Diagnose- oder Berechtigungsfälle kann ein vorhandener Export verarbeitet werden:
@@ -306,8 +333,14 @@ Das DR-Runbook muss mindestens enthalten:
## 10. Resilienz
- Collector-Isolation mit eigenem Status und Zeitmessung
-- WMI-Timeout
+- WMI nur lokal über `\\.\root\MicrosoftBizTalkServer`
+- WMI-Timeout, WQL-, Laufzeit- und Datensatzprotokollierung
+- vollständige Anwendungsliste über read-only ExplorerOM statt
+ `MSBTS_Application`
+- 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
- Offline-Bindingmodus
- optionale WMI-Klassen stoppen den Lauf nicht
@@ -317,6 +350,8 @@ Das DR-Runbook muss mindestens enthalten:
- atomare DOCX-Ausgabe
- eindeutige Dateinamen mit Umgebung, Server und Zeitstempel
- Exitcode `1` bei unvollständigem Pflichtabschnitt
+- vollständige Ausnahmeketten mit Typ, HRESULT, WMI-Status, inneren Ausnahmen
+ und Stacktrace auf Konsole und im Log
## 11. DOCX-Erzeugung ohne Office
@@ -343,10 +378,12 @@ Auf jedem der beiden BizTalk Server:
3. `BizTalkSapEnvironmentInventory.exe --self-test` ausführen.
4. Erfassung starten.
5. Exitcode prüfen.
-6. DOCX und Log gemeinsam kontrollieren.
-7. Anwendungsliste gegen BizTalk Administration abgleichen.
-8. SAP-Endpunkte und Hostzuordnung prüfen.
-9. WE20-/WE21-/Credential-/DR-Findings an SAP Basis geben.
+6. Im Log `MSBTS_GroupSetting`, Datensatzanzahl und Management-Datenbankziel prüfen.
+7. ExplorerOM-Assemblyname, Version und Pfad kontrollieren.
+8. DOCX und Log gemeinsam kontrollieren.
+9. Anwendungsliste gegen BizTalk Administration abgleichen.
+10. SAP-Endpunkte und Hostzuordnung prüfen.
+11. WE20-/WE21-/Credential-/DR-Findings an SAP Basis geben.
ACC:
@@ -382,6 +419,7 @@ Tests prüfen:
- Parsing eines WCF-SAP-Gruppenbindings
- SAP-Client, Host und `UseSnc`
- Anwendungszuordnung
+- Diagnose-Logging mit HRESULT und innerer Reflection-Ausnahme
- DOCX-Paketstruktur
- XML-Gültigkeit aller DOCX-Parts
- Ausschluss von Testkennwörtern
@@ -395,12 +433,15 @@ Tests prüfen:
- keine Garantie, dass Schemanamen alle verwendeten IDoc-Erweiterungen enthalten
- dynamisch zur Laufzeit konstruierte SAP-Adressen können im statischen Binding fehlen
- WMI-Klassen unterscheiden sich teilweise je BizTalk-Version/CU
+- die lokal installierte ExplorerOM-Version muss zur lokalen BizTalk-Installation
+ passen
- lokaler Ist-Stand; ACC und PROD müssen getrennt ausgeführt werden
## 15. Referenzen
- Microsoft: BizTalk Server 2020 Hardware and Software Requirements
- Microsoft: `BTSTask ExportBindings`
+- Microsoft: BizTalk Explorer Object Model
- Microsoft: BizTalk WMI Technical Reference
- Microsoft: WCF-SAP Port Configuration
- Microsoft: SAP System Connection URI
diff --git a/Readme.md b/Readme.md
index 32bb8dd..5f37797 100644
--- a/Readme.md
+++ b/Readme.md
@@ -7,14 +7,18 @@ Das Tool wird einmal auf dem BizTalk Server in `ACC` und einmal auf dem BizTalk
- einem BizTalk Server 2020
- einem separaten SQL Server für die BizTalk-Datenbanken
-Microsoft Office, Word, PowerShell, ein .NET SDK, Internetzugriff und zusätzliche BizTalk-Programmier-DLLs werden auf dem Zielserver nicht benötigt.
+Microsoft Office, Word, PowerShell, ein .NET SDK, Internetzugriff und separat
+mitgelieferte BizTalk-DLLs werden auf dem Zielserver nicht benötigt. Die mit den
+lokalen BizTalk-Verwaltungskomponenten installierte
+`Microsoft.BizTalk.ExplorerOM.dll` wird zur Laufzeit aus GAC beziehungsweise
+BizTalk-Installationspfad geladen.
## Erfasster Umfang
### Vollständige BizTalk-Anwendungsliste
-- alle Anwendungen aus `MSBTS_Application`
-- Anwendungsstatus und Beschreibung
+- alle Anwendungen read-only aus dem BizTalk Explorer Object Model
+- Anwendungsstatus und Beschreibung, soweit ExplorerOM sie bereitstellt
- Anzahl und Zuordnung von Orchestrierungen
- Send Ports und Send Port Groups
- Receive Ports und Receive Locations
@@ -69,7 +73,7 @@ Das Tool erfindet hierfür keine Werte. Es dokumentiert nur lokal belegbare Indi
## Sicherheitsprinzip
-- ausschließlich read-only WMI- und Registry-Abfragen
+- ausschließlich read-only ExplorerOM-, WMI- und Registry-Zugriffe
- `BTSTask ExportBindings /GroupLevel` wird nur zum Lesen verwendet
- temporärer Binding-Export wird nach dem Parsen gelöscht
- BizTalk entfernt Kennwörter bereits beim Binding-Export
@@ -86,6 +90,8 @@ DOCX und Log enthalten trotzdem interne Host-, Anwendungs-, Zertifikats- und Pfa
- Konto ist Mitglied von `BizTalk Server Administrators` oder besitzt vergleichbare Leserechte
- .NET Framework 4.7.2 oder höher
- Zugriff auf die konfigurierte BizTalk Management Database
+- lokale BizTalk-Verwaltungskomponenten einschließlich
+ `Microsoft.BizTalk.ExplorerOM.dll`
- `BTSTask.exe` aus der lokalen BizTalk-Installation
## Schnellstart
@@ -127,15 +133,25 @@ BizTalkSapEnvironmentInventory.exe ^
Das Tool zeigt jeden Abschnitt und die erzeugten Dateien direkt auf der Konsole an:
```text
-[INFO] Starte Abschnitt: BizTalk-Anwendungen und WMI-Artefakte
-[INFO] 34 BizTalk-Anwendung(en) über WMI gefunden.
+[INFO] Ermittle die BizTalk-Gruppe über MSBTS_GroupSetting.
+[DETAIL] WQL: SELECT Name,MgmtDbServerName,MgmtDbName FROM MSBTS_GroupSetting
+[INFO] MSBTS_GroupSetting abgeschlossen: Datensätze=1; Dauer=84 ms.
+[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] 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.
+[INFO] BTSTask ExportBindings erfolgreich abgeschlossen (1865 ms).
[INFO] 12 SAP-Endpunkt(e) aus dem Binding-Export ausgewertet.
-[INFO] Erzeuge Microsoft-Word-Dokument: C:\BizTalk-Doku\ACC\...
-[INFO] Word-Dokument erfolgreich erzeugt: C:\BizTalk-Doku\ACC\...
+[INFO] Ergebnisübersicht: Anwendungen=34; SAP-Endpunkte=12; Findings=4.
```
-Fehler eines Collectors stoppen die übrigen Abschnitte nicht. Sie erscheinen im Log, im Erfassungsstatus und als Finding im Word-Bericht.
+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.
## Ausgabedateien
@@ -200,7 +216,28 @@ Die Gitea-Workflowdatei `.gitea/workflows/build.yml` baut, testet und veröffent
- lokal auf dem BizTalk Server ausführen
- Konto und Mitgliedschaft in den BizTalk-Administrator-/Operator-Gruppen prüfen
-- WMI-Namespace `root\MicrosoftBizTalkServer` prüfen
+- im Log das ermittelte Management-Datenbankziel prüfen
+- geladene ExplorerOM-Assembly einschließlich Version und Pfad prüfen
+- Zugriff auf die BizTalk Management Database mit integrierter
+ Windows-Authentifizierung prüfen
+- bei fehlender Erkennung `--management-server` und
+ `--management-database` explizit angeben
+
+### 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
+`--management-*`-Optionen vorgegeben werden.
+
+### ExplorerOM-Assembly fehlt
+
+- prüfen, ob die BizTalk-Verwaltungskomponenten lokal installiert sind
+- im Log die geprüften GAC-/Installationspfade kontrollieren
+- keine DLL von einem anderen BizTalk-Server oder einer anderen CU-Version
+ in den Deployment-Ordner kopieren
### BTSTask findet die Management Database nicht
@@ -228,4 +265,3 @@ BizTalkSapEnvironmentInventory.exe ^
- DOCX-Findings und Log prüfen
Weitere Architektur- und Sicherheitsdetails stehen in [Dokumentation.md](Dokumentation.md).
-
diff --git a/src/BizTalkSapEnvironmentInventory/Collectors/BindingExportCollector.cs b/src/BizTalkSapEnvironmentInventory/Collectors/BindingExportCollector.cs
index 0d1c4fb..9dcb2ff 100644
--- a/src/BizTalkSapEnvironmentInventory/Collectors/BindingExportCollector.cs
+++ b/src/BizTalkSapEnvironmentInventory/Collectors/BindingExportCollector.cs
@@ -530,7 +530,13 @@ namespace BizTalkSapEnvironmentInventory.Collectors
}
logger.Info("Exportiere BizTalk-Gruppenbindings read-only mit BTSTask.");
+ logger.Detail("BTSTask-Pfad: " + btsTaskPath);
+ logger.Detail("BTSTask-Timeout: "
+ + processTimeout.TotalSeconds.ToString(CultureInfo.InvariantCulture)
+ + " Sekunden.");
var output = new StringBuilder();
+ var outputSync = new object();
+ var timer = Stopwatch.StartNew();
var startInfo = new ProcessStartInfo
{
FileName = btsTaskPath,
@@ -548,14 +554,24 @@ namespace BizTalkSapEnvironmentInventory.Collectors
{
if (!string.IsNullOrWhiteSpace(args.Data))
{
- output.AppendLine(args.Data);
+ var safeLine = SensitiveDataSanitizer.SanitizeText(args.Data);
+ lock (outputSync)
+ {
+ output.AppendLine(safeLine);
+ }
+ logger.Detail("BTSTask: " + safeLine);
}
};
process.ErrorDataReceived += (sender, args) =>
{
if (!string.IsNullOrWhiteSpace(args.Data))
{
- output.AppendLine(args.Data);
+ var safeLine = SensitiveDataSanitizer.SanitizeText(args.Data);
+ lock (outputSync)
+ {
+ output.AppendLine(safeLine);
+ }
+ logger.Detail("BTSTask: " + safeLine);
}
};
@@ -575,20 +591,32 @@ namespace BizTalkSapEnvironmentInventory.Collectors
"BTSTask ExportBindings überschritt " + processTimeout.TotalSeconds + " Sekunden.");
}
process.WaitForExit();
+ timer.Stop();
if (process.ExitCode != 0)
{
+ string errorOutput;
+ lock (outputSync)
+ {
+ errorOutput = output.ToString();
+ }
throw new InvalidOperationException(
"BTSTask ExportBindings meldete Exitcode "
+ process.ExitCode
+ ": "
- + LastLines(output.ToString(), 8));
+ + LastLines(errorOutput, 8));
}
+ logger.Info("BTSTask ExportBindings erfolgreich abgeschlossen ("
+ + timer.ElapsedMilliseconds + " ms).");
}
if (!File.Exists(destination) || new FileInfo(destination).Length == 0)
{
throw new InvalidDataException("BTSTask erzeugte keine Binding-Datei.");
}
+ logger.Detail("Temporärer Binding-Export: "
+ + new FileInfo(destination).Length
+ .ToString(CultureInfo.InvariantCulture)
+ + " Bytes.");
}
private string ResolveBtsTaskPath()
diff --git a/src/BizTalkSapEnvironmentInventory/Collectors/BizTalkWmiCollector.cs b/src/BizTalkSapEnvironmentInventory/Collectors/BizTalkWmiCollector.cs
index bf85bc4..20b378d 100644
--- a/src/BizTalkSapEnvironmentInventory/Collectors/BizTalkWmiCollector.cs
+++ b/src/BizTalkSapEnvironmentInventory/Collectors/BizTalkWmiCollector.cs
@@ -1,19 +1,29 @@
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.Runtime.InteropServices;
+using System.Security.Principal;
using BizTalkSapEnvironmentInventory.Infrastructure;
using BizTalkSapEnvironmentInventory.Models;
namespace BizTalkSapEnvironmentInventory.Collectors
{
///
- /// Liest Anwendungen, Artefakte, Adapter und Hosts aus dem BizTalk-WMI-Provider.
+ /// Liest die vollständige Anwendungsliste über ExplorerOM und ergänzt sie um
+ /// optionale Artefakt-, Adapter- und Hostdaten aus dem BizTalk-WMI-Provider.
///
internal sealed class BizTalkWmiCollector
{
- private const string NamespacePath = @"root\MicrosoftBizTalkServer";
+ 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;
@@ -31,18 +41,104 @@ namespace BizTalkSapEnvironmentInventory.Collectors
this.logger = logger;
timeout = TimeSpan.FromSeconds(Math.Max(5, timeoutSeconds));
this.maxArtifactDetailsPerType = Math.Max(100, maxArtifactDetailsPerType);
- scope = new ManagementScope(@"\\" + Environment.MachineName + @"\" + NamespacePath);
+ scope = new ManagementScope(WmiNamespace);
scope.Options.Timeout = timeout;
+ scope.Options.EnablePrivileges = false;
+ scope.Options.Impersonation = ImpersonationLevel.Impersonate;
}
///
- /// Baut die vollständige Anwendungsliste auf und ergänzt verfügbare Artefaktbeziehungen.
+ /// Baut die vollständige Anwendungsliste auf und ergänzt verfügbare WMI-Daten.
///
public void Collect()
{
- scope.Connect();
+ LogRuntimeInformation();
CollectApplications();
+ if (TryConnectWmi())
+ {
+ CollectOptionalWmiData();
+ }
+
+ document.Applications.Sort((left, right) =>
+ string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase));
+ }
+
+ private void CollectApplications()
+ {
+ if (string.IsNullOrWhiteSpace(document.System.ManagementServer))
+ {
+ throw new InvalidOperationException(
+ "Der BizTalk Management SQL Server wurde weder über "
+ + "MSBTS_GroupSetting/Registry noch über --management-server ermittelt.");
+ }
+
+ if (string.IsNullOrWhiteSpace(document.System.ManagementDatabase))
+ {
+ throw new InvalidOperationException(
+ "Die BizTalk Management Database wurde weder automatisch noch "
+ + "über --management-database ermittelt.");
+ }
+
+ var explorerType = ResolveExplorerType();
+ object explorer = null;
+ try
+ {
+ explorer = Activator.CreateInstance(explorerType);
+ logger.Info(
+ "Öffne das BizTalk Explorer Object Model ausschließlich lesend.");
+ logger.Info("ExplorerOM-Ziel: Server="
+ + document.System.ManagementServer
+ + "; Datenbank="
+ + document.System.ManagementDatabase
+ + ".");
+
+ // Die integrierte Verbindungszeichenfolge wird bewusst nicht protokolliert.
+ SetRequiredProperty(
+ explorer,
+ "ConnectionString",
+ "Data Source=" + document.System.ManagementServer
+ + ";Initial Catalog=" + document.System.ManagementDatabase
+ + ";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).");
+
+ foreach (var item in applications)
+ {
+ var name = RequiredText(item, "Name");
+ var application = GetOrCreateApplication(name);
+ application.Description = OptionalText(item, "Description");
+ application.Status = FormatStatus(OptionalText(item, "Status"));
+ logger.Detail("ExplorerOM-Anwendung: " + name
+ + "; Status=" + application.Status + ".");
+ }
+
+ if (document.Applications.Count == 0)
+ {
+ throw new InvalidOperationException(
+ "Das BizTalk Explorer Object Model lieferte keine Anwendungen.");
+ }
+ }
+ finally
+ {
+ var disposable = explorer as IDisposable;
+ if (disposable != null)
+ {
+ disposable.Dispose();
+ logger.Detail("BizTalk Explorer Object Model wurde geschlossen.");
+ }
+ }
+ }
+
+ private void CollectOptionalWmiData()
+ {
var artifactClasses = new[]
{
new ClassDescriptor("MSBTS_Orchestration", "Orchestration"),
@@ -72,41 +168,210 @@ namespace BizTalkSapEnvironmentInventory.Collectors
TryCollectWmiSapEndpoints("MSBTS_SendPort", "Senden");
TryCollectWmiSapEndpoints("MSBTS_ReceiveLocation", "Empfangen");
-
- document.Applications.Sort((left, right) =>
- string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase));
}
- private void CollectApplications()
+ private void LogRuntimeInformation()
{
- var rows = Query("SELECT * FROM MSBTS_Application");
- foreach (var row in rows)
+ 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
{
- var name = First(row, "Name", "ApplicationName");
- if (string.IsNullOrWhiteSpace(name))
+ using (var identity = WindowsIdentity.GetCurrent())
+ {
+ var principal = new WindowsPrincipal(identity);
+ logger.Info("Sicherheitskontext: Benutzer=" + identity.Name
+ + "; administrativ="
+ + (principal.IsInRole(WindowsBuiltInRole.Administrator)
+ ? "Ja"
+ : "Nein")
+ + ".");
+ }
+ }
+ catch (Exception exception)
+ {
+ // Diagnosefehler dürfen die eigentliche Inventarisierung nicht verhindern.
+ logger.Warning("Sicherheitskontext konnte nicht ermittelt werden: "
+ + exception.Message);
+ }
+ }
+
+ private bool TryConnectWmi()
+ {
+ var timer = Stopwatch.StartNew();
+ logger.Info(
+ "Verbinde lokal mit dem BizTalk-WMI-Namespace für optionale Zusatzdaten.");
+ try
+ {
+ scope.Connect();
+ timer.Stop();
+ logger.Info("BizTalk-WMI-Verbindung hergestellt ("
+ + timer.ElapsedMilliseconds + " ms).");
+ return true;
+ }
+ catch (Exception exception) when (IsRecoverableWmiException(exception))
+ {
+ timer.Stop();
+ document.Findings.Add(new Finding
+ {
+ Severity = "Warnung",
+ Area = "BizTalk WMI",
+ Message =
+ "Der BizTalk-WMI-Namespace ist für optionale Artefakt-, "
+ + "Adapter- und Hostdaten nicht erreichbar: " + exception.Message,
+ RecommendedAction =
+ "WMI-Provider und Berechtigungen anhand des Logs prüfen. "
+ + "Die über ExplorerOM geladene Anwendungsliste bleibt verwendbar.",
+ Owner = "BizTalk-Betrieb"
+ });
+ logger.Exception("BizTalk-WMI-Verbindung fehlgeschlagen", exception);
+ logger.Warning(
+ "Optionale WMI-Zusatzdaten werden übersprungen; "
+ + "die ExplorerOM-Anwendungsliste bleibt erhalten.");
+ return false;
+ }
+ }
+
+ private Type ResolveExplorerType()
+ {
+ logger.Info("Suche die lokale Assembly " + ExplorerAssemblyName + ".dll.");
+ var qualifiedTypeName = ExplorerTypeName + ", " + ExplorerAssemblyName;
+ var explorerType = Type.GetType(qualifiedTypeName, false);
+ if (explorerType != null)
+ {
+ LogExplorerAssembly(explorerType.Assembly);
+ return explorerType;
+ }
+
+ Assembly assembly = null;
+ try
+ {
+ assembly = Assembly.Load(ExplorerAssemblyName);
+ }
+ catch (Exception exception)
+ {
+ logger.Detail("Assembly.Load fehlgeschlagen: "
+ + exception.GetType().Name + ": " + exception.Message);
+ }
+
+ if (assembly == null)
+ {
+#pragma warning disable 618
+ // Kompatibilitäts-Fallback für unterschiedliche BizTalk-GAC-Versionen.
+ assembly = Assembly.LoadWithPartialName(ExplorerAssemblyName);
+#pragma warning restore 618
+ }
+
+ if (assembly == null)
+ {
+ assembly = LoadExplorerAssemblyFromInstallDirectory();
+ }
+
+ if (assembly == null)
+ {
+ throw new FileNotFoundException(
+ "Microsoft.BizTalk.ExplorerOM.dll wurde weder im GAC noch "
+ + "im BizTalk-Installationsverzeichnis gefunden. "
+ + "Die BizTalk-Verwaltungskomponenten müssen lokal installiert sein.");
+ }
+
+ explorerType = assembly.GetType(ExplorerTypeName, false);
+ if (explorerType == null)
+ {
+ throw new TypeLoadException(
+ "Typ " + ExplorerTypeName + " fehlt in " + assembly.FullName + ".");
+ }
+
+ LogExplorerAssembly(assembly);
+ return explorerType;
+ }
+
+ private Assembly LoadExplorerAssemblyFromInstallDirectory()
+ {
+ var candidates = new List();
+ if (!string.IsNullOrWhiteSpace(document.System.BizTalkInstallPath))
+ {
+ candidates.Add(Path.Combine(
+ document.System.BizTalkInstallPath,
+ ExplorerAssemblyName + ".dll"));
+ }
+
+ var roots = new[]
+ {
+ Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86),
+ Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)
+ };
+ var folders = new[]
+ {
+ "Microsoft BizTalk Server",
+ "Microsoft BizTalk Server 2020"
+ };
+ foreach (var root in roots.Where(item => !string.IsNullOrWhiteSpace(item)))
+ {
+ foreach (var folder in folders)
+ {
+ candidates.Add(Path.Combine(
+ root,
+ folder,
+ ExplorerAssemblyName + ".dll"));
+ }
+ }
+
+ foreach (var path in candidates.Distinct(StringComparer.OrdinalIgnoreCase))
+ {
+ logger.Detail("Prüfe ExplorerOM-Assemblypfad: " + path);
+ if (!File.Exists(path))
{
continue;
}
- GetOrCreateApplication(name).Description = First(row, "Description");
- GetOrCreateApplication(name).Status = FormatStatus(First(row, "Status"));
+ try
+ {
+ return Assembly.LoadFrom(path);
+ }
+ catch (Exception exception)
+ {
+ logger.Detail("Assembly.LoadFrom fehlgeschlagen: "
+ + exception.GetType().Name + ": " + exception.Message);
+ }
}
- if (document.Applications.Count == 0)
+ return null;
+ }
+
+ private void LogExplorerAssembly(Assembly assembly)
+ {
+ logger.Info("ExplorerOM-Assembly geladen: " + assembly.FullName + ".");
+ try
{
- throw new InvalidOperationException(
- "WMI lieferte keine BizTalk-Anwendungen. Berechtigungen und BizTalk-Gruppe prüfen.");
+ logger.Detail("ExplorerOM-Assemblypfad: " + assembly.Location);
+ }
+ catch (NotSupportedException)
+ {
+ logger.Detail(
+ "ExplorerOM-Assemblypfad ist für diese Assembly nicht verfügbar.");
}
-
- logger.Info(document.Applications.Count + " BizTalk-Anwendung(en) über WMI gefunden.");
}
private void TryCollectArtifacts(ClassDescriptor descriptor)
{
+ List rows = null;
try
{
var count = 0;
- foreach (var row in Query("SELECT * FROM " + descriptor.ClassName))
+ rows = Query(
+ descriptor.DisplayName,
+ "SELECT * FROM " + descriptor.ClassName);
+ foreach (var row in rows)
{
if (count >= maxArtifactDetailsPerType)
{
@@ -164,10 +429,14 @@ namespace BizTalkSapEnvironmentInventory.Collectors
logger.Info(descriptor.DisplayName + ": " + count + " Artefakt(e).");
}
- catch (ManagementException exception)
+ catch (Exception exception) when (IsRecoverableWmiException(exception))
{
AddOptionalWmiFinding(descriptor.ClassName, exception);
}
+ finally
+ {
+ DisposeRows(rows);
+ }
}
private bool TryCollectComponents(
@@ -176,9 +445,11 @@ namespace BizTalkSapEnvironmentInventory.Collectors
List target,
bool onlySap)
{
+ List rows = null;
try
{
- foreach (var row in Query("SELECT * FROM " + className))
+ rows = Query(category, "SELECT * FROM " + className);
+ foreach (var row in rows)
{
if (onlySap && !LooksLikeSap(row))
{
@@ -199,18 +470,26 @@ namespace BizTalkSapEnvironmentInventory.Collectors
}
return true;
}
- catch (ManagementException exception)
+ catch (Exception exception) when (IsRecoverableWmiException(exception))
{
AddOptionalWmiFinding(className, exception);
return false;
}
+ finally
+ {
+ DisposeRows(rows);
+ }
}
private void TryCollectWmiSapEndpoints(string className, string direction)
{
+ List rows = null;
try
{
- foreach (var row in Query("SELECT * FROM " + className))
+ rows = Query(
+ className + " SAP-Fallback",
+ "SELECT * FROM " + className);
+ foreach (var row in rows)
{
if (!LooksLikeSap(row))
{
@@ -249,13 +528,17 @@ namespace BizTalkSapEnvironmentInventory.Collectors
document.SapEndpoints.Add(endpoint);
}
}
- catch (ManagementException exception)
+ catch (Exception exception) when (IsRecoverableWmiException(exception))
{
AddOptionalWmiFinding(className + " SAP-Fallback", exception);
}
+ finally
+ {
+ DisposeRows(rows);
+ }
}
- private List Query(string query)
+ private List Query(string name, string query)
{
var options = new EnumerationOptions
{
@@ -263,15 +546,125 @@ namespace BizTalkSapEnvironmentInventory.Collectors
Rewindable = false,
Timeout = timeout
};
+ var timer = Stopwatch.StartNew();
+ logger.Info("WMI-Abfrage startet: " + name + ".");
+ logger.Detail("WQL: " + query);
using (var searcher = new ManagementObjectSearcher(
scope,
new ObjectQuery(query),
options))
{
- return searcher.Get().Cast().ToList();
+ using (var rows = searcher.Get())
+ {
+ var result = rows.Cast().ToList();
+ timer.Stop();
+ logger.Info("WMI-Abfrage abgeschlossen: " + name
+ + "; Datensätze=" + result.Count
+ + "; Dauer=" + timer.ElapsedMilliseconds + " ms.");
+ return result;
+ }
}
}
+ private static IList