Stabilize BizTalk inventory collection and diagnostics
Build und Test / build (push) Has been cancelled
Build und Test / build (push) Has been cancelled
This commit is contained in:
+60
-19
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user