Fix BizTalk catalog collection and diagnostics
Build und Test / build (push) Has been cancelled
Build und Test / build (push) Has been cancelled
This commit is contained in:
@@ -1,24 +1,33 @@
|
||||
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.Security.Principal;
|
||||
using BizTalkApplicationCatalog.Infrastructure;
|
||||
using BizTalkApplicationCatalog.Models;
|
||||
|
||||
namespace BizTalkApplicationCatalog.Collectors
|
||||
{
|
||||
/// <summary>
|
||||
/// Liest nur die Daten, die für die Phase-1-Ansicht erforderlich sind.
|
||||
/// Ermittelt die lokale BizTalk-Gruppe lesend über WMI und liest den
|
||||
/// Anwendungskatalog anschließend über das BizTalk Explorer Object Model.
|
||||
/// </summary>
|
||||
internal sealed class BizTalkWmiCollector
|
||||
{
|
||||
private const string WmiNamespace = @"\\.\root\MicrosoftBizTalkServer";
|
||||
private const string ExplorerAssemblyName = "Microsoft.BizTalk.ExplorerOM";
|
||||
private const string ExplorerTypeName =
|
||||
"Microsoft.BizTalk.ExplorerOM.BtsCatalogExplorer";
|
||||
|
||||
private readonly InventoryDocument document;
|
||||
private readonly ConsoleFileLogger logger;
|
||||
private readonly ManagementScope scope;
|
||||
private readonly TimeSpan timeout;
|
||||
private readonly Dictionary<string, string> receivePortApplications =
|
||||
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public BizTalkWmiCollector(
|
||||
InventoryDocument document,
|
||||
@@ -28,18 +37,18 @@ namespace BizTalkApplicationCatalog.Collectors
|
||||
this.document = document;
|
||||
this.logger = logger;
|
||||
timeout = TimeSpan.FromSeconds(Math.Max(5, timeoutSeconds));
|
||||
scope = new ManagementScope(
|
||||
@"\\" + Environment.MachineName + @"\root\MicrosoftBizTalkServer");
|
||||
scope = new ManagementScope(WmiNamespace);
|
||||
scope.Options.Timeout = timeout;
|
||||
scope.Options.EnablePrivileges = false;
|
||||
scope.Options.Impersonation = ImpersonationLevel.Impersonate;
|
||||
}
|
||||
|
||||
public void Collect()
|
||||
{
|
||||
scope.Connect();
|
||||
CollectApplications();
|
||||
CollectReceivePortMappings();
|
||||
CollectSendPortEndpoints();
|
||||
CollectReceiveLocationEndpoints();
|
||||
LogRuntimeInformation();
|
||||
ConnectWmi();
|
||||
var group = ReadBizTalkGroup();
|
||||
CollectWithExplorerObjectModel(group);
|
||||
|
||||
document.Applications.Sort((left, right) =>
|
||||
string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase));
|
||||
@@ -58,108 +67,345 @@ namespace BizTalkApplicationCatalog.Collectors
|
||||
});
|
||||
}
|
||||
|
||||
private void CollectApplications()
|
||||
private void LogRuntimeInformation()
|
||||
{
|
||||
foreach (var row in Query("SELECT * FROM MSBTS_Application"))
|
||||
logger.Info("Diagnose: Computer=" + Environment.MachineName
|
||||
+ "; Betriebssystem=" + Environment.OSVersion.VersionString
|
||||
+ "; CLR=" + Environment.Version
|
||||
+ "; Prozess=" + (Environment.Is64BitProcess ? "64-Bit" : "32-Bit")
|
||||
+ "; Betriebssystem="
|
||||
+ (Environment.Is64BitOperatingSystem ? "64-Bit" : "32-Bit") + ".");
|
||||
logger.Detail("WMI-Namespace: " + WmiNamespace);
|
||||
logger.Detail("WMI-Timeout: "
|
||||
+ timeout.TotalSeconds.ToString(CultureInfo.InvariantCulture)
|
||||
+ " Sekunden.");
|
||||
|
||||
try
|
||||
{
|
||||
using (row)
|
||||
using (var identity = WindowsIdentity.GetCurrent())
|
||||
{
|
||||
var name = First(row, "Name", "ApplicationName");
|
||||
if (!string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
document.Applications.Add(new ApplicationRecord { Name = name.Trim() });
|
||||
}
|
||||
var principal = new WindowsPrincipal(identity);
|
||||
logger.Info("Sicherheitskontext: Benutzer=" + identity.Name
|
||||
+ "; administrativ="
|
||||
+ (principal.IsInRole(WindowsBuiltInRole.Administrator)
|
||||
? "Ja"
|
||||
: "Nein")
|
||||
+ ".");
|
||||
}
|
||||
}
|
||||
|
||||
if (document.Applications.Count == 0)
|
||||
catch (Exception exception)
|
||||
{
|
||||
// Ein Diagnosefehler darf die eigentliche Inventarisierung nicht verhindern.
|
||||
logger.Warning("Sicherheitskontext konnte nicht ermittelt werden: "
|
||||
+ exception.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void ConnectWmi()
|
||||
{
|
||||
var timer = Stopwatch.StartNew();
|
||||
logger.Info("Verbinde lokal mit dem BizTalk-WMI-Namespace.");
|
||||
scope.Connect();
|
||||
timer.Stop();
|
||||
logger.Info("BizTalk-WMI-Verbindung hergestellt ("
|
||||
+ timer.ElapsedMilliseconds + " ms).");
|
||||
}
|
||||
|
||||
private BizTalkGroupLocation ReadBizTalkGroup()
|
||||
{
|
||||
const string query =
|
||||
"SELECT Name, MgmtDbServerName, MgmtDbName FROM MSBTS_GroupSetting";
|
||||
var rows = Query("BizTalk-Gruppenkonfiguration", query);
|
||||
if (rows.Count != 1)
|
||||
{
|
||||
DisposeRows(rows);
|
||||
throw new InvalidOperationException(
|
||||
"MSBTS_Application lieferte keine Anwendungen.");
|
||||
"MSBTS_GroupSetting lieferte "
|
||||
+ rows.Count
|
||||
+ " Datensätze; exakt ein Datensatz wurde erwartet.");
|
||||
}
|
||||
|
||||
logger.Info(document.Applications.Count
|
||||
+ " BizTalk-Anwendung(en) gefunden.");
|
||||
using (var row = rows[0])
|
||||
{
|
||||
var groupName = RequiredWmiText(row, "Name");
|
||||
var serverName = RequiredWmiText(row, "MgmtDbServerName");
|
||||
var databaseName = RequiredWmiText(row, "MgmtDbName");
|
||||
logger.Info("BizTalk-Gruppe gefunden: " + groupName + ".");
|
||||
logger.Info("Management-Datenbankziel: Server="
|
||||
+ serverName + "; Datenbank=" + databaseName + ".");
|
||||
return new BizTalkGroupLocation(
|
||||
groupName,
|
||||
serverName,
|
||||
databaseName);
|
||||
}
|
||||
}
|
||||
|
||||
private void CollectReceivePortMappings()
|
||||
private void CollectWithExplorerObjectModel(BizTalkGroupLocation group)
|
||||
{
|
||||
var count = 0;
|
||||
foreach (var row in Query("SELECT * FROM MSBTS_ReceivePort"))
|
||||
var explorerType = ResolveExplorerType();
|
||||
object explorer = null;
|
||||
try
|
||||
{
|
||||
using (row)
|
||||
explorer = Activator.CreateInstance(explorerType);
|
||||
logger.Info("BizTalk Explorer Object Model wird ausschließlich lesend geöffnet.");
|
||||
|
||||
// Die Verbindungszeichenfolge enthält nur Zielserver, Datenbankname
|
||||
// und integrierte Windows-Authentifizierung. Sie wird nicht protokolliert.
|
||||
SetRequiredProperty(
|
||||
explorer,
|
||||
"ConnectionString",
|
||||
"Data Source=" + group.DatabaseServer
|
||||
+ ";Initial Catalog=" + group.DatabaseName
|
||||
+ ";Integrated Security=SSPI;Persist Security Info=False;");
|
||||
|
||||
var timer = Stopwatch.StartNew();
|
||||
var applications = EnumerateRequiredProperty(
|
||||
explorer,
|
||||
"Applications",
|
||||
"BizTalk-Anwendungen");
|
||||
timer.Stop();
|
||||
logger.Info(applications.Count + " BizTalk-Anwendung(en) geladen ("
|
||||
+ timer.ElapsedMilliseconds + " ms).");
|
||||
|
||||
if (applications.Count == 0)
|
||||
{
|
||||
var name = First(row, "Name", "ReceivePortName");
|
||||
var application = First(row, "ApplicationName", "Application");
|
||||
if (string.IsNullOrWhiteSpace(name)) continue;
|
||||
receivePortApplications[name] = application;
|
||||
count++;
|
||||
throw new InvalidOperationException(
|
||||
"Das BizTalk Explorer Object Model lieferte keine Anwendungen.");
|
||||
}
|
||||
|
||||
CollectApplicationsAndEndpoints(applications);
|
||||
}
|
||||
finally
|
||||
{
|
||||
var disposable = explorer as IDisposable;
|
||||
if (disposable != null)
|
||||
{
|
||||
disposable.Dispose();
|
||||
logger.Detail("BizTalk Explorer Object Model wurde geschlossen.");
|
||||
}
|
||||
}
|
||||
logger.Info(count + " Receive-Port-Zuordnung(en) gelesen.");
|
||||
}
|
||||
|
||||
private void CollectSendPortEndpoints()
|
||||
private void CollectApplicationsAndEndpoints(IList<object> applications)
|
||||
{
|
||||
var count = 0;
|
||||
foreach (var row in Query("SELECT * FROM MSBTS_SendPort"))
|
||||
{
|
||||
using (row)
|
||||
{
|
||||
var application = First(row, "ApplicationName", "Application");
|
||||
AddEndpoint(application, First(
|
||||
row,
|
||||
"PTTransportType",
|
||||
"PrimaryTransportType",
|
||||
"AdapterName",
|
||||
"TransportType"));
|
||||
count++;
|
||||
var sendPortCount = 0;
|
||||
var secondaryTransportCount = 0;
|
||||
var receivePortCount = 0;
|
||||
var receiveLocationCount = 0;
|
||||
|
||||
var secondaryAdapter = First(
|
||||
row,
|
||||
"STTransportType",
|
||||
"SecondaryTransportType");
|
||||
if (!string.IsNullOrWhiteSpace(secondaryAdapter))
|
||||
foreach (var application in applications)
|
||||
{
|
||||
var applicationName = RequiredText(application, "Name");
|
||||
document.Applications.Add(new ApplicationRecord
|
||||
{
|
||||
Name = applicationName
|
||||
});
|
||||
logger.Detail("Lese Anwendung: " + applicationName + ".");
|
||||
|
||||
var sendPorts = EnumerateRequiredProperty(
|
||||
application,
|
||||
"SendPorts",
|
||||
"Send Ports der Anwendung " + applicationName);
|
||||
foreach (var sendPort in sendPorts)
|
||||
{
|
||||
sendPortCount++;
|
||||
var sendPortName = RequiredText(sendPort, "Name");
|
||||
var primaryTransport = OptionalProperty(sendPort, "PrimaryTransport");
|
||||
var primaryAdapter = AdapterName(primaryTransport);
|
||||
AddEndpoint(applicationName, primaryAdapter);
|
||||
logger.Detail("Send Port: Anwendung=" + applicationName
|
||||
+ "; Name=" + sendPortName
|
||||
+ "; Primäradapter=" + DisplayAdapter(primaryAdapter) + ".");
|
||||
|
||||
var secondaryTransport = OptionalProperty(sendPort, "SecondaryTransport");
|
||||
if (secondaryTransport != null)
|
||||
{
|
||||
AddEndpoint(application, secondaryAdapter);
|
||||
count++;
|
||||
var secondaryAdapter = AdapterName(secondaryTransport);
|
||||
// ExplorerOM kann ein leeres TransportInfo-Objekt liefern,
|
||||
// obwohl kein Backup-Transport konfiguriert ist.
|
||||
if (!string.IsNullOrWhiteSpace(secondaryAdapter))
|
||||
{
|
||||
AddEndpoint(applicationName, secondaryAdapter);
|
||||
secondaryTransportCount++;
|
||||
logger.Detail("Send Port: Anwendung=" + applicationName
|
||||
+ "; Name=" + sendPortName
|
||||
+ "; Sekundäradapter=" + secondaryAdapter + ".");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var receivePorts = EnumerateRequiredProperty(
|
||||
application,
|
||||
"ReceivePorts",
|
||||
"Receive Ports der Anwendung " + applicationName);
|
||||
foreach (var receivePort in receivePorts)
|
||||
{
|
||||
receivePortCount++;
|
||||
var receivePortName = RequiredText(receivePort, "Name");
|
||||
var receiveLocations = EnumerateRequiredProperty(
|
||||
receivePort,
|
||||
"ReceiveLocations",
|
||||
"Receive Locations des Receive Ports " + receivePortName);
|
||||
foreach (var receiveLocation in receiveLocations)
|
||||
{
|
||||
receiveLocationCount++;
|
||||
var receiveLocationName = RequiredText(receiveLocation, "Name");
|
||||
var adapter = AdapterName(
|
||||
OptionalProperty(receiveLocation, "TransportType"));
|
||||
AddEndpoint(applicationName, adapter);
|
||||
logger.Detail("Receive Location: Anwendung=" + applicationName
|
||||
+ "; Receive Port=" + receivePortName
|
||||
+ "; Name=" + receiveLocationName
|
||||
+ "; Adapter=" + DisplayAdapter(adapter) + ".");
|
||||
}
|
||||
}
|
||||
|
||||
logger.Info("Anwendung abgeschlossen: " + applicationName
|
||||
+ "; Send Ports=" + sendPorts.Count
|
||||
+ "; Receive Ports=" + receivePorts.Count + ".");
|
||||
}
|
||||
|
||||
logger.Info("Katalogerfassung abgeschlossen: Anwendungen="
|
||||
+ document.Applications.Count
|
||||
+ "; Send Ports=" + sendPortCount
|
||||
+ "; sekundäre Send-Transporte=" + secondaryTransportCount
|
||||
+ "; Receive Ports=" + receivePortCount
|
||||
+ "; Receive Locations=" + receiveLocationCount
|
||||
+ "; Endpunkte=" + document.Endpoints.Count + ".");
|
||||
}
|
||||
|
||||
private Type ResolveExplorerType()
|
||||
{
|
||||
logger.Info("Suche die lokale Assembly " + ExplorerAssemblyName + ".dll.");
|
||||
var qualifiedTypeName = ExplorerTypeName + ", " + ExplorerAssemblyName;
|
||||
var explorerType = Type.GetType(qualifiedTypeName, false);
|
||||
if (explorerType != null)
|
||||
{
|
||||
LogExplorerAssembly(explorerType.Assembly);
|
||||
return explorerType;
|
||||
}
|
||||
|
||||
Assembly assembly = null;
|
||||
try
|
||||
{
|
||||
assembly = Assembly.Load(ExplorerAssemblyName);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
logger.Detail("Assembly.Load fehlgeschlagen: "
|
||||
+ exception.GetType().Name + ": " + exception.Message);
|
||||
}
|
||||
|
||||
if (assembly == null)
|
||||
{
|
||||
#pragma warning disable 618
|
||||
// LoadWithPartialName ist hier bewusst nur ein Kompatibilitäts-Fallback,
|
||||
// damit unterschiedliche BizTalk-GAC-Versionen gefunden werden.
|
||||
assembly = Assembly.LoadWithPartialName(ExplorerAssemblyName);
|
||||
#pragma warning restore 618
|
||||
}
|
||||
|
||||
if (assembly == null)
|
||||
{
|
||||
assembly = LoadExplorerAssemblyFromInstallDirectory();
|
||||
}
|
||||
|
||||
if (assembly == null)
|
||||
{
|
||||
throw new FileNotFoundException(
|
||||
"Microsoft.BizTalk.ExplorerOM.dll wurde weder im GAC noch "
|
||||
+ "im BizTalk-Installationsverzeichnis gefunden. "
|
||||
+ "Die BizTalk-Verwaltungskomponenten müssen lokal installiert sein.");
|
||||
}
|
||||
|
||||
explorerType = assembly.GetType(ExplorerTypeName, false);
|
||||
if (explorerType == null)
|
||||
{
|
||||
throw new TypeLoadException(
|
||||
"Typ " + ExplorerTypeName + " fehlt in " + assembly.FullName + ".");
|
||||
}
|
||||
|
||||
LogExplorerAssembly(assembly);
|
||||
return explorerType;
|
||||
}
|
||||
|
||||
private Assembly LoadExplorerAssemblyFromInstallDirectory()
|
||||
{
|
||||
var roots = new[]
|
||||
{
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86),
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)
|
||||
};
|
||||
var folders = new[]
|
||||
{
|
||||
"Microsoft BizTalk Server",
|
||||
"Microsoft BizTalk Server 2020"
|
||||
};
|
||||
|
||||
foreach (var root in roots.Where(item => !string.IsNullOrWhiteSpace(item)))
|
||||
{
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
var path = Path.Combine(
|
||||
root,
|
||||
folder,
|
||||
ExplorerAssemblyName + ".dll");
|
||||
logger.Detail("Prüfe Assembly-Pfad: " + path);
|
||||
if (!File.Exists(path)) continue;
|
||||
try
|
||||
{
|
||||
return Assembly.LoadFrom(path);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
logger.Detail("Assembly.LoadFrom fehlgeschlagen: "
|
||||
+ exception.GetType().Name + ": " + exception.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.Info(count + " Send-Endpunkt(e) gefunden.");
|
||||
return null;
|
||||
}
|
||||
|
||||
private void CollectReceiveLocationEndpoints()
|
||||
private void LogExplorerAssembly(Assembly assembly)
|
||||
{
|
||||
var count = 0;
|
||||
foreach (var row in Query("SELECT * FROM MSBTS_ReceiveLocation"))
|
||||
logger.Info("ExplorerOM-Assembly geladen: " + assembly.FullName + ".");
|
||||
try
|
||||
{
|
||||
using (row)
|
||||
{
|
||||
var application = First(row, "ApplicationName", "Application");
|
||||
if (string.IsNullOrWhiteSpace(application))
|
||||
{
|
||||
var receivePort = First(row, "ReceivePortName");
|
||||
receivePortApplications.TryGetValue(receivePort, out application);
|
||||
}
|
||||
|
||||
AddEndpoint(
|
||||
application,
|
||||
First(row, "AdapterName", "TransportType", "PTTransportType"));
|
||||
count++;
|
||||
}
|
||||
logger.Detail("ExplorerOM-Assemblypfad: " + assembly.Location);
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
logger.Detail("ExplorerOM-Assemblypfad ist für diese Assembly nicht verfügbar.");
|
||||
}
|
||||
}
|
||||
|
||||
private List<ManagementObject> Query(string name, string query)
|
||||
{
|
||||
var options = new System.Management.EnumerationOptions
|
||||
{
|
||||
ReturnImmediately = false,
|
||||
Rewindable = false,
|
||||
Timeout = timeout
|
||||
};
|
||||
var timer = Stopwatch.StartNew();
|
||||
logger.Info("WMI-Abfrage startet: " + name + ".");
|
||||
logger.Detail("WQL: " + query);
|
||||
using (var searcher = new ManagementObjectSearcher(
|
||||
scope,
|
||||
new ObjectQuery(query),
|
||||
options))
|
||||
{
|
||||
var rows = searcher.Get().Cast<ManagementObject>().ToList();
|
||||
timer.Stop();
|
||||
logger.Info("WMI-Abfrage abgeschlossen: " + name
|
||||
+ "; Datensätze=" + rows.Count
|
||||
+ "; Dauer=" + timer.ElapsedMilliseconds + " ms.");
|
||||
return rows;
|
||||
}
|
||||
logger.Info(count + " Receive-Endpunkt(e) gefunden.");
|
||||
}
|
||||
|
||||
private void AddEndpoint(string applicationName, string adapterType)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(applicationName))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Ein Endpunkt konnte keiner BizTalk-Anwendung zugeordnet werden.");
|
||||
}
|
||||
|
||||
document.Endpoints.Add(new EndpointRecord
|
||||
{
|
||||
ApplicationName = applicationName.Trim(),
|
||||
@@ -169,40 +415,135 @@ namespace BizTalkApplicationCatalog.Collectors
|
||||
});
|
||||
}
|
||||
|
||||
private List<ManagementObject> Query(string query)
|
||||
private static string AdapterName(object transportTypeOrInfo)
|
||||
{
|
||||
var options = new EnumerationOptions
|
||||
{
|
||||
ReturnImmediately = false,
|
||||
Rewindable = false,
|
||||
Timeout = timeout
|
||||
};
|
||||
using (var searcher = new ManagementObjectSearcher(
|
||||
scope,
|
||||
new ObjectQuery(query),
|
||||
options))
|
||||
{
|
||||
return searcher.Get().Cast<ManagementObject>().ToList();
|
||||
}
|
||||
if (transportTypeOrInfo == null) return string.Empty;
|
||||
|
||||
// Send-Transporte liefern zuerst TransportInfo und darin TransportType.
|
||||
// Receive Locations liefern direkt den ProtocolType.
|
||||
var protocolType = OptionalProperty(
|
||||
transportTypeOrInfo,
|
||||
"TransportType") ?? transportTypeOrInfo;
|
||||
return Convert.ToString(
|
||||
OptionalProperty(protocolType, "Name"),
|
||||
CultureInfo.InvariantCulture) ?? string.Empty;
|
||||
}
|
||||
|
||||
private static string First(ManagementBaseObject row, params string[] names)
|
||||
private static string DisplayAdapter(string adapter)
|
||||
{
|
||||
foreach (var name in names)
|
||||
return string.IsNullOrWhiteSpace(adapter) ? "Unbekannt" : adapter;
|
||||
}
|
||||
|
||||
private static IList<object> EnumerateRequiredProperty(
|
||||
object instance,
|
||||
string propertyName,
|
||||
string description)
|
||||
{
|
||||
var value = RequiredProperty(instance, propertyName);
|
||||
var enumerable = value as IEnumerable;
|
||||
if (enumerable == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var value = row[name];
|
||||
if (value == null) continue;
|
||||
var text = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
if (!string.IsNullOrWhiteSpace(text)) return text;
|
||||
}
|
||||
catch (ManagementException)
|
||||
{
|
||||
// WMI-Properties unterscheiden sich zwischen Providerständen.
|
||||
}
|
||||
throw new InvalidOperationException(
|
||||
description + " ist keine aufzählbare Sammlung.");
|
||||
}
|
||||
return string.Empty;
|
||||
|
||||
var result = new List<object>();
|
||||
foreach (var item in enumerable)
|
||||
{
|
||||
if (item != null) result.Add(item);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string RequiredText(object instance, string propertyName)
|
||||
{
|
||||
var value = Convert.ToString(
|
||||
RequiredProperty(instance, propertyName),
|
||||
CultureInfo.InvariantCulture);
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
instance.GetType().FullName + "." + propertyName
|
||||
+ " ist leer.");
|
||||
}
|
||||
return value.Trim();
|
||||
}
|
||||
|
||||
private static string RequiredWmiText(
|
||||
ManagementBaseObject instance,
|
||||
string propertyName)
|
||||
{
|
||||
var value = Convert.ToString(
|
||||
instance[propertyName],
|
||||
CultureInfo.InvariantCulture);
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"WMI-Eigenschaft " + propertyName + " ist leer.");
|
||||
}
|
||||
return value.Trim();
|
||||
}
|
||||
|
||||
private static object RequiredProperty(object instance, string propertyName)
|
||||
{
|
||||
var property = instance.GetType().GetProperty(
|
||||
propertyName,
|
||||
BindingFlags.Instance | BindingFlags.Public);
|
||||
if (property == null)
|
||||
{
|
||||
throw new MissingMemberException(
|
||||
instance.GetType().FullName,
|
||||
propertyName);
|
||||
}
|
||||
return property.GetValue(instance, null);
|
||||
}
|
||||
|
||||
private static object OptionalProperty(object instance, string propertyName)
|
||||
{
|
||||
if (instance == null) return null;
|
||||
var property = instance.GetType().GetProperty(
|
||||
propertyName,
|
||||
BindingFlags.Instance | BindingFlags.Public);
|
||||
return property == null ? null : property.GetValue(instance, null);
|
||||
}
|
||||
|
||||
private static void SetRequiredProperty(
|
||||
object instance,
|
||||
string propertyName,
|
||||
object value)
|
||||
{
|
||||
var property = instance.GetType().GetProperty(
|
||||
propertyName,
|
||||
BindingFlags.Instance | BindingFlags.Public);
|
||||
if (property == null || !property.CanWrite)
|
||||
{
|
||||
throw new MissingMemberException(
|
||||
instance.GetType().FullName,
|
||||
propertyName);
|
||||
}
|
||||
property.SetValue(instance, value, null);
|
||||
}
|
||||
|
||||
private static void DisposeRows(IEnumerable<ManagementObject> rows)
|
||||
{
|
||||
foreach (var row in rows) row.Dispose();
|
||||
}
|
||||
|
||||
private sealed class BizTalkGroupLocation
|
||||
{
|
||||
public BizTalkGroupLocation(
|
||||
string groupName,
|
||||
string databaseServer,
|
||||
string databaseName)
|
||||
{
|
||||
GroupName = groupName;
|
||||
DatabaseServer = databaseServer;
|
||||
DatabaseName = databaseName;
|
||||
}
|
||||
|
||||
public string GroupName { get; private set; }
|
||||
public string DatabaseServer { get; private set; }
|
||||
public string DatabaseName { get; private set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Management;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace BizTalkApplicationCatalog.Infrastructure
|
||||
@@ -19,8 +21,57 @@ namespace BizTalkApplicationCatalog.Infrastructure
|
||||
}
|
||||
|
||||
public void Info(string message) { Write("INFO", message); }
|
||||
public void Detail(string message) { Write("DETAIL", message); }
|
||||
public void Warning(string message) { Write("WARNUNG", message); }
|
||||
public void Error(string message) { Write("FEHLER", message); }
|
||||
|
||||
/// <summary>
|
||||
/// Schreibt die vollständige Ausnahmekette einschließlich HRESULT,
|
||||
/// WMI-Status und Stacktrace auf Konsole und in die Logdatei.
|
||||
/// </summary>
|
||||
public void Exception(string context, Exception exception)
|
||||
{
|
||||
if (exception == null)
|
||||
{
|
||||
Error(context + ": Unbekannter Fehler ohne Ausnahmeobjekt.");
|
||||
return;
|
||||
}
|
||||
|
||||
Error(context + ": " + exception.Message);
|
||||
var current = exception;
|
||||
var depth = 0;
|
||||
while (current != null)
|
||||
{
|
||||
Detail(string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Ausnahme[{0}]: Typ={1}; HRESULT=0x{2:X8}; Meldung={3}",
|
||||
depth,
|
||||
current.GetType().FullName,
|
||||
current.HResult,
|
||||
current.Message));
|
||||
|
||||
var managementException = current as ManagementException;
|
||||
if (managementException != null)
|
||||
{
|
||||
Detail("WMI-Status: " + managementException.ErrorCode);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(current.StackTrace))
|
||||
{
|
||||
Detail("Stacktrace[" + depth + "]:" + Environment.NewLine
|
||||
+ current.StackTrace);
|
||||
}
|
||||
|
||||
// Reflection kapselt Providerfehler häufig in TargetInvocationException.
|
||||
// Die innere Ausnahme wird deshalb ausdrücklich mit ausgegeben.
|
||||
var targetInvocation = current as TargetInvocationException;
|
||||
current = targetInvocation != null && targetInvocation.InnerException != null
|
||||
? targetInvocation.InnerException
|
||||
: current.InnerException;
|
||||
depth++;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() { writer.Dispose(); }
|
||||
|
||||
private void Write(string level, string message)
|
||||
|
||||
@@ -52,9 +52,11 @@ namespace BizTalkApplicationCatalog.Infrastructure
|
||||
Severity = required ? "Fehler" : "Warnung",
|
||||
Area = name,
|
||||
Message = "Datenerfassung fehlgeschlagen: " + exception.Message,
|
||||
RecommendedAction = "Logdatei, WMI-Provider und Leseberechtigungen prüfen."
|
||||
RecommendedAction =
|
||||
"Logdatei, WMI-Provider, ExplorerOM-Installation "
|
||||
+ "und BizTalk-Leseberechtigungen prüfen."
|
||||
});
|
||||
logger.Error(name + ": " + exception.Message);
|
||||
logger.Exception(name, exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,16 @@ namespace BizTalkApplicationCatalog
|
||||
logger.Info("BEW BizTalk Application Catalog – Phase 1 startet.");
|
||||
logger.Info("Umgebung: " + options.EnvironmentName);
|
||||
logger.Info("Erfasst werden nur Anwendungen, Adaptertypen und Endpunkte.");
|
||||
logger.Info("Modus: ausschließlich lesender lokaler BizTalk-WMI-Zugriff.");
|
||||
logger.Info(
|
||||
"Modus: ausschließlich lesender Zugriff; lokales BizTalk-WMI "
|
||||
+ "für die Gruppenermittlung und ExplorerOM für den Katalog.");
|
||||
logger.Info("Ausgabeordner: " + options.OutputDirectory);
|
||||
logger.Detail("Logdatei: " + logPath);
|
||||
logger.Detail("JSON-Zieldatei: " + snapshotPath);
|
||||
logger.Detail("XLSX-Zieldatei: " + reportPath);
|
||||
var wmiTimeoutSeconds = ReadInt("WmiTimeoutSeconds", 30);
|
||||
logger.Detail("Konfiguration WmiTimeoutSeconds="
|
||||
+ wmiTimeoutSeconds + ".");
|
||||
|
||||
new SafeCollector(document, logger).Execute(
|
||||
"Phase-1-Anwendungen und Endpunkte",
|
||||
@@ -81,7 +90,7 @@ namespace BizTalkApplicationCatalog
|
||||
() => new BizTalkWmiCollector(
|
||||
document,
|
||||
logger,
|
||||
ReadInt("WmiTimeoutSeconds", 30)).Collect());
|
||||
wmiTimeoutSeconds).Collect());
|
||||
|
||||
document.CompletedUtc = DateTime.UtcNow;
|
||||
var snapshot = SnapshotStore.FromInventory(
|
||||
|
||||
@@ -76,7 +76,8 @@ namespace BizTalkApplicationCatalog.Reporting
|
||||
{
|
||||
snapshots.Count > 1
|
||||
? "Phase 1 – BizTalk Anwendungen & Adapter (Quelle: ACC/PROD JSON-Snapshots)"
|
||||
: "Phase 1 – BizTalk Anwendungen & Adapter (Quelle: lokaler BizTalk-WMI-Provider)",
|
||||
: "Phase 1 – BizTalk Anwendungen & Adapter "
|
||||
+ "(Quelle: lokale BizTalk-Verwaltungsschnittstellen)",
|
||||
"", "", "", "", "", ""
|
||||
},
|
||||
new object[]
|
||||
|
||||
Reference in New Issue
Block a user