Initial commit: BizTalk SAP environment inventory
This commit is contained in:
@@ -0,0 +1,796 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using BizTalkSapEnvironmentInventory.Configuration;
|
||||
using BizTalkSapEnvironmentInventory.Infrastructure;
|
||||
using BizTalkSapEnvironmentInventory.Models;
|
||||
|
||||
namespace BizTalkSapEnvironmentInventory.Collectors
|
||||
{
|
||||
/// <summary>
|
||||
/// Exportiert BizTalk-Gruppenbindings read-only und extrahiert sichere SAP-Endpunktparameter.
|
||||
/// </summary>
|
||||
internal sealed class BindingExportCollector
|
||||
{
|
||||
private static readonly Regex ApplicationModuleName = new Regex(
|
||||
@"^\[Application:(.+)\]$",
|
||||
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||||
|
||||
private readonly CommandLineOptions options;
|
||||
private readonly InventoryDocument document;
|
||||
private readonly ConsoleFileLogger logger;
|
||||
private readonly TimeSpan processTimeout;
|
||||
|
||||
public BindingExportCollector(
|
||||
CommandLineOptions options,
|
||||
InventoryDocument document,
|
||||
ConsoleFileLogger logger,
|
||||
int timeoutSeconds)
|
||||
{
|
||||
this.options = options;
|
||||
this.document = document;
|
||||
this.logger = logger;
|
||||
processTimeout = TimeSpan.FromSeconds(Math.Max(30, timeoutSeconds));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verarbeitet entweder eine vorgegebene Binding-Datei oder einen temporären BTSTask-Export.
|
||||
/// </summary>
|
||||
public void Collect()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(options.BindingFilePath))
|
||||
{
|
||||
logger.Info("Werte vorhandenen Binding-Export offline aus: " + options.BindingFilePath);
|
||||
ParseBindingDocument(LoadXml(options.BindingFilePath), "Angegebene Binding-Datei");
|
||||
return;
|
||||
}
|
||||
|
||||
var btsTask = ResolveBtsTaskPath();
|
||||
if (string.IsNullOrWhiteSpace(btsTask))
|
||||
{
|
||||
throw new FileNotFoundException(
|
||||
"BTSTask.exe wurde nicht gefunden. --btstask oder --binding-file verwenden.");
|
||||
}
|
||||
|
||||
var temporaryPath = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
"BizTalkSapInventory-" + Guid.NewGuid().ToString("N") + ".xml");
|
||||
try
|
||||
{
|
||||
ExportBindings(btsTask, temporaryPath);
|
||||
ParseBindingDocument(LoadXml(temporaryPath), "BTSTask GroupLevel Export");
|
||||
}
|
||||
finally
|
||||
{
|
||||
TryDelete(temporaryPath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parst ein Binding-Dokument; intern sichtbar, damit der Self-Test reale XML-Strukturen prüft.
|
||||
/// </summary>
|
||||
internal void ParseBindingDocument(XDocument bindingDocument, string source)
|
||||
{
|
||||
if (bindingDocument == null || bindingDocument.Root == null)
|
||||
{
|
||||
throw new InvalidDataException("Binding-XML ist leer.");
|
||||
}
|
||||
|
||||
AddApplicationsFromBinding(bindingDocument);
|
||||
|
||||
var parsed = 0;
|
||||
foreach (var sendPort in bindingDocument.Descendants()
|
||||
.Where(item => IsName(item, "SendPort") && HasAncestor(item, "SendPortCollection")))
|
||||
{
|
||||
var endpoint = ParseSendPort(sendPort, source);
|
||||
if (endpoint != null)
|
||||
{
|
||||
MergeEndpoint(endpoint);
|
||||
parsed++;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var receiveLocation in bindingDocument.Descendants()
|
||||
.Where(item => IsName(item, "ReceiveLocation")
|
||||
&& HasAncestor(item, "ReceiveLocations")))
|
||||
{
|
||||
var endpoint = ParseReceiveLocation(receiveLocation, source);
|
||||
if (endpoint != null)
|
||||
{
|
||||
MergeEndpoint(endpoint);
|
||||
parsed++;
|
||||
}
|
||||
}
|
||||
|
||||
logger.Info(parsed + " SAP-Endpunkt(e) aus dem Binding-Export ausgewertet.");
|
||||
}
|
||||
|
||||
private SapEndpointRecord ParseSendPort(XElement sendPort, string source)
|
||||
{
|
||||
var primaryTransport = Child(sendPort, "PrimaryTransport");
|
||||
var transportType = primaryTransport == null ? null : Child(primaryTransport, "TransportType");
|
||||
var transportData = primaryTransport == null ? null : Child(primaryTransport, "TransportTypeData");
|
||||
var properties = ParsePropertyBag(transportData);
|
||||
var adapterName = Attribute(transportType, "Name");
|
||||
var address = ChildValue(primaryTransport, "Address");
|
||||
|
||||
if (!LooksLikeSap(adapterName, address, properties))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var endpoint = new SapEndpointRecord
|
||||
{
|
||||
ApplicationName = FirstNonEmpty(
|
||||
Attribute(sendPort, "ApplicationName"),
|
||||
ChildValue(sendPort, "ApplicationName")),
|
||||
Direction = "Senden",
|
||||
Name = Attribute(sendPort, "Name"),
|
||||
ParentPortName = Attribute(sendPort, "Name"),
|
||||
AdapterName = adapterName,
|
||||
HostName = FirstNonEmpty(
|
||||
Attribute(Child(primaryTransport, "SendHandler"), "Name"),
|
||||
ChildValue(primaryTransport, "SendHandler")),
|
||||
Status = FirstNonEmpty(Attribute(sendPort, "Status"), "Konfiguriert"),
|
||||
Address = SensitiveDataSanitizer.SanitizeText(address),
|
||||
Source = source
|
||||
};
|
||||
AddProperties(endpoint, properties);
|
||||
ParseSapAddress(endpoint, address);
|
||||
FinalizeEndpoint(endpoint);
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
private SapEndpointRecord ParseReceiveLocation(XElement receiveLocation, string source)
|
||||
{
|
||||
var transportType = Child(receiveLocation, "ReceiveLocationTransportType");
|
||||
var transportData = Child(receiveLocation, "ReceiveLocationTransportTypeData");
|
||||
var properties = ParsePropertyBag(transportData);
|
||||
var adapterName = Attribute(transportType, "Name");
|
||||
var address = FirstNonEmpty(
|
||||
ChildValue(receiveLocation, "Address"),
|
||||
Attribute(receiveLocation, "Address"));
|
||||
|
||||
if (!LooksLikeSap(adapterName, address, properties))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var receivePort = receiveLocation.Ancestors()
|
||||
.FirstOrDefault(item => IsName(item, "ReceivePort"));
|
||||
var endpoint = new SapEndpointRecord
|
||||
{
|
||||
ApplicationName = FirstNonEmpty(
|
||||
Attribute(receiveLocation, "ApplicationName"),
|
||||
ChildValue(receiveLocation, "ApplicationName"),
|
||||
Attribute(receivePort, "ApplicationName"),
|
||||
ChildValue(receivePort, "ApplicationName")),
|
||||
Direction = "Empfangen",
|
||||
Name = Attribute(receiveLocation, "Name"),
|
||||
ParentPortName = Attribute(receivePort, "Name"),
|
||||
AdapterName = adapterName,
|
||||
HostName = FirstNonEmpty(
|
||||
Attribute(Child(receiveLocation, "ReceiveHandler"), "Name"),
|
||||
ChildValue(receiveLocation, "ReceiveHandler")),
|
||||
Status = FirstNonEmpty(
|
||||
Attribute(receiveLocation, "Enable"),
|
||||
Attribute(receiveLocation, "Status"),
|
||||
"Konfiguriert"),
|
||||
Address = SensitiveDataSanitizer.SanitizeText(address),
|
||||
Source = source
|
||||
};
|
||||
AddProperties(endpoint, properties);
|
||||
ParseSapAddress(endpoint, address);
|
||||
FinalizeEndpoint(endpoint);
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
private static List<NameValueRecord> ParsePropertyBag(XElement dataElement)
|
||||
{
|
||||
var result = new List<NameValueRecord>();
|
||||
if (dataElement == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
var raw = dataElement.HasElements
|
||||
? string.Concat(dataElement.Nodes().Select(item => item.ToString(SaveOptions.DisableFormatting)))
|
||||
: dataElement.Value;
|
||||
ParseXmlProperties(raw, "Binding", result, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void ParseXmlProperties(
|
||||
string raw,
|
||||
string source,
|
||||
List<NameValueRecord> target,
|
||||
int depth)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(raw) || depth > 4)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
raw = WebUtility.HtmlDecode(raw.Trim());
|
||||
if (!raw.StartsWith("<", StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
XElement root;
|
||||
try
|
||||
{
|
||||
root = XElement.Parse(raw, LoadOptions.None);
|
||||
}
|
||||
catch (XmlException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var element in root.DescendantsAndSelf())
|
||||
{
|
||||
foreach (var attribute in element.Attributes())
|
||||
{
|
||||
if (string.Equals(attribute.Name.LocalName, "vt", StringComparison.OrdinalIgnoreCase)
|
||||
|| attribute.IsNamespaceDeclaration)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
AddUnique(
|
||||
target,
|
||||
element.Name.LocalName + "." + attribute.Name.LocalName,
|
||||
SensitiveDataSanitizer.RedactValue(attribute.Name.LocalName, attribute.Value),
|
||||
source);
|
||||
AddUnique(
|
||||
target,
|
||||
NormalizePropertyName(attribute.Name.LocalName),
|
||||
SensitiveDataSanitizer.RedactValue(attribute.Name.LocalName, attribute.Value),
|
||||
source);
|
||||
}
|
||||
|
||||
if (element.HasElements)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var name = element.Name.LocalName;
|
||||
if (!SensitiveDataSanitizer.IsSensitiveName(name)
|
||||
&& element.Value.TrimStart().StartsWith("<", StringComparison.Ordinal))
|
||||
{
|
||||
ParseXmlProperties(element.Value, name, target, depth + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = SensitiveDataSanitizer.RedactValue(name, element.Value);
|
||||
AddUnique(target, name, value, source);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ParseSapAddress(SapEndpointRecord endpoint, string rawAddress)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rawAddress)
|
||||
|| !rawAddress.StartsWith("sap://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var address = SensitiveDataSanitizer.SanitizeText(rawAddress);
|
||||
var withoutScheme = address.Substring("sap://".Length);
|
||||
var atIndex = withoutScheme.IndexOf('@');
|
||||
var userInfo = atIndex >= 0 ? withoutScheme.Substring(0, atIndex) : string.Empty;
|
||||
var hostAndQuery = atIndex >= 0 ? withoutScheme.Substring(atIndex + 1) : withoutScheme;
|
||||
|
||||
foreach (var item in userInfo.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
var pair = item.Split(new[] { '=' }, 2);
|
||||
if (pair.Length == 2)
|
||||
{
|
||||
AddUnique(
|
||||
endpoint.Properties,
|
||||
NormalizePropertyName(pair[0]),
|
||||
Decode(pair[1]),
|
||||
"SAP-URI");
|
||||
}
|
||||
}
|
||||
|
||||
var queryIndex = hostAndQuery.IndexOf('?');
|
||||
var hostPart = queryIndex >= 0
|
||||
? hostAndQuery.Substring(0, queryIndex)
|
||||
: hostAndQuery;
|
||||
var query = queryIndex >= 0
|
||||
? hostAndQuery.Substring(queryIndex + 1)
|
||||
: string.Empty;
|
||||
|
||||
var hostSegments = hostPart.Split('/');
|
||||
if (hostSegments.Length > 0)
|
||||
{
|
||||
AddUnique(endpoint.Properties, "ConnectionType", Decode(hostSegments[0]), "SAP-URI");
|
||||
if (string.Equals(hostSegments[0], "A", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (hostSegments.Length > 1)
|
||||
{
|
||||
AddUnique(endpoint.Properties, "ApplicationServerHost", Decode(hostSegments[1]), "SAP-URI");
|
||||
}
|
||||
if (hostSegments.Length > 2)
|
||||
{
|
||||
AddUnique(endpoint.Properties, "SystemNumber", Decode(hostSegments[2]), "SAP-URI");
|
||||
}
|
||||
}
|
||||
else if (string.Equals(hostSegments[0], "B", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (hostSegments.Length > 1)
|
||||
{
|
||||
AddUnique(endpoint.Properties, "MessageServerHost", Decode(hostSegments[1]), "SAP-URI");
|
||||
}
|
||||
if (hostSegments.Length > 2)
|
||||
{
|
||||
AddUnique(endpoint.Properties, "R3SystemName", Decode(hostSegments[2]), "SAP-URI");
|
||||
}
|
||||
}
|
||||
else if (string.Equals(hostSegments[0], "D", StringComparison.OrdinalIgnoreCase)
|
||||
&& hostSegments.Length > 1)
|
||||
{
|
||||
AddUnique(endpoint.Properties, "DestinationName", Decode(hostSegments[1]), "SAP-URI");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var item in query.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
var pair = item.Split(new[] { '=' }, 2);
|
||||
if (pair.Length == 2)
|
||||
{
|
||||
AddUnique(
|
||||
endpoint.Properties,
|
||||
NormalizePropertyName(Decode(pair[0])),
|
||||
SensitiveDataSanitizer.RedactValue(pair[0], Decode(pair[1])),
|
||||
"SAP-URI");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void FinalizeEndpoint(SapEndpointRecord endpoint)
|
||||
{
|
||||
ResolveApplication(endpoint);
|
||||
|
||||
var useSnc = endpoint.GetProperty("UseSnc", "UseSNC");
|
||||
var sncEnabled = IsTrue(useSnc);
|
||||
var affiliateApplication = endpoint.GetProperty(
|
||||
"AffiliateApplicationName",
|
||||
"SsoAffiliateApplication",
|
||||
"SSOApplication");
|
||||
var userName = endpoint.GetProperty("UserName", "Username");
|
||||
|
||||
if (sncEnabled)
|
||||
{
|
||||
endpoint.SecurityMode = "SAP SNC";
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(affiliateApplication))
|
||||
{
|
||||
endpoint.SecurityMode = "Enterprise SSO";
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(userName))
|
||||
{
|
||||
endpoint.SecurityMode = "SAP-Benutzer";
|
||||
}
|
||||
else
|
||||
{
|
||||
endpoint.SecurityMode = "Nicht eindeutig aus Binding ableitbar";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(affiliateApplication))
|
||||
{
|
||||
endpoint.CredentialReference = "SSO Affiliate Application: " + affiliateApplication;
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(userName))
|
||||
{
|
||||
endpoint.CredentialReference = "SAP-Benutzer: " + userName
|
||||
+ "; Kennwort wird nicht exportiert/dokumentiert.";
|
||||
}
|
||||
else
|
||||
{
|
||||
endpoint.CredentialReference =
|
||||
"Keine Kennwortinformation im Binding-Export; Credentials separat kontrollieren.";
|
||||
}
|
||||
|
||||
foreach (var property in endpoint.Properties.Where(item =>
|
||||
item.Name.IndexOf("Action", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| item.Value.IndexOf("Microsoft.LobServices.Sap", StringComparison.OrdinalIgnoreCase) >= 0))
|
||||
{
|
||||
var operation = SensitiveDataSanitizer.SanitizeText(property.Value);
|
||||
if (!string.IsNullOrWhiteSpace(operation)
|
||||
&& !endpoint.Operations.Contains(operation, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
endpoint.Operations.Add(operation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ResolveApplication(SapEndpointRecord endpoint)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(endpoint.ApplicationName))
|
||||
{
|
||||
EnsureApplication(endpoint.ApplicationName);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var application in document.Applications)
|
||||
{
|
||||
if (application.Artifacts.Any(item =>
|
||||
string.Equals(item.Name, endpoint.Name, StringComparison.OrdinalIgnoreCase)
|
||||
|| (!string.IsNullOrWhiteSpace(endpoint.ParentPortName)
|
||||
&& string.Equals(
|
||||
item.Name,
|
||||
endpoint.ParentPortName,
|
||||
StringComparison.OrdinalIgnoreCase))))
|
||||
{
|
||||
endpoint.ApplicationName = application.Name;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MergeEndpoint(SapEndpointRecord parsed)
|
||||
{
|
||||
var existing = document.SapEndpoints.FirstOrDefault(item =>
|
||||
string.Equals(item.Direction, parsed.Direction, StringComparison.OrdinalIgnoreCase)
|
||||
&& string.Equals(item.Name, parsed.Name, StringComparison.OrdinalIgnoreCase));
|
||||
if (existing == null)
|
||||
{
|
||||
document.SapEndpoints.Add(parsed);
|
||||
return;
|
||||
}
|
||||
|
||||
existing.ApplicationName = FirstNonEmpty(parsed.ApplicationName, existing.ApplicationName);
|
||||
existing.ParentPortName = FirstNonEmpty(parsed.ParentPortName, existing.ParentPortName);
|
||||
existing.AdapterName = FirstNonEmpty(parsed.AdapterName, existing.AdapterName);
|
||||
existing.HostName = FirstNonEmpty(parsed.HostName, existing.HostName);
|
||||
existing.Status = FirstNonEmpty(parsed.Status, existing.Status);
|
||||
existing.Address = FirstNonEmpty(parsed.Address, existing.Address);
|
||||
existing.SecurityMode = FirstNonEmpty(parsed.SecurityMode, existing.SecurityMode);
|
||||
existing.CredentialReference = FirstNonEmpty(
|
||||
parsed.CredentialReference,
|
||||
existing.CredentialReference);
|
||||
existing.Source = FirstNonEmpty(parsed.Source, existing.Source);
|
||||
foreach (var property in parsed.Properties)
|
||||
{
|
||||
AddUnique(existing.Properties, property.Name, property.Value, property.Source);
|
||||
}
|
||||
foreach (var operation in parsed.Operations)
|
||||
{
|
||||
if (!existing.Operations.Contains(operation, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
existing.Operations.Add(operation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddApplicationsFromBinding(XDocument bindingDocument)
|
||||
{
|
||||
foreach (var module in bindingDocument.Descendants().Where(item => IsName(item, "ModuleRef")))
|
||||
{
|
||||
var match = ApplicationModuleName.Match(Attribute(module, "Name"));
|
||||
if (match.Success)
|
||||
{
|
||||
EnsureApplication(match.Groups[1].Value);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var applicationName in bindingDocument.Descendants()
|
||||
.Where(item => IsName(item, "ApplicationName"))
|
||||
.Select(item => item.Value)
|
||||
.Where(item => !string.IsNullOrWhiteSpace(item))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
EnsureApplication(applicationName);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureApplication(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name)
|
||||
|| document.Applications.Any(item =>
|
||||
string.Equals(item.Name, name, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
document.Applications.Add(new ApplicationRecord
|
||||
{
|
||||
Name = name.Trim(),
|
||||
Status = "Aus Binding-Export erkannt"
|
||||
});
|
||||
}
|
||||
|
||||
private void ExportBindings(string btsTaskPath, string destination)
|
||||
{
|
||||
var arguments = new StringBuilder();
|
||||
arguments.Append("ExportBindings /GroupLevel /Destination:\"")
|
||||
.Append(EscapeCommandArgument(destination))
|
||||
.Append("\"");
|
||||
if (!string.IsNullOrWhiteSpace(document.System.ManagementServer))
|
||||
{
|
||||
arguments.Append(" /Server:\"")
|
||||
.Append(EscapeCommandArgument(document.System.ManagementServer))
|
||||
.Append("\"");
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(document.System.ManagementDatabase))
|
||||
{
|
||||
arguments.Append(" /Database:\"")
|
||||
.Append(EscapeCommandArgument(document.System.ManagementDatabase))
|
||||
.Append("\"");
|
||||
}
|
||||
|
||||
logger.Info("Exportiere BizTalk-Gruppenbindings read-only mit BTSTask.");
|
||||
var output = new StringBuilder();
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = btsTaskPath,
|
||||
Arguments = arguments.ToString(),
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
WorkingDirectory = Path.GetDirectoryName(btsTaskPath)
|
||||
};
|
||||
|
||||
using (var process = new Process { StartInfo = startInfo })
|
||||
{
|
||||
process.OutputDataReceived += (sender, args) =>
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(args.Data))
|
||||
{
|
||||
output.AppendLine(args.Data);
|
||||
}
|
||||
};
|
||||
process.ErrorDataReceived += (sender, args) =>
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(args.Data))
|
||||
{
|
||||
output.AppendLine(args.Data);
|
||||
}
|
||||
};
|
||||
|
||||
process.Start();
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
if (!process.WaitForExit((int)processTimeout.TotalMilliseconds))
|
||||
{
|
||||
try
|
||||
{
|
||||
process.Kill();
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
}
|
||||
throw new TimeoutException(
|
||||
"BTSTask ExportBindings überschritt " + processTimeout.TotalSeconds + " Sekunden.");
|
||||
}
|
||||
process.WaitForExit();
|
||||
if (process.ExitCode != 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"BTSTask ExportBindings meldete Exitcode "
|
||||
+ process.ExitCode
|
||||
+ ": "
|
||||
+ LastLines(output.ToString(), 8));
|
||||
}
|
||||
}
|
||||
|
||||
if (!File.Exists(destination) || new FileInfo(destination).Length == 0)
|
||||
{
|
||||
throw new InvalidDataException("BTSTask erzeugte keine Binding-Datei.");
|
||||
}
|
||||
}
|
||||
|
||||
private string ResolveBtsTaskPath()
|
||||
{
|
||||
var candidates = new List<string>();
|
||||
if (!string.IsNullOrWhiteSpace(options.BtsTaskPath))
|
||||
{
|
||||
candidates.Add(options.BtsTaskPath);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(document.System.BizTalkInstallPath))
|
||||
{
|
||||
candidates.Add(Path.Combine(document.System.BizTalkInstallPath, "BTSTask.exe"));
|
||||
}
|
||||
|
||||
var programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
|
||||
var programFilesX86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
|
||||
candidates.Add(Path.Combine(programFiles, "Microsoft BizTalk Server 2020", "BTSTask.exe"));
|
||||
candidates.Add(Path.Combine(programFilesX86, "Microsoft BizTalk Server 2020", "BTSTask.exe"));
|
||||
|
||||
var path = Environment.GetEnvironmentVariable("PATH") ?? string.Empty;
|
||||
candidates.AddRange(path.Split(Path.PathSeparator)
|
||||
.Where(item => !string.IsNullOrWhiteSpace(item))
|
||||
.Select(item => Path.Combine(item.Trim(), "BTSTask.exe")));
|
||||
|
||||
return candidates.FirstOrDefault(File.Exists);
|
||||
}
|
||||
|
||||
private static XDocument LoadXml(string path)
|
||||
{
|
||||
var settings = new XmlReaderSettings
|
||||
{
|
||||
DtdProcessing = DtdProcessing.Prohibit,
|
||||
XmlResolver = null,
|
||||
IgnoreComments = true
|
||||
};
|
||||
using (var stream = File.OpenRead(path))
|
||||
using (var reader = XmlReader.Create(stream, settings))
|
||||
{
|
||||
return XDocument.Load(reader, LoadOptions.None);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool LooksLikeSap(
|
||||
string adapterName,
|
||||
string address,
|
||||
IEnumerable<NameValueRecord> properties)
|
||||
{
|
||||
if ((adapterName ?? string.Empty).IndexOf("SAP", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| (address ?? string.Empty).StartsWith("sap://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return properties.Any(item =>
|
||||
item.Name.IndexOf("SAP", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| item.Value.IndexOf("sapBinding", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| item.Value.IndexOf("Microsoft.Adapters.SAP", StringComparison.OrdinalIgnoreCase) >= 0);
|
||||
}
|
||||
|
||||
private static void AddProperties(
|
||||
SapEndpointRecord endpoint,
|
||||
IEnumerable<NameValueRecord> properties)
|
||||
{
|
||||
foreach (var property in properties)
|
||||
{
|
||||
AddUnique(endpoint.Properties, property.Name, property.Value, property.Source);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddUnique(
|
||||
List<NameValueRecord> target,
|
||||
string name,
|
||||
string value,
|
||||
string source)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var safeValue = SensitiveDataSanitizer.RedactValue(name, value);
|
||||
if (target.Any(item =>
|
||||
string.Equals(item.Name, name, StringComparison.OrdinalIgnoreCase)
|
||||
&& string.Equals(item.Value, safeValue, StringComparison.Ordinal)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
target.Add(new NameValueRecord(name, safeValue, source));
|
||||
}
|
||||
|
||||
private static string NormalizePropertyName(string name)
|
||||
{
|
||||
var normalized = (name ?? string.Empty).Trim();
|
||||
var mappings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "Client", "Client" },
|
||||
{ "lang", "Language" },
|
||||
{ "Language", "Language" },
|
||||
{ "GwHost", "GatewayHost" },
|
||||
{ "GwServ", "GatewayService" },
|
||||
{ "ListenerDest", "ListenerDestination" },
|
||||
{ "ListenerGwHost", "ListenerGatewayHost" },
|
||||
{ "ListenerGwServ", "ListenerGatewayService" },
|
||||
{ "ListenerProgramId", "ListenerProgramId" },
|
||||
{ "UseSnc", "UseSnc" },
|
||||
{ "SAPROUTER", "SapRouter" }
|
||||
};
|
||||
string mapped;
|
||||
return mappings.TryGetValue(normalized, out mapped) ? mapped : normalized;
|
||||
}
|
||||
|
||||
private static XElement Child(XElement parent, string localName)
|
||||
{
|
||||
return parent == null
|
||||
? null
|
||||
: parent.Elements().FirstOrDefault(item => IsName(item, localName));
|
||||
}
|
||||
|
||||
private static string ChildValue(XElement parent, string localName)
|
||||
{
|
||||
var child = Child(parent, localName);
|
||||
return child == null ? string.Empty : child.Value;
|
||||
}
|
||||
|
||||
private static string Attribute(XElement element, string localName)
|
||||
{
|
||||
if (element == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
var attribute = element.Attributes().FirstOrDefault(item =>
|
||||
string.Equals(item.Name.LocalName, localName, StringComparison.OrdinalIgnoreCase));
|
||||
return attribute == null ? string.Empty : attribute.Value;
|
||||
}
|
||||
|
||||
private static bool IsName(XElement element, string localName)
|
||||
{
|
||||
return string.Equals(
|
||||
element.Name.LocalName,
|
||||
localName,
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static bool HasAncestor(XElement element, string localName)
|
||||
{
|
||||
return element.Ancestors().Any(item => IsName(item, localName));
|
||||
}
|
||||
|
||||
private static string FirstNonEmpty(params string[] values)
|
||||
{
|
||||
return values.FirstOrDefault(value => !string.IsNullOrWhiteSpace(value)) ?? string.Empty;
|
||||
}
|
||||
|
||||
private static bool IsTrue(string value)
|
||||
{
|
||||
return string.Equals(value, "true", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(value, "1", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(value, "yes", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(value, "ja", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static string Decode(string value)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Uri.UnescapeDataString((value ?? string.Empty).Replace("+", " "));
|
||||
}
|
||||
catch (UriFormatException)
|
||||
{
|
||||
return value ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private static string EscapeCommandArgument(string value)
|
||||
{
|
||||
return (value ?? string.Empty).Replace("\"", string.Empty)
|
||||
.Replace("\r", string.Empty)
|
||||
.Replace("\n", string.Empty);
|
||||
}
|
||||
|
||||
private static string LastLines(string text, int count)
|
||||
{
|
||||
var lines = (text ?? string.Empty)
|
||||
.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
return string.Join(" | ", lines.Skip(Math.Max(0, lines.Length - count)));
|
||||
}
|
||||
|
||||
private static void TryDelete(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user