Updated to .NET Framework 4.7.2 due to platform limitations, added email cc, bcc and some fixes.
This commit is contained in:
@@ -0,0 +1,517 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using BizTalkMonitorConfigGenerator.Configuration;
|
||||
using BizTalkMonitorConfigGenerator.Logging;
|
||||
using Microsoft.BizTalk.ExplorerOM;
|
||||
|
||||
namespace BizTalkMonitorConfigGenerator.BizTalk
|
||||
{
|
||||
public sealed class BizTalkCatalogDiscoveryService
|
||||
{
|
||||
private readonly BizTalkOptions _options;
|
||||
private readonly OutputOptions _outputOptions;
|
||||
private readonly GeneratorFileLogger _logger;
|
||||
|
||||
public BizTalkCatalogDiscoveryService(BizTalkOptions options, OutputOptions outputOptions, GeneratorFileLogger logger)
|
||||
{
|
||||
_options = options ?? new BizTalkOptions();
|
||||
_outputOptions = outputOptions ?? new OutputOptions();
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<DiscoveredEndpoint> Discover()
|
||||
{
|
||||
var discovered = new List<DiscoveredEndpoint>();
|
||||
|
||||
var catalog = new BtsCatalogExplorer();
|
||||
catalog.ConnectionString = _options.ConnectionString;
|
||||
|
||||
foreach (Application application in catalog.Applications)
|
||||
{
|
||||
if (application == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ShouldSkipApplication(application))
|
||||
{
|
||||
_logger.Info("BizTalk-Anwendung übersprungen: " + application.Name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_options.IncludeSendPorts)
|
||||
{
|
||||
DiscoverSendPorts(application, discovered);
|
||||
}
|
||||
|
||||
if (_options.IncludeReceiveLocations)
|
||||
{
|
||||
DiscoverReceiveLocations(application, discovered);
|
||||
}
|
||||
}
|
||||
|
||||
return discovered
|
||||
.OrderBy(x => x.MappedApplicationName, StringComparer.OrdinalIgnoreCase)
|
||||
.ThenBy(x => x.TargetName, StringComparer.OrdinalIgnoreCase)
|
||||
.ThenBy(x => x.NormalizedEndpoint, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private void DiscoverSendPorts(Application application, List<DiscoveredEndpoint> discovered)
|
||||
{
|
||||
foreach (SendPort sendPort in application.SendPorts)
|
||||
{
|
||||
if (sendPort == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_options.OnlyStartedSendPorts && sendPort.Status != PortStatus.Started)
|
||||
{
|
||||
_logger.Discovery("SKIP", "SendPort", application.Name, sendPort.Name, ResolveAdapterName(sendPort.PrimaryTransport), SafeAddress(sendPort.PrimaryTransport), string.Empty, "SendPort ist nicht gestartet. Status=" + sendPort.Status);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sendPort.IsDynamic && !_options.IncludeDynamicSendPorts)
|
||||
{
|
||||
_logger.Discovery("SKIP", "SendPort", application.Name, sendPort.Name, ResolveAdapterName(sendPort.PrimaryTransport), SafeAddress(sendPort.PrimaryTransport), string.Empty, "Dynamischer SendPort wird per Konfiguration übersprungen.");
|
||||
continue;
|
||||
}
|
||||
|
||||
AddTransportIfSupported(discovered, application.Name, "SendPort", sendPort.Name, sendPort.Name, sendPort.PrimaryTransport, false);
|
||||
|
||||
if (_options.IncludeSecondaryTransportOnSendPorts)
|
||||
{
|
||||
AddTransportIfSupported(discovered, application.Name, "SendPortSecondary", sendPort.Name + " (Secondary Transport)", sendPort.Name, sendPort.SecondaryTransport, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DiscoverReceiveLocations(Application application, List<DiscoveredEndpoint> discovered)
|
||||
{
|
||||
foreach (ReceivePort receivePort in application.ReceivePorts)
|
||||
{
|
||||
if (receivePort == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (ReceiveLocation receiveLocation in receivePort.ReceiveLocations)
|
||||
{
|
||||
if (receiveLocation == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_options.OnlyEnabledReceiveLocations && !receiveLocation.Enable)
|
||||
{
|
||||
_logger.Discovery("SKIP", "ReceiveLocation", application.Name, receiveLocation.Name, ResolveAdapterName(receiveLocation.TransportType), receiveLocation.Address, string.Empty, "Receive Location ist deaktiviert.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ShouldSkipArtifact(receiveLocation.Name))
|
||||
{
|
||||
_logger.Discovery("SKIP", "ReceiveLocation", application.Name, receiveLocation.Name, ResolveAdapterName(receiveLocation.TransportType), receiveLocation.Address, string.Empty, "Receive Location entspricht einem Ignore-Pattern.");
|
||||
continue;
|
||||
}
|
||||
|
||||
AddEndpointIfSupported(
|
||||
discovered,
|
||||
application.Name,
|
||||
"ReceiveLocation",
|
||||
receivePort.Name + " / " + receiveLocation.Name,
|
||||
receiveLocation.Name,
|
||||
ResolveAdapterName(receiveLocation.TransportType),
|
||||
receiveLocation.Address,
|
||||
false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddTransportIfSupported(List<DiscoveredEndpoint> discovered, string bizTalkApplicationName, string artifactType, string targetNameBase, string artifactName, TransportInfo transport, bool isSecondary)
|
||||
{
|
||||
if (transport == null)
|
||||
{
|
||||
_logger.Discovery("SKIP", artifactType, bizTalkApplicationName, artifactName, string.Empty, string.Empty, string.Empty, "Kein Transport konfiguriert.");
|
||||
return;
|
||||
}
|
||||
|
||||
AddEndpointIfSupported(
|
||||
discovered,
|
||||
bizTalkApplicationName,
|
||||
artifactType,
|
||||
targetNameBase + (isSecondary ? " (Secondary)" : " (Primary)"),
|
||||
artifactName,
|
||||
ResolveAdapterName(transport),
|
||||
SafeAddress(transport),
|
||||
isSecondary);
|
||||
}
|
||||
|
||||
private void AddEndpointIfSupported(List<DiscoveredEndpoint> discovered, string bizTalkApplicationName, string artifactType, string targetNameBase, string artifactName, string adapterName, string sourceAddress, bool isSecondary)
|
||||
{
|
||||
if (ShouldSkipArtifact(artifactName))
|
||||
{
|
||||
_logger.Discovery("SKIP", artifactType, bizTalkApplicationName, artifactName, adapterName, sourceAddress, string.Empty, "Artefakt entspricht einem Ignore-Pattern.");
|
||||
return;
|
||||
}
|
||||
|
||||
string normalizedEndpoint;
|
||||
int? port;
|
||||
string httpMethod;
|
||||
string reason;
|
||||
if (!TryNormalizeEndpoint(sourceAddress, adapterName, out normalizedEndpoint, out port, out httpMethod, out reason))
|
||||
{
|
||||
_logger.Discovery("SKIP", artifactType, bizTalkApplicationName, artifactName, adapterName, sourceAddress, string.Empty, reason);
|
||||
return;
|
||||
}
|
||||
|
||||
var mappedApplication = ResolveMappedApplicationName(bizTalkApplicationName, artifactType, artifactName);
|
||||
var targetName = BuildTargetName(artifactType, targetNameBase, adapterName, isSecondary);
|
||||
var endpoint = new DiscoveredEndpoint
|
||||
{
|
||||
ArtifactType = artifactType,
|
||||
BizTalkApplicationName = bizTalkApplicationName,
|
||||
MappedApplicationName = mappedApplication,
|
||||
ArtifactName = artifactName,
|
||||
AdapterName = adapterName,
|
||||
SourceAddress = sourceAddress,
|
||||
NormalizedEndpoint = normalizedEndpoint,
|
||||
TargetName = targetName,
|
||||
HttpMethod = httpMethod,
|
||||
Port = port,
|
||||
ValidateUncPathAccess = null,
|
||||
Detail = "Endpoint erfolgreich aus BizTalk übernommen."
|
||||
};
|
||||
|
||||
discovered.Add(endpoint);
|
||||
_logger.Discovery("ADD", artifactType, bizTalkApplicationName, artifactName, adapterName, sourceAddress, normalizedEndpoint, "Zuordnung zu ApplicationName='" + mappedApplication + "'.");
|
||||
}
|
||||
|
||||
private bool ShouldSkipApplication(Application application)
|
||||
{
|
||||
if (application == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_options.IgnoreSystemApplications && application.IsSystem)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return _options.IgnoreApplications.Any(pattern => WildcardMatch(application.Name, pattern));
|
||||
}
|
||||
|
||||
private bool ShouldSkipArtifact(string artifactName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(artifactName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _options.IgnoreArtifactNamePatterns.Any(pattern => WildcardMatch(artifactName, pattern));
|
||||
}
|
||||
|
||||
private string ResolveMappedApplicationName(string bizTalkApplicationName, string artifactType, string artifactName)
|
||||
{
|
||||
foreach (var rule in _options.ArtifactMappings.Where(r => r != null && !string.IsNullOrWhiteSpace(r.ApplicationName)))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(rule.ArtifactType) && !string.Equals(rule.ArtifactType.Trim(), artifactType, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (WildcardMatch(artifactName, rule.ArtifactNamePattern))
|
||||
{
|
||||
return rule.ApplicationName.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var rule in _options.ApplicationMappings.Where(r => r != null && !string.IsNullOrWhiteSpace(r.ApplicationName)))
|
||||
{
|
||||
if (WildcardMatch(bizTalkApplicationName, rule.BizTalkApplicationName))
|
||||
{
|
||||
return rule.ApplicationName.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
return string.IsNullOrWhiteSpace(bizTalkApplicationName) ? "Unassigned Application" : bizTalkApplicationName.Trim();
|
||||
}
|
||||
|
||||
private string BuildTargetName(string artifactType, string baseName, string adapterName, bool isSecondary)
|
||||
{
|
||||
var prefix = string.Equals(artifactType, "ReceiveLocation", StringComparison.OrdinalIgnoreCase)
|
||||
? "ReceiveLocation"
|
||||
: "SendPort";
|
||||
|
||||
var name = prefix + ": " + baseName;
|
||||
if (!string.IsNullOrWhiteSpace(adapterName))
|
||||
{
|
||||
name += " [" + adapterName + "]";
|
||||
}
|
||||
|
||||
if (isSecondary && name.IndexOf("Secondary", StringComparison.OrdinalIgnoreCase) < 0)
|
||||
{
|
||||
name += " [Secondary]";
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
private bool TryNormalizeEndpoint(string sourceAddress, string adapterName, out string normalizedEndpoint, out int? port, out string httpMethod, out string reason)
|
||||
{
|
||||
normalizedEndpoint = string.Empty;
|
||||
port = null;
|
||||
httpMethod = null;
|
||||
reason = string.Empty;
|
||||
|
||||
var address = (sourceAddress ?? string.Empty).Trim();
|
||||
var adapter = (adapterName ?? string.Empty).Trim();
|
||||
if (string.IsNullOrWhiteSpace(address))
|
||||
{
|
||||
reason = "Address/URI ist leer.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (AdapterContains(adapter, "SMTP"))
|
||||
{
|
||||
reason = "SMTP-Adapter wird uebersprungen, weil dessen Address-Wert keine pruefbare Netzwerkadresse, sondern eine Empfaengerliste ist.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (LooksLikeEmailAddressList(address))
|
||||
{
|
||||
reason = "Adresse sieht wie eine SMTP-/E-Mail-Empfaengerliste aus und wird uebersprungen.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (address.StartsWith("\\\\", StringComparison.Ordinal))
|
||||
{
|
||||
normalizedEndpoint = address;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Path.IsPathRooted(address))
|
||||
{
|
||||
if (!_options.IncludeLocalFilePaths)
|
||||
{
|
||||
reason = "Lokaler Dateipfad wird per Konfiguration nicht übernommen.";
|
||||
return false;
|
||||
}
|
||||
|
||||
normalizedEndpoint = address;
|
||||
return true;
|
||||
}
|
||||
|
||||
Uri uri;
|
||||
if (Uri.TryCreate(address, UriKind.Absolute, out uri))
|
||||
{
|
||||
switch ((uri.Scheme ?? string.Empty).ToLowerInvariant())
|
||||
{
|
||||
case "http":
|
||||
case "https":
|
||||
normalizedEndpoint = uri.AbsoluteUri;
|
||||
httpMethod = _outputOptions.DefaultHttpMethod;
|
||||
return true;
|
||||
case "ftp":
|
||||
case "sftp":
|
||||
case "tcp":
|
||||
case "icmp":
|
||||
case "file":
|
||||
normalizedEndpoint = uri.IsAbsoluteUri ? uri.AbsoluteUri : address;
|
||||
return true;
|
||||
case "net.tcp":
|
||||
if (string.IsNullOrWhiteSpace(uri.Host) || uri.Port <= 0)
|
||||
{
|
||||
reason = "net.tcp-Adresse konnte nicht auf Host/Port aufgelöst werden.";
|
||||
return false;
|
||||
}
|
||||
|
||||
normalizedEndpoint = "tcp://" + uri.Host + ":" + uri.Port + uri.PathAndQuery;
|
||||
port = uri.Port;
|
||||
return true;
|
||||
case "net.pipe":
|
||||
case "npipe":
|
||||
reason = "Named-Pipe-Endpunkte werden vom Monitor nicht unterstützt.";
|
||||
return false;
|
||||
default:
|
||||
reason = "Schema '" + uri.Scheme + "' wird vom Monitor nicht unterstützt.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (LooksLikeHostPort(address))
|
||||
{
|
||||
if (AdapterContains(adapter, "SFTP"))
|
||||
{
|
||||
normalizedEndpoint = "sftp://" + address;
|
||||
port = ParsePort(address);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (AdapterContains(adapter, "FTP"))
|
||||
{
|
||||
normalizedEndpoint = "ftp://" + address;
|
||||
port = ParsePort(address);
|
||||
return true;
|
||||
}
|
||||
|
||||
normalizedEndpoint = "tcp://" + address;
|
||||
port = ParsePort(address);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (AdapterContains(adapter, "SFTP"))
|
||||
{
|
||||
normalizedEndpoint = "sftp://" + address.TrimStart('/');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (AdapterContains(adapter, "FTP"))
|
||||
{
|
||||
normalizedEndpoint = "ftp://" + address.TrimStart('/');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (AdapterContains(adapter, "HTTP") || AdapterContains(adapter, "SOAP") || AdapterContains(adapter, "WCF"))
|
||||
{
|
||||
reason = "HTTP/WCF-Adapteradresse konnte nicht als absolute URI erkannt werden.";
|
||||
return false;
|
||||
}
|
||||
|
||||
normalizedEndpoint = address;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string ResolveAdapterName(TransportInfo transport)
|
||||
{
|
||||
try
|
||||
{
|
||||
return transport == null || transport.TransportType == null ? string.Empty : transport.TransportType.Name;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private static string ResolveAdapterName(ProtocolType transportType)
|
||||
{
|
||||
try
|
||||
{
|
||||
return transportType == null ? string.Empty : transportType.Name;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private static string SafeAddress(TransportInfo transport)
|
||||
{
|
||||
try
|
||||
{
|
||||
return transport == null ? string.Empty : transport.Address;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool AdapterContains(string adapterName, string fragment)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(adapterName)
|
||||
&& adapterName.IndexOf(fragment, StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
|
||||
private static bool LooksLikeEmailAddressList(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var trimmed = value.Trim();
|
||||
if (trimmed.IndexOf('@') < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (trimmed.IndexOf("http://", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| trimmed.IndexOf("https://", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| trimmed.IndexOf("sftp://", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| trimmed.IndexOf("ftp://", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| trimmed.IndexOf("tcp://", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| trimmed.IndexOf("file://", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| trimmed.StartsWith("\\", StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var entries = trimmed.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (entries.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
var part = entry.Trim();
|
||||
if (string.IsNullOrWhiteSpace(part))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (part.IndexOf('@') <= 0 || part.IndexOf(' ') >= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool WildcardMatch(string input, string pattern)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(pattern))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
input = input ?? string.Empty;
|
||||
var regex = "^" + Regex.Escape(pattern.Trim()).Replace("\\*", ".*").Replace("\\?", ".") + "$";
|
||||
return Regex.IsMatch(input, regex, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
}
|
||||
|
||||
private static bool LooksLikeHostPort(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Regex.IsMatch(value.Trim(), @"^[a-zA-Z0-9._-]+:\d{1,5}($|/.*$)");
|
||||
}
|
||||
|
||||
private static int? ParsePort(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var match = Regex.Match(value, @":(?<port>\d{1,5})(?:$|/)");
|
||||
int parsed;
|
||||
if (match.Success && int.TryParse(match.Groups["port"].Value, out parsed))
|
||||
{
|
||||
return parsed;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace BizTalkMonitorConfigGenerator.BizTalk
|
||||
{
|
||||
public sealed class DiscoveredEndpoint
|
||||
{
|
||||
public string ArtifactType { get; set; }
|
||||
public string BizTalkApplicationName { get; set; }
|
||||
public string MappedApplicationName { get; set; }
|
||||
public string ArtifactName { get; set; }
|
||||
public string AdapterName { get; set; }
|
||||
public string SourceAddress { get; set; }
|
||||
public string NormalizedEndpoint { get; set; }
|
||||
public string TargetName { get; set; }
|
||||
public string HttpMethod { get; set; }
|
||||
public int? Port { get; set; }
|
||||
public bool? ValidateUncPathAccess { get; set; }
|
||||
public string Detail { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user