Initial commit: BizTalk application catalog

This commit is contained in:
2026-07-27 14:46:02 +02:00
commit 2b5b97c869
25 changed files with 3026 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
<appSettings>
<add key="WmiTimeoutSeconds" value="30" />
<add key="MaxRowsPerArtifactType" value="10000" />
</appSettings>
</configuration>
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{41CB5701-3FBC-49F4-856A-6AE930B8513D}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>BizTalkApplicationCatalog</RootNamespace>
<AssemblyName>BizTalkApplicationCatalog</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<TargetFrameworkProfile />
<FileAlignment>512</FileAlignment>
<LangVersion>7.3</LangVersion>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<WarningLevel>4</WarningLevel>
<PlatformTarget>AnyCPU</PlatformTarget>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<WarningLevel>4</WarningLevel>
<PlatformTarget>AnyCPU</PlatformTarget>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.IO.Compression" />
<Reference Include="System.IO.Compression.FileSystem" />
<Reference Include="System.Management" />
<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="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Reporting\XlsxReportWriter.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
@@ -0,0 +1,421 @@
using System;
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.
/// </summary>
internal sealed class BizTalkWmiCollector
{
private readonly InventoryDocument document;
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)
{
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");
}
document.Applications.Sort((left, right) =>
string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase));
document.Artifacts.Sort(CompareArtifacts);
document.Hosts.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);
});
}
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
{
Name = name,
Description = First(row, "Description"),
Status = FormatStatus(First(row, "Status")),
IsDefault = FormatBoolean(First(row, "IsDefault")),
Source = "MSBTS_Application"
});
count++;
}
}
document.Coverage.Add(new CoverageRecord
{
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)
{
throw new InvalidOperationException(
"MSBTS_Application lieferte keine Anwendungen. Zielserver und Berechtigung prüfen.");
}
logger.Info(count + " installierte BizTalk-Anwendung(en) gefunden.");
}
private void CollectOptionalArtifacts(ArtifactDescriptor descriptor)
{
try
{
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")
};
}
private List<ManagementObject> Query(string query)
{
var options = new EnumerationOptions
{
ReturnImmediately = false,
Rewindable = false,
Timeout = timeout
};
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)
{
try
{
var value = row[name];
if (value == null) continue;
var text = ConvertValue(value);
if (!string.IsNullOrWhiteSpace(text)) return text;
}
catch (Exception exception) when (IsRecoverableWmiException(exception))
{
// Die Eigenschaft ist in dieser BizTalk-Version/Klasse nicht vorhanden.
}
}
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; }
}
}
}
@@ -0,0 +1,223 @@
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;
}
}
}
@@ -0,0 +1,115 @@
using System;
using System.IO;
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 =
new Regex("[^A-Z0-9_-]+", RegexOptions.Compiled | RegexOptions.CultureInvariant);
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 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();
for (var index = 0; index < args.Length; index++)
{
var argument = args[index];
switch (argument.ToLowerInvariant())
{
case "--environment":
result.EnvironmentName = Value(args, ref index, argument);
break;
case "--output":
result.OutputDirectory = Value(args, ref index, argument);
break;
case "--management-server":
result.ManagementServer = Value(args, ref index, argument);
break;
case "--management-database":
result.ManagementDatabase = Value(args, ref index, argument);
break;
case "--self-test":
result.SelfTest = true;
break;
case "--help":
case "-h":
case "/?":
result.ShowHelp = true;
break;
default:
throw new ArgumentException("Unbekannte Option: " + argument);
}
}
if (result.SelfTest || result.ShowHelp)
{
return result;
}
if (string.IsNullOrWhiteSpace(result.EnvironmentName))
{
throw new ArgumentException("--environment fehlt.");
}
result.EnvironmentName = UnsafeEnvironment.Replace(
result.EnvironmentName.Trim().ToUpperInvariant(), "-").Trim('-');
if (result.EnvironmentName.Length == 0)
{
throw new ArgumentException("--environment enthält keinen gültigen Namen.");
}
if (string.IsNullOrWhiteSpace(result.OutputDirectory))
{
result.OutputDirectory = Path.Combine(
Environment.CurrentDirectory, "BizTalk-Anwendungsinventar", result.EnvironmentName);
}
result.OutputDirectory = Path.GetFullPath(
Environment.ExpandEnvironmentVariables(result.OutputDirectory));
return result;
}
public static string Usage()
{
return string.Join(Environment.NewLine, new[]
{
"BEW BizTalk Application Catalog",
string.Empty,
"Aufruf:",
" BizTalkApplicationCatalog.exe --environment ACC [--output PFAD]",
" BizTalkApplicationCatalog.exe --environment PROD [--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."
});
}
private static string Value(string[] args, ref int index, string option)
{
index++;
if (index >= args.Length || args[index].StartsWith("--", StringComparison.Ordinal))
{
throw new ArgumentException("Wert für " + option + " fehlt.");
}
return args[index];
}
}
}
@@ -0,0 +1,41 @@
using System;
using System.Globalization;
using System.IO;
using System.Text;
namespace BizTalkApplicationCatalog.Infrastructure
{
/// <summary>
/// Spiegelt alle Fortschrittsmeldungen zeitgleich auf die Konsole und in eine UTF-8-Logdatei.
/// </summary>
internal sealed class ConsoleFileLogger : IDisposable
{
private readonly object sync = new object();
private readonly StreamWriter writer;
public ConsoleFileLogger(string path)
{
writer = new StreamWriter(path, false, new UTF8Encoding(false)) { AutoFlush = true };
}
public void Info(string message) { Write("INFO", message); }
public void Warning(string message) { Write("WARNUNG", message); }
public void Error(string message) { Write("FEHLER", message); }
public void Dispose() { writer.Dispose(); }
private void Write(string level, string message)
{
var line = string.Format(
CultureInfo.InvariantCulture,
"{0:yyyy-MM-dd HH:mm:ss.fff} [{1}] {2}",
DateTime.Now,
level,
message ?? string.Empty);
lock (sync)
{
Console.WriteLine(line);
writer.WriteLine(line);
}
}
}
}
@@ -0,0 +1,61 @@
using System;
using System.Diagnostics;
using BizTalkApplicationCatalog.Models;
namespace BizTalkApplicationCatalog.Infrastructure
{
/// <summary>
/// Isoliert Erfassungsfehler, protokolliert sie und lässt unabhängige Abschnitte weiterlaufen.
/// </summary>
internal sealed class SafeCollector
{
private readonly InventoryDocument document;
private readonly ConsoleFileLogger logger;
public SafeCollector(InventoryDocument document, ConsoleFileLogger logger)
{
this.document = document;
this.logger = logger;
}
public void Execute(string name, bool required, Action action)
{
var timer = Stopwatch.StartNew();
logger.Info("Starte Abschnitt: " + name);
try
{
action();
timer.Stop();
document.SectionStatuses.Add(new SectionStatus
{
Name = name,
Status = "Erfolgreich",
Message = "Abschnitt vollständig ausgeführt.",
Required = required,
DurationMilliseconds = timer.ElapsedMilliseconds
});
logger.Info("Abschnitt abgeschlossen: " + name + " (" + timer.ElapsedMilliseconds + " ms)");
}
catch (Exception exception)
{
timer.Stop();
document.SectionStatuses.Add(new SectionStatus
{
Name = name,
Status = required ? "Fehler" : "Teilweise",
Message = exception.GetType().Name + ": " + exception.Message,
Required = required,
DurationMilliseconds = timer.ElapsedMilliseconds
});
document.Findings.Add(new Finding
{
Severity = required ? "Fehler" : "Warnung",
Area = name,
Message = "Datenerfassung fehlgeschlagen: " + exception.Message,
RecommendedAction = "Logdatei, WMI-Provider und Leseberechtigungen prüfen."
});
logger.Error(name + ": " + exception.Message);
}
}
}
}
@@ -0,0 +1,190 @@
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.Reporting;
namespace BizTalkApplicationCatalog.Infrastructure
{
/// <summary>
/// Prüft die zentrale Berichtslogik ohne Zugriff auf Windows- oder BizTalk-WMI.
/// </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))
: Path.Combine(
Path.GetTempPath(),
"BizTalkApplicationCatalogTests-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(directory);
var path = keepOutput ? Path.GetFullPath(outputPath) : Path.Combine(directory, "test.xlsx");
try
{
var document = SampleDocument();
new XlsxReportWriter().Write(document, path);
Assert(File.Exists(path), "XLSX-Datei wurde nicht erzeugt.");
using (var archive = ZipFile.OpenRead(path))
{
var required = new[]
{
"[Content_Types].xml",
"_rels/.rels",
"docProps/core.xml",
"docProps/app.xml",
"xl/workbook.xml",
"xl/_rels/workbook.xml.rels",
"xl/styles.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);
}
foreach (var entry in archive.Entries.Where(item =>
item.FullName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase)
|| item.FullName.EndsWith(".rels", StringComparison.OrdinalIgnoreCase)))
{
using (var stream = entry.Open())
{
XDocument.Load(stream);
}
}
var workbook = LoadXml(archive, "xl/workbook.xml");
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.");
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.");
var relationships = LoadXml(archive, "_rels/.rels");
XNamespace relationshipNamespace =
"http://schemas.openxmlformats.org/package/2006/relationships";
Assert(
relationships.Root.Elements(relationshipNamespace + "Relationship").Count() == 3,
"Paketbeziehungen sind unvollständig oder im falschen Namespace.");
var combinedText = new StringBuilder();
foreach (var entry in archive.Entries.Where(item =>
item.FullName.StartsWith("xl/worksheets/", StringComparison.Ordinal)))
{
using (var reader = new StreamReader(entry.Open(), Encoding.UTF8))
{
combinedText.Append(reader.ReadToEnd());
}
}
Assert(!combinedText.ToString().Contains("supersecret"), "XLSX enthält Testkennwort.");
Assert(combinedText.ToString().Contains("[REDACTED]"), "Redaktionsmarker fehlt.");
}
}
finally
{
try
{
if (!keepOutput && Directory.Exists(directory)) Directory.Delete(directory, true);
}
catch (IOException)
{
}
catch (UnauthorizedAccessException)
{
}
}
}
private static InventoryDocument SampleDocument()
{
var document = new InventoryDocument
{
EnvironmentName = "ACC",
ComputerName = "BIZTALK-ACC",
ToolVersion = "1.0.0.0",
ManagementServer = "SQL-ACC",
ManagementDatabase = "BizTalkMgmtDb",
StartedUtc = DateTime.UtcNow.AddSeconds(-1),
CompletedUtc = DateTime.UtcNow
};
document.SystemProperties.Add(new NameValueRecord(
"Betriebssystem", "Windows Server 2019", "Self-Test"));
document.Applications.Add(new ApplicationRecord
{
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")
};
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;
}
private static XDocument LoadXml(ZipArchive archive, string name)
{
using (var stream = archive.GetEntry(name).Open())
{
return XDocument.Load(stream);
}
}
private static void Assert(bool condition, string message)
{
if (!condition) throw new InvalidOperationException(message);
}
}
}
@@ -0,0 +1,39 @@
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);
}
}
}
@@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace BizTalkApplicationCatalog.Models
{
/// <summary>
/// Enthält die vollständig normalisierten Ergebnisse eines 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>();
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<Finding> Findings { get; private set; }
public List<SectionStatus> SectionStatuses { get; private set; }
public bool HasRequiredFailure
{
get
{
return SectionStatuses.Any(item => item.Required && item.Status != "Erfolgreich")
|| Findings.Any(item => item.Severity == "Fehler");
}
}
}
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
{
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; }
}
internal sealed class Finding
{
public string Severity { get; set; }
public string Area { get; set; }
public string Message { get; set; }
public string RecommendedAction { get; set; }
}
internal sealed class SectionStatus
{
public string Name { get; set; }
public string Status { get; set; }
public string Message { get; set; }
public bool Required { get; set; }
public long DurationMilliseconds { get; set; }
}
internal sealed class NameValueRecord
{
public NameValueRecord()
{
}
public NameValueRecord(string name, string value, string source = "")
{
Name = name ?? string.Empty;
Value = value ?? string.Empty;
Source = source ?? string.Empty;
}
public string Name { get; set; }
public string Value { get; set; }
public string Source { get; set; }
}
}
+194
View File
@@ -0,0 +1,194 @@
using System;
using System.Configuration;
using System.Globalization;
using System.IO;
using System.Linq;
using BizTalkApplicationCatalog.Collectors;
using BizTalkApplicationCatalog.Configuration;
using BizTalkApplicationCatalog.Infrastructure;
using BizTalkApplicationCatalog.Models;
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)
{
CommandLineOptions options;
try
{
options = CommandLineOptions.Parse(args);
}
catch (Exception exception)
{
Console.Error.WriteLine("FEHLER: " + exception.Message);
Console.Error.WriteLine();
Console.Error.WriteLine(CommandLineOptions.Usage());
return 2;
}
if (options.ShowHelp)
{
Console.WriteLine(CommandLineOptions.Usage());
return 0;
}
if (options.SelfTest)
{
return RunSelfTest();
}
try
{
Directory.CreateDirectory(options.OutputDirectory);
}
catch (Exception exception) when (
exception is IOException
|| exception is UnauthorizedAccessException
|| exception is ArgumentException)
{
Console.Error.WriteLine("FEHLER: Ausgabeordner kann nicht erstellt werden: " + exception.Message);
return 2;
}
var timestamp = DateTime.Now.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture);
var safeMachine = FileName(Environment.MachineName);
var baseName = "BizTalk-Anwendungsinventar-"
+ options.EnvironmentName + "-" + safeMachine + "-" + timestamp;
var logPath = Path.Combine(options.OutputDirectory, baseName + ".log");
var reportPath = Path.Combine(options.OutputDirectory, baseName + ".xlsx");
using (var logger = new ConsoleFileLogger(logPath))
{
var document = new InventoryDocument
{
EnvironmentName = options.EnvironmentName,
ComputerName = Environment.MachineName,
StartedUtc = DateTime.UtcNow
};
logger.Info("BEW BizTalk Application Catalog 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.");
var safe = new SafeCollector(document, logger);
safe.Execute(
"System und BizTalk-Gruppe",
false,
() => new SystemCollector(options).Collect(document));
safe.Execute(
"Anwendungen und Artefakte",
true,
() => new BizTalkWmiCollector(
document,
logger,
ReadInt("WmiTimeoutSeconds", 30),
ReadInt("MaxRowsPerArtifactType", 10000)).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;
}
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.");
return 1;
}
logger.Info("Inventarisierung erfolgreich abgeschlossen.");
return 0;
}
}
private static void Evaluate(InventoryDocument document)
{
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 withoutApplication = document.Artifacts.Count(item =>
string.IsNullOrWhiteSpace(item.ApplicationName));
if (withoutApplication > 0)
{
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."
});
}
}
private static int RunSelfTest()
{
try
{
SelfTestRunner.Run();
Console.WriteLine("Self-Test erfolgreich: XLSX-Paket, Tabellen und Secret-Redaktion sind gültig.");
return 0;
}
catch (Exception exception)
{
Console.Error.WriteLine("SELF-TEST FEHLGESCHLAGEN: " + exception);
return 1;
}
}
private static int ReadInt(string key, int fallback)
{
int value;
return int.TryParse(
ConfigurationManager.AppSettings[key],
NumberStyles.Integer,
CultureInfo.InvariantCulture,
out value)
? value
: fallback;
}
private static string FileName(string value)
{
var invalid = Path.GetInvalidFileNameChars();
return new string((value ?? "SERVER").Select(character =>
invalid.Contains(character) ? '-' : character).ToArray());
}
}
}
@@ -0,0 +1,14 @@
using System.Reflection;
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: 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")]
@@ -0,0 +1,793 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
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.
/// </summary>
internal sealed class XlsxReportWriter
{
private const string SpreadsheetNamespace =
"http://schemas.openxmlformats.org/spreadsheetml/2006/main";
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)
{
if (document == null) throw new ArgumentNullException("document");
if (string.IsNullOrWhiteSpace(outputPath)) throw new ArgumentException("Ausgabepfad fehlt.");
var fullPath = Path.GetFullPath(outputPath);
var directory = Path.GetDirectoryName(fullPath);
if (!string.IsNullOrWhiteSpace(directory)) Directory.CreateDirectory(directory);
var temporaryPath = fullPath + "." + Guid.NewGuid().ToString("N") + ".tmp";
try
{
var sheets = BuildSheets(document);
using (var archive = ZipFile.Open(temporaryPath, ZipArchiveMode.Create))
{
WriteContentTypes(archive, sheets.Count);
WritePackageRelationships(archive);
WriteCoreProperties(archive, document);
WriteApplicationProperties(archive);
WriteWorkbook(archive, sheets);
WriteWorkbookRelationships(archive, sheets.Count);
WriteStyles(archive);
for (var index = 0; index < sheets.Count; index++)
{
WriteWorksheet(archive, index + 1, sheets[index]);
}
}
if (File.Exists(fullPath)) File.Delete(fullPath);
File.Move(temporaryPath, fullPath);
}
finally
{
if (File.Exists(temporaryPath)) File.Delete(temporaryPath);
}
}
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)
{
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[]>
{
new object[]
{
"Kategorie", "Name", "Server", "Status", "Typ", "Windows-Gruppe",
"Nur 32 Bit", "Vertrauenswürdig", "Adapter"
}
};
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)
.ToList();
return incomplete.Count == 0
? "Vollständig"
: "Teilweise siehe Abdeckung: " + string.Join(", ", incomplete);
}
private static int Count(IEnumerable<ArtifactRecord> artifacts, string type)
{
return artifacts.Count(item => item.Type == type);
}
private static string JoinDistinct(IEnumerable<string> values)
{
return string.Join(
", ",
values.Where(item => !string.IsNullOrWhiteSpace(item))
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(item => item, StringComparer.OrdinalIgnoreCase));
}
private static string FirstNonEmpty(params string[] values)
{
return values.FirstOrDefault(item => !string.IsNullOrWhiteSpace(item)) ?? string.Empty;
}
private static string Empty(string value)
{
return string.IsNullOrWhiteSpace(value) ? "Nicht ermittelt" : value;
}
private static void WriteWorksheet(ZipArchive archive, int sheetNumber, SheetDefinition sheet)
{
using (var writer = CreateXmlWriter(archive, "xl/worksheets/sheet" + sheetNumber + ".xml"))
{
writer.WriteStartDocument();
writer.WriteStartElement("worksheet", SpreadsheetNamespace);
writer.WriteAttributeString("xmlns", "r", null, RelationshipsNamespace);
WriteSheetViews(writer, sheet.FreezeRows);
WriteColumns(writer, sheet);
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++)
{
var style = RowStyle(sheet, rowIndex);
WriteCell(
writer,
columnIndex + 1,
rowIndex + 1,
sheet.Rows[rowIndex][columnIndex],
style);
}
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("pageMargins", SpreadsheetNamespace);
writer.WriteAttributeString("left", "0.25");
writer.WriteAttributeString("right", "0.25");
writer.WriteAttributeString("top", "0.5");
writer.WriteAttributeString("bottom", "0.5");
writer.WriteAttributeString("header", "0.2");
writer.WriteAttributeString("footer", "0.2");
writer.WriteEndElement();
writer.WriteEndElement();
writer.WriteEndDocument();
}
}
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;
}
private static void WriteSheetViews(XmlWriter writer, int freezeRows)
{
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.WriteEndElement();
writer.WriteEndElement();
}
private static void WriteColumns(XmlWriter writer, SheetDefinition sheet)
{
writer.WriteStartElement("cols", SpreadsheetNamespace);
for (var columnIndex = 0; columnIndex < sheet.MaximumColumns; columnIndex++)
{
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("customWidth", "1");
writer.WriteEndElement();
}
writer.WriteEndElement();
}
private static void WriteCell(
XmlWriter writer,
int column,
int row,
object value,
int style)
{
writer.WriteStartElement("c", SpreadsheetNamespace);
writer.WriteAttributeString("r", ColumnName(column) + row);
if (style > 0) writer.WriteAttributeString("s", style.ToString(CultureInfo.InvariantCulture));
if (IsNumber(value))
{
writer.WriteStartElement("v", SpreadsheetNamespace);
writer.WriteString(Convert.ToString(value, CultureInfo.InvariantCulture));
writer.WriteEndElement();
}
else
{
writer.WriteAttributeString("t", "inlineStr");
writer.WriteStartElement("is", SpreadsheetNamespace);
writer.WriteStartElement("t", SpreadsheetNamespace);
writer.WriteAttributeString("xml", "space", null, "preserve");
writer.WriteString(ExcelText(Convert.ToString(value, CultureInfo.InvariantCulture)));
writer.WriteEndElement();
writer.WriteEndElement();
}
writer.WriteEndElement();
}
private static bool IsNumber(object value)
{
return value is byte || value is short || value is int || value is long
|| value is float || value is double || value is decimal;
}
private static string ExcelText(string value)
{
if (string.IsNullOrEmpty(value)) return string.Empty;
var builder = new StringBuilder(Math.Min(value.Length, 32767));
foreach (var character in value)
{
if (builder.Length >= 32767) break;
if (XmlConvert.IsXmlChar(character)) builder.Append(character);
}
return builder.ToString();
}
private static string ColumnName(int number)
{
var result = string.Empty;
while (number > 0)
{
number--;
result = (char)('A' + (number % 26)) + result;
number /= 26;
}
return result;
}
private static void WriteContentTypes(ZipArchive archive, int sheetCount)
{
using (var writer = CreateXmlWriter(archive, "[Content_Types].xml"))
{
writer.WriteStartDocument();
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");
}
writer.WriteEndElement();
writer.WriteEndDocument();
}
}
private static void WriteDefault(XmlWriter writer, string extension, string type)
{
writer.WriteStartElement("Default");
writer.WriteAttributeString("Extension", extension);
writer.WriteAttributeString("ContentType", type);
writer.WriteEndElement();
}
private static void WriteOverride(XmlWriter writer, string partName, string type)
{
writer.WriteStartElement("Override");
writer.WriteAttributeString("PartName", partName);
writer.WriteAttributeString("ContentType", type);
writer.WriteEndElement();
}
private static void WritePackageRelationships(ZipArchive archive)
{
using (var writer = CreateXmlWriter(archive, "_rels/.rels"))
{
writer.WriteStartDocument();
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");
writer.WriteEndElement();
writer.WriteEndDocument();
}
}
private static void WriteWorkbook(ZipArchive archive, List<SheetDefinition> sheets)
{
using (var writer = CreateXmlWriter(archive, "xl/workbook.xml"))
{
writer.WriteStartDocument();
writer.WriteStartElement("workbook", SpreadsheetNamespace);
writer.WriteAttributeString("xmlns", "r", null, RelationshipsNamespace);
writer.WriteStartElement("bookViews", SpreadsheetNamespace);
writer.WriteStartElement("workbookView", SpreadsheetNamespace);
writer.WriteAttributeString("activeTab", "0");
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.WriteEndElement();
writer.WriteEndElement();
writer.WriteEndDocument();
}
}
private static void WriteWorkbookRelationships(ZipArchive archive, int sheetCount)
{
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.WriteEndElement();
writer.WriteEndDocument();
}
}
private static void WriteRelationship(XmlWriter writer, string id, string type, string target)
{
writer.WriteStartElement("Relationship");
writer.WriteAttributeString("Id", id);
writer.WriteAttributeString("Type", type);
writer.WriteAttributeString("Target", target);
writer.WriteEndElement();
}
private static void WriteStyles(ZipArchive archive)
{
using (var writer = CreateXmlWriter(archive, "xl/styles.xml"))
{
writer.WriteStartDocument();
writer.WriteStartElement("styleSheet", SpreadsheetNamespace);
writer.WriteStartElement("fonts", SpreadsheetNamespace);
writer.WriteAttributeString("count", "3");
WriteFont(writer, false, "000000", 10);
WriteFont(writer, true, "FFFFFF", 10);
WriteFont(writer, true, "FFFFFF", 16);
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");
writer.WriteEndElement();
writer.WriteStartElement("borders", SpreadsheetNamespace);
writer.WriteAttributeString("count", "2");
WriteBorder(writer, false);
WriteBorder(writer, true);
writer.WriteEndElement();
writer.WriteStartElement("cellStyleXfs", SpreadsheetNamespace);
writer.WriteAttributeString("count", "1");
WriteXf(writer, 0, 0, 0, false);
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.WriteEndElement();
writer.WriteStartElement("cellStyles", SpreadsheetNamespace);
writer.WriteAttributeString("count", "1");
writer.WriteStartElement("cellStyle", SpreadsheetNamespace);
writer.WriteAttributeString("name", "Normal");
writer.WriteAttributeString("xfId", "0");
writer.WriteAttributeString("builtinId", "0");
writer.WriteEndElement();
writer.WriteEndElement();
writer.WriteEndElement();
writer.WriteEndDocument();
}
}
private static void WriteFont(XmlWriter writer, bool bold, string color, int size)
{
writer.WriteStartElement("font", SpreadsheetNamespace);
if (bold) writer.WriteElementString("b", SpreadsheetNamespace, string.Empty);
writer.WriteStartElement("sz", SpreadsheetNamespace);
writer.WriteAttributeString("val", size.ToString(CultureInfo.InvariantCulture));
writer.WriteEndElement();
writer.WriteStartElement("color", SpreadsheetNamespace);
writer.WriteAttributeString("rgb", "FF" + color);
writer.WriteEndElement();
writer.WriteStartElement("name", SpreadsheetNamespace);
writer.WriteAttributeString("val", "Calibri");
writer.WriteEndElement();
writer.WriteEndElement();
}
private static void WritePatternFill(XmlWriter writer, string pattern, string color)
{
writer.WriteStartElement("fill", SpreadsheetNamespace);
writer.WriteStartElement("patternFill", SpreadsheetNamespace);
writer.WriteAttributeString("patternType", pattern);
if (color != null)
{
writer.WriteStartElement("fgColor", SpreadsheetNamespace);
writer.WriteAttributeString("rgb", "FF" + color);
writer.WriteEndElement();
writer.WriteStartElement("bgColor", SpreadsheetNamespace);
writer.WriteAttributeString("indexed", "64");
writer.WriteEndElement();
}
writer.WriteEndElement();
writer.WriteEndElement();
}
private static void WriteBorder(XmlWriter writer, bool thin)
{
writer.WriteStartElement("border", SpreadsheetNamespace);
foreach (var side in new[] { "left", "right", "top", "bottom" })
{
writer.WriteStartElement(side, SpreadsheetNamespace);
if (thin)
{
writer.WriteAttributeString("style", "thin");
writer.WriteStartElement("color", SpreadsheetNamespace);
writer.WriteAttributeString("rgb", "FFD9E2F3");
writer.WriteEndElement();
}
writer.WriteEndElement();
}
writer.WriteElementString("diagonal", SpreadsheetNamespace, string.Empty);
writer.WriteEndElement();
}
private static void WriteXf(
XmlWriter writer,
int fontId,
int fillId,
int borderId,
bool alignment)
{
writer.WriteStartElement("xf", SpreadsheetNamespace);
writer.WriteAttributeString("numFmtId", "0");
writer.WriteAttributeString("fontId", fontId.ToString(CultureInfo.InvariantCulture));
writer.WriteAttributeString("fillId", fillId.ToString(CultureInfo.InvariantCulture));
writer.WriteAttributeString("borderId", borderId.ToString(CultureInfo.InvariantCulture));
writer.WriteAttributeString("xfId", "0");
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.WriteEndElement();
}
private static void WriteCoreProperties(ZipArchive archive, InventoryDocument document)
{
using (var writer = CreateXmlWriter(archive, "docProps/core.xml"))
{
writer.WriteStartDocument();
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", "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.WriteEndElement();
writer.WriteEndElement();
writer.WriteEndDocument();
}
}
private static void WriteApplicationProperties(ZipArchive archive)
{
using (var writer = CreateXmlWriter(archive, "docProps/app.xml"))
{
writer.WriteStartDocument();
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.WriteEndElement();
writer.WriteEndDocument();
}
}
private static XmlWriter CreateXmlWriter(ZipArchive archive, string path)
{
var entry = archive.CreateEntry(path, CompressionLevel.Optimal);
return XmlWriter.Create(entry.Open(), new XmlWriterSettings
{
Encoding = new UTF8Encoding(false),
Indent = false,
CloseOutput = true
});
}
internal sealed class SheetDefinition
{
public SheetDefinition(
string name,
List<object[]> rows,
bool autoFilter,
int freezeRows)
{
Name = name;
Rows = rows;
AutoFilter = autoFilter;
FreezeRows = freezeRows;
}
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); }
}
}
}
}