Fix BizTalk catalog collection and diagnostics
Build und Test / build (push) Has been cancelled
Build und Test / build (push) Has been cancelled
This commit is contained in:
+101
-30
@@ -21,7 +21,7 @@ ACC und PROD besitzen jeweils einen lokalen BizTalk Server 2020. Ein Prozess auf
|
|||||||
```text
|
```text
|
||||||
ACC BizTalk Server PROD BizTalk Server
|
ACC BizTalk Server PROD BizTalk Server
|
||||||
| |
|
| |
|
||||||
| lokales WMI | lokales WMI
|
| WMI + ExplorerOM | WMI + ExplorerOM
|
||||||
v v
|
v v
|
||||||
ACC JSON + lokale XLSX PROD JSON + lokale XLSX
|
ACC JSON + lokale XLSX PROD JSON + lokale XLSX
|
||||||
\ /
|
\ /
|
||||||
@@ -42,10 +42,9 @@ CommandLineOptions
|
|||||||
+-- validiert lokalen Modus oder Merge-Modus
|
+-- validiert lokalen Modus oder Merge-Modus
|
||||||
|
|
||||||
BizTalkWmiCollector
|
BizTalkWmiCollector
|
||||||
+-- MSBTS_Application
|
+-- lokales MSBTS_GroupSetting per WMI
|
||||||
+-- MSBTS_ReceivePort
|
+-- lädt Microsoft.BizTalk.ExplorerOM aus GAC/Installationspfad
|
||||||
+-- MSBTS_SendPort
|
+-- Anwendungen und Ports per BtsCatalogExplorer
|
||||||
+-- MSBTS_ReceiveLocation
|
|
||||||
|
|
||||||
SnapshotStore
|
SnapshotStore
|
||||||
+-- normalisiert je Anwendung Adapterzählungen
|
+-- normalisiert je Anwendung Adapterzählungen
|
||||||
@@ -58,37 +57,83 @@ XlsxReportWriter
|
|||||||
+-- Titel, Filter, Freeze Pane und Summenzeile
|
+-- 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`
|
### 4.3 Kataloglesung per Explorer Object Model
|
||||||
- Fallback `PrimaryTransportType`
|
|
||||||
- Fallback `AdapterName`
|
|
||||||
- Fallback `TransportType`
|
|
||||||
|
|
||||||
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`
|
Ladestrategie:
|
||||||
- Fallback `SecondaryTransportType`
|
|
||||||
|
|
||||||
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
|
## 5. Snapshot-Format
|
||||||
|
|
||||||
@@ -170,7 +215,8 @@ Der Filter umfasst nur Kopf- und Datenzeilen, nicht die Summenzeile. Die ersten
|
|||||||
|
|
||||||
## 8. Fehlerverhalten
|
## 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`
|
- Exitcode `1`
|
||||||
- Logeintrag
|
- 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`.
|
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
|
## 9. Sicherheit
|
||||||
|
|
||||||
- ausschließlich lesende lokale WMI-Abfragen
|
- ausschließlich lesende lokale WMI-Abfragen
|
||||||
- keine Änderungen an BizTalk-Konfiguration oder Runtime
|
- ausschließlich lesende Katalogabfragen über das lokale BizTalk Explorer Object Model
|
||||||
- keine SQL-Abfragen
|
- 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 Endpunktadressen
|
||||||
- keine Binding-Exporte
|
- keine Binding-Exporte
|
||||||
- keine Credentials, Secrets oder Tokens
|
- keine Credentials, Secrets oder Tokens
|
||||||
@@ -203,6 +271,7 @@ Baseline:
|
|||||||
- C# 7.3
|
- C# 7.3
|
||||||
- keine NuGet-Pakete
|
- keine NuGet-Pakete
|
||||||
- kein .NET SDK erforderlich
|
- kein .NET SDK erforderlich
|
||||||
|
- keine compile-time-Referenz auf `Microsoft.BizTalk.ExplorerOM.dll`
|
||||||
|
|
||||||
```cmd
|
```cmd
|
||||||
scripts\build-release.cmd
|
scripts\build-release.cmd
|
||||||
@@ -227,10 +296,12 @@ Je Umgebung:
|
|||||||
|
|
||||||
1. `--self-test` erfolgreich ausführen.
|
1. `--self-test` erfolgreich ausführen.
|
||||||
2. Lokalen Lauf in administrativer `cmd.exe` starten.
|
2. Lokalen Lauf in administrativer `cmd.exe` starten.
|
||||||
3. Exitcode `0` und Log prüfen.
|
3. Im Log die erfolgreiche WMI-Abfrage `MSBTS_GroupSetting` prüfen.
|
||||||
4. Anwendungsgesamtzahl mit der BizTalk Administration Console vergleichen.
|
4. Im Log Assemblyname, Version und Pfad von ExplorerOM prüfen.
|
||||||
5. Send Ports, sekundäre Transporte und Receive Locations stichprobenartig zählen.
|
5. Exitcode `0` und `IsComplete: true` prüfen.
|
||||||
6. Nur die vollständige JSON-Datei für den Merge verwenden.
|
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:
|
Nach dem Merge:
|
||||||
|
|
||||||
|
|||||||
@@ -26,12 +26,18 @@ Jeder lokale Lauf erzeugt zusätzlich eine einblättrige Excel-Sicht für die je
|
|||||||
|
|
||||||
## Erfasste Daten
|
## 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
|
- WMI-Klasse `MSBTS_GroupSetting` im Namespace
|
||||||
- `MSBTS_SendPort` für primäre und gegebenenfalls sekundäre Send-Endpunkte
|
`root\MicrosoftBizTalkServer`, um Server und Name der BizTalk-Management-Datenbank
|
||||||
- `MSBTS_ReceivePort` nur zur Zuordnung einer Receive Location zur Anwendung
|
der lokalen BizTalk-Gruppe zu ermitteln;
|
||||||
- `MSBTS_ReceiveLocation` für Receive-Endpunkte
|
- 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:
|
Ein Endpunkt ist:
|
||||||
|
|
||||||
@@ -48,8 +54,13 @@ Nicht erfasst werden insbesondere Adressen, Credentials, Beschreibungen, Status,
|
|||||||
- BizTalk Server 2020 auf Windows Server 2019
|
- BizTalk Server 2020 auf Windows Server 2019
|
||||||
- lokale Ausführung auf dem jeweiligen BizTalk-Server
|
- lokale Ausführung auf dem jeweiligen BizTalk-Server
|
||||||
- .NET Framework 4.7.2 oder höher
|
- .NET Framework 4.7.2 oder höher
|
||||||
|
- lokal installierte BizTalk-Verwaltungskomponenten einschließlich
|
||||||
|
`Microsoft.BizTalk.ExplorerOM.dll`
|
||||||
- Leserechte auf `root\MicrosoftBizTalkServer`
|
- 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.
|
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.
|
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
|
## ACC und PROD zusammenführen
|
||||||
|
|
||||||
Beide JSON-Dateien in einen gemeinsamen, geschützten Ordner kopieren und ausfü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
|
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).
|
Technische Details stehen in [Dokumentation.md](Dokumentation.md).
|
||||||
|
|||||||
@@ -1,24 +1,33 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Management;
|
using System.Management;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Security.Principal;
|
||||||
using BizTalkApplicationCatalog.Infrastructure;
|
using BizTalkApplicationCatalog.Infrastructure;
|
||||||
using BizTalkApplicationCatalog.Models;
|
using BizTalkApplicationCatalog.Models;
|
||||||
|
|
||||||
namespace BizTalkApplicationCatalog.Collectors
|
namespace BizTalkApplicationCatalog.Collectors
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class BizTalkWmiCollector
|
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 InventoryDocument document;
|
||||||
private readonly ConsoleFileLogger logger;
|
private readonly ConsoleFileLogger logger;
|
||||||
private readonly ManagementScope scope;
|
private readonly ManagementScope scope;
|
||||||
private readonly TimeSpan timeout;
|
private readonly TimeSpan timeout;
|
||||||
private readonly Dictionary<string, string> receivePortApplications =
|
|
||||||
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
public BizTalkWmiCollector(
|
public BizTalkWmiCollector(
|
||||||
InventoryDocument document,
|
InventoryDocument document,
|
||||||
@@ -28,18 +37,18 @@ namespace BizTalkApplicationCatalog.Collectors
|
|||||||
this.document = document;
|
this.document = document;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
timeout = TimeSpan.FromSeconds(Math.Max(5, timeoutSeconds));
|
timeout = TimeSpan.FromSeconds(Math.Max(5, timeoutSeconds));
|
||||||
scope = new ManagementScope(
|
scope = new ManagementScope(WmiNamespace);
|
||||||
@"\\" + Environment.MachineName + @"\root\MicrosoftBizTalkServer");
|
|
||||||
scope.Options.Timeout = timeout;
|
scope.Options.Timeout = timeout;
|
||||||
|
scope.Options.EnablePrivileges = false;
|
||||||
|
scope.Options.Impersonation = ImpersonationLevel.Impersonate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Collect()
|
public void Collect()
|
||||||
{
|
{
|
||||||
scope.Connect();
|
LogRuntimeInformation();
|
||||||
CollectApplications();
|
ConnectWmi();
|
||||||
CollectReceivePortMappings();
|
var group = ReadBizTalkGroup();
|
||||||
CollectSendPortEndpoints();
|
CollectWithExplorerObjectModel(group);
|
||||||
CollectReceiveLocationEndpoints();
|
|
||||||
|
|
||||||
document.Applications.Sort((left, right) =>
|
document.Applications.Sort((left, right) =>
|
||||||
string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase));
|
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");
|
var principal = new WindowsPrincipal(identity);
|
||||||
if (!string.IsNullOrWhiteSpace(name))
|
logger.Info("Sicherheitskontext: Benutzer=" + identity.Name
|
||||||
{
|
+ "; administrativ="
|
||||||
document.Applications.Add(new ApplicationRecord { Name = name.Trim() });
|
+ (principal.IsInRole(WindowsBuiltInRole.Administrator)
|
||||||
}
|
? "Ja"
|
||||||
|
: "Nein")
|
||||||
|
+ ".");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception exception)
|
||||||
if (document.Applications.Count == 0)
|
|
||||||
{
|
{
|
||||||
|
// 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(
|
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
|
using (var row = rows[0])
|
||||||
+ " BizTalk-Anwendung(en) gefunden.");
|
{
|
||||||
|
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;
|
var explorerType = ResolveExplorerType();
|
||||||
foreach (var row in Query("SELECT * FROM MSBTS_ReceivePort"))
|
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");
|
throw new InvalidOperationException(
|
||||||
var application = First(row, "ApplicationName", "Application");
|
"Das BizTalk Explorer Object Model lieferte keine Anwendungen.");
|
||||||
if (string.IsNullOrWhiteSpace(name)) continue;
|
}
|
||||||
receivePortApplications[name] = application;
|
|
||||||
count++;
|
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<object> applications)
|
||||||
{
|
{
|
||||||
var count = 0;
|
var sendPortCount = 0;
|
||||||
foreach (var row in Query("SELECT * FROM MSBTS_SendPort"))
|
var secondaryTransportCount = 0;
|
||||||
{
|
var receivePortCount = 0;
|
||||||
using (row)
|
var receiveLocationCount = 0;
|
||||||
{
|
|
||||||
var application = First(row, "ApplicationName", "Application");
|
|
||||||
AddEndpoint(application, First(
|
|
||||||
row,
|
|
||||||
"PTTransportType",
|
|
||||||
"PrimaryTransportType",
|
|
||||||
"AdapterName",
|
|
||||||
"TransportType"));
|
|
||||||
count++;
|
|
||||||
|
|
||||||
var secondaryAdapter = First(
|
foreach (var application in applications)
|
||||||
row,
|
{
|
||||||
"STTransportType",
|
var applicationName = RequiredText(application, "Name");
|
||||||
"SecondaryTransportType");
|
document.Applications.Add(new ApplicationRecord
|
||||||
if (!string.IsNullOrWhiteSpace(secondaryAdapter))
|
{
|
||||||
|
Name = applicationName
|
||||||
|
});
|
||||||
|
logger.Detail("Lese Anwendung: " + applicationName + ".");
|
||||||
|
|
||||||
|
var sendPorts = EnumerateRequiredProperty(
|
||||||
|
application,
|
||||||
|
"SendPorts",
|
||||||
|
"Send Ports der Anwendung " + applicationName);
|
||||||
|
foreach (var sendPort in sendPorts)
|
||||||
|
{
|
||||||
|
sendPortCount++;
|
||||||
|
var sendPortName = RequiredText(sendPort, "Name");
|
||||||
|
var primaryTransport = OptionalProperty(sendPort, "PrimaryTransport");
|
||||||
|
var primaryAdapter = AdapterName(primaryTransport);
|
||||||
|
AddEndpoint(applicationName, primaryAdapter);
|
||||||
|
logger.Detail("Send Port: Anwendung=" + applicationName
|
||||||
|
+ "; Name=" + sendPortName
|
||||||
|
+ "; Primäradapter=" + DisplayAdapter(primaryAdapter) + ".");
|
||||||
|
|
||||||
|
var secondaryTransport = OptionalProperty(sendPort, "SecondaryTransport");
|
||||||
|
if (secondaryTransport != null)
|
||||||
{
|
{
|
||||||
AddEndpoint(application, secondaryAdapter);
|
var secondaryAdapter = AdapterName(secondaryTransport);
|
||||||
count++;
|
// ExplorerOM kann ein leeres TransportInfo-Objekt liefern,
|
||||||
|
// obwohl kein Backup-Transport konfiguriert ist.
|
||||||
|
if (!string.IsNullOrWhiteSpace(secondaryAdapter))
|
||||||
|
{
|
||||||
|
AddEndpoint(applicationName, secondaryAdapter);
|
||||||
|
secondaryTransportCount++;
|
||||||
|
logger.Detail("Send Port: Anwendung=" + applicationName
|
||||||
|
+ "; Name=" + sendPortName
|
||||||
|
+ "; Sekundäradapter=" + secondaryAdapter + ".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var receivePorts = EnumerateRequiredProperty(
|
||||||
|
application,
|
||||||
|
"ReceivePorts",
|
||||||
|
"Receive Ports der Anwendung " + applicationName);
|
||||||
|
foreach (var receivePort in receivePorts)
|
||||||
|
{
|
||||||
|
receivePortCount++;
|
||||||
|
var receivePortName = RequiredText(receivePort, "Name");
|
||||||
|
var receiveLocations = EnumerateRequiredProperty(
|
||||||
|
receivePort,
|
||||||
|
"ReceiveLocations",
|
||||||
|
"Receive Locations des Receive Ports " + receivePortName);
|
||||||
|
foreach (var receiveLocation in receiveLocations)
|
||||||
|
{
|
||||||
|
receiveLocationCount++;
|
||||||
|
var receiveLocationName = RequiredText(receiveLocation, "Name");
|
||||||
|
var adapter = AdapterName(
|
||||||
|
OptionalProperty(receiveLocation, "TransportType"));
|
||||||
|
AddEndpoint(applicationName, adapter);
|
||||||
|
logger.Detail("Receive Location: Anwendung=" + applicationName
|
||||||
|
+ "; Receive Port=" + receivePortName
|
||||||
|
+ "; Name=" + receiveLocationName
|
||||||
|
+ "; Adapter=" + DisplayAdapter(adapter) + ".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("Anwendung abgeschlossen: " + applicationName
|
||||||
|
+ "; Send Ports=" + sendPorts.Count
|
||||||
|
+ "; Receive Ports=" + receivePorts.Count + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("Katalogerfassung abgeschlossen: Anwendungen="
|
||||||
|
+ document.Applications.Count
|
||||||
|
+ "; Send Ports=" + sendPortCount
|
||||||
|
+ "; sekundäre Send-Transporte=" + secondaryTransportCount
|
||||||
|
+ "; Receive Ports=" + receivePortCount
|
||||||
|
+ "; Receive Locations=" + receiveLocationCount
|
||||||
|
+ "; Endpunkte=" + document.Endpoints.Count + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
// LoadWithPartialName ist hier bewusst nur ein Kompatibilitäts-Fallback,
|
||||||
|
// damit unterschiedliche BizTalk-GAC-Versionen gefunden werden.
|
||||||
|
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 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)
|
||||||
|
{
|
||||||
|
var path = Path.Combine(
|
||||||
|
root,
|
||||||
|
folder,
|
||||||
|
ExplorerAssemblyName + ".dll");
|
||||||
|
logger.Detail("Prüfe Assembly-Pfad: " + path);
|
||||||
|
if (!File.Exists(path)) continue;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Assembly.LoadFrom(path);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
logger.Detail("Assembly.LoadFrom fehlgeschlagen: "
|
||||||
|
+ exception.GetType().Name + ": " + exception.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.Info(count + " Send-Endpunkt(e) gefunden.");
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CollectReceiveLocationEndpoints()
|
private void LogExplorerAssembly(Assembly assembly)
|
||||||
{
|
{
|
||||||
var count = 0;
|
logger.Info("ExplorerOM-Assembly geladen: " + assembly.FullName + ".");
|
||||||
foreach (var row in Query("SELECT * FROM MSBTS_ReceiveLocation"))
|
try
|
||||||
{
|
{
|
||||||
using (row)
|
logger.Detail("ExplorerOM-Assemblypfad: " + assembly.Location);
|
||||||
{
|
}
|
||||||
var application = First(row, "ApplicationName", "Application");
|
catch (NotSupportedException)
|
||||||
if (string.IsNullOrWhiteSpace(application))
|
{
|
||||||
{
|
logger.Detail("ExplorerOM-Assemblypfad ist für diese Assembly nicht verfügbar.");
|
||||||
var receivePort = First(row, "ReceivePortName");
|
}
|
||||||
receivePortApplications.TryGetValue(receivePort, out application);
|
}
|
||||||
}
|
|
||||||
|
private List<ManagementObject> Query(string name, string query)
|
||||||
AddEndpoint(
|
{
|
||||||
application,
|
var options = new System.Management.EnumerationOptions
|
||||||
First(row, "AdapterName", "TransportType", "PTTransportType"));
|
{
|
||||||
count++;
|
ReturnImmediately = false,
|
||||||
}
|
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))
|
||||||
|
{
|
||||||
|
var rows = searcher.Get().Cast<ManagementObject>().ToList();
|
||||||
|
timer.Stop();
|
||||||
|
logger.Info("WMI-Abfrage abgeschlossen: " + name
|
||||||
|
+ "; Datensätze=" + rows.Count
|
||||||
|
+ "; Dauer=" + timer.ElapsedMilliseconds + " ms.");
|
||||||
|
return rows;
|
||||||
}
|
}
|
||||||
logger.Info(count + " Receive-Endpunkt(e) gefunden.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddEndpoint(string applicationName, string adapterType)
|
private void AddEndpoint(string applicationName, string adapterType)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(applicationName))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
"Ein Endpunkt konnte keiner BizTalk-Anwendung zugeordnet werden.");
|
|
||||||
}
|
|
||||||
|
|
||||||
document.Endpoints.Add(new EndpointRecord
|
document.Endpoints.Add(new EndpointRecord
|
||||||
{
|
{
|
||||||
ApplicationName = applicationName.Trim(),
|
ApplicationName = applicationName.Trim(),
|
||||||
@@ -169,40 +415,135 @@ namespace BizTalkApplicationCatalog.Collectors
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ManagementObject> Query(string query)
|
private static string AdapterName(object transportTypeOrInfo)
|
||||||
{
|
{
|
||||||
var options = new EnumerationOptions
|
if (transportTypeOrInfo == null) return string.Empty;
|
||||||
{
|
|
||||||
ReturnImmediately = false,
|
// Send-Transporte liefern zuerst TransportInfo und darin TransportType.
|
||||||
Rewindable = false,
|
// Receive Locations liefern direkt den ProtocolType.
|
||||||
Timeout = timeout
|
var protocolType = OptionalProperty(
|
||||||
};
|
transportTypeOrInfo,
|
||||||
using (var searcher = new ManagementObjectSearcher(
|
"TransportType") ?? transportTypeOrInfo;
|
||||||
scope,
|
return Convert.ToString(
|
||||||
new ObjectQuery(query),
|
OptionalProperty(protocolType, "Name"),
|
||||||
options))
|
CultureInfo.InvariantCulture) ?? string.Empty;
|
||||||
{
|
|
||||||
return searcher.Get().Cast<ManagementObject>().ToList();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string First(ManagementBaseObject row, params string[] names)
|
private static string DisplayAdapter(string adapter)
|
||||||
{
|
{
|
||||||
foreach (var name in names)
|
return string.IsNullOrWhiteSpace(adapter) ? "Unbekannt" : adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IList<object> EnumerateRequiredProperty(
|
||||||
|
object instance,
|
||||||
|
string propertyName,
|
||||||
|
string description)
|
||||||
|
{
|
||||||
|
var value = RequiredProperty(instance, propertyName);
|
||||||
|
var enumerable = value as IEnumerable;
|
||||||
|
if (enumerable == null)
|
||||||
{
|
{
|
||||||
try
|
throw new InvalidOperationException(
|
||||||
{
|
description + " ist keine aufzählbare Sammlung.");
|
||||||
var value = row[name];
|
|
||||||
if (value == null) continue;
|
|
||||||
var text = Convert.ToString(value, CultureInfo.InvariantCulture);
|
|
||||||
if (!string.IsNullOrWhiteSpace(text)) return text;
|
|
||||||
}
|
|
||||||
catch (ManagementException)
|
|
||||||
{
|
|
||||||
// WMI-Properties unterscheiden sich zwischen Providerständen.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return string.Empty;
|
|
||||||
|
var result = new List<object>();
|
||||||
|
foreach (var item in enumerable)
|
||||||
|
{
|
||||||
|
if (item != null) result.Add(item);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string RequiredText(object instance, string propertyName)
|
||||||
|
{
|
||||||
|
var value = Convert.ToString(
|
||||||
|
RequiredProperty(instance, propertyName),
|
||||||
|
CultureInfo.InvariantCulture);
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
instance.GetType().FullName + "." + propertyName
|
||||||
|
+ " ist leer.");
|
||||||
|
}
|
||||||
|
return value.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string RequiredWmiText(
|
||||||
|
ManagementBaseObject instance,
|
||||||
|
string propertyName)
|
||||||
|
{
|
||||||
|
var value = Convert.ToString(
|
||||||
|
instance[propertyName],
|
||||||
|
CultureInfo.InvariantCulture);
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"WMI-Eigenschaft " + propertyName + " ist leer.");
|
||||||
|
}
|
||||||
|
return value.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object RequiredProperty(object instance, string propertyName)
|
||||||
|
{
|
||||||
|
var property = instance.GetType().GetProperty(
|
||||||
|
propertyName,
|
||||||
|
BindingFlags.Instance | BindingFlags.Public);
|
||||||
|
if (property == null)
|
||||||
|
{
|
||||||
|
throw new MissingMemberException(
|
||||||
|
instance.GetType().FullName,
|
||||||
|
propertyName);
|
||||||
|
}
|
||||||
|
return property.GetValue(instance, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object OptionalProperty(object instance, string propertyName)
|
||||||
|
{
|
||||||
|
if (instance == null) return null;
|
||||||
|
var property = instance.GetType().GetProperty(
|
||||||
|
propertyName,
|
||||||
|
BindingFlags.Instance | BindingFlags.Public);
|
||||||
|
return property == null ? null : property.GetValue(instance, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetRequiredProperty(
|
||||||
|
object instance,
|
||||||
|
string propertyName,
|
||||||
|
object value)
|
||||||
|
{
|
||||||
|
var property = instance.GetType().GetProperty(
|
||||||
|
propertyName,
|
||||||
|
BindingFlags.Instance | BindingFlags.Public);
|
||||||
|
if (property == null || !property.CanWrite)
|
||||||
|
{
|
||||||
|
throw new MissingMemberException(
|
||||||
|
instance.GetType().FullName,
|
||||||
|
propertyName);
|
||||||
|
}
|
||||||
|
property.SetValue(instance, value, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DisposeRows(IEnumerable<ManagementObject> rows)
|
||||||
|
{
|
||||||
|
foreach (var row in rows) row.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class BizTalkGroupLocation
|
||||||
|
{
|
||||||
|
public BizTalkGroupLocation(
|
||||||
|
string groupName,
|
||||||
|
string databaseServer,
|
||||||
|
string databaseName)
|
||||||
|
{
|
||||||
|
GroupName = groupName;
|
||||||
|
DatabaseServer = databaseServer;
|
||||||
|
DatabaseName = databaseName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GroupName { get; private set; }
|
||||||
|
public string DatabaseServer { get; private set; }
|
||||||
|
public string DatabaseName { get; private set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Management;
|
||||||
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace BizTalkApplicationCatalog.Infrastructure
|
namespace BizTalkApplicationCatalog.Infrastructure
|
||||||
@@ -19,8 +21,57 @@ namespace BizTalkApplicationCatalog.Infrastructure
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void Info(string message) { Write("INFO", message); }
|
public void Info(string message) { Write("INFO", message); }
|
||||||
|
public void Detail(string message) { Write("DETAIL", message); }
|
||||||
public void Warning(string message) { Write("WARNUNG", message); }
|
public void Warning(string message) { Write("WARNUNG", message); }
|
||||||
public void Error(string message) { Write("FEHLER", message); }
|
public void Error(string message) { Write("FEHLER", message); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Schreibt die vollständige Ausnahmekette einschließlich HRESULT,
|
||||||
|
/// WMI-Status und Stacktrace auf Konsole und in die Logdatei.
|
||||||
|
/// </summary>
|
||||||
|
public void Exception(string context, Exception exception)
|
||||||
|
{
|
||||||
|
if (exception == null)
|
||||||
|
{
|
||||||
|
Error(context + ": Unbekannter Fehler ohne Ausnahmeobjekt.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Error(context + ": " + exception.Message);
|
||||||
|
var current = exception;
|
||||||
|
var depth = 0;
|
||||||
|
while (current != null)
|
||||||
|
{
|
||||||
|
Detail(string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"Ausnahme[{0}]: Typ={1}; HRESULT=0x{2:X8}; Meldung={3}",
|
||||||
|
depth,
|
||||||
|
current.GetType().FullName,
|
||||||
|
current.HResult,
|
||||||
|
current.Message));
|
||||||
|
|
||||||
|
var managementException = current as ManagementException;
|
||||||
|
if (managementException != null)
|
||||||
|
{
|
||||||
|
Detail("WMI-Status: " + managementException.ErrorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(current.StackTrace))
|
||||||
|
{
|
||||||
|
Detail("Stacktrace[" + depth + "]:" + Environment.NewLine
|
||||||
|
+ current.StackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reflection kapselt Providerfehler häufig in TargetInvocationException.
|
||||||
|
// Die innere Ausnahme wird deshalb ausdrücklich mit ausgegeben.
|
||||||
|
var targetInvocation = current as TargetInvocationException;
|
||||||
|
current = targetInvocation != null && targetInvocation.InnerException != null
|
||||||
|
? targetInvocation.InnerException
|
||||||
|
: current.InnerException;
|
||||||
|
depth++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose() { writer.Dispose(); }
|
public void Dispose() { writer.Dispose(); }
|
||||||
|
|
||||||
private void Write(string level, string message)
|
private void Write(string level, string message)
|
||||||
|
|||||||
@@ -52,9 +52,11 @@ namespace BizTalkApplicationCatalog.Infrastructure
|
|||||||
Severity = required ? "Fehler" : "Warnung",
|
Severity = required ? "Fehler" : "Warnung",
|
||||||
Area = name,
|
Area = name,
|
||||||
Message = "Datenerfassung fehlgeschlagen: " + exception.Message,
|
Message = "Datenerfassung fehlgeschlagen: " + exception.Message,
|
||||||
RecommendedAction = "Logdatei, WMI-Provider und Leseberechtigungen prüfen."
|
RecommendedAction =
|
||||||
|
"Logdatei, WMI-Provider, ExplorerOM-Installation "
|
||||||
|
+ "und BizTalk-Leseberechtigungen prüfen."
|
||||||
});
|
});
|
||||||
logger.Error(name + ": " + exception.Message);
|
logger.Exception(name, exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,16 @@ namespace BizTalkApplicationCatalog
|
|||||||
logger.Info("BEW BizTalk Application Catalog – Phase 1 startet.");
|
logger.Info("BEW BizTalk Application Catalog – Phase 1 startet.");
|
||||||
logger.Info("Umgebung: " + options.EnvironmentName);
|
logger.Info("Umgebung: " + options.EnvironmentName);
|
||||||
logger.Info("Erfasst werden nur Anwendungen, Adaptertypen und Endpunkte.");
|
logger.Info("Erfasst werden nur Anwendungen, Adaptertypen und Endpunkte.");
|
||||||
logger.Info("Modus: ausschließlich lesender lokaler BizTalk-WMI-Zugriff.");
|
logger.Info(
|
||||||
|
"Modus: ausschließlich lesender Zugriff; lokales BizTalk-WMI "
|
||||||
|
+ "für die Gruppenermittlung und ExplorerOM für den Katalog.");
|
||||||
|
logger.Info("Ausgabeordner: " + options.OutputDirectory);
|
||||||
|
logger.Detail("Logdatei: " + logPath);
|
||||||
|
logger.Detail("JSON-Zieldatei: " + snapshotPath);
|
||||||
|
logger.Detail("XLSX-Zieldatei: " + reportPath);
|
||||||
|
var wmiTimeoutSeconds = ReadInt("WmiTimeoutSeconds", 30);
|
||||||
|
logger.Detail("Konfiguration WmiTimeoutSeconds="
|
||||||
|
+ wmiTimeoutSeconds + ".");
|
||||||
|
|
||||||
new SafeCollector(document, logger).Execute(
|
new SafeCollector(document, logger).Execute(
|
||||||
"Phase-1-Anwendungen und Endpunkte",
|
"Phase-1-Anwendungen und Endpunkte",
|
||||||
@@ -81,7 +90,7 @@ namespace BizTalkApplicationCatalog
|
|||||||
() => new BizTalkWmiCollector(
|
() => new BizTalkWmiCollector(
|
||||||
document,
|
document,
|
||||||
logger,
|
logger,
|
||||||
ReadInt("WmiTimeoutSeconds", 30)).Collect());
|
wmiTimeoutSeconds).Collect());
|
||||||
|
|
||||||
document.CompletedUtc = DateTime.UtcNow;
|
document.CompletedUtc = DateTime.UtcNow;
|
||||||
var snapshot = SnapshotStore.FromInventory(
|
var snapshot = SnapshotStore.FromInventory(
|
||||||
|
|||||||
@@ -76,7 +76,8 @@ namespace BizTalkApplicationCatalog.Reporting
|
|||||||
{
|
{
|
||||||
snapshots.Count > 1
|
snapshots.Count > 1
|
||||||
? "Phase 1 – BizTalk Anwendungen & Adapter (Quelle: ACC/PROD JSON-Snapshots)"
|
? "Phase 1 – BizTalk Anwendungen & Adapter (Quelle: ACC/PROD JSON-Snapshots)"
|
||||||
: "Phase 1 – BizTalk Anwendungen & Adapter (Quelle: lokaler BizTalk-WMI-Provider)",
|
: "Phase 1 – BizTalk Anwendungen & Adapter "
|
||||||
|
+ "(Quelle: lokale BizTalk-Verwaltungsschnittstellen)",
|
||||||
"", "", "", "", "", ""
|
"", "", "", "", "", ""
|
||||||
},
|
},
|
||||||
new object[]
|
new object[]
|
||||||
|
|||||||
Reference in New Issue
Block a user