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 { /// /// Orchestriert die read-only Erfassung, Fortschrittsausgabe und atomare DOCX-Erzeugung. /// internal static class Program { /// /// Programmeinstieg mit den dokumentierten Exitcodes 0, 1 und 2. /// 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()); } } }