Initial commit: BizTalk SAP environment inventory
This commit is contained in:
@@ -0,0 +1,303 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user