Stabilize BizTalk inventory collection and diagnostics
Build und Test / build (push) Has been cancelled

This commit is contained in:
2026-07-28 10:55:57 +02:00
parent 439427cc17
commit d1e10920c9
11 changed files with 1008 additions and 172 deletions
+60 -19
View File
@@ -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
+47 -11
View File
@@ -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).
@@ -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()
@@ -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
{
/// <summary>
/// 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.
/// </summary>
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;
}
/// <summary>
/// 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.
/// </summary>
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<string>();
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<ManagementObject> 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<ComponentRecord> target,
bool onlySap)
{
List<ManagementObject> 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<ManagementObject> 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<ManagementObject> Query(string query)
private List<ManagementObject> 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<ManagementObject>().ToList();
using (var rows = searcher.Get())
{
var result = rows.Cast<ManagementObject>().ToList();
timer.Stop();
logger.Info("WMI-Abfrage abgeschlossen: " + name
+ "; Datensätze=" + result.Count
+ "; Dauer=" + timer.ElapsedMilliseconds + " ms.");
return result;
}
}
}
private static IList<object> EnumerateRequiredProperty(
object instance,
string propertyName,
string description)
{
var value = RequiredProperty(instance, propertyName);
var enumerable = value as IEnumerable;
if (enumerable == null)
{
throw new InvalidOperationException(
description + " ist keine aufzählbare Sammlung.");
}
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 string OptionalText(object instance, string propertyName)
{
var property = instance.GetType().GetProperty(
propertyName,
BindingFlags.Instance | BindingFlags.Public);
if (property == null)
{
return string.Empty;
}
try
{
return Convert.ToString(
property.GetValue(instance, null),
CultureInfo.InvariantCulture) ?? string.Empty;
}
catch (Exception exception) when (
exception is TargetInvocationException
|| exception is COMException
|| exception is NotSupportedException)
{
logger.Detail("Optionale ExplorerOM-Eigenschaft "
+ instance.GetType().FullName
+ "."
+ propertyName
+ " konnte nicht gelesen werden: "
+ exception.Message);
return string.Empty;
}
}
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 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 ApplicationRecord GetOrCreateApplication(string name)
{
var application = document.Applications.FirstOrDefault(item =>
@@ -405,7 +798,7 @@ namespace BizTalkSapEnvironmentInventory.Collectors
}
}
private void AddOptionalWmiFinding(string className, ManagementException exception)
private void AddOptionalWmiFinding(string className, Exception exception)
{
document.Findings.Add(new Finding
{
@@ -415,7 +808,32 @@ namespace BizTalkSapEnvironmentInventory.Collectors
RecommendedAction = "Nur relevant, wenn die zugehörige Artefaktspalte vollständig benötigt wird.",
Owner = "BizTalk-Betrieb"
});
logger.Warning("Optionale WMI-Abfrage " + className + " fehlgeschlagen: " + exception.Message);
logger.Exception(
"Optionale WMI-Abfrage " + className + " fehlgeschlagen",
exception);
}
private static bool IsRecoverableWmiException(Exception exception)
{
return exception is ManagementException
|| exception is COMException
|| exception is UnauthorizedAccessException;
}
private static void DisposeRows(IEnumerable<ManagementObject> rows)
{
if (rows == null)
{
return;
}
foreach (var row in rows)
{
if (row != null)
{
row.Dispose();
}
}
}
private sealed class ClassDescriptor
@@ -46,15 +46,32 @@ namespace BizTalkSapEnvironmentInventory.Collectors
/// </summary>
public void Collect()
{
logger.Info("Prüfe installierte SAP-/BizTalk-Adapter-Komponenten.");
CollectInstalledProducts();
logger.Info("Lese konfigurierte SNC- und SAP-Runtime-Pfade.");
var configuredPaths = CollectConfiguredSecurityPaths();
logger.Info("Lese SAP-relevante Umgebungsvariablen.");
CollectEnvironment(document.RuntimeComponents);
logger.Info("Suche SAP-Runtime-Dateien in begrenzten Installationspfaden.");
CollectRuntimeFiles(configuredPaths);
logger.Info("Prüfe sichere Metadaten lokaler PSE-/Security-Dateien.");
CollectSecurityFiles(configuredPaths);
if (includeCertificates)
{
logger.Info("Lese SAP-relevante Zertifikatsmetadaten.");
CollectCertificates();
}
else
{
logger.Detail(
"Zertifikatserfassung ist per Konfiguration deaktiviert.");
}
logger.Info("SAP-Runtime-Erfassung abgeschlossen: Komponenten="
+ document.RuntimeComponents.Count
+ "; Security-Materialien="
+ document.SecurityMaterials.Count
+ ".");
if (!document.RuntimeComponents.Any(item =>
string.Equals(
@@ -1,9 +1,14 @@
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Management;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security;
using Microsoft.Win32;
using BizTalkSapEnvironmentInventory.Configuration;
using BizTalkSapEnvironmentInventory.Infrastructure;
using BizTalkSapEnvironmentInventory.Models;
namespace BizTalkSapEnvironmentInventory.Collectors
@@ -17,10 +22,17 @@ namespace BizTalkSapEnvironmentInventory.Collectors
private const string AdministrationKey = BizTalkKey + @"\Administration";
private readonly CommandLineOptions options;
private readonly ConsoleFileLogger logger;
private readonly TimeSpan timeout;
public SystemCollector(CommandLineOptions options)
public SystemCollector(
CommandLineOptions options,
ConsoleFileLogger logger,
int timeoutSeconds)
{
this.options = options;
this.logger = logger;
timeout = TimeSpan.FromSeconds(Math.Max(5, timeoutSeconds));
}
/// <summary>
@@ -43,8 +55,11 @@ namespace BizTalkSapEnvironmentInventory.Collectors
".NET Runtime",
Environment.Version.ToString()));
logger.Info("Lese lokale Windows-Betriebssysteminformationen.");
CollectOperatingSystem(document);
logger.Info("Lese lokale BizTalk-Installation aus der Registry.");
CollectBizTalkRegistry(document);
logger.Info("Ermittle die BizTalk-Gruppe über MSBTS_GroupSetting.");
CollectBizTalkGroupSetting(document);
if (!string.IsNullOrWhiteSpace(options.ManagementServer))
@@ -67,6 +82,11 @@ namespace BizTalkSapEnvironmentInventory.Collectors
document.System.Properties.Add(new NameValueRecord(
"BizTalk Management Database",
EmptyAsUnknown(document.System.ManagementDatabase)));
logger.Info("Management-Datenbankziel: Server="
+ EmptyAsUnknown(document.System.ManagementServer)
+ "; Datenbank="
+ EmptyAsUnknown(document.System.ManagementDatabase)
+ ".");
if (!string.Equals(document.EnvironmentName, "ACC", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(document.EnvironmentName, "PROD", StringComparison.OrdinalIgnoreCase))
@@ -82,78 +102,146 @@ namespace BizTalkSapEnvironmentInventory.Collectors
}
}
private static void CollectOperatingSystem(InventoryDocument document)
private void CollectOperatingSystem(InventoryDocument document)
{
using (var searcher = new ManagementObjectSearcher(
"root\\cimv2",
"SELECT Caption,Version,BuildNumber,OSArchitecture,LastBootUpTime FROM Win32_OperatingSystem"))
const string query =
"SELECT Caption,Version,BuildNumber,OSArchitecture,LastBootUpTime "
+ "FROM Win32_OperatingSystem";
var timer = Stopwatch.StartNew();
logger.Detail("WMI-Namespace: root\\cimv2");
logger.Detail("WQL: " + query);
try
{
foreach (ManagementObject item in searcher.Get())
using (var searcher = new ManagementObjectSearcher("root\\cimv2", query))
{
document.System.Properties.Add(new NameValueRecord("Betriebssystem", Value(item, "Caption")));
document.System.Properties.Add(new NameValueRecord("Windows-Version", Value(item, "Version")));
document.System.Properties.Add(new NameValueRecord("Windows-Build", Value(item, "BuildNumber")));
document.System.Properties.Add(new NameValueRecord("Architektur", Value(item, "OSArchitecture")));
document.System.Properties.Add(new NameValueRecord(
"Letzter Systemstart",
ConvertWmiDate(Value(item, "LastBootUpTime"))));
break;
ConfigureSearcher(searcher);
using (var rows = searcher.Get())
{
var count = 0;
foreach (ManagementObject item in rows)
{
using (item)
{
count++;
if (count != 1)
{
continue;
}
document.System.Properties.Add(new NameValueRecord(
"Betriebssystem",
Value(item, "Caption")));
document.System.Properties.Add(new NameValueRecord(
"Windows-Version",
Value(item, "Version")));
document.System.Properties.Add(new NameValueRecord(
"Windows-Build",
Value(item, "BuildNumber")));
document.System.Properties.Add(new NameValueRecord(
"Architektur",
Value(item, "OSArchitecture")));
document.System.Properties.Add(new NameValueRecord(
"Letzter Systemstart",
ConvertWmiDate(Value(item, "LastBootUpTime"))));
}
}
timer.Stop();
logger.Info("Windows-WMI-Abfrage abgeschlossen: Datensätze="
+ count + "; Dauer=" + timer.ElapsedMilliseconds + " ms.");
}
}
}
catch (Exception exception) when (IsRecoverableWmiException(exception))
{
timer.Stop();
document.Findings.Add(new Finding
{
Severity = "Warnung",
Area = "Windows WMI",
Message = "Win32_OperatingSystem konnte nicht gelesen werden: "
+ exception.Message,
RecommendedAction =
"Lokalen WMI-Dienst und Leseberechtigungen prüfen; "
+ "die BizTalk-Erfassung wird fortgesetzt.",
Owner = "Windows-/BizTalk-Betrieb"
});
logger.Exception("Windows-WMI-Abfrage fehlgeschlagen", exception);
}
}
private static void CollectBizTalkRegistry(InventoryDocument document)
private void CollectBizTalkRegistry(InventoryDocument document)
{
foreach (var view in new[] { RegistryView.Registry64, RegistryView.Registry32 })
{
using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, view))
using (var product = baseKey.OpenSubKey(BizTalkKey, false))
logger.Detail("Prüfe BizTalk-Registryansicht: " + view + ".");
try
{
if (product != null)
using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, view))
using (var product = baseKey.OpenSubKey(BizTalkKey, false))
{
AddRegistryValue(document, product, "ProductVersion", "BizTalk Produktversion", view);
AddRegistryValue(document, product, "ProductName", "BizTalk Produktname", view);
AddRegistryValue(document, product, "Edition", "BizTalk Edition", view);
var installPath = FirstRegistryValue(
product,
"InstallPath",
"BizTalkServerInstallPath",
"Path");
if (!string.IsNullOrWhiteSpace(installPath)
&& string.IsNullOrWhiteSpace(document.System.BizTalkInstallPath))
if (product != null)
{
document.System.BizTalkInstallPath = installPath;
document.System.Properties.Add(new NameValueRecord(
"BizTalk Installationspfad",
installPath,
"Registry " + view));
AddRegistryValue(document, product, "ProductVersion", "BizTalk Produktversion", view);
AddRegistryValue(document, product, "ProductName", "BizTalk Produktname", view);
AddRegistryValue(document, product, "Edition", "BizTalk Edition", view);
var installPath = FirstRegistryValue(
product,
"InstallPath",
"BizTalkServerInstallPath",
"Path");
if (!string.IsNullOrWhiteSpace(installPath)
&& string.IsNullOrWhiteSpace(document.System.BizTalkInstallPath))
{
document.System.BizTalkInstallPath = installPath;
document.System.Properties.Add(new NameValueRecord(
"BizTalk Installationspfad",
installPath,
"Registry " + view));
}
}
}
using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, view))
using (var administration = baseKey.OpenSubKey(AdministrationKey, false))
{
if (administration == null)
{
continue;
}
if (string.IsNullOrWhiteSpace(document.System.ManagementServer))
{
document.System.ManagementServer = FirstRegistryValue(
administration,
"MgmtDBServer",
"ManagementDBServer");
}
if (string.IsNullOrWhiteSpace(document.System.ManagementDatabase))
{
document.System.ManagementDatabase = FirstRegistryValue(
administration,
"MgmtDBName",
"ManagementDBName");
}
}
}
using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, view))
using (var administration = baseKey.OpenSubKey(AdministrationKey, false))
catch (Exception exception) when (IsRecoverableRegistryException(exception))
{
if (administration == null)
document.Findings.Add(new Finding
{
continue;
}
if (string.IsNullOrWhiteSpace(document.System.ManagementServer))
{
document.System.ManagementServer = FirstRegistryValue(
administration,
"MgmtDBServer",
"ManagementDBServer");
}
if (string.IsNullOrWhiteSpace(document.System.ManagementDatabase))
{
document.System.ManagementDatabase = FirstRegistryValue(
administration,
"MgmtDBName",
"ManagementDBName");
}
Severity = "Hinweis",
Area = "BizTalk Registry " + view,
Message = "BizTalk-Registryansicht konnte nicht gelesen werden: "
+ exception.Message,
RecommendedAction =
"Berechtigungen prüfen; WMI oder explizite "
+ "--management-Parameter können die Ermittlung übernehmen.",
Owner = "Windows-/BizTalk-Betrieb"
});
logger.Exception(
"BizTalk-Registryansicht " + view + " fehlgeschlagen",
exception);
}
}
@@ -170,64 +258,138 @@ namespace BizTalkSapEnvironmentInventory.Collectors
}
}
private static void CollectBizTalkGroupSetting(InventoryDocument document)
private void CollectBizTalkGroupSetting(InventoryDocument document)
{
const string query =
"SELECT Name,MgmtDbServerName,MgmtDbName FROM MSBTS_GroupSetting";
var timer = Stopwatch.StartNew();
logger.Detail(@"WMI-Namespace: \\.\root\MicrosoftBizTalkServer");
logger.Detail("WQL: " + query);
try
{
var scope = CreateBizTalkScope();
logger.Info("Verbinde lokal mit dem BizTalk-WMI-Namespace.");
scope.Connect();
using (var searcher = new ManagementObjectSearcher(
@"root\MicrosoftBizTalkServer",
"SELECT Name,MgmtDbServerName,MgmtDbName,BizTalkAdministratorGroup,"
+ "BizTalkOperatorGroup,BizTalkReadOnlyUserGroup,SSOServerName "
+ "FROM MSBTS_GroupSetting"))
scope,
new ObjectQuery(query),
CreateEnumerationOptions()))
{
foreach (ManagementObject item in searcher.Get())
using (var rows = searcher.Get())
{
var managementServer = Value(item, "MgmtDbServerName");
var managementDatabase = Value(item, "MgmtDbName");
if (!string.IsNullOrWhiteSpace(managementServer))
var count = 0;
foreach (ManagementObject item in rows)
{
document.System.ManagementServer = managementServer;
}
if (!string.IsNullOrWhiteSpace(managementDatabase))
{
document.System.ManagementDatabase = managementDatabase;
using (item)
{
count++;
if (count != 1)
{
continue;
}
var managementServer = Value(item, "MgmtDbServerName");
var managementDatabase = Value(item, "MgmtDbName");
if (!string.IsNullOrWhiteSpace(managementServer))
{
document.System.ManagementServer = managementServer;
}
if (!string.IsNullOrWhiteSpace(managementDatabase))
{
document.System.ManagementDatabase = managementDatabase;
}
AddWmiValue(document, item, "Name", "BizTalk Gruppenname");
}
}
AddWmiValue(document, item, "Name", "BizTalk Gruppenname");
AddWmiValue(
document,
item,
"BizTalkAdministratorGroup",
"BizTalk Administratorengruppe");
AddWmiValue(
document,
item,
"BizTalkOperatorGroup",
"BizTalk Operatorengruppe");
AddWmiValue(
document,
item,
"BizTalkReadOnlyUserGroup",
"BizTalk ReadOnly-Gruppe");
AddWmiValue(document, item, "SSOServerName", "Enterprise SSO Server");
break;
timer.Stop();
logger.Info("MSBTS_GroupSetting abgeschlossen: Datensätze="
+ count + "; Dauer=" + timer.ElapsedMilliseconds + " ms.");
if (count == 0)
{
AddGroupSettingFinding(
document,
"MSBTS_GroupSetting lieferte keinen Datensatz.");
}
else if (count > 1)
{
AddGroupSettingFinding(
document,
"MSBTS_GroupSetting lieferte "
+ count
+ " Datensätze; der erste Datensatz wurde verwendet.");
}
}
}
}
catch (ManagementException exception)
catch (Exception exception) when (IsRecoverableWmiException(exception))
{
document.Findings.Add(new Finding
{
Severity = "Hinweis",
Area = "BizTalk GroupSetting",
Message = "MSBTS_GroupSetting konnte für die Management-DB-Erkennung nicht gelesen werden: "
+ exception.Message,
RecommendedAction = "Registry-Ergebnis prüfen oder --management-server explizit angeben.",
Owner = "BizTalk-Betrieb"
});
timer.Stop();
AddGroupSettingFinding(
document,
"MSBTS_GroupSetting konnte für die Management-DB-Erkennung "
+ "nicht gelesen werden: " + exception.Message);
logger.Exception("MSBTS_GroupSetting fehlgeschlagen", exception);
}
}
private ManagementScope CreateBizTalkScope()
{
var scope = new ManagementScope(@"\\.\root\MicrosoftBizTalkServer");
scope.Options.Timeout = timeout;
scope.Options.EnablePrivileges = false;
scope.Options.Impersonation = ImpersonationLevel.Impersonate;
return scope;
}
private EnumerationOptions CreateEnumerationOptions()
{
return new EnumerationOptions
{
ReturnImmediately = false,
Rewindable = false,
Timeout = timeout
};
}
private void ConfigureSearcher(ManagementObjectSearcher searcher)
{
searcher.Options.ReturnImmediately = false;
searcher.Options.Rewindable = false;
searcher.Options.Timeout = timeout;
}
private static void AddGroupSettingFinding(
InventoryDocument document,
string message)
{
document.Findings.Add(new Finding
{
Severity = "Hinweis",
Area = "BizTalk GroupSetting",
Message = message,
RecommendedAction =
"Registry-Ergebnis prüfen oder --management-server und "
+ "--management-database explizit angeben.",
Owner = "BizTalk-Betrieb"
});
}
private static bool IsRecoverableWmiException(Exception exception)
{
return exception is ManagementException
|| exception is COMException
|| exception is UnauthorizedAccessException;
}
private static bool IsRecoverableRegistryException(Exception exception)
{
return exception is IOException
|| exception is SecurityException
|| exception is UnauthorizedAccessException;
}
private static void AddWmiValue(
InventoryDocument document,
ManagementBaseObject item,
@@ -1,6 +1,7 @@
using System;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Text;
namespace BizTalkSapEnvironmentInventory.Infrastructure
@@ -26,6 +27,11 @@ namespace BizTalkSapEnvironmentInventory.Infrastructure
Write("INFO", message);
}
public void Detail(string message)
{
Write("DETAIL", message);
}
public void Warning(string message)
{
Write("WARNUNG", message);
@@ -36,6 +42,62 @@ namespace BizTalkSapEnvironmentInventory.Infrastructure
Write("FEHLER", message);
}
/// <summary>
/// Protokolliert die vollständige Ausnahmekette einschließlich HRESULT,
/// WMI-Status und Stacktrace, damit Providerfehler vor Ort analysierbar bleiben.
/// </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));
if (string.Equals(
current.GetType().FullName,
"System.Management.ManagementException",
StringComparison.Ordinal))
{
var errorCodeProperty = current.GetType().GetProperty(
"ErrorCode",
BindingFlags.Instance | BindingFlags.Public);
if (errorCodeProperty != null)
{
Detail("WMI-Status: " + Convert.ToString(
errorCodeProperty.GetValue(current, null),
CultureInfo.InvariantCulture));
}
}
if (!string.IsNullOrWhiteSpace(current.StackTrace))
{
Detail("Stacktrace[" + depth + "]:" + Environment.NewLine
+ current.StackTrace);
}
// Reflection kapselt ExplorerOM-Fehler häufig in TargetInvocationException.
var targetInvocation = current as TargetInvocationException;
current = targetInvocation != null && targetInvocation.InnerException != null
? targetInvocation.InnerException
: current.InnerException;
depth++;
}
}
public void Dispose()
{
writer.Dispose();
@@ -58,10 +58,12 @@ namespace BizTalkSapEnvironmentInventory.Infrastructure
Severity = required ? "Fehler" : "Warnung",
Area = name,
Message = "Datenerfassung fehlgeschlagen: " + exception.Message,
RecommendedAction = "Logdatei, Berechtigungen und lokale BizTalk-Konfiguration prüfen.",
RecommendedAction =
"Logdatei, WMI-Provider, ExplorerOM-Installation, Berechtigungen "
+ "und lokale BizTalk-Konfiguration prüfen.",
Owner = "BizTalk-Betrieb"
});
logger.Error(name + ": " + exception.Message);
logger.Exception(name, exception);
}
}
}
@@ -2,6 +2,7 @@ using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml.Linq;
using BizTalkSapEnvironmentInventory.Collectors;
@@ -23,6 +24,7 @@ namespace BizTalkSapEnvironmentInventory.Infrastructure
{
TestSanitizer();
TestBindingParser();
TestLoggerDiagnostics();
TestDocxPackage();
}
@@ -236,6 +238,47 @@ namespace BizTalkSapEnvironmentInventory.Infrastructure
}
}
private static void TestLoggerDiagnostics()
{
var directory = CreateTemporaryDirectory();
var path = Path.Combine(directory, "diagnostics.log");
try
{
var exception = new TargetInvocationException(
new InvalidOperationException("Innerer Diagnosefehler"));
using (var logger = new ConsoleFileLogger(path))
{
var originalOutput = Console.Out;
try
{
// Die absichtlich erzeugte Testausnahme soll den Self-Test
// auf der Konsole nicht wie einen echten Laufzeitfehler aussehen lassen.
Console.SetOut(TextWriter.Null);
logger.Exception("Self-Test-Diagnose", exception);
}
finally
{
Console.SetOut(originalOutput);
}
}
var log = File.ReadAllText(path);
Assert(
log.Contains(typeof(TargetInvocationException).FullName),
"Logger dokumentierte die Reflection-Ausnahme nicht.");
Assert(
log.Contains(typeof(InvalidOperationException).FullName),
"Logger dokumentierte die innere Ausnahme nicht.");
Assert(
log.Contains("HRESULT=0x"),
"Logger dokumentierte den HRESULT nicht.");
}
finally
{
DeleteDirectory(directory);
}
}
private static void AssertPackageNamespaces(ZipArchive archive)
{
var contentTypes = LoadEntryXml(archive, "[Content_Types].xml");
+33 -7
View File
@@ -82,21 +82,36 @@ namespace BizTalkSapEnvironmentInventory
logger.Info("Umgebung: " + options.EnvironmentName);
logger.Info("Server: " + Environment.MachineName);
logger.Info("Ausgabeordner: " + options.OutputDirectory);
logger.Info("Modus: read-only");
logger.Info(
"Modus: read-only; ExplorerOM für Anwendungen, WMI für "
+ "Gruppenermittlung und optionale Artefakte.");
logger.Detail("Logdatei: " + logPath);
logger.Detail("DOCX-Zieldatei: " + reportPath);
var wmiTimeoutSeconds = ReadIntSetting("WmiTimeoutSeconds", 30);
var processTimeoutSeconds = ReadIntSetting("ProcessTimeoutSeconds", 120);
var maxArtifactDetails = ReadIntSetting("MaxArtifactDetailsPerType", 5000);
logger.Detail("Konfiguration WmiTimeoutSeconds=" + wmiTimeoutSeconds + ".");
logger.Detail("Konfiguration ProcessTimeoutSeconds=" + processTimeoutSeconds + ".");
logger.Detail("Konfiguration MaxArtifactDetailsPerType="
+ maxArtifactDetails + ".");
var safeCollector = new SafeCollector(document, logger);
safeCollector.Execute(
"System und BizTalk-Installation",
true,
() => new SystemCollector(options).Collect(document));
() => new SystemCollector(
options,
logger,
wmiTimeoutSeconds).Collect(document));
safeCollector.Execute(
"BizTalk-Anwendungen und WMI-Artefakte",
"BizTalk-Anwendungen und optionale WMI-Artefakte",
true,
() => new BizTalkWmiCollector(
document,
logger,
ReadIntSetting("WmiTimeoutSeconds", 30),
ReadIntSetting("MaxArtifactDetailsPerType", 5000)).Collect());
wmiTimeoutSeconds,
maxArtifactDetails).Collect());
safeCollector.Execute(
"SAP-Bindingparameter",
true,
@@ -104,7 +119,7 @@ namespace BizTalkSapEnvironmentInventory
options,
document,
logger,
ReadIntSetting("ProcessTimeoutSeconds", 120)).Collect());
processTimeoutSeconds).Collect());
safeCollector.Execute(
"SAP NCo, SNC und Zertifikatsmetadaten",
false,
@@ -125,11 +140,22 @@ namespace BizTalkSapEnvironmentInventory
}
catch (Exception exception)
{
logger.Error("Word-Dokument konnte nicht erzeugt werden: " + exception);
logger.Exception(
"Word-Dokument konnte nicht erzeugt werden",
exception);
return 2;
}
var exitCode = document.HasRequiredSectionFailure ? 1 : 0;
logger.Info("Ergebnisübersicht: Anwendungen=" + document.Applications.Count
+ "; SAP-Endpunkte=" + document.SapEndpoints.Count
+ "; Findings=" + document.Findings.Count + ".");
if (exitCode != 0)
{
logger.Warning(
"Der Bericht wurde erzeugt, ist wegen mindestens eines "
+ "Pflichtfehlers aber unvollständig.");
}
logger.Info("Erfassung beendet. Exitcode: " + exitCode);
return exitCode;
}
@@ -167,9 +167,10 @@ namespace BizTalkSapEnvironmentInventory.Reporting
WriteHeading(writer, "5. Vollständige BizTalk-Anwendungsliste", 1);
WriteParagraph(
writer,
"Die Liste stammt primär aus MSBTS_Application. Artefaktzahlen werden aus den "
+ "lokal verfügbaren BizTalk-WMI-Klassen gebildet; optionale WMI-Klassen können "
+ "versionsabhängig fehlen und werden dann im Erfassungsstatus bzw. als Finding ausgewiesen.",
"Die vollständige Anwendungsliste stammt aus dem read-only BizTalk Explorer "
+ "Object Model. Artefaktzahlen werden aus lokal verfügbaren, optionalen "
+ "BizTalk-WMI-Klassen gebildet; fehlende Klassen werden im Log und als "
+ "Finding ausgewiesen, ohne die Anwendungsliste zu verwerfen.",
"Normal",
false,
null);