Handle BizTalk artifact collection via ExplorerOM
Build und Test / build (push) Has been cancelled
Build und Test / build (push) Has been cancelled
This commit is contained in:
+25
-19
@@ -41,9 +41,10 @@ Administrative cmd.exe
|
||||
| +-- MSBTS_GroupSetting für Management SQL/DB
|
||||
|
|
||||
+-- BizTalkWmiCollector
|
||||
| +-- ExplorerOM: vollständige Anwendungsliste
|
||||
| +-- ExplorerOM: Anwendungen, Assemblies, Schemas,
|
||||
| | Maps und Pipelines
|
||||
| +-- root\MicrosoftBizTalkServer
|
||||
| +-- optionale Artefakte, Adapter, Hosts und Handler
|
||||
| +-- Orchestrierungen, Ports, Adapter, Hosts und Handler
|
||||
| +-- optionaler SAP-Endpunkt-Fallback
|
||||
|
|
||||
+-- BindingExportCollector
|
||||
@@ -104,8 +105,9 @@ 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.
|
||||
keine ändernden ExplorerOM-Methoden aufgerufen. Die bereitgestellten Assemblies
|
||||
und deren Schemas, Transforms/Maps und Pipelines werden aus denselben
|
||||
Anwendungsobjekten gelesen. 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.
|
||||
@@ -114,7 +116,7 @@ ExplorerOM-Assemblyname, Version und Dateipfad werden protokolliert. Wenn
|
||||
`--management-server`/`--management-database` ermittelte Datenbankziel weiterhin
|
||||
für ExplorerOM verwendet werden.
|
||||
|
||||
### 3.3 Optionale BizTalk-WMI-Daten
|
||||
### 3.3 BizTalk-WMI-Laufzeitdaten
|
||||
|
||||
Primärer Namespace:
|
||||
|
||||
@@ -122,31 +124,31 @@ Primärer Namespace:
|
||||
root\MicrosoftBizTalkServer
|
||||
```
|
||||
|
||||
Optionale beziehungsweise versionsabhängige Artefaktklassen:
|
||||
Verwendete BizTalk-WMI-Klassen:
|
||||
|
||||
- `MSBTS_Orchestration`
|
||||
- `MSBTS_SendPort`
|
||||
- `MSBTS_SendPortGroup`
|
||||
- `MSBTS_ReceivePort`
|
||||
- `MSBTS_ReceiveLocation`
|
||||
- `MSBTS_Assembly`
|
||||
- `MSBTS_Schema`
|
||||
- `MSBTS_Map`
|
||||
- `MSBTS_Pipeline`
|
||||
- `MSBTS_AdapterSetting`
|
||||
- `MSBTS_ReceiveHandler`
|
||||
- `MSBTS_SendHandler2` bzw. `MSBTS_SendHandler`
|
||||
- `MSBTS_HostSetting`
|
||||
- `MSBTS_HostInstance`
|
||||
|
||||
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.
|
||||
`MSBTS_Assembly`, `MSBTS_Schema`, `MSBTS_Map` und `MSBTS_Pipeline` werden bewusst
|
||||
nicht abgefragt: Sie waren auf ACC nicht registriert und gehören nicht zu den
|
||||
dokumentierten WMI-Kernklassen. Die entsprechenden Daten stammen aus ExplorerOM.
|
||||
|
||||
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.
|
||||
Eine fehlende optionale Laufzeitklasse 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 je Artefakttyp auf
|
||||
standardmäßig 5000. Die Anwendungsliste selbst wird nicht begrenzt.
|
||||
|
||||
### 3.4 BTSTask-Gruppenbinding
|
||||
|
||||
@@ -337,13 +339,16 @@ Das DR-Runbook muss mindestens enthalten:
|
||||
- WMI-Timeout, WQL-, Laufzeit- und Datensatzprotokollierung
|
||||
- vollständige Anwendungsliste über read-only ExplorerOM statt
|
||||
`MSBTS_Application`
|
||||
- Assemblies, Schemas, Maps und Pipelines über ExplorerOM statt nicht
|
||||
registrierter WMI-Klassen
|
||||
- Reflection unterstützt auch explizit implementierte ExplorerOM-Interfaces
|
||||
- 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
|
||||
- WMI-Fallback für SAP-Endpunkte ohne doppelte Port-Abfragen
|
||||
- Offline-Bindingmodus
|
||||
- optionale WMI-Klassen stoppen den Lauf nicht
|
||||
- optionale Zugriffsfehler werden als Warnung protokolliert und stoppen den Lauf nicht
|
||||
- begrenzte Dateisystemsuche
|
||||
- Fehlerisolation bei Registry, Zertifikaten und Dateimetadaten
|
||||
- XML-Parser ohne DTD/External Entities
|
||||
@@ -420,6 +425,7 @@ Tests prüfen:
|
||||
- SAP-Client, Host und `UseSnc`
|
||||
- Anwendungszuordnung
|
||||
- Diagnose-Logging mit HRESULT und innerer Reflection-Ausnahme
|
||||
- ExplorerOM-Artefakte einschließlich expliziter Interface-Eigenschaften
|
||||
- DOCX-Paketstruktur
|
||||
- XML-Gültigkeit aller DOCX-Parts
|
||||
- Ausschluss von Testkennwörtern
|
||||
|
||||
@@ -22,7 +22,7 @@ BizTalk-Installationspfad geladen.
|
||||
- Anzahl und Zuordnung von Orchestrierungen
|
||||
- Send Ports und Send Port Groups
|
||||
- Receive Ports und Receive Locations
|
||||
- Assemblies, Schemas, Maps und Pipelines, soweit die jeweilige WMI-Klasse verfügbar ist
|
||||
- Assemblies, Schemas, Maps und Pipelines aus ExplorerOM
|
||||
- SAP-/IDoc-Indikatoren aus bereitgestellten Schemas, Actions und Artefaktnamen
|
||||
|
||||
Die Liste ist nicht auf eine bisher bekannte 15-App-Liste begrenzt. Damit werden auch weitere Anwendungen wie beispielsweise `MasterData_SAP_WebGIS`, `MasterDataHeat`, `WoWHeatInvoice` oder `MeterChangeInfo/Order` sichtbar, sofern sie tatsächlich in der jeweiligen BizTalk-Gruppe installiert sind.
|
||||
@@ -139,6 +139,7 @@ Das Tool zeigt jeden Abschnitt und die erzeugten Dateien direkt auf der Konsole
|
||||
[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] ExplorerOM Schema: 87 Artefakt(e).
|
||||
[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.
|
||||
@@ -147,11 +148,12 @@ Das Tool zeigt jeden Abschnitt und die erzeugten Dateien direkt auf der Konsole
|
||||
[INFO] Ergebnisübersicht: Anwendungen=34; SAP-Endpunkte=12; Findings=4.
|
||||
```
|
||||
|
||||
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.
|
||||
Konsole und Log erhalten dieselben Fortschritts- und Diagnosemeldungen.
|
||||
Pflichtfehler erscheinen als `[FEHLER]`, durch einen Fallback abgedeckte oder
|
||||
optionale Zugriffsfehler als `[WARNUNG]`. Ausnahmedetails enthalten Typ, HRESULT,
|
||||
WMI-Status, innere Ausnahmen und Stacktrace. Fehler eines Collectors stoppen die
|
||||
übrigen Abschnitte nicht; Fehler optionaler Zusatzdaten verwerfen insbesondere
|
||||
nicht die über ExplorerOM geladene Anwendungsliste.
|
||||
|
||||
## Ausgabedateien
|
||||
|
||||
@@ -226,10 +228,12 @@ Die Gitea-Workflowdatei `.gitea/workflows/build.yml` baut, testet und veröffent
|
||||
### 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
|
||||
`MSBTS_Application`, sondern ExplorerOM. Auch Assemblies, Schemas, Maps und
|
||||
Pipelines werden über ExplorerOM gelesen, weil `MSBTS_Assembly`, `MSBTS_Schema`,
|
||||
`MSBTS_Map` und `MSBTS_Pipeline` nicht zu den verlässlich registrierten
|
||||
BizTalk-WMI-Kernklassen gehören. Verbleibende optionale WMI-Fehler erscheinen mit
|
||||
WQL, HRESULT und WMI-Status als `[WARNUNG]`; der Lauf wird fortgesetzt. Schlägt
|
||||
bereits `MSBTS_GroupSetting` fehl, kann die Management-Datenbank über die beiden
|
||||
`--management-*`-Optionen vorgegeben werden.
|
||||
|
||||
### ExplorerOM-Assembly fehlt
|
||||
|
||||
@@ -15,8 +15,8 @@ using BizTalkSapEnvironmentInventory.Models;
|
||||
namespace BizTalkSapEnvironmentInventory.Collectors
|
||||
{
|
||||
/// <summary>
|
||||
/// Liest die vollständige Anwendungsliste über ExplorerOM und ergänzt sie um
|
||||
/// optionale Artefakt-, Adapter- und Hostdaten aus dem BizTalk-WMI-Provider.
|
||||
/// Liest Anwendungen sowie bereitgestellte Assembly-Artefakte über ExplorerOM
|
||||
/// und ergänzt sie um die vom BizTalk-WMI-Provider angebotenen Laufzeitdaten.
|
||||
/// </summary>
|
||||
internal sealed class BizTalkWmiCollector
|
||||
{
|
||||
@@ -36,11 +36,31 @@ namespace BizTalkSapEnvironmentInventory.Collectors
|
||||
ConsoleFileLogger logger,
|
||||
int timeoutSeconds,
|
||||
int maxArtifactDetailsPerType)
|
||||
: this(
|
||||
document,
|
||||
logger,
|
||||
timeoutSeconds,
|
||||
maxArtifactDetailsPerType,
|
||||
true)
|
||||
{
|
||||
}
|
||||
|
||||
private BizTalkWmiCollector(
|
||||
InventoryDocument document,
|
||||
ConsoleFileLogger logger,
|
||||
int timeoutSeconds,
|
||||
int maxArtifactDetailsPerType,
|
||||
bool initializeWmi)
|
||||
{
|
||||
this.document = document;
|
||||
this.logger = logger;
|
||||
timeout = TimeSpan.FromSeconds(Math.Max(5, timeoutSeconds));
|
||||
this.maxArtifactDetailsPerType = Math.Max(100, maxArtifactDetailsPerType);
|
||||
if (!initializeWmi)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
scope = new ManagementScope(WmiNamespace);
|
||||
scope.Options.Timeout = timeout;
|
||||
scope.Options.EnablePrivileges = false;
|
||||
@@ -110,6 +130,8 @@ namespace BizTalkSapEnvironmentInventory.Collectors
|
||||
logger.Info(applications.Count + " BizTalk-Anwendung(en) geladen ("
|
||||
+ timer.ElapsedMilliseconds + " ms).");
|
||||
|
||||
var explorerArtifactCounts =
|
||||
new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var item in applications)
|
||||
{
|
||||
var name = RequiredText(item, "Name");
|
||||
@@ -118,6 +140,10 @@ namespace BizTalkSapEnvironmentInventory.Collectors
|
||||
application.Status = FormatStatus(OptionalText(item, "Status"));
|
||||
logger.Detail("ExplorerOM-Anwendung: " + name
|
||||
+ "; Status=" + application.Status + ".");
|
||||
CollectExplorerArtifacts(
|
||||
item,
|
||||
application,
|
||||
explorerArtifactCounts);
|
||||
}
|
||||
|
||||
if (document.Applications.Count == 0)
|
||||
@@ -125,6 +151,14 @@ namespace BizTalkSapEnvironmentInventory.Collectors
|
||||
throw new InvalidOperationException(
|
||||
"Das BizTalk Explorer Object Model lieferte keine Anwendungen.");
|
||||
}
|
||||
|
||||
foreach (var artifactType in new[] { "Assembly", "Schema", "Map", "Pipeline" })
|
||||
{
|
||||
int count;
|
||||
explorerArtifactCounts.TryGetValue(artifactType, out count);
|
||||
logger.Info("ExplorerOM " + artifactType + ": "
|
||||
+ count + " Artefakt(e).");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -145,11 +179,7 @@ namespace BizTalkSapEnvironmentInventory.Collectors
|
||||
new ClassDescriptor("MSBTS_SendPort", "SendPort"),
|
||||
new ClassDescriptor("MSBTS_SendPortGroup", "SendPortGroup"),
|
||||
new ClassDescriptor("MSBTS_ReceivePort", "ReceivePort"),
|
||||
new ClassDescriptor("MSBTS_ReceiveLocation", "ReceiveLocation"),
|
||||
new ClassDescriptor("MSBTS_Assembly", "Assembly"),
|
||||
new ClassDescriptor("MSBTS_Schema", "Schema"),
|
||||
new ClassDescriptor("MSBTS_Map", "Map"),
|
||||
new ClassDescriptor("MSBTS_Pipeline", "Pipeline")
|
||||
new ClassDescriptor("MSBTS_ReceiveLocation", "ReceiveLocation")
|
||||
};
|
||||
|
||||
foreach (var descriptor in artifactClasses)
|
||||
@@ -165,9 +195,6 @@ namespace BizTalkSapEnvironmentInventory.Collectors
|
||||
}
|
||||
TryCollectComponents("MSBTS_HostSetting", "Host", document.Hosts, false);
|
||||
TryCollectComponents("MSBTS_HostInstance", "HostInstance", document.Hosts, false);
|
||||
|
||||
TryCollectWmiSapEndpoints("MSBTS_SendPort", "Senden");
|
||||
TryCollectWmiSapEndpoints("MSBTS_ReceiveLocation", "Empfangen");
|
||||
}
|
||||
|
||||
private void LogRuntimeInformation()
|
||||
@@ -233,7 +260,9 @@ namespace BizTalkSapEnvironmentInventory.Collectors
|
||||
+ "Die über ExplorerOM geladene Anwendungsliste bleibt verwendbar.",
|
||||
Owner = "BizTalk-Betrieb"
|
||||
});
|
||||
logger.Exception("BizTalk-WMI-Verbindung fehlgeschlagen", exception);
|
||||
logger.WarningException(
|
||||
"BizTalk-WMI-Verbindung fehlgeschlagen",
|
||||
exception);
|
||||
logger.Warning(
|
||||
"Optionale WMI-Zusatzdaten werden übersprungen; "
|
||||
+ "die ExplorerOM-Anwendungsliste bleibt erhalten.");
|
||||
@@ -424,6 +453,24 @@ namespace BizTalkSapEnvironmentInventory.Collectors
|
||||
}
|
||||
}
|
||||
|
||||
// SendPort und ReceiveLocation werden bereits hier gelesen.
|
||||
// Der SAP-Fallback nutzt dieselben Zeilen und belastet den
|
||||
// WMI-Provider daher nicht mit einer zweiten Vollabfrage.
|
||||
if (string.Equals(
|
||||
descriptor.ClassName,
|
||||
"MSBTS_SendPort",
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
TryAddWmiSapEndpoint(row, "Senden");
|
||||
}
|
||||
else if (string.Equals(
|
||||
descriptor.ClassName,
|
||||
"MSBTS_ReceiveLocation",
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
TryAddWmiSapEndpoint(row, "Empfangen");
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
@@ -481,27 +528,20 @@ namespace BizTalkSapEnvironmentInventory.Collectors
|
||||
}
|
||||
}
|
||||
|
||||
private void TryCollectWmiSapEndpoints(string className, string direction)
|
||||
{
|
||||
List<ManagementObject> rows = null;
|
||||
try
|
||||
{
|
||||
rows = Query(
|
||||
className + " SAP-Fallback",
|
||||
"SELECT * FROM " + className);
|
||||
foreach (var row in rows)
|
||||
private void TryAddWmiSapEndpoint(ManagementObject row, string direction)
|
||||
{
|
||||
if (!LooksLikeSap(row))
|
||||
{
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
var endpointName = First(row, "Name");
|
||||
if (document.SapEndpoints.Any(item =>
|
||||
if (string.IsNullOrWhiteSpace(endpointName)
|
||||
|| document.SapEndpoints.Any(item =>
|
||||
string.Equals(item.Direction, direction, StringComparison.OrdinalIgnoreCase)
|
||||
&& string.Equals(item.Name, endpointName, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
var endpoint = new SapEndpointRecord
|
||||
@@ -527,16 +567,6 @@ namespace BizTalkSapEnvironmentInventory.Collectors
|
||||
AddSelectedProperties(row, endpoint.Properties);
|
||||
document.SapEndpoints.Add(endpoint);
|
||||
}
|
||||
}
|
||||
catch (Exception exception) when (IsRecoverableWmiException(exception))
|
||||
{
|
||||
AddOptionalWmiFinding(className + " SAP-Fallback", exception);
|
||||
}
|
||||
finally
|
||||
{
|
||||
DisposeRows(rows);
|
||||
}
|
||||
}
|
||||
|
||||
private List<ManagementObject> Query(string name, string query)
|
||||
{
|
||||
@@ -591,6 +621,247 @@ namespace BizTalkSapEnvironmentInventory.Collectors
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Liest Assemblies und deren Schemas, Maps und Pipelines über ExplorerOM.
|
||||
/// Diese Objekte sind keine verlässlich verfügbaren BizTalk-WMI-Klassen.
|
||||
/// Fehler in den Zusatzsammlungen dürfen die Anwendungsliste nicht verwerfen.
|
||||
/// </summary>
|
||||
private void CollectExplorerArtifacts(
|
||||
object applicationObject,
|
||||
ApplicationRecord application,
|
||||
IDictionary<string, int> counts)
|
||||
{
|
||||
foreach (var assembly in EnumerateOptionalProperty(
|
||||
applicationObject,
|
||||
"Assemblies",
|
||||
"Assemblies der Anwendung " + application.Name))
|
||||
{
|
||||
var assemblyName = FirstOptionalText(assembly, "DisplayName", "Name");
|
||||
TryAddExplorerArtifact(
|
||||
application,
|
||||
counts,
|
||||
"Assembly",
|
||||
assemblyName,
|
||||
string.Empty);
|
||||
|
||||
CollectExplorerAssemblyArtifacts(
|
||||
assembly,
|
||||
application,
|
||||
counts,
|
||||
"Schemas",
|
||||
"Schema",
|
||||
assemblyName);
|
||||
CollectExplorerAssemblyArtifacts(
|
||||
assembly,
|
||||
application,
|
||||
counts,
|
||||
"Transforms",
|
||||
"Map",
|
||||
assemblyName);
|
||||
CollectExplorerAssemblyArtifacts(
|
||||
assembly,
|
||||
application,
|
||||
counts,
|
||||
"Pipelines",
|
||||
"Pipeline",
|
||||
assemblyName);
|
||||
}
|
||||
}
|
||||
|
||||
private void CollectExplorerAssemblyArtifacts(
|
||||
object assembly,
|
||||
ApplicationRecord application,
|
||||
IDictionary<string, int> counts,
|
||||
string collectionProperty,
|
||||
string artifactType,
|
||||
string assemblyName)
|
||||
{
|
||||
foreach (var item in EnumerateOptionalProperty(
|
||||
assembly,
|
||||
collectionProperty,
|
||||
collectionProperty + " aus Assembly " + assemblyName))
|
||||
{
|
||||
var name = FirstOptionalText(
|
||||
item,
|
||||
"FullName",
|
||||
"AssemblyQualifiedName",
|
||||
"Name");
|
||||
TryAddExplorerArtifact(
|
||||
application,
|
||||
counts,
|
||||
artifactType,
|
||||
name,
|
||||
assemblyName);
|
||||
}
|
||||
}
|
||||
|
||||
private void TryAddExplorerArtifact(
|
||||
ApplicationRecord application,
|
||||
IDictionary<string, int> counts,
|
||||
string artifactType,
|
||||
string name,
|
||||
string assemblyName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
logger.Detail("ExplorerOM-" + artifactType
|
||||
+ " ohne auswertbaren Namen wurde übersprungen.");
|
||||
return;
|
||||
}
|
||||
|
||||
int count;
|
||||
counts.TryGetValue(artifactType, out count);
|
||||
if (count >= maxArtifactDetailsPerType)
|
||||
{
|
||||
AddArtifactLimitFindingOnce(artifactType);
|
||||
return;
|
||||
}
|
||||
|
||||
var artifact = new ArtifactRecord
|
||||
{
|
||||
Type = artifactType,
|
||||
Name = name.Trim(),
|
||||
ApplicationName = application.Name,
|
||||
Status = "Bereitgestellt"
|
||||
};
|
||||
artifact.Properties.Add(new NameValueRecord(
|
||||
"Quelle",
|
||||
"BizTalk Explorer Object Model",
|
||||
"ExplorerOM"));
|
||||
if (!string.IsNullOrWhiteSpace(assemblyName)
|
||||
&& !string.Equals(name, assemblyName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
artifact.Properties.Add(new NameValueRecord(
|
||||
"Assembly",
|
||||
assemblyName,
|
||||
"ExplorerOM"));
|
||||
}
|
||||
|
||||
application.Artifacts.Add(artifact);
|
||||
counts[artifactType] = count + 1;
|
||||
}
|
||||
|
||||
private void AddArtifactLimitFindingOnce(string artifactType)
|
||||
{
|
||||
if (document.Findings.Any(item =>
|
||||
string.Equals(item.Area, artifactType, StringComparison.OrdinalIgnoreCase)
|
||||
&& item.Message.IndexOf(
|
||||
"Artefaktdetails wurden bei",
|
||||
StringComparison.OrdinalIgnoreCase) >= 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
document.Findings.Add(new Finding
|
||||
{
|
||||
Severity = "Warnung",
|
||||
Area = artifactType,
|
||||
Message = "Artefaktdetails wurden bei "
|
||||
+ maxArtifactDetailsPerType
|
||||
+ " Einträgen begrenzt.",
|
||||
RecommendedAction =
|
||||
"MaxArtifactDetailsPerType bei Bedarf kontrolliert erhöhen.",
|
||||
Owner = "BizTalk-Betrieb"
|
||||
});
|
||||
}
|
||||
|
||||
private IList<object> EnumerateOptionalProperty(
|
||||
object instance,
|
||||
string propertyName,
|
||||
string description)
|
||||
{
|
||||
try
|
||||
{
|
||||
var property = FindProperty(instance, propertyName);
|
||||
if (property == null)
|
||||
{
|
||||
throw new MissingMemberException(
|
||||
instance.GetType().FullName,
|
||||
propertyName);
|
||||
}
|
||||
|
||||
var enumerable = property.GetValue(instance, null) 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;
|
||||
}
|
||||
catch (Exception exception) when (IsOptionalExplorerException(exception))
|
||||
{
|
||||
AddOptionalExplorerFinding(propertyName, description, exception);
|
||||
return new List<object>();
|
||||
}
|
||||
}
|
||||
|
||||
private void AddOptionalExplorerFinding(
|
||||
string propertyName,
|
||||
string description,
|
||||
Exception exception)
|
||||
{
|
||||
var area = "ExplorerOM " + propertyName;
|
||||
if (!document.Findings.Any(item =>
|
||||
string.Equals(item.Area, area, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
document.Findings.Add(new Finding
|
||||
{
|
||||
Severity = "Warnung",
|
||||
Area = area,
|
||||
Message = description + " konnten nicht gelesen werden: "
|
||||
+ exception.Message,
|
||||
RecommendedAction =
|
||||
"ExplorerOM-Version, Berechtigung und Management-Datenbankzugriff "
|
||||
+ "anhand des Logs prüfen. Die Anwendungsliste bleibt verwendbar.",
|
||||
Owner = "BizTalk-Betrieb"
|
||||
});
|
||||
logger.WarningException(
|
||||
"Optionale ExplorerOM-Sammlung " + description
|
||||
+ " konnte nicht gelesen werden",
|
||||
exception);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Detail("Weitere optionale ExplorerOM-Sammlung "
|
||||
+ description + " konnte nicht gelesen werden: "
|
||||
+ exception.Message);
|
||||
}
|
||||
}
|
||||
|
||||
// Wird vom Self-Test mit expliziten Interface-Eigenschaften ausgeführt.
|
||||
internal void CollectExplorerArtifactsForTest(
|
||||
object applicationObject,
|
||||
ApplicationRecord application)
|
||||
{
|
||||
CollectExplorerArtifacts(
|
||||
applicationObject,
|
||||
application,
|
||||
new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
internal static BizTalkWmiCollector CreateForExplorerArtifactTest(
|
||||
InventoryDocument document,
|
||||
ConsoleFileLogger logger,
|
||||
int maxArtifactDetailsPerType)
|
||||
{
|
||||
return new BizTalkWmiCollector(
|
||||
document,
|
||||
logger,
|
||||
30,
|
||||
maxArtifactDetailsPerType,
|
||||
false);
|
||||
}
|
||||
|
||||
private static string RequiredText(object instance, string propertyName)
|
||||
{
|
||||
var value = Convert.ToString(
|
||||
@@ -607,9 +878,7 @@ namespace BizTalkSapEnvironmentInventory.Collectors
|
||||
|
||||
private string OptionalText(object instance, string propertyName)
|
||||
{
|
||||
var property = instance.GetType().GetProperty(
|
||||
propertyName,
|
||||
BindingFlags.Instance | BindingFlags.Public);
|
||||
var property = FindProperty(instance, propertyName);
|
||||
if (property == null)
|
||||
{
|
||||
return string.Empty;
|
||||
@@ -621,10 +890,7 @@ namespace BizTalkSapEnvironmentInventory.Collectors
|
||||
property.GetValue(instance, null),
|
||||
CultureInfo.InvariantCulture) ?? string.Empty;
|
||||
}
|
||||
catch (Exception exception) when (
|
||||
exception is TargetInvocationException
|
||||
|| exception is COMException
|
||||
|| exception is NotSupportedException)
|
||||
catch (Exception exception) when (IsOptionalExplorerException(exception))
|
||||
{
|
||||
logger.Detail("Optionale ExplorerOM-Eigenschaft "
|
||||
+ instance.GetType().FullName
|
||||
@@ -636,11 +902,23 @@ namespace BizTalkSapEnvironmentInventory.Collectors
|
||||
}
|
||||
}
|
||||
|
||||
private string FirstOptionalText(object instance, params string[] propertyNames)
|
||||
{
|
||||
foreach (var propertyName in propertyNames)
|
||||
{
|
||||
var value = OptionalText(instance, propertyName);
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private static object RequiredProperty(object instance, string propertyName)
|
||||
{
|
||||
var property = instance.GetType().GetProperty(
|
||||
propertyName,
|
||||
BindingFlags.Instance | BindingFlags.Public);
|
||||
var property = FindProperty(instance, propertyName);
|
||||
if (property == null)
|
||||
{
|
||||
throw new MissingMemberException(instance.GetType().FullName, propertyName);
|
||||
@@ -654,9 +932,7 @@ namespace BizTalkSapEnvironmentInventory.Collectors
|
||||
string propertyName,
|
||||
object value)
|
||||
{
|
||||
var property = instance.GetType().GetProperty(
|
||||
propertyName,
|
||||
BindingFlags.Instance | BindingFlags.Public);
|
||||
var property = FindProperty(instance, propertyName);
|
||||
if (property == null || !property.CanWrite)
|
||||
{
|
||||
throw new MissingMemberException(instance.GetType().FullName, propertyName);
|
||||
@@ -665,6 +941,23 @@ namespace BizTalkSapEnvironmentInventory.Collectors
|
||||
property.SetValue(instance, value, null);
|
||||
}
|
||||
|
||||
private static PropertyInfo FindProperty(object instance, string propertyName)
|
||||
{
|
||||
var flags = BindingFlags.Instance
|
||||
| BindingFlags.Public
|
||||
| BindingFlags.NonPublic;
|
||||
var properties = instance.GetType().GetProperties(flags);
|
||||
return properties.FirstOrDefault(item =>
|
||||
string.Equals(
|
||||
item.Name,
|
||||
propertyName,
|
||||
StringComparison.Ordinal))
|
||||
?? properties.FirstOrDefault(item =>
|
||||
item.Name.EndsWith(
|
||||
"." + propertyName,
|
||||
StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
private ApplicationRecord GetOrCreateApplication(string name)
|
||||
{
|
||||
var application = document.Applications.FirstOrDefault(item =>
|
||||
@@ -808,11 +1101,21 @@ namespace BizTalkSapEnvironmentInventory.Collectors
|
||||
RecommendedAction = "Nur relevant, wenn die zugehörige Artefaktspalte vollständig benötigt wird.",
|
||||
Owner = "BizTalk-Betrieb"
|
||||
});
|
||||
logger.Exception(
|
||||
logger.WarningException(
|
||||
"Optionale WMI-Abfrage " + className + " fehlgeschlagen",
|
||||
exception);
|
||||
}
|
||||
|
||||
private static bool IsOptionalExplorerException(Exception exception)
|
||||
{
|
||||
// Die Sammlungen sind Zusatzdaten. Auch versionsspezifische
|
||||
// ExplorerOM-/SQL-Ausnahmen sollen daher nur diese Sammlung auslassen.
|
||||
// Prozesskritische Laufzeitfehler werden weiterhin nicht abgefangen.
|
||||
return !(exception is OutOfMemoryException)
|
||||
&& !(exception is AccessViolationException)
|
||||
&& !(exception is AppDomainUnloadedException);
|
||||
}
|
||||
|
||||
private static bool IsRecoverableWmiException(Exception exception)
|
||||
{
|
||||
return exception is ManagementException
|
||||
|
||||
@@ -165,7 +165,9 @@ namespace BizTalkSapEnvironmentInventory.Collectors
|
||||
+ "die BizTalk-Erfassung wird fortgesetzt.",
|
||||
Owner = "Windows-/BizTalk-Betrieb"
|
||||
});
|
||||
logger.Exception("Windows-WMI-Abfrage fehlgeschlagen", exception);
|
||||
logger.WarningException(
|
||||
"Windows-WMI-Abfrage fehlgeschlagen",
|
||||
exception);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,7 +241,7 @@ namespace BizTalkSapEnvironmentInventory.Collectors
|
||||
+ "--management-Parameter können die Ermittlung übernehmen.",
|
||||
Owner = "Windows-/BizTalk-Betrieb"
|
||||
});
|
||||
logger.Exception(
|
||||
logger.WarningException(
|
||||
"BizTalk-Registryansicht " + view + " fehlgeschlagen",
|
||||
exception);
|
||||
}
|
||||
@@ -330,7 +332,9 @@ namespace BizTalkSapEnvironmentInventory.Collectors
|
||||
document,
|
||||
"MSBTS_GroupSetting konnte für die Management-DB-Erkennung "
|
||||
+ "nicht gelesen werden: " + exception.Message);
|
||||
logger.Exception("MSBTS_GroupSetting fehlgeschlagen", exception);
|
||||
logger.WarningException(
|
||||
"MSBTS_GroupSetting fehlgeschlagen",
|
||||
exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,14 +47,45 @@ namespace BizTalkSapEnvironmentInventory.Infrastructure
|
||||
/// WMI-Status und Stacktrace, damit Providerfehler vor Ort analysierbar bleiben.
|
||||
/// </summary>
|
||||
public void Exception(string context, Exception exception)
|
||||
{
|
||||
WriteException(context, exception, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Protokolliert eine vollständige Ausnahmekette als Warnung, wenn ein
|
||||
/// optionaler oder durch einen Fallback abgedeckter Zugriff fehlschlägt.
|
||||
/// </summary>
|
||||
public void WarningException(string context, Exception exception)
|
||||
{
|
||||
WriteException(context, exception, true);
|
||||
}
|
||||
|
||||
private void WriteException(
|
||||
string context,
|
||||
Exception exception,
|
||||
bool warning)
|
||||
{
|
||||
if (exception == null)
|
||||
{
|
||||
if (warning)
|
||||
{
|
||||
Warning(context + ": Unbekannter Fehler ohne Ausnahmeobjekt.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Error(context + ": Unbekannter Fehler ohne Ausnahmeobjekt.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (warning)
|
||||
{
|
||||
Warning(context + ": " + exception.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Error(context + ": " + exception.Message);
|
||||
}
|
||||
var current = exception;
|
||||
var depth = 0;
|
||||
while (current != null)
|
||||
|
||||
@@ -63,8 +63,15 @@ namespace BizTalkSapEnvironmentInventory.Infrastructure
|
||||
+ "und lokale BizTalk-Konfiguration prüfen.",
|
||||
Owner = "BizTalk-Betrieb"
|
||||
});
|
||||
if (required)
|
||||
{
|
||||
logger.Exception(name, exception);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.WarningException(name, exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
@@ -24,6 +25,7 @@ namespace BizTalkSapEnvironmentInventory.Infrastructure
|
||||
{
|
||||
TestSanitizer();
|
||||
TestBindingParser();
|
||||
TestExplorerArtifactReader();
|
||||
TestLoggerDiagnostics();
|
||||
TestDocxPackage();
|
||||
}
|
||||
@@ -238,6 +240,53 @@ namespace BizTalkSapEnvironmentInventory.Infrastructure
|
||||
}
|
||||
}
|
||||
|
||||
private static void TestExplorerArtifactReader()
|
||||
{
|
||||
var directory = CreateTemporaryDirectory();
|
||||
var logPath = Path.Combine(directory, "explorer-artifacts.log");
|
||||
try
|
||||
{
|
||||
var document = new InventoryDocument();
|
||||
var application = new ApplicationRecord
|
||||
{
|
||||
Name = "ExplorerOM-Test",
|
||||
Status = "Gestartet"
|
||||
};
|
||||
using (var logger = new ConsoleFileLogger(logPath))
|
||||
{
|
||||
var collector = BizTalkWmiCollector.CreateForExplorerArtifactTest(
|
||||
document,
|
||||
logger,
|
||||
5000);
|
||||
collector.CollectExplorerArtifactsForTest(
|
||||
new FakeApplication(new FakeAssembly(
|
||||
"Test.Assembly, Version=1.0.0.0",
|
||||
new FakeArtifact("Test.Schema"),
|
||||
new FakeArtifact("Test.Map"),
|
||||
new FakeArtifact("Test.Pipeline"))),
|
||||
application);
|
||||
}
|
||||
|
||||
Assert(application.Count("Assembly") == 1,
|
||||
"ExplorerOM-Assembly wurde nicht gelesen.");
|
||||
Assert(application.Count("Schema") == 1,
|
||||
"ExplorerOM-Schema wurde nicht gelesen.");
|
||||
Assert(application.Count("Map") == 1,
|
||||
"ExplorerOM-Map wurde nicht gelesen.");
|
||||
Assert(application.Count("Pipeline") == 1,
|
||||
"ExplorerOM-Pipeline wurde nicht gelesen.");
|
||||
Assert(
|
||||
application.Artifacts.All(item =>
|
||||
item.Properties.Any(property =>
|
||||
property.Value == "BizTalk Explorer Object Model")),
|
||||
"ExplorerOM-Quelle fehlt an einem Artefakt.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
DeleteDirectory(directory);
|
||||
}
|
||||
}
|
||||
|
||||
private static void TestLoggerDiagnostics()
|
||||
{
|
||||
var directory = CreateTemporaryDirectory();
|
||||
@@ -254,7 +303,7 @@ namespace BizTalkSapEnvironmentInventory.Infrastructure
|
||||
// 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);
|
||||
logger.WarningException("Self-Test-Diagnose", exception);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -272,6 +321,10 @@ namespace BizTalkSapEnvironmentInventory.Infrastructure
|
||||
Assert(
|
||||
log.Contains("HRESULT=0x"),
|
||||
"Logger dokumentierte den HRESULT nicht.");
|
||||
Assert(
|
||||
log.Contains("[WARNUNG] Self-Test-Diagnose")
|
||||
&& !log.Contains("[FEHLER] Self-Test-Diagnose"),
|
||||
"Optionale Ausnahme wurde nicht als Warnung protokolliert.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -381,5 +434,79 @@ namespace BizTalkSapEnvironmentInventory.Infrastructure
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
}
|
||||
|
||||
private interface IFakeApplication
|
||||
{
|
||||
IEnumerable Assemblies { get; }
|
||||
}
|
||||
|
||||
private interface IFakeAssembly
|
||||
{
|
||||
IEnumerable Schemas { get; }
|
||||
IEnumerable Transforms { get; }
|
||||
IEnumerable Pipelines { get; }
|
||||
}
|
||||
|
||||
private sealed class FakeApplication : IFakeApplication
|
||||
{
|
||||
private readonly IEnumerable assemblies;
|
||||
|
||||
public FakeApplication(params object[] assemblies)
|
||||
{
|
||||
this.assemblies = assemblies;
|
||||
}
|
||||
|
||||
// ExplorerOM stellt einige Sammlungen explizit über Interfaces bereit.
|
||||
IEnumerable IFakeApplication.Assemblies
|
||||
{
|
||||
get { return assemblies; }
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class FakeAssembly : IFakeAssembly
|
||||
{
|
||||
private readonly IEnumerable schemas;
|
||||
private readonly IEnumerable transforms;
|
||||
private readonly IEnumerable pipelines;
|
||||
|
||||
public FakeAssembly(
|
||||
string displayName,
|
||||
object schema,
|
||||
object transform,
|
||||
object pipeline)
|
||||
{
|
||||
DisplayName = displayName;
|
||||
schemas = new[] { schema };
|
||||
transforms = new[] { transform };
|
||||
pipelines = new[] { pipeline };
|
||||
}
|
||||
|
||||
public string DisplayName { get; private set; }
|
||||
|
||||
IEnumerable IFakeAssembly.Schemas
|
||||
{
|
||||
get { return schemas; }
|
||||
}
|
||||
|
||||
IEnumerable IFakeAssembly.Transforms
|
||||
{
|
||||
get { return transforms; }
|
||||
}
|
||||
|
||||
IEnumerable IFakeAssembly.Pipelines
|
||||
{
|
||||
get { return pipelines; }
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class FakeArtifact
|
||||
{
|
||||
public FakeArtifact(string fullName)
|
||||
{
|
||||
FullName = fullName;
|
||||
}
|
||||
|
||||
public string FullName { get; private set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,8 +83,9 @@ namespace BizTalkSapEnvironmentInventory
|
||||
logger.Info("Server: " + Environment.MachineName);
|
||||
logger.Info("Ausgabeordner: " + options.OutputDirectory);
|
||||
logger.Info(
|
||||
"Modus: read-only; ExplorerOM für Anwendungen, WMI für "
|
||||
+ "Gruppenermittlung und optionale Artefakte.");
|
||||
"Modus: read-only; ExplorerOM für Anwendungen und "
|
||||
+ "bereitgestellte Assembly-Artefakte; WMI für "
|
||||
+ "Gruppenermittlung und Laufzeitobjekte.");
|
||||
logger.Detail("Logdatei: " + logPath);
|
||||
logger.Detail("DOCX-Zieldatei: " + reportPath);
|
||||
|
||||
@@ -105,7 +106,7 @@ namespace BizTalkSapEnvironmentInventory
|
||||
logger,
|
||||
wmiTimeoutSeconds).Collect(document));
|
||||
safeCollector.Execute(
|
||||
"BizTalk-Anwendungen und optionale WMI-Artefakte",
|
||||
"BizTalk-Anwendungen und -Artefakte",
|
||||
true,
|
||||
() => new BizTalkWmiCollector(
|
||||
document,
|
||||
|
||||
@@ -168,9 +168,10 @@ namespace BizTalkSapEnvironmentInventory.Reporting
|
||||
WriteParagraph(
|
||||
writer,
|
||||
"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.",
|
||||
+ "Object Model. Assemblies, Schemas, Maps und Pipelines werden ebenfalls "
|
||||
+ "über ExplorerOM gelesen. Laufzeitobjekte wie Ports und Orchestrierungen "
|
||||
+ "stammen aus den lokal verfügbaren BizTalk-WMI-Klassen. Fehler optionaler "
|
||||
+ "Zusatzdaten verwerfen die Anwendungsliste nicht.",
|
||||
"Normal",
|
||||
false,
|
||||
null);
|
||||
|
||||
Reference in New Issue
Block a user