Files
biztalk-sap-environment-inv…/src/BizTalkSapEnvironmentInventory/Program.cs
T

304 lines
12 KiB
C#

using System;
using System.Configuration;
using System.Globalization;
using System.IO;
using System.Linq;
using BizTalkSapEnvironmentInventory.Collectors;
using BizTalkSapEnvironmentInventory.Configuration;
using BizTalkSapEnvironmentInventory.Infrastructure;
using BizTalkSapEnvironmentInventory.Models;
using BizTalkSapEnvironmentInventory.Reporting;
namespace BizTalkSapEnvironmentInventory
{
/// <summary>
/// Orchestriert die read-only Erfassung, Fortschrittsausgabe und atomare DOCX-Erzeugung.
/// </summary>
internal static class Program
{
/// <summary>
/// Programmeinstieg mit den dokumentierten Exitcodes 0, 1 und 2.
/// </summary>
private static int Main(string[] args)
{
CommandLineOptions options;
try
{
options = CommandLineOptions.Parse(args);
}
catch (Exception exception)
{
Console.Error.WriteLine("FEHLER: " + exception.Message);
Console.Error.WriteLine();
Console.Error.WriteLine(CommandLineOptions.Usage());
return 2;
}
if (options.ShowHelp)
{
Console.WriteLine(CommandLineOptions.Usage());
return 0;
}
if (options.SelfTest)
{
return RunSelfTest();
}
try
{
Directory.CreateDirectory(options.OutputDirectory);
}
catch (Exception exception) when (
exception is IOException
|| exception is UnauthorizedAccessException
|| exception is ArgumentException)
{
Console.Error.WriteLine("FEHLER: Ausgabeordner kann nicht erstellt werden: " + exception.Message);
return 2;
}
var timestamp = DateTime.Now.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture);
var safeMachine = SanitizeFileName(Environment.MachineName);
var baseName = "BizTalk-SAP-Dokumentation-"
+ options.EnvironmentName
+ "-"
+ safeMachine
+ "-"
+ timestamp;
var logPath = Path.Combine(options.OutputDirectory, baseName + ".log");
var reportPath = Path.Combine(options.OutputDirectory, baseName + ".docx");
using (var logger = new ConsoleFileLogger(logPath))
{
var document = new InventoryDocument
{
EnvironmentName = options.EnvironmentName,
ComputerName = Environment.MachineName,
StartedUtc = DateTime.UtcNow
};
logger.Info("BEW BizTalk SAP Environment Inventory startet.");
logger.Info("Umgebung: " + options.EnvironmentName);
logger.Info("Server: " + Environment.MachineName);
logger.Info("Ausgabeordner: " + options.OutputDirectory);
logger.Info("Modus: read-only");
var safeCollector = new SafeCollector(document, logger);
safeCollector.Execute(
"System und BizTalk-Installation",
true,
() => new SystemCollector(options).Collect(document));
safeCollector.Execute(
"BizTalk-Anwendungen und WMI-Artefakte",
true,
() => new BizTalkWmiCollector(
document,
logger,
ReadIntSetting("WmiTimeoutSeconds", 30),
ReadIntSetting("MaxArtifactDetailsPerType", 5000)).Collect());
safeCollector.Execute(
"SAP-Bindingparameter",
true,
() => new BindingExportCollector(
options,
document,
logger,
ReadIntSetting("ProcessTimeoutSeconds", 120)).Collect());
safeCollector.Execute(
"SAP NCo, SNC und Zertifikatsmetadaten",
false,
() => new SapRuntimeCollector(
document,
logger,
ReadBoolSetting("IncludeSapRelatedLocalMachineCertificates", true)).Collect());
EvaluateCompleteness(document, ReadBoolSetting("ExpectedUseSnc", true));
document.CompletedUtc = DateTime.UtcNow;
try
{
logger.Info("Erzeuge Microsoft-Word-Dokument: " + reportPath);
new DocxReportWriter().Write(document, reportPath);
logger.Info("Word-Dokument erfolgreich erzeugt: " + reportPath);
logger.Info("Logdatei: " + logPath);
}
catch (Exception exception)
{
logger.Error("Word-Dokument konnte nicht erzeugt werden: " + exception);
return 2;
}
var exitCode = document.HasRequiredSectionFailure ? 1 : 0;
logger.Info("Erfassung beendet. Exitcode: " + exitCode);
return exitCode;
}
}
private static int RunSelfTest()
{
try
{
Console.WriteLine("Starte Sicherheits-, Binding- und DOCX-Self-Test ...");
SelfTestRunner.Run();
Console.WriteLine("Self-Test erfolgreich.");
return 0;
}
catch (Exception exception)
{
Console.Error.WriteLine("Self-Test fehlgeschlagen: " + exception);
return 1;
}
}
private static void EvaluateCompleteness(InventoryDocument document, bool expectedUseSnc)
{
if (document.Applications.Count == 0)
{
document.Findings.Add(new Finding
{
Severity = "Fehler",
Area = "BizTalk-Anwendungen",
Message = "Keine BizTalk-Anwendungen wurden ermittelt.",
RecommendedAction = "Mit einem Mitglied der BizTalk Server Administrators oder Operators lokal erneut ausführen.",
Owner = "BizTalk-Betrieb"
});
}
if (document.SapEndpoints.Count == 0)
{
document.Findings.Add(new Finding
{
Severity = "Fehler",
Area = "SAP-Endpunkte",
Message = "Keine WCF-SAP-/SAP-Endpunkte wurden erkannt.",
RecommendedAction = "BTSTask-Gruppenexport, WCF-Custom sapBinding und WCF-SAP-Adapterdefinition prüfen.",
Owner = "BizTalk-Betrieb"
});
}
foreach (var endpoint in document.SapEndpoints)
{
if (string.IsNullOrWhiteSpace(endpoint.ApplicationName))
{
document.Findings.Add(new Finding
{
Severity = "Warnung",
Area = "Anwendungszuordnung",
Message = "SAP-Endpunkt " + endpoint.Name + " konnte keiner BizTalk-Anwendung zugeordnet werden.",
RecommendedAction = "Port in der BizTalk Administration Console prüfen und Zuordnung ergänzen.",
Owner = "BizTalk-Betrieb"
});
}
var useSnc = endpoint.GetProperty("UseSnc", "UseSNC");
if (expectedUseSnc && !IsTrue(useSnc))
{
document.Findings.Add(new Finding
{
Severity = "Warnung",
Area = "SNC",
Message = "Für SAP-Endpunkt " + endpoint.Name
+ " ist UseSnc nicht eindeutig true (Wert: "
+ (string.IsNullOrWhiteSpace(useSnc) ? "nicht ermittelt" : useSnc)
+ ").",
RecommendedAction = "WCF-SAP-Binding und tatsächlichen SNC-Verbindungsaufbau prüfen.",
Owner = "BizTalk-Betrieb/SAP Basis"
});
}
if (IsTrue(useSnc)
&& string.IsNullOrWhiteSpace(endpoint.GetProperty(
"SncLibrary",
"sncLibrary",
"SncLibraryPath")))
{
document.Findings.Add(new Finding
{
Severity = "Warnung",
Area = "SNC",
Message = "SNC ist für " + endpoint.Name
+ " aktiv, aber die SNC-Bibliothek wurde nicht eindeutig aus dem Binding gelesen.",
RecommendedAction = "SncLibrary, Prozessarchitektur und Bibliotheksablage auf dem BizTalk Server bestätigen.",
Owner = "BizTalk-Betrieb/Security"
});
}
}
AddManualEvidenceFindings(document);
}
private static void AddManualEvidenceFindings(InventoryDocument document)
{
document.Findings.Add(new Finding
{
Severity = "Offen",
Area = "SAP WE20",
Message = "Partnerprofile (WE20) liegen im SAP-System und sind lokal nicht vollständig auslesbar.",
RecommendedAction = "Partner, Nachrichtentyp, Basistyp/Erweiterung und Empfängerport aus SAP exportieren.",
Owner = "SAP Basis/Fachteam"
});
document.Findings.Add(new Finding
{
Severity = "Offen",
Area = "SAP WE21",
Message = "Portdefinitionen (WE21) liegen im SAP-System und sind lokal nicht vollständig auslesbar.",
RecommendedAction = "Portname, RFC-Destination, IDoc-Version und WE20-Zuordnung aus SAP ergänzen.",
Owner = "SAP Basis"
});
document.Findings.Add(new Finding
{
Severity = "Offen",
Area = "Credential-/SNC-Rotation",
Message = "Ablage, Verantwortlicher und Rotationsprozess für SAP-Credentials bzw. SNC-PSE/Zertifikat sind keine technische Bindingeigenschaft.",
RecommendedAction = "Security-Runbook mit Ablaufdatum, Rotation, Berechtigungen und Recovery referenzieren.",
Owner = "SAP Basis/Security"
});
document.Findings.Add(new Finding
{
Severity = "Offen",
Area = "Disaster Recovery",
Message = "Ob die RFC-Destination SAP-seitig für Frankfurt umgeschaltet wird, ist vom BizTalk Server nicht feststellbar.",
RecommendedAction = "Umschalt- und Rückschaltverfahren mit SAP Basis klären und testen.",
Owner = "SAP Basis/DR-Verantwortliche"
});
}
private static int ReadIntSetting(string name, int defaultValue)
{
int parsed;
return int.TryParse(
ConfigurationManager.AppSettings[name],
NumberStyles.Integer,
CultureInfo.InvariantCulture,
out parsed)
? parsed
: defaultValue;
}
private static bool ReadBoolSetting(string name, bool defaultValue)
{
bool parsed;
return bool.TryParse(ConfigurationManager.AppSettings[name], out parsed)
? parsed
: defaultValue;
}
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 SanitizeFileName(string value)
{
var invalid = Path.GetInvalidFileNameChars();
return new string((value ?? "UNKNOWN")
.Select(character => invalid.Contains(character) ? '-' : character)
.ToArray());
}
}
}