Reduce catalog to Phase 1 ACC/PROD view
This commit is contained in:
@@ -5,6 +5,5 @@
|
||||
</startup>
|
||||
<appSettings>
|
||||
<add key="WmiTimeoutSeconds" value="30" />
|
||||
<add key="MaxRowsPerArtifactType" value="10000" />
|
||||
</appSettings>
|
||||
</configuration>
|
||||
|
||||
@@ -41,18 +41,18 @@
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.IO.Compression.FileSystem" />
|
||||
<Reference Include="System.Management" />
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Collectors\BizTalkWmiCollector.cs" />
|
||||
<Compile Include="Collectors\SystemCollector.cs" />
|
||||
<Compile Include="Configuration\CommandLineOptions.cs" />
|
||||
<Compile Include="Infrastructure\ConsoleFileLogger.cs" />
|
||||
<Compile Include="Infrastructure\SafeCollector.cs" />
|
||||
<Compile Include="Infrastructure\SelfTestRunner.cs" />
|
||||
<Compile Include="Infrastructure\SensitiveDataSanitizer.cs" />
|
||||
<Compile Include="Models\InventoryModels.cs" />
|
||||
<Compile Include="Persistence\SnapshotStore.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Reporting\XlsxReportWriter.cs" />
|
||||
|
||||
@@ -3,14 +3,13 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
using System.Runtime.InteropServices;
|
||||
using BizTalkApplicationCatalog.Infrastructure;
|
||||
using BizTalkApplicationCatalog.Models;
|
||||
|
||||
namespace BizTalkApplicationCatalog.Collectors
|
||||
{
|
||||
/// <summary>
|
||||
/// Liest die installierten Anwendungen und zugehörigen Artefakte über den lokalen BizTalk-WMI-Provider.
|
||||
/// Liest nur die Daten, die für die Phase-1-Ansicht erforderlich sind.
|
||||
/// </summary>
|
||||
internal sealed class BizTalkWmiCollector
|
||||
{
|
||||
@@ -18,282 +17,156 @@ namespace BizTalkApplicationCatalog.Collectors
|
||||
private readonly ConsoleFileLogger logger;
|
||||
private readonly ManagementScope scope;
|
||||
private readonly TimeSpan timeout;
|
||||
private readonly int maxRowsPerType;
|
||||
private readonly Dictionary<string, string> receivePortApplications =
|
||||
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public BizTalkWmiCollector(
|
||||
InventoryDocument document,
|
||||
ConsoleFileLogger logger,
|
||||
int timeoutSeconds,
|
||||
int maxRowsPerType)
|
||||
int timeoutSeconds)
|
||||
{
|
||||
this.document = document;
|
||||
this.logger = logger;
|
||||
timeout = TimeSpan.FromSeconds(Math.Max(5, timeoutSeconds));
|
||||
this.maxRowsPerType = Math.Max(100, maxRowsPerType);
|
||||
scope = new ManagementScope(
|
||||
@"\\" + Environment.MachineName + @"\root\MicrosoftBizTalkServer");
|
||||
scope.Options.Timeout = timeout;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stellt zuerst die vollständige Primärliste her und ergänzt anschließend optionale Details.
|
||||
/// </summary>
|
||||
public void Collect()
|
||||
{
|
||||
scope.Connect();
|
||||
CollectApplications();
|
||||
|
||||
// Receive Ports werden vor Receive Locations gelesen, damit deren Anwendung
|
||||
// auch dann aufgelöst werden kann, wenn die Location sie nicht direkt liefert.
|
||||
foreach (var descriptor in ArtifactDescriptors())
|
||||
{
|
||||
CollectOptionalArtifacts(descriptor);
|
||||
}
|
||||
|
||||
CollectOptionalHosts("MSBTS_HostSetting", "Host");
|
||||
CollectOptionalHosts("MSBTS_HostInstance", "Hostinstanz");
|
||||
CollectOptionalHosts("MSBTS_ReceiveHandler", "Receive Handler");
|
||||
if (!CollectOptionalHosts("MSBTS_SendHandler2", "Send Handler"))
|
||||
{
|
||||
CollectOptionalHosts("MSBTS_SendHandler", "Send Handler");
|
||||
}
|
||||
CollectReceivePortMappings();
|
||||
CollectSendPortEndpoints();
|
||||
CollectReceiveLocationEndpoints();
|
||||
|
||||
document.Applications.Sort((left, right) =>
|
||||
string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase));
|
||||
document.Artifacts.Sort(CompareArtifacts);
|
||||
document.Hosts.Sort((left, right) =>
|
||||
document.Endpoints.Sort((left, right) =>
|
||||
{
|
||||
var category = string.Compare(left.Category, right.Category, StringComparison.OrdinalIgnoreCase);
|
||||
return category != 0
|
||||
? category
|
||||
: string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase);
|
||||
var application = string.Compare(
|
||||
left.ApplicationName,
|
||||
right.ApplicationName,
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
return application != 0
|
||||
? application
|
||||
: string.Compare(
|
||||
left.AdapterType,
|
||||
right.AdapterType,
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
});
|
||||
}
|
||||
|
||||
private void CollectApplications()
|
||||
{
|
||||
var count = 0;
|
||||
foreach (var row in Query("SELECT * FROM MSBTS_Application"))
|
||||
{
|
||||
using (row)
|
||||
{
|
||||
var name = First(row, "Name", "ApplicationName");
|
||||
if (string.IsNullOrWhiteSpace(name)) continue;
|
||||
document.Applications.Add(new ApplicationRecord
|
||||
if (!string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
Name = name,
|
||||
Description = First(row, "Description"),
|
||||
Status = FormatStatus(First(row, "Status")),
|
||||
IsDefault = FormatBoolean(First(row, "IsDefault")),
|
||||
Source = "MSBTS_Application"
|
||||
});
|
||||
document.Applications.Add(new ApplicationRecord { Name = name.Trim() });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (document.Applications.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"MSBTS_Application lieferte keine Anwendungen.");
|
||||
}
|
||||
|
||||
logger.Info(document.Applications.Count
|
||||
+ " BizTalk-Anwendung(en) gefunden.");
|
||||
}
|
||||
|
||||
private void CollectReceivePortMappings()
|
||||
{
|
||||
var count = 0;
|
||||
foreach (var row in Query("SELECT * FROM MSBTS_ReceivePort"))
|
||||
{
|
||||
using (row)
|
||||
{
|
||||
var name = First(row, "Name", "ReceivePortName");
|
||||
var application = First(row, "ApplicationName", "Application");
|
||||
if (string.IsNullOrWhiteSpace(name)) continue;
|
||||
receivePortApplications[name] = application;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
logger.Info(count + " Receive-Port-Zuordnung(en) gelesen.");
|
||||
}
|
||||
|
||||
document.Coverage.Add(new CoverageRecord
|
||||
private void CollectSendPortEndpoints()
|
||||
{
|
||||
var count = 0;
|
||||
foreach (var row in Query("SELECT * FROM MSBTS_SendPort"))
|
||||
{
|
||||
DataSource = "MSBTS_Application",
|
||||
Status = count > 0 ? "Vollständig" : "Fehler",
|
||||
RowCount = count,
|
||||
Required = "Ja",
|
||||
Message = count > 0
|
||||
? "Primärquelle der vollständigen Anwendungsliste."
|
||||
: "Keine Anwendung geliefert."
|
||||
});
|
||||
if (count == 0)
|
||||
using (row)
|
||||
{
|
||||
var application = First(row, "ApplicationName", "Application");
|
||||
AddEndpoint(application, First(
|
||||
row,
|
||||
"PTTransportType",
|
||||
"PrimaryTransportType",
|
||||
"AdapterName",
|
||||
"TransportType"));
|
||||
count++;
|
||||
|
||||
var secondaryAdapter = First(
|
||||
row,
|
||||
"STTransportType",
|
||||
"SecondaryTransportType");
|
||||
if (!string.IsNullOrWhiteSpace(secondaryAdapter))
|
||||
{
|
||||
AddEndpoint(application, secondaryAdapter);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.Info(count + " Send-Endpunkt(e) gefunden.");
|
||||
}
|
||||
|
||||
private void CollectReceiveLocationEndpoints()
|
||||
{
|
||||
var count = 0;
|
||||
foreach (var row in Query("SELECT * FROM MSBTS_ReceiveLocation"))
|
||||
{
|
||||
using (row)
|
||||
{
|
||||
var application = First(row, "ApplicationName", "Application");
|
||||
if (string.IsNullOrWhiteSpace(application))
|
||||
{
|
||||
var receivePort = First(row, "ReceivePortName");
|
||||
receivePortApplications.TryGetValue(receivePort, out application);
|
||||
}
|
||||
|
||||
AddEndpoint(
|
||||
application,
|
||||
First(row, "AdapterName", "TransportType", "PTTransportType"));
|
||||
count++;
|
||||
}
|
||||
}
|
||||
logger.Info(count + " Receive-Endpunkt(e) gefunden.");
|
||||
}
|
||||
|
||||
private void AddEndpoint(string applicationName, string adapterType)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(applicationName))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"MSBTS_Application lieferte keine Anwendungen. Zielserver und Berechtigung prüfen.");
|
||||
"Ein Endpunkt konnte keiner BizTalk-Anwendung zugeordnet werden.");
|
||||
}
|
||||
|
||||
logger.Info(count + " installierte BizTalk-Anwendung(en) gefunden.");
|
||||
}
|
||||
|
||||
private void CollectOptionalArtifacts(ArtifactDescriptor descriptor)
|
||||
{
|
||||
try
|
||||
document.Endpoints.Add(new EndpointRecord
|
||||
{
|
||||
var count = 0;
|
||||
var truncated = false;
|
||||
foreach (var row in Query("SELECT * FROM " + descriptor.ClassName))
|
||||
{
|
||||
using (row)
|
||||
{
|
||||
if (count >= maxRowsPerType)
|
||||
{
|
||||
truncated = true;
|
||||
break;
|
||||
}
|
||||
|
||||
var record = CreateArtifact(row, descriptor.DisplayName);
|
||||
if (string.IsNullOrWhiteSpace(record.Name)) continue;
|
||||
if (record.Type == "Receive Port")
|
||||
{
|
||||
receivePortApplications[record.Name] = record.ApplicationName;
|
||||
}
|
||||
if (record.Type == "Receive Location"
|
||||
&& string.IsNullOrWhiteSpace(record.ApplicationName)
|
||||
&& receivePortApplications.ContainsKey(record.ParentName))
|
||||
{
|
||||
record.ApplicationName = receivePortApplications[record.ParentName];
|
||||
}
|
||||
document.Artifacts.Add(record);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
document.Coverage.Add(new CoverageRecord
|
||||
{
|
||||
DataSource = descriptor.ClassName,
|
||||
Status = truncated ? "Begrenzt" : "Vollständig",
|
||||
RowCount = count,
|
||||
Required = "Nein",
|
||||
Message = truncated
|
||||
? "Detailzeilen auf " + maxRowsPerType + " begrenzt."
|
||||
: "WMI-Klasse erfolgreich gelesen."
|
||||
});
|
||||
logger.Info(descriptor.DisplayName + ": " + count + " Datensatz/Datensätze.");
|
||||
if (truncated)
|
||||
{
|
||||
AddFinding(
|
||||
"Warnung",
|
||||
descriptor.DisplayName,
|
||||
"Detailzeilen wurden bei " + maxRowsPerType + " Einträgen begrenzt.",
|
||||
"MaxRowsPerArtifactType kontrolliert erhöhen und Inventar erneut ausführen.");
|
||||
}
|
||||
}
|
||||
catch (Exception exception) when (IsRecoverableWmiException(exception))
|
||||
{
|
||||
document.Coverage.Add(new CoverageRecord
|
||||
{
|
||||
DataSource = descriptor.ClassName,
|
||||
Status = "Nicht verfügbar",
|
||||
RowCount = 0,
|
||||
Required = "Nein",
|
||||
Message = exception.Message
|
||||
});
|
||||
AddFinding(
|
||||
"Hinweis",
|
||||
descriptor.DisplayName,
|
||||
"Optionale WMI-Klasse konnte nicht gelesen werden: " + exception.Message,
|
||||
"Berechtigung und Verfügbarkeit der WMI-Klasse prüfen; Wert 0 nicht als fachlich bestätigt werten.");
|
||||
logger.Warning(descriptor.ClassName + " nicht verfügbar: " + exception.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private bool CollectOptionalHosts(string className, string category)
|
||||
{
|
||||
try
|
||||
{
|
||||
var count = 0;
|
||||
foreach (var row in Query("SELECT * FROM " + className))
|
||||
{
|
||||
using (row)
|
||||
{
|
||||
var name = First(row, "Name", "HostName", "AdapterName", "RunningServer");
|
||||
if (string.IsNullOrWhiteSpace(name)) continue;
|
||||
document.Hosts.Add(new HostRecord
|
||||
{
|
||||
Category = category,
|
||||
Name = name,
|
||||
Server = First(row, "RunningServer", "ServerName"),
|
||||
Status = FormatStatus(First(row, "ServiceState", "Status")),
|
||||
Type = First(row, "HostType"),
|
||||
WindowsGroup = First(row, "NTGroupName"),
|
||||
Is32BitOnly = FormatBoolean(First(row, "IsHost32BitOnly")),
|
||||
Trusted = FormatBoolean(First(row, "AuthTrusted")),
|
||||
AdapterName = First(row, "AdapterName")
|
||||
});
|
||||
count++;
|
||||
}
|
||||
}
|
||||
document.Coverage.Add(new CoverageRecord
|
||||
{
|
||||
DataSource = className,
|
||||
Status = "Vollständig",
|
||||
RowCount = count,
|
||||
Required = "Nein",
|
||||
Message = "WMI-Klasse erfolgreich gelesen."
|
||||
});
|
||||
logger.Info(category + ": " + count + " Datensatz/Datensätze.");
|
||||
return true;
|
||||
}
|
||||
catch (Exception exception) when (IsRecoverableWmiException(exception))
|
||||
{
|
||||
document.Coverage.Add(new CoverageRecord
|
||||
{
|
||||
DataSource = className,
|
||||
Status = "Nicht verfügbar",
|
||||
RowCount = 0,
|
||||
Required = "Nein",
|
||||
Message = exception.Message
|
||||
});
|
||||
logger.Warning(className + " nicht verfügbar: " + exception.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private ArtifactRecord CreateArtifact(ManagementBaseObject row, string type)
|
||||
{
|
||||
var record = new ArtifactRecord
|
||||
{
|
||||
Type = type,
|
||||
ApplicationName = First(row, "ApplicationName", "Application"),
|
||||
Name = First(
|
||||
row,
|
||||
"Name",
|
||||
"AssemblyName",
|
||||
"FullName",
|
||||
"ReceivePortName",
|
||||
"OrchestrationName"),
|
||||
Status = FormatStatus(First(row, "Status", "ServiceStatus", "IsDisabled")),
|
||||
HostName = First(row, "HostName", "SendHandler", "ReceiveHandler"),
|
||||
AdapterName = First(
|
||||
row,
|
||||
"PTTransportType",
|
||||
"AdapterName",
|
||||
"TransportType"),
|
||||
ParentName = First(row, "ReceivePortName", "SendPortGroupName"),
|
||||
Address = SensitiveDataSanitizer.Sanitize(First(
|
||||
row,
|
||||
"PTAddress",
|
||||
"InboundTransportURL",
|
||||
"Address"))
|
||||
};
|
||||
|
||||
AddProperty(row, record, "Description");
|
||||
AddProperty(row, record, "IsTwoWay");
|
||||
AddProperty(row, record, "IsDynamic");
|
||||
AddProperty(row, record, "IsDisabled");
|
||||
AddProperty(row, record, "ReceivePipeline");
|
||||
AddProperty(row, record, "SendPipeline");
|
||||
AddProperty(row, record, "STTransportType");
|
||||
AddProperty(row, record, "STAddress");
|
||||
AddProperty(row, record, "AssemblyName");
|
||||
AddProperty(row, record, "FullName");
|
||||
AddProperty(row, record, "TargetNameSpace");
|
||||
AddProperty(row, record, "RootName");
|
||||
AddProperty(row, record, "Tracking");
|
||||
return record;
|
||||
}
|
||||
|
||||
private static IEnumerable<ArtifactDescriptor> ArtifactDescriptors()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new ArtifactDescriptor("MSBTS_Orchestration", "Orchestrierung"),
|
||||
new ArtifactDescriptor("MSBTS_SendPort", "Send Port"),
|
||||
new ArtifactDescriptor("MSBTS_SendPortGroup", "Send Port Group"),
|
||||
new ArtifactDescriptor("MSBTS_ReceivePort", "Receive Port"),
|
||||
new ArtifactDescriptor("MSBTS_ReceiveLocation", "Receive Location"),
|
||||
new ArtifactDescriptor("MSBTS_Assembly", "Assembly"),
|
||||
new ArtifactDescriptor("MSBTS_Schema", "Schema"),
|
||||
new ArtifactDescriptor("MSBTS_Map", "Map"),
|
||||
new ArtifactDescriptor("MSBTS_Pipeline", "Pipeline")
|
||||
};
|
||||
ApplicationName = applicationName.Trim(),
|
||||
AdapterType = string.IsNullOrWhiteSpace(adapterType)
|
||||
? "Unbekannt"
|
||||
: adapterType.Trim()
|
||||
});
|
||||
}
|
||||
|
||||
private List<ManagementObject> Query(string query)
|
||||
@@ -304,38 +177,15 @@ namespace BizTalkApplicationCatalog.Collectors
|
||||
Rewindable = false,
|
||||
Timeout = timeout
|
||||
};
|
||||
using (var searcher = new ManagementObjectSearcher(scope, new ObjectQuery(query), options))
|
||||
using (var searcher = new ManagementObjectSearcher(
|
||||
scope,
|
||||
new ObjectQuery(query),
|
||||
options))
|
||||
{
|
||||
return searcher.Get().Cast<ManagementObject>().ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private void AddFinding(string severity, string area, string message, string action)
|
||||
{
|
||||
document.Findings.Add(new Finding
|
||||
{
|
||||
Severity = severity,
|
||||
Area = area,
|
||||
Message = message,
|
||||
RecommendedAction = action
|
||||
});
|
||||
}
|
||||
|
||||
private static void AddProperty(
|
||||
ManagementBaseObject row,
|
||||
ArtifactRecord target,
|
||||
string propertyName)
|
||||
{
|
||||
var value = First(row, propertyName);
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
target.Properties.Add(new NameValueRecord(
|
||||
propertyName,
|
||||
SensitiveDataSanitizer.RedactProperty(propertyName, value),
|
||||
"WMI"));
|
||||
}
|
||||
}
|
||||
|
||||
private static string First(ManagementBaseObject row, params string[] names)
|
||||
{
|
||||
foreach (var name in names)
|
||||
@@ -344,78 +194,15 @@ namespace BizTalkApplicationCatalog.Collectors
|
||||
{
|
||||
var value = row[name];
|
||||
if (value == null) continue;
|
||||
var text = ConvertValue(value);
|
||||
var text = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
if (!string.IsNullOrWhiteSpace(text)) return text;
|
||||
}
|
||||
catch (Exception exception) when (IsRecoverableWmiException(exception))
|
||||
catch (ManagementException)
|
||||
{
|
||||
// Die Eigenschaft ist in dieser BizTalk-Version/Klasse nicht vorhanden.
|
||||
// WMI-Properties unterscheiden sich zwischen Providerständen.
|
||||
}
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private static string ConvertValue(object value)
|
||||
{
|
||||
var array = value as Array;
|
||||
if (array != null && !(value is byte[]))
|
||||
{
|
||||
return string.Join(", ", array.Cast<object>().Select(item =>
|
||||
Convert.ToString(item, CultureInfo.InvariantCulture)));
|
||||
}
|
||||
return Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty;
|
||||
}
|
||||
|
||||
private static bool IsRecoverableWmiException(Exception exception)
|
||||
{
|
||||
return exception is ManagementException
|
||||
|| exception is COMException
|
||||
|| exception is UnauthorizedAccessException;
|
||||
}
|
||||
|
||||
private static string FormatStatus(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) return "Unbekannt";
|
||||
switch (value.Trim())
|
||||
{
|
||||
case "1": return "Gestoppt (1)";
|
||||
case "2": return "Gestartet (2)";
|
||||
case "3": return "Teilweise gestartet (3)";
|
||||
case "True": return "Ja";
|
||||
case "False": return "Nein";
|
||||
default: return value;
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatBoolean(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) return "Unbekannt";
|
||||
if (value.Equals("True", StringComparison.OrdinalIgnoreCase) || value == "1") return "Ja";
|
||||
if (value.Equals("False", StringComparison.OrdinalIgnoreCase) || value == "0") return "Nein";
|
||||
return value;
|
||||
}
|
||||
|
||||
private static int CompareArtifacts(ArtifactRecord left, ArtifactRecord right)
|
||||
{
|
||||
var application = string.Compare(
|
||||
left.ApplicationName, right.ApplicationName, StringComparison.OrdinalIgnoreCase);
|
||||
if (application != 0) return application;
|
||||
var type = string.Compare(left.Type, right.Type, StringComparison.OrdinalIgnoreCase);
|
||||
return type != 0
|
||||
? type
|
||||
: string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private sealed class ArtifactDescriptor
|
||||
{
|
||||
public ArtifactDescriptor(string className, string displayName)
|
||||
{
|
||||
ClassName = className;
|
||||
DisplayName = displayName;
|
||||
}
|
||||
|
||||
public string ClassName { get; private set; }
|
||||
public string DisplayName { get; private set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,223 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Management;
|
||||
using System.Reflection;
|
||||
using Microsoft.Win32;
|
||||
using BizTalkApplicationCatalog.Configuration;
|
||||
using BizTalkApplicationCatalog.Models;
|
||||
|
||||
namespace BizTalkApplicationCatalog.Collectors
|
||||
{
|
||||
/// <summary>
|
||||
/// Erfasst lokale Windows-, BizTalk- und Gruppenmetadaten ausschließlich lesend.
|
||||
/// </summary>
|
||||
internal sealed class SystemCollector
|
||||
{
|
||||
private const string ProductKey = @"SOFTWARE\Microsoft\BizTalk Server\3.0";
|
||||
private const string AdministrationKey = ProductKey + @"\Administration";
|
||||
private readonly CommandLineOptions options;
|
||||
|
||||
public SystemCollector(CommandLineOptions options)
|
||||
{
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public void Collect(InventoryDocument document)
|
||||
{
|
||||
document.ComputerName = Environment.MachineName;
|
||||
document.ToolVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
Add(document, "Ausführender Benutzer", Environment.UserDomainName + "\\" + Environment.UserName, "Prozess");
|
||||
Add(document, "64-Bit-Betriebssystem", Environment.Is64BitOperatingSystem ? "Ja" : "Nein", "Prozess");
|
||||
Add(document, "64-Bit-Prozess", Environment.Is64BitProcess ? "Ja" : "Nein", "Prozess");
|
||||
Add(document, ".NET Runtime", Environment.Version.ToString(), "Prozess");
|
||||
|
||||
CollectOperatingSystem(document);
|
||||
CollectRegistry(document);
|
||||
CollectGroupSetting(document);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(options.ManagementServer))
|
||||
{
|
||||
document.ManagementServer = options.ManagementServer;
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(options.ManagementDatabase))
|
||||
{
|
||||
document.ManagementDatabase = options.ManagementDatabase;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(document.ManagementDatabase))
|
||||
{
|
||||
document.ManagementDatabase = "BizTalkMgmtDb";
|
||||
}
|
||||
|
||||
Add(document, "BizTalk Management SQL Server", Unknown(document.ManagementServer), "Ermittelt/Parameter");
|
||||
Add(document, "BizTalk Management Database", document.ManagementDatabase, "Ermittelt/Parameter");
|
||||
|
||||
if (document.EnvironmentName != "ACC" && document.EnvironmentName != "PROD")
|
||||
{
|
||||
document.Findings.Add(new Finding
|
||||
{
|
||||
Severity = "Warnung",
|
||||
Area = "Aufruf",
|
||||
Message = "Die Umgebung ist weder ACC noch PROD: " + document.EnvironmentName,
|
||||
RecommendedAction = "Umgebung und Zielserver vor der Ablage bestätigen."
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void CollectOperatingSystem(InventoryDocument document)
|
||||
{
|
||||
using (var searcher = new ManagementObjectSearcher(
|
||||
"root\\cimv2",
|
||||
"SELECT Caption,Version,BuildNumber,OSArchitecture,LastBootUpTime FROM Win32_OperatingSystem"))
|
||||
{
|
||||
foreach (ManagementObject row in searcher.Get())
|
||||
{
|
||||
using (row)
|
||||
{
|
||||
Add(document, "Betriebssystem", Value(row, "Caption"), "Win32_OperatingSystem");
|
||||
Add(document, "Windows-Version", Value(row, "Version"), "Win32_OperatingSystem");
|
||||
Add(document, "Windows-Build", Value(row, "BuildNumber"), "Win32_OperatingSystem");
|
||||
Add(document, "Architektur", Value(row, "OSArchitecture"), "Win32_OperatingSystem");
|
||||
Add(document, "Letzter Systemstart", WmiDate(Value(row, "LastBootUpTime")), "Win32_OperatingSystem");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void CollectRegistry(InventoryDocument document)
|
||||
{
|
||||
foreach (var view in new[] { RegistryView.Registry64, RegistryView.Registry32 })
|
||||
{
|
||||
using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, view))
|
||||
using (var product = baseKey.OpenSubKey(ProductKey, false))
|
||||
{
|
||||
if (product != null)
|
||||
{
|
||||
AddRegistry(document, product, "ProductName", "BizTalk Produktname", view);
|
||||
AddRegistry(document, product, "ProductVersion", "BizTalk Produktversion", view);
|
||||
AddRegistry(document, product, "Edition", "BizTalk Edition", view);
|
||||
}
|
||||
}
|
||||
|
||||
using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, view))
|
||||
using (var administration = baseKey.OpenSubKey(AdministrationKey, false))
|
||||
{
|
||||
if (administration == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(document.ManagementServer))
|
||||
{
|
||||
document.ManagementServer = FirstRegistry(
|
||||
administration, "MgmtDBServer", "ManagementDBServer");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(document.ManagementDatabase))
|
||||
{
|
||||
document.ManagementDatabase = FirstRegistry(
|
||||
administration, "MgmtDBName", "ManagementDBName");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void CollectGroupSetting(InventoryDocument document)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var searcher = new ManagementObjectSearcher(
|
||||
@"root\MicrosoftBizTalkServer", "SELECT * FROM MSBTS_GroupSetting"))
|
||||
{
|
||||
foreach (ManagementObject row in searcher.Get())
|
||||
{
|
||||
using (row)
|
||||
{
|
||||
var server = Value(row, "MgmtDbServerName");
|
||||
var database = Value(row, "MgmtDbName");
|
||||
if (!string.IsNullOrWhiteSpace(server)) document.ManagementServer = server;
|
||||
if (!string.IsNullOrWhiteSpace(database)) document.ManagementDatabase = database;
|
||||
Add(document, "BizTalk Gruppenname", Value(row, "Name"), "MSBTS_GroupSetting");
|
||||
Add(document, "BizTalk Administratorengruppe", Value(row, "BizTalkAdministratorGroup"), "MSBTS_GroupSetting");
|
||||
Add(document, "BizTalk Operatorengruppe", Value(row, "BizTalkOperatorGroup"), "MSBTS_GroupSetting");
|
||||
Add(document, "Enterprise SSO Server", Value(row, "SSOServerName"), "MSBTS_GroupSetting");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ManagementException exception)
|
||||
{
|
||||
document.Findings.Add(new Finding
|
||||
{
|
||||
Severity = "Hinweis",
|
||||
Area = "MSBTS_GroupSetting",
|
||||
Message = "Gruppenmetadaten konnten nicht vollständig gelesen werden: " + exception.Message,
|
||||
RecommendedAction = "WMI-Berechtigung prüfen; die Anwendungserfassung läuft unabhängig weiter."
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddRegistry(
|
||||
InventoryDocument document,
|
||||
RegistryKey key,
|
||||
string valueName,
|
||||
string displayName,
|
||||
RegistryView view)
|
||||
{
|
||||
var value = Convert.ToString(key.GetValue(valueName), CultureInfo.InvariantCulture);
|
||||
if (!string.IsNullOrWhiteSpace(value)
|
||||
&& !document.SystemProperties.Exists(item => item.Name == displayName))
|
||||
{
|
||||
Add(document, displayName, value, "Registry " + view);
|
||||
}
|
||||
}
|
||||
|
||||
private static string FirstRegistry(RegistryKey key, params string[] names)
|
||||
{
|
||||
foreach (var name in names)
|
||||
{
|
||||
var value = Convert.ToString(key.GetValue(name), CultureInfo.InvariantCulture);
|
||||
if (!string.IsNullOrWhiteSpace(value)) return value.Trim();
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private static string Value(ManagementBaseObject row, string property)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Convert.ToString(row[property], CultureInfo.InvariantCulture) ?? string.Empty;
|
||||
}
|
||||
catch (ManagementException)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private static void Add(InventoryDocument document, string name, string value, string source)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
document.SystemProperties.Add(new NameValueRecord(name, value, source));
|
||||
}
|
||||
}
|
||||
|
||||
private static string WmiDate(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) return "Unbekannt";
|
||||
try
|
||||
{
|
||||
return ManagementDateTimeConverter.ToDateTime(value)
|
||||
.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private static string Unknown(string value)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(value) ? "Nicht ermittelt" : value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,6 @@ using System.Text.RegularExpressions;
|
||||
|
||||
namespace BizTalkApplicationCatalog.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Validiert die Kommandozeile und stellt ausschließlich normalisierte Werte bereit.
|
||||
/// </summary>
|
||||
internal sealed class CommandLineOptions
|
||||
{
|
||||
private static readonly Regex UnsafeEnvironment =
|
||||
@@ -14,14 +11,12 @@ namespace BizTalkApplicationCatalog.Configuration
|
||||
|
||||
public string EnvironmentName { get; private set; }
|
||||
public string OutputDirectory { get; private set; }
|
||||
public string ManagementServer { get; private set; }
|
||||
public string ManagementDatabase { get; private set; }
|
||||
public string AccSnapshotPath { get; private set; }
|
||||
public string ProdSnapshotPath { get; private set; }
|
||||
public bool MergeMode { get; private set; }
|
||||
public bool SelfTest { get; private set; }
|
||||
public bool ShowHelp { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Parst die Argumente. Unbekannte oder unvollständige Optionen werden abgelehnt.
|
||||
/// </summary>
|
||||
public static CommandLineOptions Parse(string[] args)
|
||||
{
|
||||
var result = new CommandLineOptions();
|
||||
@@ -36,11 +31,14 @@ namespace BizTalkApplicationCatalog.Configuration
|
||||
case "--output":
|
||||
result.OutputDirectory = Value(args, ref index, argument);
|
||||
break;
|
||||
case "--management-server":
|
||||
result.ManagementServer = Value(args, ref index, argument);
|
||||
case "--merge":
|
||||
result.MergeMode = true;
|
||||
break;
|
||||
case "--management-database":
|
||||
result.ManagementDatabase = Value(args, ref index, argument);
|
||||
case "--acc-json":
|
||||
result.AccSnapshotPath = Value(args, ref index, argument);
|
||||
break;
|
||||
case "--prod-json":
|
||||
result.ProdSnapshotPath = Value(args, ref index, argument);
|
||||
break;
|
||||
case "--self-test":
|
||||
result.SelfTest = true;
|
||||
@@ -55,31 +53,54 @@ namespace BizTalkApplicationCatalog.Configuration
|
||||
}
|
||||
}
|
||||
|
||||
if (result.SelfTest || result.ShowHelp)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
if (result.SelfTest || result.ShowHelp) return result;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(result.EnvironmentName))
|
||||
if (result.MergeMode)
|
||||
{
|
||||
throw new ArgumentException("--environment fehlt.");
|
||||
if (!string.IsNullOrWhiteSpace(result.EnvironmentName))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"--environment darf nicht mit --merge kombiniert werden.");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(result.AccSnapshotPath)
|
||||
|| string.IsNullOrWhiteSpace(result.ProdSnapshotPath))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"--merge benötigt --acc-json und --prod-json.");
|
||||
}
|
||||
result.AccSnapshotPath = FullPath(result.AccSnapshotPath);
|
||||
result.ProdSnapshotPath = FullPath(result.ProdSnapshotPath);
|
||||
}
|
||||
|
||||
result.EnvironmentName = UnsafeEnvironment.Replace(
|
||||
result.EnvironmentName.Trim().ToUpperInvariant(), "-").Trim('-');
|
||||
if (result.EnvironmentName.Length == 0)
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("--environment enthält keinen gültigen Namen.");
|
||||
if (!string.IsNullOrWhiteSpace(result.AccSnapshotPath)
|
||||
|| !string.IsNullOrWhiteSpace(result.ProdSnapshotPath))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"--acc-json und --prod-json sind nur mit --merge zulässig.");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(result.EnvironmentName))
|
||||
{
|
||||
throw new ArgumentException("--environment fehlt.");
|
||||
}
|
||||
result.EnvironmentName = UnsafeEnvironment.Replace(
|
||||
result.EnvironmentName.Trim().ToUpperInvariant(),
|
||||
"-").Trim('-');
|
||||
if (result.EnvironmentName != "ACC" && result.EnvironmentName != "PROD")
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"--environment muss ACC oder PROD sein.");
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(result.OutputDirectory))
|
||||
{
|
||||
result.OutputDirectory = Path.Combine(
|
||||
Environment.CurrentDirectory, "BizTalk-Anwendungsinventar", result.EnvironmentName);
|
||||
Environment.CurrentDirectory,
|
||||
"BizTalk-Anwendungskatalog",
|
||||
result.MergeMode ? "ACC-PROD" : result.EnvironmentName);
|
||||
}
|
||||
|
||||
result.OutputDirectory = Path.GetFullPath(
|
||||
Environment.ExpandEnvironmentVariables(result.OutputDirectory));
|
||||
result.OutputDirectory = FullPath(result.OutputDirectory);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -87,29 +108,40 @@ namespace BizTalkApplicationCatalog.Configuration
|
||||
{
|
||||
return string.Join(Environment.NewLine, new[]
|
||||
{
|
||||
"BEW BizTalk Application Catalog",
|
||||
"BEW BizTalk Application Catalog – Phase 1",
|
||||
string.Empty,
|
||||
"Aufruf:",
|
||||
" BizTalkApplicationCatalog.exe --environment ACC [--output PFAD]",
|
||||
" BizTalkApplicationCatalog.exe --environment PROD [--output PFAD]",
|
||||
"Lokalen Snapshot und lokale Excel-Sicht erzeugen:",
|
||||
" BizTalkApplicationCatalog.exe --environment ACC --output PFAD",
|
||||
" BizTalkApplicationCatalog.exe --environment PROD --output PFAD",
|
||||
string.Empty,
|
||||
"ACC und PROD zur gemeinsamen Excel-Sicht zusammenführen:",
|
||||
" BizTalkApplicationCatalog.exe --merge --acc-json ACC.json --prod-json PROD.json --output PFAD",
|
||||
string.Empty,
|
||||
"Optionen:",
|
||||
" --management-server NAME Optionaler SQL-Server-Hinweis.",
|
||||
" --management-database NAME Optionale Management-Datenbank.",
|
||||
" --self-test Prüft XLSX-Struktur und Kernlogik ohne BizTalk.",
|
||||
" --help Diese Hilfe."
|
||||
" --environment ACC|PROD Lokal zu inventarisierende Umgebung.",
|
||||
" --output PFAD Zielordner für XLSX, JSON und Log.",
|
||||
" --merge Führt zwei vollständige JSON-Snapshots zusammen.",
|
||||
" --acc-json PFAD Vollständiger ACC-Snapshot.",
|
||||
" --prod-json PFAD Vollständiger PROD-Snapshot.",
|
||||
" --self-test Prüft JSON, Merge und XLSX ohne BizTalk.",
|
||||
" --help Diese Hilfe."
|
||||
});
|
||||
}
|
||||
|
||||
private static string Value(string[] args, ref int index, string option)
|
||||
{
|
||||
index++;
|
||||
if (index >= args.Length || args[index].StartsWith("--", StringComparison.Ordinal))
|
||||
if (index >= args.Length
|
||||
|| args[index].StartsWith("--", StringComparison.Ordinal))
|
||||
{
|
||||
throw new ArgumentException("Wert für " + option + " fehlt.");
|
||||
}
|
||||
|
||||
return args[index];
|
||||
}
|
||||
|
||||
private static string FullPath(string value)
|
||||
{
|
||||
return Path.GetFullPath(Environment.ExpandEnvironmentVariables(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
using BizTalkApplicationCatalog.Models;
|
||||
using BizTalkApplicationCatalog.Persistence;
|
||||
using BizTalkApplicationCatalog.Reporting;
|
||||
|
||||
namespace BizTalkApplicationCatalog.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// Prüft die zentrale Berichtslogik ohne Zugriff auf Windows- oder BizTalk-WMI.
|
||||
/// Prüft Snapshot, Merge und Excel-Ausgabe ohne BizTalk-Zugriff.
|
||||
/// </summary>
|
||||
internal static class SelfTestRunner
|
||||
{
|
||||
public static void Run(string outputPath = null)
|
||||
{
|
||||
Assert(SensitiveDataSanitizer.Sanitize(
|
||||
"https://host/path?password=secret&client=100")
|
||||
== "https://host/path?password=[REDACTED]&client=100",
|
||||
"Secret-Redaktion ist fehlerhaft.");
|
||||
|
||||
var keepOutput = !string.IsNullOrWhiteSpace(outputPath);
|
||||
var directory = keepOutput
|
||||
? Path.GetDirectoryName(Path.GetFullPath(outputPath))
|
||||
@@ -29,11 +24,26 @@ namespace BizTalkApplicationCatalog.Infrastructure
|
||||
Path.GetTempPath(),
|
||||
"BizTalkApplicationCatalogTests-" + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(directory);
|
||||
var path = keepOutput ? Path.GetFullPath(outputPath) : Path.Combine(directory, "test.xlsx");
|
||||
var path = keepOutput
|
||||
? Path.GetFullPath(outputPath)
|
||||
: Path.Combine(directory, "phase1-test.xlsx");
|
||||
var jsonPath = Path.Combine(
|
||||
directory,
|
||||
"phase1-test-" + Guid.NewGuid().ToString("N") + ".json");
|
||||
|
||||
try
|
||||
{
|
||||
var document = SampleDocument();
|
||||
new XlsxReportWriter().Write(document, path);
|
||||
var acc = AccSnapshot();
|
||||
SnapshotStore.Save(acc, jsonPath);
|
||||
var loadedAcc = SnapshotStore.Load(jsonPath);
|
||||
SnapshotStore.ValidateForMerge(loadedAcc, "ACC");
|
||||
Assert(
|
||||
loadedAcc.Applications.Count == 2,
|
||||
"JSON-Roundtrip hat Anwendungen verloren.");
|
||||
|
||||
var prod = ProdSnapshot();
|
||||
SnapshotStore.ValidateForMerge(prod, "PROD");
|
||||
new XlsxReportWriter().Write(new[] { loadedAcc, prod }, path);
|
||||
Assert(File.Exists(path), "XLSX-Datei wurde nicht erzeugt.");
|
||||
|
||||
using (var archive = ZipFile.OpenRead(path))
|
||||
@@ -46,19 +56,18 @@ namespace BizTalkApplicationCatalog.Infrastructure
|
||||
"docProps/app.xml",
|
||||
"xl/workbook.xml",
|
||||
"xl/_rels/workbook.xml.rels",
|
||||
"xl/styles.xml"
|
||||
"xl/styles.xml",
|
||||
"xl/worksheets/sheet1.xml"
|
||||
};
|
||||
foreach (var name in required)
|
||||
{
|
||||
Assert(archive.GetEntry(name) != null, "XLSX-Part fehlt: " + name);
|
||||
}
|
||||
|
||||
for (var index = 1; index <= 10; index++)
|
||||
{
|
||||
Assert(
|
||||
archive.GetEntry("xl/worksheets/sheet" + index + ".xml") != null,
|
||||
"Arbeitsblatt fehlt: " + index);
|
||||
archive.GetEntry(name) != null,
|
||||
"XLSX-Part fehlt: " + name);
|
||||
}
|
||||
Assert(
|
||||
archive.GetEntry("xl/worksheets/sheet2.xml") == null,
|
||||
"Die Arbeitsmappe enthält mehr als ein Datenblatt.");
|
||||
|
||||
foreach (var entry in archive.Entries.Where(item =>
|
||||
item.FullName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase)
|
||||
@@ -74,42 +83,60 @@ namespace BizTalkApplicationCatalog.Infrastructure
|
||||
XNamespace spreadsheet =
|
||||
"http://schemas.openxmlformats.org/spreadsheetml/2006/main";
|
||||
var names = workbook.Descendants(spreadsheet + "sheet")
|
||||
.Select(item => (string)item.Attribute("name")).ToList();
|
||||
Assert(names.Contains("Anwendungen"), "Anwendungsblatt fehlt.");
|
||||
Assert(names.Contains("Abdeckung"), "Abdeckungsblatt fehlt.");
|
||||
.Select(item => (string)item.Attribute("name"))
|
||||
.ToList();
|
||||
Assert(
|
||||
names.SequenceEqual(new[] { "Phase 1" }),
|
||||
"Es wird nicht exakt das Blatt 'Phase 1' erzeugt.");
|
||||
|
||||
var contentTypes = LoadXml(archive, "[Content_Types].xml");
|
||||
XNamespace contentTypeNamespace =
|
||||
"http://schemas.openxmlformats.org/package/2006/content-types";
|
||||
Assert(
|
||||
contentTypes.Root.Elements(contentTypeNamespace + "Override").Count() == 14,
|
||||
"Content-Type-Overrides sind unvollständig oder im falschen Namespace.");
|
||||
contentTypes.Root
|
||||
.Elements(contentTypeNamespace + "Override")
|
||||
.Count() == 5,
|
||||
"Content-Type-Overrides sind unvollständig.");
|
||||
|
||||
var relationships = LoadXml(archive, "_rels/.rels");
|
||||
XNamespace relationshipNamespace =
|
||||
"http://schemas.openxmlformats.org/package/2006/relationships";
|
||||
var worksheet = LoadXml(
|
||||
archive,
|
||||
"xl/worksheets/sheet1.xml");
|
||||
Assert(
|
||||
relationships.Root.Elements(relationshipNamespace + "Relationship").Count() == 3,
|
||||
"Paketbeziehungen sind unvollständig oder im falschen Namespace.");
|
||||
worksheet.Descendants(spreadsheet + "mergeCell")
|
||||
.Select(item => (string)item.Attribute("ref"))
|
||||
.Contains("A1:G1"),
|
||||
"Titelzeile ist nicht zusammengeführt.");
|
||||
Assert(
|
||||
(string)worksheet
|
||||
.Descendants(spreadsheet + "autoFilter")
|
||||
.Single()
|
||||
.Attribute("ref") == "A2:G5",
|
||||
"Filterbereich ist falsch.");
|
||||
|
||||
var combinedText = new StringBuilder();
|
||||
foreach (var entry in archive.Entries.Where(item =>
|
||||
item.FullName.StartsWith("xl/worksheets/", StringComparison.Ordinal)))
|
||||
string text;
|
||||
using (var reader = new StreamReader(
|
||||
archive.GetEntry("xl/worksheets/sheet1.xml").Open(),
|
||||
Encoding.UTF8))
|
||||
{
|
||||
using (var reader = new StreamReader(entry.Open(), Encoding.UTF8))
|
||||
{
|
||||
combinedText.Append(reader.ReadToEnd());
|
||||
}
|
||||
text = reader.ReadToEnd();
|
||||
}
|
||||
Assert(!combinedText.ToString().Contains("supersecret"), "XLSX enthält Testkennwort.");
|
||||
Assert(combinedText.ToString().Contains("[REDACTED]"), "Redaktionsmarker fehlt.");
|
||||
Assert(text.Contains("BizTalk-Anwendung in ACC"), "ACC-Spalte fehlt.");
|
||||
Assert(text.Contains("BizTalk-Anwendung in PRD"), "PRD-Spalte fehlt.");
|
||||
Assert(text.Contains("FILE (1x) / SFTP (1x)"), "Adapteraggregation ist falsch.");
|
||||
Assert(text.Contains("Nur in ACC vorhanden"), "ACC-Hinweis fehlt.");
|
||||
Assert(text.Contains("Nur in PRD vorhanden"), "PRD-Hinweis fehlt.");
|
||||
Assert(text.Contains("Gesamt: 3 BizTalk-Anwendungen"), "Summenzeile ist falsch.");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!keepOutput && Directory.Exists(directory)) Directory.Delete(directory, true);
|
||||
if (File.Exists(jsonPath)) File.Delete(jsonPath);
|
||||
if (!keepOutput && Directory.Exists(directory))
|
||||
{
|
||||
Directory.Delete(directory, true);
|
||||
}
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
@@ -120,58 +147,56 @@ namespace BizTalkApplicationCatalog.Infrastructure
|
||||
}
|
||||
}
|
||||
|
||||
private static InventoryDocument SampleDocument()
|
||||
private static CatalogSnapshot AccSnapshot()
|
||||
{
|
||||
var document = new InventoryDocument
|
||||
var snapshot = Snapshot("ACC", "BIZTALK-ACC");
|
||||
snapshot.Applications.Add(Application(
|
||||
"OrderProcessing",
|
||||
new AdapterCount { AdapterType = "FILE", Count = 1 },
|
||||
new AdapterCount { AdapterType = "SFTP", Count = 1 }));
|
||||
snapshot.Applications.Add(Application(
|
||||
"AccOnly",
|
||||
new AdapterCount { AdapterType = "FILE", Count = 1 }));
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
private static CatalogSnapshot ProdSnapshot()
|
||||
{
|
||||
var snapshot = Snapshot("PROD", "BIZTALK-PROD");
|
||||
snapshot.Applications.Add(Application(
|
||||
"OrderProcessing",
|
||||
new AdapterCount { AdapterType = "FILE", Count = 1 },
|
||||
new AdapterCount { AdapterType = "SFTP", Count = 1 }));
|
||||
snapshot.Applications.Add(Application(
|
||||
"ProdOnly",
|
||||
new AdapterCount { AdapterType = "SFTP", Count = 1 }));
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
private static CatalogSnapshot Snapshot(
|
||||
string environment,
|
||||
string computer)
|
||||
{
|
||||
return new CatalogSnapshot
|
||||
{
|
||||
EnvironmentName = "ACC",
|
||||
ComputerName = "BIZTALK-ACC",
|
||||
ToolVersion = "1.0.0.0",
|
||||
ManagementServer = "SQL-ACC",
|
||||
ManagementDatabase = "BizTalkMgmtDb",
|
||||
StartedUtc = DateTime.UtcNow.AddSeconds(-1),
|
||||
CompletedUtc = DateTime.UtcNow
|
||||
EnvironmentName = environment,
|
||||
ComputerName = computer,
|
||||
CreatedUtc = DateTime.UtcNow.ToString("o"),
|
||||
IsComplete = true
|
||||
};
|
||||
document.SystemProperties.Add(new NameValueRecord(
|
||||
"Betriebssystem", "Windows Server 2019", "Self-Test"));
|
||||
document.Applications.Add(new ApplicationRecord
|
||||
}
|
||||
|
||||
private static CatalogApplication Application(
|
||||
string name,
|
||||
params AdapterCount[] adapters)
|
||||
{
|
||||
var result = new CatalogApplication
|
||||
{
|
||||
Name = "OrderProcessing",
|
||||
Description = "Aufträge & Sonderzeichen <Test>",
|
||||
Status = "Gestartet (2)",
|
||||
IsDefault = "Nein",
|
||||
Source = "Self-Test"
|
||||
});
|
||||
var port = new ArtifactRecord
|
||||
{
|
||||
Type = "Send Port",
|
||||
ApplicationName = "OrderProcessing",
|
||||
Name = "Send_Orders",
|
||||
Status = "Gestartet (2)",
|
||||
HostName = "SendHost",
|
||||
AdapterName = "WCF-Custom",
|
||||
Address = SensitiveDataSanitizer.Sanitize(
|
||||
"https://service/orders?password=supersecret")
|
||||
Name = name,
|
||||
EndpointCount = adapters.Sum(item => item.Count)
|
||||
};
|
||||
port.Properties.Add(new NameValueRecord("IsTwoWay", "True", "Self-Test"));
|
||||
document.Artifacts.Add(port);
|
||||
document.Coverage.Add(new CoverageRecord
|
||||
{
|
||||
DataSource = "MSBTS_Application",
|
||||
Status = "Vollständig",
|
||||
RowCount = 1,
|
||||
Required = "Ja",
|
||||
Message = "Self-Test"
|
||||
});
|
||||
document.SectionStatuses.Add(new SectionStatus
|
||||
{
|
||||
Name = "Self-Test",
|
||||
Status = "Erfolgreich",
|
||||
Message = "OK",
|
||||
Required = true,
|
||||
DurationMilliseconds = 1
|
||||
});
|
||||
return document;
|
||||
result.AdapterCounts.AddRange(adapters);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static XDocument LoadXml(ZipArchive archive, string name)
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace BizTalkApplicationCatalog.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// Verhindert, dass versehentlich Kennwörter oder Token aus WMI-Textwerten in den Bericht gelangen.
|
||||
/// </summary>
|
||||
internal static class SensitiveDataSanitizer
|
||||
{
|
||||
private static readonly Regex NamedSecret = new Regex(
|
||||
@"(?i)(password|passwd|pwd|secret|token|clientsecret|accesskey)\s*([=:])\s*([^;&\s""']+)",
|
||||
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||||
|
||||
public static string Sanitize(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return NamedSecret.Replace(value, match =>
|
||||
match.Groups[1].Value + match.Groups[2].Value + "[REDACTED]");
|
||||
}
|
||||
|
||||
public static string RedactProperty(string name, string value)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(name)
|
||||
&& (name.IndexOf("password", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| name.IndexOf("secret", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| name.IndexOf("token", StringComparison.OrdinalIgnoreCase) >= 0))
|
||||
{
|
||||
return "[REDACTED]";
|
||||
}
|
||||
|
||||
return Sanitize(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace BizTalkApplicationCatalog.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Enthält die vollständig normalisierten Ergebnisse eines Inventarlaufs.
|
||||
/// Arbeitsmodell eines lokalen, ausschließlich lesenden Inventarlaufs.
|
||||
/// </summary>
|
||||
internal sealed class InventoryDocument
|
||||
{
|
||||
public InventoryDocument()
|
||||
{
|
||||
SystemProperties = new List<NameValueRecord>();
|
||||
Applications = new List<ApplicationRecord>();
|
||||
Artifacts = new List<ArtifactRecord>();
|
||||
Hosts = new List<HostRecord>();
|
||||
Coverage = new List<CoverageRecord>();
|
||||
Endpoints = new List<EndpointRecord>();
|
||||
Findings = new List<Finding>();
|
||||
SectionStatuses = new List<SectionStatus>();
|
||||
}
|
||||
|
||||
public string EnvironmentName { get; set; }
|
||||
public string ComputerName { get; set; }
|
||||
public string ToolVersion { get; set; }
|
||||
public string ManagementServer { get; set; }
|
||||
public string ManagementDatabase { get; set; }
|
||||
public DateTime StartedUtc { get; set; }
|
||||
public DateTime CompletedUtc { get; set; }
|
||||
public List<NameValueRecord> SystemProperties { get; private set; }
|
||||
public List<ApplicationRecord> Applications { get; private set; }
|
||||
public List<ArtifactRecord> Artifacts { get; private set; }
|
||||
public List<HostRecord> Hosts { get; private set; }
|
||||
public List<CoverageRecord> Coverage { get; private set; }
|
||||
public List<EndpointRecord> Endpoints { get; private set; }
|
||||
public List<Finding> Findings { get; private set; }
|
||||
public List<SectionStatus> SectionStatuses { get; private set; }
|
||||
|
||||
@@ -48,57 +40,12 @@ namespace BizTalkApplicationCatalog.Models
|
||||
internal sealed class ApplicationRecord
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Status { get; set; }
|
||||
public string IsDefault { get; set; }
|
||||
public string Source { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class ArtifactRecord
|
||||
internal sealed class EndpointRecord
|
||||
{
|
||||
public ArtifactRecord()
|
||||
{
|
||||
Properties = new List<NameValueRecord>();
|
||||
}
|
||||
|
||||
public string Type { get; set; }
|
||||
public string ApplicationName { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Status { get; set; }
|
||||
public string HostName { get; set; }
|
||||
public string AdapterName { get; set; }
|
||||
public string ParentName { get; set; }
|
||||
public string Address { get; set; }
|
||||
public List<NameValueRecord> Properties { get; private set; }
|
||||
|
||||
public string Property(string name)
|
||||
{
|
||||
var match = Properties.FirstOrDefault(item =>
|
||||
string.Equals(item.Name, name, StringComparison.OrdinalIgnoreCase));
|
||||
return match == null ? string.Empty : match.Value;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class HostRecord
|
||||
{
|
||||
public string Category { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Server { get; set; }
|
||||
public string Status { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string WindowsGroup { get; set; }
|
||||
public string Is32BitOnly { get; set; }
|
||||
public string Trusted { get; set; }
|
||||
public string AdapterName { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class CoverageRecord
|
||||
{
|
||||
public string DataSource { get; set; }
|
||||
public string Status { get; set; }
|
||||
public int RowCount { get; set; }
|
||||
public string Required { get; set; }
|
||||
public string Message { get; set; }
|
||||
public string AdapterType { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class Finding
|
||||
@@ -118,21 +65,62 @@ namespace BizTalkApplicationCatalog.Models
|
||||
public long DurationMilliseconds { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class NameValueRecord
|
||||
/// <summary>
|
||||
/// Kleines, transportierbares JSON-Modell für den ACC/PRD-Abgleich.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
internal sealed class CatalogSnapshot
|
||||
{
|
||||
public NameValueRecord()
|
||||
public CatalogSnapshot()
|
||||
{
|
||||
SchemaVersion = 1;
|
||||
Applications = new List<CatalogApplication>();
|
||||
}
|
||||
|
||||
public NameValueRecord(string name, string value, string source = "")
|
||||
[DataMember(Order = 1)]
|
||||
public int SchemaVersion { get; set; }
|
||||
|
||||
[DataMember(Order = 2)]
|
||||
public string EnvironmentName { get; set; }
|
||||
|
||||
[DataMember(Order = 3)]
|
||||
public string ComputerName { get; set; }
|
||||
|
||||
[DataMember(Order = 4)]
|
||||
public string CreatedUtc { get; set; }
|
||||
|
||||
[DataMember(Order = 5)]
|
||||
public bool IsComplete { get; set; }
|
||||
|
||||
[DataMember(Order = 6)]
|
||||
public List<CatalogApplication> Applications { get; set; }
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
internal sealed class CatalogApplication
|
||||
{
|
||||
public CatalogApplication()
|
||||
{
|
||||
Name = name ?? string.Empty;
|
||||
Value = value ?? string.Empty;
|
||||
Source = source ?? string.Empty;
|
||||
AdapterCounts = new List<AdapterCount>();
|
||||
}
|
||||
|
||||
[DataMember(Order = 1)]
|
||||
public string Name { get; set; }
|
||||
public string Value { get; set; }
|
||||
public string Source { get; set; }
|
||||
|
||||
[DataMember(Order = 2)]
|
||||
public int EndpointCount { get; set; }
|
||||
|
||||
[DataMember(Order = 3)]
|
||||
public List<AdapterCount> AdapterCounts { get; set; }
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
internal sealed class AdapterCount
|
||||
{
|
||||
[DataMember(Order = 1)]
|
||||
public string AdapterType { get; set; }
|
||||
|
||||
[DataMember(Order = 2)]
|
||||
public int Count { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization.Json;
|
||||
using System.Text;
|
||||
using BizTalkApplicationCatalog.Models;
|
||||
|
||||
namespace BizTalkApplicationCatalog.Persistence
|
||||
{
|
||||
/// <summary>
|
||||
/// Erzeugt und liest die kompakten JSON-Snapshots für den ACC/PRD-Abgleich.
|
||||
/// </summary>
|
||||
internal static class SnapshotStore
|
||||
{
|
||||
public static CatalogSnapshot FromInventory(
|
||||
InventoryDocument document,
|
||||
bool isComplete)
|
||||
{
|
||||
if (document == null) throw new ArgumentNullException("document");
|
||||
|
||||
var snapshot = new CatalogSnapshot
|
||||
{
|
||||
EnvironmentName = document.EnvironmentName,
|
||||
ComputerName = document.ComputerName,
|
||||
CreatedUtc = document.CompletedUtc.ToString("o"),
|
||||
IsComplete = isComplete
|
||||
};
|
||||
|
||||
foreach (var application in document.Applications)
|
||||
{
|
||||
var endpoints = document.Endpoints.Where(item =>
|
||||
string.Equals(
|
||||
item.ApplicationName,
|
||||
application.Name,
|
||||
StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
var catalogApplication = new CatalogApplication
|
||||
{
|
||||
Name = application.Name,
|
||||
EndpointCount = endpoints.Count
|
||||
};
|
||||
|
||||
foreach (var adapter in endpoints
|
||||
.GroupBy(item => item.AdapterType, StringComparer.OrdinalIgnoreCase)
|
||||
.OrderBy(item => item.Key, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
catalogApplication.AdapterCounts.Add(new AdapterCount
|
||||
{
|
||||
AdapterType = adapter.Key,
|
||||
Count = adapter.Count()
|
||||
});
|
||||
}
|
||||
snapshot.Applications.Add(catalogApplication);
|
||||
}
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
public static void Save(CatalogSnapshot snapshot, string path)
|
||||
{
|
||||
if (snapshot == null) throw new ArgumentNullException("snapshot");
|
||||
var fullPath = Path.GetFullPath(path);
|
||||
var directory = Path.GetDirectoryName(fullPath);
|
||||
if (!string.IsNullOrWhiteSpace(directory)) Directory.CreateDirectory(directory);
|
||||
var temporaryPath = fullPath + "." + Guid.NewGuid().ToString("N") + ".tmp";
|
||||
try
|
||||
{
|
||||
using (var stream = File.Create(temporaryPath))
|
||||
using (var writer = JsonReaderWriterFactory.CreateJsonWriter(
|
||||
stream,
|
||||
Encoding.UTF8,
|
||||
true,
|
||||
true))
|
||||
{
|
||||
Serializer().WriteObject(writer, snapshot);
|
||||
}
|
||||
|
||||
if (File.Exists(fullPath)) File.Delete(fullPath);
|
||||
File.Move(temporaryPath, fullPath);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(temporaryPath)) File.Delete(temporaryPath);
|
||||
}
|
||||
}
|
||||
|
||||
public static CatalogSnapshot Load(string path)
|
||||
{
|
||||
var fullPath = Path.GetFullPath(path);
|
||||
using (var stream = File.OpenRead(fullPath))
|
||||
{
|
||||
var snapshot = Serializer().ReadObject(stream) as CatalogSnapshot;
|
||||
Validate(snapshot, fullPath);
|
||||
return snapshot;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ValidateForMerge(
|
||||
CatalogSnapshot snapshot,
|
||||
string expectedEnvironment)
|
||||
{
|
||||
Validate(snapshot, expectedEnvironment + "-Snapshot");
|
||||
if (!snapshot.IsComplete)
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
expectedEnvironment + "-Snapshot ist unvollständig und darf nicht zusammengeführt werden.");
|
||||
}
|
||||
if (!string.Equals(
|
||||
snapshot.EnvironmentName,
|
||||
expectedEnvironment,
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
"Erwartet wurde ein " + expectedEnvironment
|
||||
+ "-Snapshot, gefunden wurde "
|
||||
+ snapshot.EnvironmentName + ".");
|
||||
}
|
||||
}
|
||||
|
||||
private static DataContractJsonSerializer Serializer()
|
||||
{
|
||||
return new DataContractJsonSerializer(typeof(CatalogSnapshot));
|
||||
}
|
||||
|
||||
private static void Validate(CatalogSnapshot snapshot, string source)
|
||||
{
|
||||
if (snapshot == null)
|
||||
{
|
||||
throw new InvalidDataException("Leerer JSON-Snapshot: " + source);
|
||||
}
|
||||
if (snapshot.SchemaVersion != 1)
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
"Nicht unterstützte Snapshot-Version in "
|
||||
+ source + ": " + snapshot.SchemaVersion + ".");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(snapshot.EnvironmentName))
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
"Umgebung fehlt im JSON-Snapshot: " + source);
|
||||
}
|
||||
if (snapshot.Applications == null)
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
"Anwendungsliste fehlt im JSON-Snapshot: " + source);
|
||||
}
|
||||
if (snapshot.Applications.Any(item =>
|
||||
item == null
|
||||
|| string.IsNullOrWhiteSpace(item.Name)
|
||||
|| item.EndpointCount < 0
|
||||
|| item.AdapterCounts == null
|
||||
|| item.AdapterCounts.Any(adapter =>
|
||||
adapter == null
|
||||
|| string.IsNullOrWhiteSpace(adapter.AdapterType)
|
||||
|| adapter.Count < 0)
|
||||
|| item.AdapterCounts.Sum(adapter => adapter.Count) != item.EndpointCount))
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
"Ungültige Anwendungs- oder Adapterdaten im JSON-Snapshot: " + source);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,11 @@ using BizTalkApplicationCatalog.Collectors;
|
||||
using BizTalkApplicationCatalog.Configuration;
|
||||
using BizTalkApplicationCatalog.Infrastructure;
|
||||
using BizTalkApplicationCatalog.Models;
|
||||
using BizTalkApplicationCatalog.Persistence;
|
||||
using BizTalkApplicationCatalog.Reporting;
|
||||
|
||||
namespace BizTalkApplicationCatalog
|
||||
{
|
||||
/// <summary>
|
||||
/// Orchestriert die read-only Erfassung, die Fortschrittsanzeige und den Excel-Export.
|
||||
/// </summary>
|
||||
internal static class Program
|
||||
{
|
||||
private static int Main(string[] args)
|
||||
@@ -36,30 +34,32 @@ namespace BizTalkApplicationCatalog
|
||||
Console.WriteLine(CommandLineOptions.Usage());
|
||||
return 0;
|
||||
}
|
||||
if (options.SelfTest)
|
||||
{
|
||||
return RunSelfTest();
|
||||
}
|
||||
if (options.SelfTest) return RunSelfTest();
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(options.OutputDirectory);
|
||||
return options.MergeMode
|
||||
? RunMerge(options)
|
||||
: RunInventory(options);
|
||||
}
|
||||
catch (Exception exception) when (
|
||||
exception is IOException
|
||||
|| exception is UnauthorizedAccessException
|
||||
|| exception is ArgumentException)
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.Error.WriteLine("FEHLER: Ausgabeordner kann nicht erstellt werden: " + exception.Message);
|
||||
Console.Error.WriteLine("FEHLER: " + exception);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
var timestamp = DateTime.Now.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture);
|
||||
var safeMachine = FileName(Environment.MachineName);
|
||||
var baseName = "BizTalk-Anwendungsinventar-"
|
||||
+ options.EnvironmentName + "-" + safeMachine + "-" + timestamp;
|
||||
private static int RunInventory(CommandLineOptions options)
|
||||
{
|
||||
var timestamp = Timestamp();
|
||||
var baseName = "Phase1-BizTalk-Anwendungskatalog-"
|
||||
+ options.EnvironmentName + "-"
|
||||
+ FileName(Environment.MachineName) + "-"
|
||||
+ timestamp;
|
||||
var logPath = Path.Combine(options.OutputDirectory, baseName + ".log");
|
||||
var reportPath = Path.Combine(options.OutputDirectory, baseName + ".xlsx");
|
||||
var snapshotPath = Path.Combine(options.OutputDirectory, baseName + ".json");
|
||||
|
||||
using (var logger = new ConsoleFileLogger(logPath))
|
||||
{
|
||||
@@ -70,90 +70,66 @@ namespace BizTalkApplicationCatalog
|
||||
StartedUtc = DateTime.UtcNow
|
||||
};
|
||||
|
||||
logger.Info("BEW BizTalk Application Catalog startet.");
|
||||
logger.Info("BEW BizTalk Application Catalog – Phase 1 startet.");
|
||||
logger.Info("Umgebung: " + options.EnvironmentName);
|
||||
logger.Info("Server: " + Environment.MachineName);
|
||||
logger.Info("Ausgabeordner: " + options.OutputDirectory);
|
||||
logger.Info("Modus: ausschließlich lesend; Excel wird ohne Office erzeugt.");
|
||||
logger.Info("Erfasst werden nur Anwendungen, Adaptertypen und Endpunkte.");
|
||||
logger.Info("Modus: ausschließlich lesender lokaler BizTalk-WMI-Zugriff.");
|
||||
|
||||
var safe = new SafeCollector(document, logger);
|
||||
safe.Execute(
|
||||
"System und BizTalk-Gruppe",
|
||||
false,
|
||||
() => new SystemCollector(options).Collect(document));
|
||||
safe.Execute(
|
||||
"Anwendungen und Artefakte",
|
||||
new SafeCollector(document, logger).Execute(
|
||||
"Phase-1-Anwendungen und Endpunkte",
|
||||
true,
|
||||
() => new BizTalkWmiCollector(
|
||||
document,
|
||||
logger,
|
||||
ReadInt("WmiTimeoutSeconds", 30),
|
||||
ReadInt("MaxRowsPerArtifactType", 10000)).Collect());
|
||||
ReadInt("WmiTimeoutSeconds", 30)).Collect());
|
||||
|
||||
Evaluate(document);
|
||||
document.CompletedUtc = DateTime.UtcNow;
|
||||
try
|
||||
{
|
||||
logger.Info("Erzeuge Microsoft-Excel-Datei: " + reportPath);
|
||||
new XlsxReportWriter().Write(document, reportPath);
|
||||
logger.Info("Excel-Datei erfolgreich erzeugt: " + reportPath);
|
||||
logger.Info("Logdatei: " + logPath);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
logger.Error("Excel-Datei konnte nicht erzeugt werden: " + exception);
|
||||
return 2;
|
||||
}
|
||||
var snapshot = SnapshotStore.FromInventory(
|
||||
document,
|
||||
!document.HasRequiredFailure);
|
||||
|
||||
SnapshotStore.Save(snapshot, snapshotPath);
|
||||
logger.Info("JSON-Snapshot erzeugt: " + snapshotPath);
|
||||
new XlsxReportWriter().Write(new[] { snapshot }, reportPath);
|
||||
logger.Info("Einblättrige Excel-Sicht erzeugt: " + reportPath);
|
||||
logger.Info("Anwendungen: " + snapshot.Applications.Count);
|
||||
logger.Info("Endpunkte: "
|
||||
+ snapshot.Applications.Sum(item => item.EndpointCount));
|
||||
|
||||
logger.Info("Anwendungen: " + document.Applications.Count);
|
||||
logger.Info("Artefakte: " + document.Artifacts.Count);
|
||||
logger.Info("Findings: " + document.Findings.Count);
|
||||
if (document.HasRequiredFailure)
|
||||
{
|
||||
logger.Warning("Inventar wurde mit einem Fehler in einem Pflichtabschnitt erzeugt.");
|
||||
logger.Warning(
|
||||
"Snapshot ist unvollständig und wird beim Merge abgelehnt.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
logger.Info("Inventarisierung erfolgreich abgeschlossen.");
|
||||
logger.Info("Lokale Inventarisierung erfolgreich abgeschlossen.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static void Evaluate(InventoryDocument document)
|
||||
private static int RunMerge(CommandLineOptions options)
|
||||
{
|
||||
if (document.Applications.Count == 0)
|
||||
{
|
||||
document.Findings.Add(new Finding
|
||||
{
|
||||
Severity = "Fehler",
|
||||
Area = "Anwendungen",
|
||||
Message = "Keine installierte BizTalk-Anwendung ermittelt.",
|
||||
RecommendedAction = "Lokal auf dem BizTalk Server mit ausreichenden WMI-Leserechten ausführen."
|
||||
});
|
||||
}
|
||||
var baseName = "Phase1-BizTalk-Anwendungskatalog-ACC-PROD-"
|
||||
+ Timestamp();
|
||||
var logPath = Path.Combine(options.OutputDirectory, baseName + ".log");
|
||||
var reportPath = Path.Combine(options.OutputDirectory, baseName + ".xlsx");
|
||||
|
||||
var withoutApplication = document.Artifacts.Count(item =>
|
||||
string.IsNullOrWhiteSpace(item.ApplicationName));
|
||||
if (withoutApplication > 0)
|
||||
using (var logger = new ConsoleFileLogger(logPath))
|
||||
{
|
||||
document.Findings.Add(new Finding
|
||||
{
|
||||
Severity = "Warnung",
|
||||
Area = "Artefaktzuordnung",
|
||||
Message = withoutApplication + " Artefakt(e) besitzen keine von WMI gelieferte Anwendungszuordnung.",
|
||||
RecommendedAction = "Artefakte im Blatt 'Artefakte' prüfen und bei Bedarf mit der BizTalk Administration Console abgleichen."
|
||||
});
|
||||
}
|
||||
|
||||
if (document.Artifacts.Count == 0)
|
||||
{
|
||||
document.Findings.Add(new Finding
|
||||
{
|
||||
Severity = "Warnung",
|
||||
Area = "Artefakte",
|
||||
Message = "Keine Artefaktdetails wurden ermittelt; die Anwendungsliste kann dennoch vollständig sein.",
|
||||
RecommendedAction = "Blatt 'Abdeckung' sowie WMI-Klassen und Berechtigungen prüfen."
|
||||
});
|
||||
logger.Info("Führe ACC- und PROD-JSON-Snapshots zusammen.");
|
||||
var acc = SnapshotStore.Load(options.AccSnapshotPath);
|
||||
var prod = SnapshotStore.Load(options.ProdSnapshotPath);
|
||||
SnapshotStore.ValidateForMerge(acc, "ACC");
|
||||
SnapshotStore.ValidateForMerge(prod, "PROD");
|
||||
new XlsxReportWriter().Write(new[] { acc, prod }, reportPath);
|
||||
logger.Info("Gemeinsame Phase-1-Excel-Sicht erzeugt: " + reportPath);
|
||||
logger.Info("Anwendungen gesamt: "
|
||||
+ acc.Applications.Select(item => item.Name)
|
||||
.Concat(prod.Applications.Select(item => item.Name))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.Count());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +138,8 @@ namespace BizTalkApplicationCatalog
|
||||
try
|
||||
{
|
||||
SelfTestRunner.Run();
|
||||
Console.WriteLine("Self-Test erfolgreich: XLSX-Paket, Tabellen und Secret-Redaktion sind gültig.");
|
||||
Console.WriteLine(
|
||||
"Self-Test erfolgreich: JSON, ACC/PRD-Merge und einblättrige XLSX sind gültig.");
|
||||
return 0;
|
||||
}
|
||||
catch (Exception exception)
|
||||
@@ -184,6 +161,13 @@ namespace BizTalkApplicationCatalog
|
||||
: fallback;
|
||||
}
|
||||
|
||||
private static string Timestamp()
|
||||
{
|
||||
return DateTime.Now.ToString(
|
||||
"yyyyMMdd-HHmmss",
|
||||
CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private static string FileName(string value)
|
||||
{
|
||||
var invalid = Path.GetInvalidFileNameChars();
|
||||
|
||||
@@ -3,12 +3,12 @@ using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyTitle("BEW BizTalk Application Catalog")]
|
||||
[assembly: AssemblyDescription("Read-only BizTalk 2020 application inventory with Microsoft Excel output")]
|
||||
[assembly: AssemblyDescription("Read-only BizTalk 2020 Phase-1 application and endpoint catalog")]
|
||||
[assembly: AssemblyCompany("JR IT Services")]
|
||||
[assembly: AssemblyProduct("BEW BizTalk Application Catalog")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2026")]
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: Guid("41cb5701-3fbc-49f4-856a-6ae930b8513d")]
|
||||
[assembly: InternalsVisibleTo("BizTalkApplicationCatalog.Tests")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
[assembly: AssemblyVersion("2.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("2.0.0.0")]
|
||||
|
||||
@@ -6,14 +6,12 @@ using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using BizTalkApplicationCatalog.Infrastructure;
|
||||
using BizTalkApplicationCatalog.Models;
|
||||
|
||||
namespace BizTalkApplicationCatalog.Reporting
|
||||
{
|
||||
/// <summary>
|
||||
/// Erzeugt eine filterbare Microsoft-Excel-Arbeitsmappe direkt als Office Open XML.
|
||||
/// Excel oder eine Office-Interop-Installation werden nicht benötigt.
|
||||
/// Erzeugt genau ein Excel-Blatt für die Phase-1-Anwendungssicht.
|
||||
/// </summary>
|
||||
internal sealed class XlsxReportWriter
|
||||
{
|
||||
@@ -22,13 +20,19 @@ namespace BizTalkApplicationCatalog.Reporting
|
||||
private const string RelationshipsNamespace =
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
||||
|
||||
/// <summary>
|
||||
/// Schreibt atomar: Erst nach erfolgreichem Abschluss ersetzt die temporäre Datei das Ziel.
|
||||
/// </summary>
|
||||
public void Write(InventoryDocument document, string outputPath)
|
||||
public void Write(IEnumerable<CatalogSnapshot> source, string outputPath)
|
||||
{
|
||||
if (document == null) throw new ArgumentNullException("document");
|
||||
if (string.IsNullOrWhiteSpace(outputPath)) throw new ArgumentException("Ausgabepfad fehlt.");
|
||||
if (source == null) throw new ArgumentNullException("source");
|
||||
if (string.IsNullOrWhiteSpace(outputPath))
|
||||
{
|
||||
throw new ArgumentException("Ausgabepfad fehlt.");
|
||||
}
|
||||
|
||||
var snapshots = source.ToList();
|
||||
if (snapshots.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("Mindestens ein Snapshot ist erforderlich.");
|
||||
}
|
||||
|
||||
var fullPath = Path.GetFullPath(outputPath);
|
||||
var directory = Path.GetDirectoryName(fullPath);
|
||||
@@ -37,20 +41,17 @@ namespace BizTalkApplicationCatalog.Reporting
|
||||
|
||||
try
|
||||
{
|
||||
var sheets = BuildSheets(document);
|
||||
var sheet = BuildPhaseOneSheet(snapshots);
|
||||
using (var archive = ZipFile.Open(temporaryPath, ZipArchiveMode.Create))
|
||||
{
|
||||
WriteContentTypes(archive, sheets.Count);
|
||||
WriteContentTypes(archive);
|
||||
WritePackageRelationships(archive);
|
||||
WriteCoreProperties(archive, document);
|
||||
WriteCoreProperties(archive, snapshots);
|
||||
WriteApplicationProperties(archive);
|
||||
WriteWorkbook(archive, sheets);
|
||||
WriteWorkbookRelationships(archive, sheets.Count);
|
||||
WriteWorkbook(archive, sheet);
|
||||
WriteWorkbookRelationships(archive);
|
||||
WriteStyles(archive);
|
||||
for (var index = 0; index < sheets.Count; index++)
|
||||
{
|
||||
WriteWorksheet(archive, index + 1, sheets[index]);
|
||||
}
|
||||
WriteWorksheet(archive, sheet);
|
||||
}
|
||||
|
||||
if (File.Exists(fullPath)) File.Delete(fullPath);
|
||||
@@ -62,296 +63,224 @@ namespace BizTalkApplicationCatalog.Reporting
|
||||
}
|
||||
}
|
||||
|
||||
internal static List<SheetDefinition> BuildSheets(InventoryDocument document)
|
||||
{
|
||||
return new List<SheetDefinition>
|
||||
{
|
||||
BuildOverview(document),
|
||||
BuildApplications(document),
|
||||
BuildArtifacts("Artefakte", document.Artifacts),
|
||||
BuildArtifacts("Ports", document.Artifacts.Where(item =>
|
||||
item.Type == "Send Port"
|
||||
|| item.Type == "Send Port Group"
|
||||
|| item.Type == "Receive Port"
|
||||
|| item.Type == "Receive Location")),
|
||||
BuildArtifacts("Orchestrierungen", document.Artifacts.Where(item =>
|
||||
item.Type == "Orchestrierung")),
|
||||
BuildArtifacts("Schemas-Maps-Pipelines", document.Artifacts.Where(item =>
|
||||
item.Type == "Schema" || item.Type == "Map" || item.Type == "Pipeline")),
|
||||
BuildArtifacts("Assemblies", document.Artifacts.Where(item =>
|
||||
item.Type == "Assembly")),
|
||||
BuildHosts(document),
|
||||
BuildCoverage(document),
|
||||
BuildFindings(document)
|
||||
};
|
||||
}
|
||||
|
||||
private static SheetDefinition BuildOverview(InventoryDocument document)
|
||||
{
|
||||
var rows = new List<object[]>
|
||||
{
|
||||
new object[] { "BEW BizTalk Application Catalog", "" },
|
||||
new object[] { "Umgebung", document.EnvironmentName },
|
||||
new object[] { "BizTalk Server", document.ComputerName },
|
||||
new object[] { "Erzeugt (lokal)", document.CompletedUtc.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture) },
|
||||
new object[] { "Toolversion", document.ToolVersion },
|
||||
new object[] { "Management SQL Server", Empty(document.ManagementServer) },
|
||||
new object[] { "Management Database", Empty(document.ManagementDatabase) },
|
||||
new object[] { "Anwendungen gesamt", document.Applications.Count },
|
||||
new object[] { "Artefakte gesamt", document.Artifacts.Count },
|
||||
new object[] { "Findings", document.Findings.Count },
|
||||
new object[] { "", "" },
|
||||
new object[] { "Artefakttyp", "Anzahl" }
|
||||
};
|
||||
|
||||
foreach (var group in document.Artifacts
|
||||
.GroupBy(item => item.Type)
|
||||
.OrderBy(item => item.Key, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
rows.Add(new object[] { group.Key, group.Count() });
|
||||
}
|
||||
|
||||
rows.Add(new object[] { "", "" });
|
||||
rows.Add(new object[] { "Systemparameter", "Wert", "Quelle" });
|
||||
foreach (var property in document.SystemProperties)
|
||||
{
|
||||
rows.Add(new object[] { property.Name, property.Value, property.Source });
|
||||
}
|
||||
|
||||
rows.Add(new object[] { "", "" });
|
||||
rows.Add(new object[] { "Erfassungsabschnitt", "Status", "Pflicht", "Dauer (ms)", "Meldung" });
|
||||
foreach (var section in document.SectionStatuses)
|
||||
{
|
||||
rows.Add(new object[]
|
||||
{
|
||||
section.Name,
|
||||
section.Status,
|
||||
section.Required ? "Ja" : "Nein",
|
||||
section.DurationMilliseconds,
|
||||
section.Message
|
||||
});
|
||||
}
|
||||
|
||||
return new SheetDefinition("Übersicht", rows, false, 0);
|
||||
}
|
||||
|
||||
private static SheetDefinition BuildApplications(InventoryDocument document)
|
||||
{
|
||||
var headers = new object[]
|
||||
{
|
||||
"Umgebung", "Server", "Anwendung", "Status", "Standard", "Beschreibung",
|
||||
"Artefakte gesamt", "Orchestrierungen", "Send Ports", "Send Port Groups",
|
||||
"Receive Ports", "Receive Locations", "Assemblies", "Schemas", "Maps",
|
||||
"Pipelines", "Hosts", "Adapter", "Detailabdeckung"
|
||||
};
|
||||
var rows = new List<object[]> { headers };
|
||||
var coverage = ArtifactCoverage(document);
|
||||
|
||||
foreach (var application in document.Applications)
|
||||
{
|
||||
var artifacts = document.Artifacts.Where(item =>
|
||||
string.Equals(item.ApplicationName, application.Name, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
rows.Add(new object[]
|
||||
{
|
||||
document.EnvironmentName,
|
||||
document.ComputerName,
|
||||
application.Name,
|
||||
application.Status,
|
||||
application.IsDefault,
|
||||
application.Description,
|
||||
artifacts.Count,
|
||||
Count(artifacts, "Orchestrierung"),
|
||||
Count(artifacts, "Send Port"),
|
||||
Count(artifacts, "Send Port Group"),
|
||||
Count(artifacts, "Receive Port"),
|
||||
Count(artifacts, "Receive Location"),
|
||||
Count(artifacts, "Assembly"),
|
||||
Count(artifacts, "Schema"),
|
||||
Count(artifacts, "Map"),
|
||||
Count(artifacts, "Pipeline"),
|
||||
JoinDistinct(artifacts.Select(item => item.HostName)),
|
||||
JoinDistinct(artifacts.Select(item => item.AdapterName)),
|
||||
coverage
|
||||
});
|
||||
}
|
||||
|
||||
return new SheetDefinition("Anwendungen", rows, true, 1);
|
||||
}
|
||||
|
||||
private static SheetDefinition BuildArtifacts(
|
||||
string sheetName,
|
||||
IEnumerable<ArtifactRecord> source)
|
||||
internal static SheetDefinition BuildPhaseOneSheet(
|
||||
IList<CatalogSnapshot> snapshots)
|
||||
{
|
||||
var acc = snapshots.FirstOrDefault(item =>
|
||||
string.Equals(item.EnvironmentName, "ACC", StringComparison.OrdinalIgnoreCase));
|
||||
var prod = snapshots.FirstOrDefault(item =>
|
||||
string.Equals(item.EnvironmentName, "PROD", StringComparison.OrdinalIgnoreCase));
|
||||
var rows = new List<object[]>
|
||||
{
|
||||
new object[]
|
||||
{
|
||||
"Anwendung", "Typ", "Name", "Status", "Host/Handler", "Adapter",
|
||||
"Übergeordnet", "Adresse", "Beschreibung", "Two-Way", "Dynamisch",
|
||||
"Deaktiviert", "Receive Pipeline", "Send Pipeline", "Secondary Adapter",
|
||||
"Secondary Adresse", "Assembly/FullName", "Namespace", "Root", "Tracking"
|
||||
}
|
||||
};
|
||||
foreach (var item in source)
|
||||
{
|
||||
rows.Add(new object[]
|
||||
{
|
||||
item.ApplicationName,
|
||||
item.Type,
|
||||
item.Name,
|
||||
item.Status,
|
||||
item.HostName,
|
||||
item.AdapterName,
|
||||
item.ParentName,
|
||||
item.Address,
|
||||
item.Property("Description"),
|
||||
item.Property("IsTwoWay"),
|
||||
item.Property("IsDynamic"),
|
||||
item.Property("IsDisabled"),
|
||||
item.Property("ReceivePipeline"),
|
||||
item.Property("SendPipeline"),
|
||||
item.Property("STTransportType"),
|
||||
SensitiveDataSanitizer.Sanitize(item.Property("STAddress")),
|
||||
FirstNonEmpty(item.Property("AssemblyName"), item.Property("FullName")),
|
||||
item.Property("TargetNameSpace"),
|
||||
item.Property("RootName"),
|
||||
item.Property("Tracking")
|
||||
});
|
||||
}
|
||||
return new SheetDefinition(sheetName, rows, true, 1);
|
||||
}
|
||||
|
||||
private static SheetDefinition BuildHosts(InventoryDocument document)
|
||||
{
|
||||
var rows = new List<object[]>
|
||||
{
|
||||
snapshots.Count > 1
|
||||
? "Phase 1 – BizTalk Anwendungen & Adapter (Quelle: ACC/PROD JSON-Snapshots)"
|
||||
: "Phase 1 – BizTalk Anwendungen & Adapter (Quelle: lokaler BizTalk-WMI-Provider)",
|
||||
"", "", "", "", "", ""
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"Kategorie", "Name", "Server", "Status", "Typ", "Windows-Gruppe",
|
||||
"Nur 32 Bit", "Vertrauenswürdig", "Adapter"
|
||||
"BizTalk-Anwendung",
|
||||
"Umgebung",
|
||||
"Adapter-Typ",
|
||||
"Anzahl Endpunkte",
|
||||
"BizTalk-Anwendung in ACC",
|
||||
"BizTalk-Anwendung in PRD",
|
||||
"Hinweis"
|
||||
}
|
||||
};
|
||||
foreach (var item in document.Hosts)
|
||||
{
|
||||
rows.Add(new object[]
|
||||
{
|
||||
item.Category, item.Name, item.Server, item.Status, item.Type,
|
||||
item.WindowsGroup, item.Is32BitOnly, item.Trusted, item.AdapterName
|
||||
});
|
||||
}
|
||||
return new SheetDefinition("Hosts-Handler", rows, true, 1);
|
||||
}
|
||||
|
||||
private static SheetDefinition BuildCoverage(InventoryDocument document)
|
||||
{
|
||||
var rows = new List<object[]>
|
||||
{
|
||||
new object[] { "Datenquelle", "Status", "Zeilen", "Pflicht", "Meldung" }
|
||||
};
|
||||
foreach (var item in document.Coverage)
|
||||
{
|
||||
rows.Add(new object[]
|
||||
{
|
||||
item.DataSource, item.Status, item.RowCount, item.Required, item.Message
|
||||
});
|
||||
}
|
||||
return new SheetDefinition("Abdeckung", rows, true, 1);
|
||||
}
|
||||
|
||||
private static SheetDefinition BuildFindings(InventoryDocument document)
|
||||
{
|
||||
var rows = new List<object[]>
|
||||
{
|
||||
new object[] { "Schweregrad", "Bereich", "Feststellung", "Empfohlene Aktion" }
|
||||
};
|
||||
foreach (var item in document.Findings)
|
||||
{
|
||||
rows.Add(new object[]
|
||||
{
|
||||
item.Severity, item.Area, item.Message, item.RecommendedAction
|
||||
});
|
||||
}
|
||||
if (document.Findings.Count == 0)
|
||||
{
|
||||
rows.Add(new object[] { "Information", "Gesamt", "Keine Findings.", "" });
|
||||
}
|
||||
return new SheetDefinition("Findings", rows, true, 1);
|
||||
}
|
||||
|
||||
private static string ArtifactCoverage(InventoryDocument document)
|
||||
{
|
||||
var incomplete = document.Coverage
|
||||
.Where(item => item.DataSource.StartsWith("MSBTS_", StringComparison.Ordinal)
|
||||
&& item.DataSource != "MSBTS_Application"
|
||||
&& item.Status != "Vollständig")
|
||||
.Select(item => item.DataSource + ": " + item.Status)
|
||||
var names = snapshots
|
||||
.SelectMany(item => item.Applications)
|
||||
.Select(item => item.Name)
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.OrderBy(item => item, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
return incomplete.Count == 0
|
||||
? "Vollständig"
|
||||
: "Teilweise – siehe Abdeckung: " + string.Join(", ", incomplete);
|
||||
|
||||
foreach (var name in names)
|
||||
{
|
||||
var accApplication = Find(acc, name);
|
||||
var prodApplication = Find(prod, name);
|
||||
var environments = snapshots
|
||||
.Where(snapshot => Find(snapshot, name) != null)
|
||||
.Select(snapshot => snapshot.EnvironmentName)
|
||||
.OrderBy(EnvironmentOrder)
|
||||
.ToList();
|
||||
|
||||
rows.Add(new object[]
|
||||
{
|
||||
CanonicalName(accApplication, prodApplication, name),
|
||||
EnvironmentText(accApplication, prodApplication, environments),
|
||||
AdapterText(accApplication, prodApplication, environments),
|
||||
environments.Sum(environment =>
|
||||
{
|
||||
var snapshot = snapshots.First(item =>
|
||||
string.Equals(
|
||||
item.EnvironmentName,
|
||||
environment,
|
||||
StringComparison.OrdinalIgnoreCase));
|
||||
return Find(snapshot, name).EndpointCount;
|
||||
}),
|
||||
accApplication == null ? "–" : "✓",
|
||||
prodApplication == null ? "–" : "✓",
|
||||
Hint(accApplication, prodApplication, environments)
|
||||
});
|
||||
}
|
||||
|
||||
var accApplications = acc == null ? 0 : acc.Applications.Count;
|
||||
var prodApplications = prod == null ? 0 : prod.Applications.Count;
|
||||
var accEndpoints = acc == null ? 0 : acc.Applications.Sum(item => item.EndpointCount);
|
||||
var prodEndpoints = prod == null ? 0 : prod.Applications.Sum(item => item.EndpointCount);
|
||||
rows.Add(new object[]
|
||||
{
|
||||
"Gesamt: " + names.Count
|
||||
+ " BizTalk-Anwendungen | ACC: " + accApplications
|
||||
+ " | PRD: " + prodApplications
|
||||
+ " | ACC Endpunkte: " + accEndpoints
|
||||
+ " | PRD Endpunkte: " + prodEndpoints,
|
||||
"", "", "", "", "", ""
|
||||
});
|
||||
|
||||
return new SheetDefinition(
|
||||
"Phase 1",
|
||||
rows,
|
||||
2,
|
||||
2,
|
||||
rows.Count - 1,
|
||||
new[] { 34d, 14d, 28d, 18d, 28d, 28d, 42d });
|
||||
}
|
||||
|
||||
private static int Count(IEnumerable<ArtifactRecord> artifacts, string type)
|
||||
private static CatalogApplication Find(
|
||||
CatalogSnapshot snapshot,
|
||||
string applicationName)
|
||||
{
|
||||
return artifacts.Count(item => item.Type == type);
|
||||
return snapshot == null
|
||||
? null
|
||||
: snapshot.Applications.FirstOrDefault(item =>
|
||||
string.Equals(
|
||||
item.Name,
|
||||
applicationName,
|
||||
StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private static string JoinDistinct(IEnumerable<string> values)
|
||||
private static string CanonicalName(
|
||||
CatalogApplication acc,
|
||||
CatalogApplication prod,
|
||||
string fallback)
|
||||
{
|
||||
if (acc != null) return acc.Name;
|
||||
if (prod != null) return prod.Name;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
private static string EnvironmentText(
|
||||
CatalogApplication acc,
|
||||
CatalogApplication prod,
|
||||
IList<string> environments)
|
||||
{
|
||||
if (acc != null && prod != null) return "ACC + PRD";
|
||||
if (acc != null) return "nur ACC";
|
||||
if (prod != null) return "nur PRD";
|
||||
return environments.Count == 0 ? "–" : string.Join(" + ", environments);
|
||||
}
|
||||
|
||||
private static string AdapterText(
|
||||
CatalogApplication acc,
|
||||
CatalogApplication prod,
|
||||
IList<string> environments)
|
||||
{
|
||||
var accText = AdapterCounts(acc);
|
||||
var prodText = AdapterCounts(prod);
|
||||
if (acc != null && prod != null)
|
||||
{
|
||||
return string.Equals(accText, prodText, StringComparison.OrdinalIgnoreCase)
|
||||
? accText
|
||||
: "ACC: " + accText + " | PRD: " + prodText;
|
||||
}
|
||||
if (acc != null) return accText;
|
||||
if (prod != null) return prodText;
|
||||
return environments.Count == 0 ? "–" : "Unbekannt";
|
||||
}
|
||||
|
||||
private static string AdapterCounts(CatalogApplication application)
|
||||
{
|
||||
if (application == null || application.AdapterCounts.Count == 0) return "–";
|
||||
return string.Join(
|
||||
", ",
|
||||
values.Where(item => !string.IsNullOrWhiteSpace(item))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.OrderBy(item => item, StringComparer.OrdinalIgnoreCase));
|
||||
" / ",
|
||||
application.AdapterCounts
|
||||
.OrderBy(item => item.AdapterType, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(item => item.AdapterType + " (" + item.Count + "x)"));
|
||||
}
|
||||
|
||||
private static string FirstNonEmpty(params string[] values)
|
||||
private static string Hint(
|
||||
CatalogApplication acc,
|
||||
CatalogApplication prod,
|
||||
IList<string> environments)
|
||||
{
|
||||
return values.FirstOrDefault(item => !string.IsNullOrWhiteSpace(item)) ?? string.Empty;
|
||||
if (acc != null && prod == null) return "Nur in ACC vorhanden";
|
||||
if (prod != null && acc == null) return "Nur in PRD vorhanden – nicht in ACC";
|
||||
if (environments.Count == 1)
|
||||
{
|
||||
return "Nur in " + environments[0] + " vorhanden";
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private static string Empty(string value)
|
||||
private static int EnvironmentOrder(string environment)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(value) ? "Nicht ermittelt" : value;
|
||||
if (string.Equals(environment, "ACC", StringComparison.OrdinalIgnoreCase)) return 0;
|
||||
if (string.Equals(environment, "PROD", StringComparison.OrdinalIgnoreCase)) return 1;
|
||||
return 2;
|
||||
}
|
||||
|
||||
private static void WriteWorksheet(ZipArchive archive, int sheetNumber, SheetDefinition sheet)
|
||||
private static void WriteWorksheet(ZipArchive archive, SheetDefinition sheet)
|
||||
{
|
||||
using (var writer = CreateXmlWriter(archive, "xl/worksheets/sheet" + sheetNumber + ".xml"))
|
||||
using (var writer = CreateXmlWriter(archive, "xl/worksheets/sheet1.xml"))
|
||||
{
|
||||
writer.WriteStartDocument();
|
||||
writer.WriteStartElement("worksheet", SpreadsheetNamespace);
|
||||
writer.WriteAttributeString("xmlns", "r", null, RelationshipsNamespace);
|
||||
WriteSheetViews(writer, sheet.FreezeRows);
|
||||
WriteColumns(writer, sheet);
|
||||
WriteColumns(writer, sheet.ColumnWidths);
|
||||
writer.WriteStartElement("sheetData", SpreadsheetNamespace);
|
||||
|
||||
for (var rowIndex = 0; rowIndex < sheet.Rows.Count; rowIndex++)
|
||||
{
|
||||
writer.WriteStartElement("row", SpreadsheetNamespace);
|
||||
writer.WriteAttributeString("r", (rowIndex + 1).ToString(CultureInfo.InvariantCulture));
|
||||
for (var columnIndex = 0; columnIndex < sheet.Rows[rowIndex].Length; columnIndex++)
|
||||
writer.WriteAttributeString(
|
||||
"r",
|
||||
(rowIndex + 1).ToString(CultureInfo.InvariantCulture));
|
||||
for (var columnIndex = 0;
|
||||
columnIndex < sheet.Rows[rowIndex].Length;
|
||||
columnIndex++)
|
||||
{
|
||||
var style = RowStyle(sheet, rowIndex);
|
||||
WriteCell(
|
||||
writer,
|
||||
columnIndex + 1,
|
||||
rowIndex + 1,
|
||||
sheet.Rows[rowIndex][columnIndex],
|
||||
style);
|
||||
RowStyle(sheet, rowIndex));
|
||||
}
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
writer.WriteEndElement();
|
||||
|
||||
if (sheet.AutoFilter && sheet.Rows.Count > 0 && sheet.MaximumColumns > 0)
|
||||
{
|
||||
writer.WriteStartElement("autoFilter", SpreadsheetNamespace);
|
||||
writer.WriteAttributeString(
|
||||
"ref",
|
||||
"A1:" + ColumnName(sheet.MaximumColumns) + sheet.Rows.Count);
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
writer.WriteStartElement("autoFilter", SpreadsheetNamespace);
|
||||
writer.WriteAttributeString(
|
||||
"ref",
|
||||
"A" + sheet.FilterHeaderRow
|
||||
+ ":G" + (sheet.FooterRowIndex + 1 - 1));
|
||||
writer.WriteEndElement();
|
||||
|
||||
writer.WriteStartElement("mergeCells", SpreadsheetNamespace);
|
||||
writer.WriteAttributeString("count", "2");
|
||||
WriteMergedCell(writer, "A1:G1");
|
||||
WriteMergedCell(
|
||||
writer,
|
||||
"A" + (sheet.FooterRowIndex + 1)
|
||||
+ ":G" + (sheet.FooterRowIndex + 1));
|
||||
writer.WriteEndElement();
|
||||
|
||||
writer.WriteStartElement("pageMargins", SpreadsheetNamespace);
|
||||
writer.WriteAttributeString("left", "0.25");
|
||||
@@ -366,18 +295,19 @@ namespace BizTalkApplicationCatalog.Reporting
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteMergedCell(XmlWriter writer, string range)
|
||||
{
|
||||
writer.WriteStartElement("mergeCell", SpreadsheetNamespace);
|
||||
writer.WriteAttributeString("ref", range);
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
|
||||
private static int RowStyle(SheetDefinition sheet, int rowIndex)
|
||||
{
|
||||
if (sheet.Name == "Übersicht")
|
||||
{
|
||||
if (rowIndex == 0) return 2;
|
||||
var first = Convert.ToString(sheet.Rows[rowIndex].FirstOrDefault(), CultureInfo.InvariantCulture);
|
||||
if (first == "Artefakttyp"
|
||||
|| first == "Systemparameter"
|
||||
|| first == "Erfassungsabschnitt") return 1;
|
||||
return 0;
|
||||
}
|
||||
return rowIndex == 0 ? 1 : 0;
|
||||
if (rowIndex == 0) return 2;
|
||||
if (rowIndex == 1) return 1;
|
||||
if (rowIndex == sheet.FooterRowIndex) return 3;
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static void WriteSheetViews(XmlWriter writer, int freezeRows)
|
||||
@@ -385,35 +315,27 @@ namespace BizTalkApplicationCatalog.Reporting
|
||||
writer.WriteStartElement("sheetViews", SpreadsheetNamespace);
|
||||
writer.WriteStartElement("sheetView", SpreadsheetNamespace);
|
||||
writer.WriteAttributeString("workbookViewId", "0");
|
||||
if (freezeRows > 0)
|
||||
{
|
||||
writer.WriteStartElement("pane", SpreadsheetNamespace);
|
||||
writer.WriteAttributeString("ySplit", freezeRows.ToString(CultureInfo.InvariantCulture));
|
||||
writer.WriteAttributeString("topLeftCell", "A" + (freezeRows + 1));
|
||||
writer.WriteAttributeString("activePane", "bottomLeft");
|
||||
writer.WriteAttributeString("state", "frozen");
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
writer.WriteStartElement("pane", SpreadsheetNamespace);
|
||||
writer.WriteAttributeString(
|
||||
"ySplit",
|
||||
freezeRows.ToString(CultureInfo.InvariantCulture));
|
||||
writer.WriteAttributeString("topLeftCell", "A" + (freezeRows + 1));
|
||||
writer.WriteAttributeString("activePane", "bottomLeft");
|
||||
writer.WriteAttributeString("state", "frozen");
|
||||
writer.WriteEndElement();
|
||||
writer.WriteEndElement();
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
|
||||
private static void WriteColumns(XmlWriter writer, SheetDefinition sheet)
|
||||
private static void WriteColumns(XmlWriter writer, double[] widths)
|
||||
{
|
||||
writer.WriteStartElement("cols", SpreadsheetNamespace);
|
||||
for (var columnIndex = 0; columnIndex < sheet.MaximumColumns; columnIndex++)
|
||||
for (var index = 0; index < widths.Length; index++)
|
||||
{
|
||||
var width = 10;
|
||||
foreach (var row in sheet.Rows)
|
||||
{
|
||||
if (columnIndex >= row.Length) continue;
|
||||
var length = Convert.ToString(row[columnIndex], CultureInfo.InvariantCulture).Length + 2;
|
||||
width = Math.Max(width, Math.Min(60, length));
|
||||
}
|
||||
writer.WriteStartElement("col", SpreadsheetNamespace);
|
||||
writer.WriteAttributeString("min", (columnIndex + 1).ToString(CultureInfo.InvariantCulture));
|
||||
writer.WriteAttributeString("max", (columnIndex + 1).ToString(CultureInfo.InvariantCulture));
|
||||
writer.WriteAttributeString("width", width.ToString(CultureInfo.InvariantCulture));
|
||||
writer.WriteAttributeString("min", (index + 1).ToString(CultureInfo.InvariantCulture));
|
||||
writer.WriteAttributeString("max", (index + 1).ToString(CultureInfo.InvariantCulture));
|
||||
writer.WriteAttributeString("width", widths[index].ToString(CultureInfo.InvariantCulture));
|
||||
writer.WriteAttributeString("customWidth", "1");
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
@@ -429,7 +351,10 @@ namespace BizTalkApplicationCatalog.Reporting
|
||||
{
|
||||
writer.WriteStartElement("c", SpreadsheetNamespace);
|
||||
writer.WriteAttributeString("r", ColumnName(column) + row);
|
||||
if (style > 0) writer.WriteAttributeString("s", style.ToString(CultureInfo.InvariantCulture));
|
||||
if (style > 0)
|
||||
{
|
||||
writer.WriteAttributeString("s", style.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
if (IsNumber(value))
|
||||
{
|
||||
writer.WriteStartElement("v", SpreadsheetNamespace);
|
||||
@@ -442,7 +367,8 @@ namespace BizTalkApplicationCatalog.Reporting
|
||||
writer.WriteStartElement("is", SpreadsheetNamespace);
|
||||
writer.WriteStartElement("t", SpreadsheetNamespace);
|
||||
writer.WriteAttributeString("xml", "space", null, "preserve");
|
||||
writer.WriteString(ExcelText(Convert.ToString(value, CultureInfo.InvariantCulture)));
|
||||
writer.WriteString(ExcelText(
|
||||
Convert.ToString(value, CultureInfo.InvariantCulture)));
|
||||
writer.WriteEndElement();
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
@@ -479,25 +405,21 @@ namespace BizTalkApplicationCatalog.Reporting
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void WriteContentTypes(ZipArchive archive, int sheetCount)
|
||||
private static void WriteContentTypes(ZipArchive archive)
|
||||
{
|
||||
using (var writer = CreateXmlWriter(archive, "[Content_Types].xml"))
|
||||
{
|
||||
writer.WriteStartDocument();
|
||||
writer.WriteStartElement("Types", "http://schemas.openxmlformats.org/package/2006/content-types");
|
||||
writer.WriteStartElement(
|
||||
"Types",
|
||||
"http://schemas.openxmlformats.org/package/2006/content-types");
|
||||
WriteDefault(writer, "rels", "application/vnd.openxmlformats-package.relationships+xml");
|
||||
WriteDefault(writer, "xml", "application/xml");
|
||||
WriteOverride(writer, "/xl/workbook.xml", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml");
|
||||
WriteOverride(writer, "/xl/styles.xml", "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml");
|
||||
WriteOverride(writer, "/docProps/core.xml", "application/vnd.openxmlformats-package.core-properties+xml");
|
||||
WriteOverride(writer, "/docProps/app.xml", "application/vnd.openxmlformats-officedocument.extended-properties+xml");
|
||||
for (var index = 1; index <= sheetCount; index++)
|
||||
{
|
||||
WriteOverride(
|
||||
writer,
|
||||
"/xl/worksheets/sheet" + index + ".xml",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml");
|
||||
}
|
||||
WriteOverride(writer, "/xl/worksheets/sheet1.xml", "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml");
|
||||
writer.WriteEndElement();
|
||||
writer.WriteEndDocument();
|
||||
}
|
||||
@@ -524,7 +446,9 @@ namespace BizTalkApplicationCatalog.Reporting
|
||||
using (var writer = CreateXmlWriter(archive, "_rels/.rels"))
|
||||
{
|
||||
writer.WriteStartDocument();
|
||||
writer.WriteStartElement("Relationships", "http://schemas.openxmlformats.org/package/2006/relationships");
|
||||
writer.WriteStartElement(
|
||||
"Relationships",
|
||||
"http://schemas.openxmlformats.org/package/2006/relationships");
|
||||
WriteRelationship(writer, "rId1", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", "xl/workbook.xml");
|
||||
WriteRelationship(writer, "rId2", "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties", "docProps/core.xml");
|
||||
WriteRelationship(writer, "rId3", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties", "docProps/app.xml");
|
||||
@@ -533,7 +457,9 @@ namespace BizTalkApplicationCatalog.Reporting
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteWorkbook(ZipArchive archive, List<SheetDefinition> sheets)
|
||||
private static void WriteWorkbook(
|
||||
ZipArchive archive,
|
||||
SheetDefinition sheet)
|
||||
{
|
||||
using (var writer = CreateXmlWriter(archive, "xl/workbook.xml"))
|
||||
{
|
||||
@@ -546,45 +472,39 @@ namespace BizTalkApplicationCatalog.Reporting
|
||||
writer.WriteEndElement();
|
||||
writer.WriteEndElement();
|
||||
writer.WriteStartElement("sheets", SpreadsheetNamespace);
|
||||
for (var index = 0; index < sheets.Count; index++)
|
||||
{
|
||||
writer.WriteStartElement("sheet", SpreadsheetNamespace);
|
||||
writer.WriteAttributeString("name", sheets[index].Name);
|
||||
writer.WriteAttributeString("sheetId", (index + 1).ToString(CultureInfo.InvariantCulture));
|
||||
writer.WriteAttributeString("r", "id", RelationshipsNamespace, "rId" + (index + 1));
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
writer.WriteStartElement("sheet", SpreadsheetNamespace);
|
||||
writer.WriteAttributeString("name", sheet.Name);
|
||||
writer.WriteAttributeString("sheetId", "1");
|
||||
writer.WriteAttributeString("r", "id", RelationshipsNamespace, "rId1");
|
||||
writer.WriteEndElement();
|
||||
writer.WriteEndElement();
|
||||
writer.WriteEndElement();
|
||||
writer.WriteEndDocument();
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteWorkbookRelationships(ZipArchive archive, int sheetCount)
|
||||
private static void WriteWorkbookRelationships(ZipArchive archive)
|
||||
{
|
||||
using (var writer = CreateXmlWriter(archive, "xl/_rels/workbook.xml.rels"))
|
||||
using (var writer = CreateXmlWriter(
|
||||
archive,
|
||||
"xl/_rels/workbook.xml.rels"))
|
||||
{
|
||||
writer.WriteStartDocument();
|
||||
writer.WriteStartElement("Relationships", "http://schemas.openxmlformats.org/package/2006/relationships");
|
||||
for (var index = 1; index <= sheetCount; index++)
|
||||
{
|
||||
WriteRelationship(
|
||||
writer,
|
||||
"rId" + index,
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet",
|
||||
"worksheets/sheet" + index + ".xml");
|
||||
}
|
||||
WriteRelationship(
|
||||
writer,
|
||||
"rId" + (sheetCount + 1),
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles",
|
||||
"styles.xml");
|
||||
writer.WriteStartElement(
|
||||
"Relationships",
|
||||
"http://schemas.openxmlformats.org/package/2006/relationships");
|
||||
WriteRelationship(writer, "rId1", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet", "worksheets/sheet1.xml");
|
||||
WriteRelationship(writer, "rId2", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles", "styles.xml");
|
||||
writer.WriteEndElement();
|
||||
writer.WriteEndDocument();
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteRelationship(XmlWriter writer, string id, string type, string target)
|
||||
private static void WriteRelationship(
|
||||
XmlWriter writer,
|
||||
string id,
|
||||
string type,
|
||||
string target)
|
||||
{
|
||||
writer.WriteStartElement("Relationship");
|
||||
writer.WriteAttributeString("Id", id);
|
||||
@@ -603,14 +523,14 @@ namespace BizTalkApplicationCatalog.Reporting
|
||||
writer.WriteAttributeString("count", "3");
|
||||
WriteFont(writer, false, "000000", 10);
|
||||
WriteFont(writer, true, "FFFFFF", 10);
|
||||
WriteFont(writer, true, "FFFFFF", 16);
|
||||
WriteFont(writer, true, "FFFFFF", 14);
|
||||
writer.WriteEndElement();
|
||||
writer.WriteStartElement("fills", SpreadsheetNamespace);
|
||||
writer.WriteAttributeString("count", "4");
|
||||
WritePatternFill(writer, "none", null);
|
||||
WritePatternFill(writer, "gray125", null);
|
||||
WritePatternFill(writer, "solid", "1F4E78");
|
||||
WritePatternFill(writer, "solid", "2F75B5");
|
||||
WritePatternFill(writer, "solid", "33475B");
|
||||
WritePatternFill(writer, "solid", "005A8B");
|
||||
writer.WriteEndElement();
|
||||
writer.WriteStartElement("borders", SpreadsheetNamespace);
|
||||
writer.WriteAttributeString("count", "2");
|
||||
@@ -619,13 +539,14 @@ namespace BizTalkApplicationCatalog.Reporting
|
||||
writer.WriteEndElement();
|
||||
writer.WriteStartElement("cellStyleXfs", SpreadsheetNamespace);
|
||||
writer.WriteAttributeString("count", "1");
|
||||
WriteXf(writer, 0, 0, 0, false);
|
||||
WriteXf(writer, 0, 0, 0);
|
||||
writer.WriteEndElement();
|
||||
writer.WriteStartElement("cellXfs", SpreadsheetNamespace);
|
||||
writer.WriteAttributeString("count", "3");
|
||||
WriteXf(writer, 0, 0, 0, true);
|
||||
WriteXf(writer, 1, 2, 1, true);
|
||||
WriteXf(writer, 2, 3, 1, true);
|
||||
writer.WriteAttributeString("count", "4");
|
||||
WriteXf(writer, 0, 0, 0);
|
||||
WriteXf(writer, 1, 2, 1);
|
||||
WriteXf(writer, 2, 3, 1);
|
||||
WriteXf(writer, 1, 2, 1);
|
||||
writer.WriteEndElement();
|
||||
writer.WriteStartElement("cellStyles", SpreadsheetNamespace);
|
||||
writer.WriteAttributeString("count", "1");
|
||||
@@ -640,7 +561,11 @@ namespace BizTalkApplicationCatalog.Reporting
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteFont(XmlWriter writer, bool bold, string color, int size)
|
||||
private static void WriteFont(
|
||||
XmlWriter writer,
|
||||
bool bold,
|
||||
string color,
|
||||
int size)
|
||||
{
|
||||
writer.WriteStartElement("font", SpreadsheetNamespace);
|
||||
if (bold) writer.WriteElementString("b", SpreadsheetNamespace, string.Empty);
|
||||
@@ -656,7 +581,10 @@ namespace BizTalkApplicationCatalog.Reporting
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
|
||||
private static void WritePatternFill(XmlWriter writer, string pattern, string color)
|
||||
private static void WritePatternFill(
|
||||
XmlWriter writer,
|
||||
string pattern,
|
||||
string color)
|
||||
{
|
||||
writer.WriteStartElement("fill", SpreadsheetNamespace);
|
||||
writer.WriteStartElement("patternFill", SpreadsheetNamespace);
|
||||
@@ -697,8 +625,7 @@ namespace BizTalkApplicationCatalog.Reporting
|
||||
XmlWriter writer,
|
||||
int fontId,
|
||||
int fillId,
|
||||
int borderId,
|
||||
bool alignment)
|
||||
int borderId)
|
||||
{
|
||||
writer.WriteStartElement("xf", SpreadsheetNamespace);
|
||||
writer.WriteAttributeString("numFmtId", "0");
|
||||
@@ -709,32 +636,33 @@ namespace BizTalkApplicationCatalog.Reporting
|
||||
if (fontId > 0) writer.WriteAttributeString("applyFont", "1");
|
||||
if (fillId > 0) writer.WriteAttributeString("applyFill", "1");
|
||||
if (borderId > 0) writer.WriteAttributeString("applyBorder", "1");
|
||||
if (alignment)
|
||||
{
|
||||
writer.WriteAttributeString("applyAlignment", "1");
|
||||
writer.WriteStartElement("alignment", SpreadsheetNamespace);
|
||||
writer.WriteAttributeString("vertical", "top");
|
||||
writer.WriteAttributeString("wrapText", "1");
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
writer.WriteAttributeString("applyAlignment", "1");
|
||||
writer.WriteStartElement("alignment", SpreadsheetNamespace);
|
||||
writer.WriteAttributeString("vertical", "top");
|
||||
writer.WriteAttributeString("wrapText", "1");
|
||||
writer.WriteEndElement();
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
|
||||
private static void WriteCoreProperties(ZipArchive archive, InventoryDocument document)
|
||||
private static void WriteCoreProperties(
|
||||
ZipArchive archive,
|
||||
IList<CatalogSnapshot> snapshots)
|
||||
{
|
||||
using (var writer = CreateXmlWriter(archive, "docProps/core.xml"))
|
||||
{
|
||||
writer.WriteStartDocument();
|
||||
writer.WriteStartElement("cp", "coreProperties", "http://schemas.openxmlformats.org/package/2006/metadata/core-properties");
|
||||
writer.WriteStartElement(
|
||||
"cp",
|
||||
"coreProperties",
|
||||
"http://schemas.openxmlformats.org/package/2006/metadata/core-properties");
|
||||
writer.WriteAttributeString("xmlns", "dc", null, "http://purl.org/dc/elements/1.1/");
|
||||
writer.WriteAttributeString("xmlns", "dcterms", null, "http://purl.org/dc/terms/");
|
||||
writer.WriteAttributeString("xmlns", "xsi", null, "http://www.w3.org/2001/XMLSchema-instance");
|
||||
writer.WriteElementString("dc", "title", "http://purl.org/dc/elements/1.1/", "BizTalk-Anwendungsinventar " + document.EnvironmentName);
|
||||
writer.WriteElementString("dc", "title", "http://purl.org/dc/elements/1.1/", "Phase 1 – BizTalk Anwendungen & Adapter");
|
||||
writer.WriteElementString("dc", "creator", "http://purl.org/dc/elements/1.1/", "BEW BizTalk Application Catalog");
|
||||
writer.WriteElementString("cp", "lastModifiedBy", "http://schemas.openxmlformats.org/package/2006/metadata/core-properties", "BEW BizTalk Application Catalog");
|
||||
writer.WriteStartElement("dcterms", "created", "http://purl.org/dc/terms/");
|
||||
writer.WriteAttributeString("xsi", "type", "http://www.w3.org/2001/XMLSchema-instance", "dcterms:W3CDTF");
|
||||
writer.WriteString(document.CompletedUtc.ToString("yyyy-MM-ddTHH:mm:ssZ", CultureInfo.InvariantCulture));
|
||||
writer.WriteString(DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ", CultureInfo.InvariantCulture));
|
||||
writer.WriteEndElement();
|
||||
writer.WriteEndElement();
|
||||
writer.WriteEndDocument();
|
||||
@@ -746,16 +674,20 @@ namespace BizTalkApplicationCatalog.Reporting
|
||||
using (var writer = CreateXmlWriter(archive, "docProps/app.xml"))
|
||||
{
|
||||
writer.WriteStartDocument();
|
||||
writer.WriteStartElement("Properties", "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties");
|
||||
writer.WriteStartElement(
|
||||
"Properties",
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/extended-properties");
|
||||
writer.WriteAttributeString("xmlns", "vt", null, "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes");
|
||||
writer.WriteElementString("Application", "BEW BizTalk Application Catalog");
|
||||
writer.WriteElementString("AppVersion", "1.0");
|
||||
writer.WriteElementString("AppVersion", "2.0");
|
||||
writer.WriteEndElement();
|
||||
writer.WriteEndDocument();
|
||||
}
|
||||
}
|
||||
|
||||
private static XmlWriter CreateXmlWriter(ZipArchive archive, string path)
|
||||
private static XmlWriter CreateXmlWriter(
|
||||
ZipArchive archive,
|
||||
string path)
|
||||
{
|
||||
var entry = archive.CreateEntry(path, CompressionLevel.Optimal);
|
||||
return XmlWriter.Create(entry.Open(), new XmlWriterSettings
|
||||
@@ -771,23 +703,25 @@ namespace BizTalkApplicationCatalog.Reporting
|
||||
public SheetDefinition(
|
||||
string name,
|
||||
List<object[]> rows,
|
||||
bool autoFilter,
|
||||
int freezeRows)
|
||||
int freezeRows,
|
||||
int filterHeaderRow,
|
||||
int footerRowIndex,
|
||||
double[] columnWidths)
|
||||
{
|
||||
Name = name;
|
||||
Rows = rows;
|
||||
AutoFilter = autoFilter;
|
||||
FreezeRows = freezeRows;
|
||||
FilterHeaderRow = filterHeaderRow;
|
||||
FooterRowIndex = footerRowIndex;
|
||||
ColumnWidths = columnWidths;
|
||||
}
|
||||
|
||||
public string Name { get; private set; }
|
||||
public List<object[]> Rows { get; private set; }
|
||||
public bool AutoFilter { get; private set; }
|
||||
public int FreezeRows { get; private set; }
|
||||
public int MaximumColumns
|
||||
{
|
||||
get { return Rows.Count == 0 ? 0 : Rows.Max(item => item.Length); }
|
||||
}
|
||||
public int FilterHeaderRow { get; private set; }
|
||||
public int FooterRowIndex { get; private set; }
|
||||
public double[] ColumnWidths { get; private set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user