Fix BizTalk application ownership mapping
This commit is contained in:
@@ -23,7 +23,7 @@ namespace BizTalkPlatformManagementTool.Setup
|
||||
private const string ProductName = "BizTalk Platform Management Tool";
|
||||
|
||||
/// <summary>Aktuelle Produktversion des Installers und Uninstall-Eintrags.</summary>
|
||||
private const string ProductVersion = "2.3.2";
|
||||
private const string ProductVersion = "2.3.3";
|
||||
|
||||
/// <summary>
|
||||
/// Wartezeiten zwischen Wiederholungen atomarer Verzeichnisverschiebungen.
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace BizTalkPlatformManagementTool.Setup
|
||||
{
|
||||
AutoSize = true,
|
||||
Font = new Font(Font.FontFamily, 14, FontStyle.Bold),
|
||||
Text = "BizTalk Platform Management Tool 2.3.2"
|
||||
Text = "BizTalk Platform Management Tool 2.3.3"
|
||||
});
|
||||
root.Controls.Add(new Label
|
||||
{
|
||||
|
||||
@@ -8,6 +8,6 @@ using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyProduct("BizTalk Platform Management Tool")]
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: Guid("675b68a9-bd80-46a5-b8c5-3b11b0b374e2")]
|
||||
[assembly: AssemblyVersion("2.3.2.0")]
|
||||
[assembly: AssemblyFileVersion("2.3.2.0")]
|
||||
[assembly: AssemblyVersion("2.3.3.0")]
|
||||
[assembly: AssemblyFileVersion("2.3.3.0")]
|
||||
[assembly: InternalsVisibleTo("BizTalkPlatformManagementTool.Tests")]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="2.3.2.0" name="BizTalkPlatformManagementTool.Setup" />
|
||||
<assemblyIdentity version="2.3.3.0" name="BizTalkPlatformManagementTool.Setup" />
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security><requestedPrivileges><requestedExecutionLevel level="requireAdministrator" uiAccess="false" /></requestedPrivileges></security>
|
||||
</trustInfo>
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
<Compile Include="Models\DiffModels.cs" />
|
||||
<Compile Include="Models\OperationModels.cs" />
|
||||
<Compile Include="Services\BizTalkWmiClient.cs" />
|
||||
<Compile Include="Services\BizTalkApplicationResolver.cs" />
|
||||
<Compile Include="Services\AdapterAssemblyResolver.cs" />
|
||||
<Compile Include="Services\CsvWriter.cs" />
|
||||
<Compile Include="Services\HtmlReportWriter.cs" />
|
||||
|
||||
@@ -9,6 +9,6 @@ using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyCopyright("Copyright © 2026")]
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: Guid("2c5b2c0a-f407-46c2-9e3b-1fa09fa8445a")]
|
||||
[assembly: AssemblyVersion("2.3.2.0")]
|
||||
[assembly: AssemblyFileVersion("2.3.2.0")]
|
||||
[assembly: AssemblyVersion("2.3.3.0")]
|
||||
[assembly: AssemblyFileVersion("2.3.3.0")]
|
||||
[assembly: InternalsVisibleTo("BizTalkPlatformManagementTool.Tests")]
|
||||
|
||||
@@ -241,7 +241,7 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
}
|
||||
|
||||
/// <summary>Discovers BizTalk, ScheduledTask and explicitly configured directories.</summary>
|
||||
private static IEnumerable<string> DiscoverSearchDirectories()
|
||||
internal static IEnumerable<string> DiscoverSearchDirectories()
|
||||
{
|
||||
var result = new List<string>();
|
||||
AddConfiguredDirectories(result);
|
||||
|
||||
@@ -0,0 +1,443 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlClient;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
using System.Reflection;
|
||||
|
||||
namespace BizTalkPlatformManagementTool.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves runtime artifacts to their owning BizTalk applications by reading the
|
||||
/// ExplorerOM application hierarchy without a compile-time BizTalk dependency.
|
||||
/// </summary>
|
||||
internal sealed class BizTalkApplicationResolver
|
||||
{
|
||||
/// <summary>The strong-name token used by Microsoft BizTalk product assemblies.</summary>
|
||||
private static readonly byte[] BizTalkPublicKeyToken = { 0x31, 0xbf, 0x38, 0x56, 0xad, 0x36, 0x4e, 0x35 };
|
||||
|
||||
/// <summary>Maps group-unique send-port names to application names.</summary>
|
||||
private readonly ArtifactApplicationMap _sendPorts = new ArtifactApplicationMap();
|
||||
|
||||
/// <summary>Maps receive-port names to application names.</summary>
|
||||
private readonly ArtifactApplicationMap _receivePorts = new ArtifactApplicationMap();
|
||||
|
||||
/// <summary>Maps receive-location names to application names.</summary>
|
||||
private readonly ArtifactApplicationMap _receiveLocations = new ArtifactApplicationMap();
|
||||
|
||||
/// <summary>Maps orchestration names and supported aliases to application names.</summary>
|
||||
private readonly ArtifactApplicationMap _orchestrations = new ArtifactApplicationMap();
|
||||
|
||||
/// <summary>Initializes an empty resolver, primarily for deterministic regression tests.</summary>
|
||||
internal BizTalkApplicationResolver()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the BizTalk Management database location through WMI and builds an
|
||||
/// application/artifact index through the locally installed ExplorerOM assembly.
|
||||
/// </summary>
|
||||
/// <param name="client">Connected BizTalk WMI client.</param>
|
||||
/// <param name="logger">Optional operation logger for support diagnostics.</param>
|
||||
/// <returns>A resolver populated from the BizTalk application catalog.</returns>
|
||||
internal static BizTalkApplicationResolver Load(BizTalkWmiClient client, OperationLogger logger)
|
||||
{
|
||||
if (client == null)
|
||||
{
|
||||
throw new ArgumentNullException("client");
|
||||
}
|
||||
|
||||
string databaseServer;
|
||||
string databaseName;
|
||||
ReadManagementDatabase(client, out databaseServer, out databaseName);
|
||||
|
||||
var assembly = LoadExplorerOmAssembly();
|
||||
var catalogType = assembly.GetType("Microsoft.BizTalk.ExplorerOM.BtsCatalogExplorer", true, false);
|
||||
var catalog = Activator.CreateInstance(catalogType);
|
||||
try
|
||||
{
|
||||
var connection = new SqlConnectionStringBuilder
|
||||
{
|
||||
DataSource = databaseServer,
|
||||
InitialCatalog = databaseName,
|
||||
IntegratedSecurity = true,
|
||||
ConnectTimeout = 15,
|
||||
ApplicationName = "BizTalkPlatformManagementTool"
|
||||
};
|
||||
SetProperty(catalog, "ConnectionString", connection.ConnectionString);
|
||||
|
||||
var resolver = new BizTalkApplicationResolver();
|
||||
resolver.IndexApplications(GetProperty(catalog, "Applications"));
|
||||
Info(logger, "BizTalk application catalog loaded through ExplorerOM. "
|
||||
+ "Applications=" + resolver.ApplicationCount
|
||||
+ "; SendPorts=" + resolver._sendPorts.Count
|
||||
+ "; ReceivePorts=" + resolver._receivePorts.Count
|
||||
+ "; ReceiveLocations=" + resolver._receiveLocations.Count
|
||||
+ "; Orchestrations=" + resolver._orchestrations.Count
|
||||
+ "; ManagementDatabase=" + databaseServer + "\\" + databaseName + ".");
|
||||
return resolver;
|
||||
}
|
||||
catch (TargetInvocationException ex)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"BizTalk ExplorerOM could not read the application catalog from "
|
||||
+ databaseServer + "\\" + databaseName + ". "
|
||||
+ ExceptionDiagnostics.Format(ex.InnerException ?? ex), ex.InnerException ?? ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
var disposable = catalog as IDisposable;
|
||||
if (disposable != null)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets the number of distinct application names represented in the index.</summary>
|
||||
internal int ApplicationCount
|
||||
{
|
||||
get
|
||||
{
|
||||
return _sendPorts.ApplicationNames
|
||||
.Concat(_receivePorts.ApplicationNames)
|
||||
.Concat(_receiveLocations.ApplicationNames)
|
||||
.Concat(_orchestrations.ApplicationNames)
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.Count();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Adds a send-port ownership record to the index.</summary>
|
||||
/// <param name="application">Owning BizTalk application.</param>
|
||||
/// <param name="name">Send-port name.</param>
|
||||
internal void AddSendPort(string application, string name)
|
||||
{
|
||||
_sendPorts.Add(application, name);
|
||||
}
|
||||
|
||||
/// <summary>Adds a receive-port ownership record to the index.</summary>
|
||||
/// <param name="application">Owning BizTalk application.</param>
|
||||
/// <param name="name">Receive-port name.</param>
|
||||
internal void AddReceivePort(string application, string name)
|
||||
{
|
||||
_receivePorts.Add(application, name);
|
||||
}
|
||||
|
||||
/// <summary>Adds a receive-location ownership record to the index.</summary>
|
||||
/// <param name="application">Owning BizTalk application.</param>
|
||||
/// <param name="name">Receive-location name.</param>
|
||||
internal void AddReceiveLocation(string application, string name)
|
||||
{
|
||||
_receiveLocations.Add(application, name);
|
||||
}
|
||||
|
||||
/// <summary>Adds an orchestration ownership record to the index.</summary>
|
||||
/// <param name="application">Owning BizTalk application.</param>
|
||||
/// <param name="name">Orchestration name or alias.</param>
|
||||
internal void AddOrchestration(string application, string name)
|
||||
{
|
||||
_orchestrations.Add(application, name);
|
||||
}
|
||||
|
||||
/// <summary>Resolves a send port to its owning application.</summary>
|
||||
/// <param name="name">Send-port name.</param>
|
||||
/// <returns>The application name, or null when absent or ambiguous.</returns>
|
||||
internal string ResolveSendPort(string name)
|
||||
{
|
||||
return _sendPorts.Resolve(name);
|
||||
}
|
||||
|
||||
/// <summary>Resolves a receive location, with its receive port as a stable fallback.</summary>
|
||||
/// <param name="locationName">Receive-location name.</param>
|
||||
/// <param name="receivePortName">Parent receive-port name.</param>
|
||||
/// <returns>The application name, or null when absent or ambiguous.</returns>
|
||||
internal string ResolveReceiveLocation(string locationName, string receivePortName)
|
||||
{
|
||||
return _receiveLocations.Resolve(locationName) ?? _receivePorts.Resolve(receivePortName);
|
||||
}
|
||||
|
||||
/// <summary>Resolves an orchestration to its owning application.</summary>
|
||||
/// <param name="name">WMI orchestration name.</param>
|
||||
/// <returns>The application name, or null when absent or ambiguous.</returns>
|
||||
internal string ResolveOrchestration(string name)
|
||||
{
|
||||
return _orchestrations.Resolve(name);
|
||||
}
|
||||
|
||||
/// <summary>Enumerates all applications and their runtime artifact collections.</summary>
|
||||
/// <param name="applications">ExplorerOM application collection.</param>
|
||||
private void IndexApplications(object applications)
|
||||
{
|
||||
foreach (var application in Enumerate(applications, "Applications"))
|
||||
{
|
||||
var applicationName = ReadString(application, "Name");
|
||||
if (string.IsNullOrWhiteSpace(applicationName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var sendPort in Enumerate(GetProperty(application, "SendPorts"), "Application.SendPorts"))
|
||||
{
|
||||
AddSendPort(applicationName, ReadString(sendPort, "Name"));
|
||||
}
|
||||
|
||||
foreach (var receivePort in Enumerate(GetProperty(application, "ReceivePorts"), "Application.ReceivePorts"))
|
||||
{
|
||||
AddReceivePort(applicationName, ReadString(receivePort, "Name"));
|
||||
foreach (var location in Enumerate(GetProperty(receivePort, "ReceiveLocations"), "ReceivePort.ReceiveLocations"))
|
||||
{
|
||||
AddReceiveLocation(applicationName, ReadString(location, "Name"));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var orchestration in Enumerate(GetProperty(application, "Orchestrations"), "Application.Orchestrations"))
|
||||
{
|
||||
AddOrchestration(applicationName, ReadString(orchestration, "FullName"));
|
||||
AddOrchestration(applicationName, ReadString(orchestration, "AssemblyQualifiedName"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Reads the BizTalk Management database endpoint from MSBTS_GroupSetting.</summary>
|
||||
/// <param name="client">Connected WMI client.</param>
|
||||
/// <param name="server">Resolved SQL Server name.</param>
|
||||
/// <param name="database">Resolved management database name.</param>
|
||||
private static void ReadManagementDatabase(BizTalkWmiClient client, out string server, out string database)
|
||||
{
|
||||
var settings = client.Query("MSBTS_GroupSetting");
|
||||
try
|
||||
{
|
||||
if (settings.Count != 1)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Expected exactly one MSBTS_GroupSetting instance but found " + settings.Count + ".");
|
||||
}
|
||||
server = BizTalkWmiClient.SafeGetString(settings[0], "MgmtDbServerName", null);
|
||||
database = BizTalkWmiClient.SafeGetString(settings[0], "MgmtDbName", null);
|
||||
if (string.IsNullOrWhiteSpace(server) || string.IsNullOrWhiteSpace(database))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"MSBTS_GroupSetting did not expose MgmtDbServerName and MgmtDbName.");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (ManagementObject setting in settings)
|
||||
{
|
||||
setting.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Loads an identity-verified ExplorerOM assembly from the CLR/GAC or installed BizTalk folders.</summary>
|
||||
/// <returns>The loaded Microsoft.BizTalk.ExplorerOM assembly.</returns>
|
||||
private static Assembly LoadExplorerOmAssembly()
|
||||
{
|
||||
var requested = new AssemblyName("Microsoft.BizTalk.ExplorerOM");
|
||||
requested.SetPublicKeyToken(BizTalkPublicKeyToken);
|
||||
|
||||
var loaded = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.FirstOrDefault(x => string.Equals(x.GetName().Name, requested.Name, StringComparison.OrdinalIgnoreCase));
|
||||
if (loaded != null)
|
||||
{
|
||||
RequireMicrosoftIdentity(loaded);
|
||||
return loaded;
|
||||
}
|
||||
|
||||
var explorerType = Type.GetType(
|
||||
"Microsoft.BizTalk.ExplorerOM.BtsCatalogExplorer, Microsoft.BizTalk.ExplorerOM",
|
||||
false);
|
||||
if (explorerType != null)
|
||||
{
|
||||
RequireMicrosoftIdentity(explorerType.Assembly);
|
||||
return explorerType.Assembly;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Assembly.Load(requested);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
// The installed-directory fallback below keeps non-GAC BizTalk admin installations usable.
|
||||
}
|
||||
catch (FileLoadException)
|
||||
{
|
||||
// The fallback still enforces the Microsoft strong-name identity before loading a file.
|
||||
}
|
||||
|
||||
#pragma warning disable 618
|
||||
// ExplorerOM versions vary across supported BizTalk installations. This GAC-only
|
||||
// compatibility API is followed by an explicit Microsoft public-key-token check.
|
||||
var partial = Assembly.LoadWithPartialName(requested.Name);
|
||||
#pragma warning restore 618
|
||||
if (partial != null)
|
||||
{
|
||||
RequireMicrosoftIdentity(partial);
|
||||
return partial;
|
||||
}
|
||||
|
||||
var path = AdapterAssemblyResolver.FindCandidateFile(
|
||||
requested.Name,
|
||||
requested,
|
||||
AdapterAssemblyResolver.DiscoverSearchDirectories());
|
||||
if (path == null)
|
||||
{
|
||||
throw new FileNotFoundException(
|
||||
"Microsoft.BizTalk.ExplorerOM.dll was not found through CLR/GAC resolution or in the configured/installed BizTalk directories. "
|
||||
+ "The application association cannot be derived from the runtime WMI classes alone.");
|
||||
}
|
||||
var fromPath = Assembly.LoadFrom(path);
|
||||
RequireMicrosoftIdentity(fromPath);
|
||||
return fromPath;
|
||||
}
|
||||
|
||||
/// <summary>Rejects a same-named assembly that is not signed with the BizTalk product token.</summary>
|
||||
/// <param name="assembly">Loaded ExplorerOM candidate.</param>
|
||||
private static void RequireMicrosoftIdentity(Assembly assembly)
|
||||
{
|
||||
var actual = assembly == null ? null : assembly.GetName().GetPublicKeyToken();
|
||||
if (actual == null || actual.Length != BizTalkPublicKeyToken.Length)
|
||||
{
|
||||
throw new FileLoadException("Microsoft.BizTalk.ExplorerOM has no valid BizTalk product public key token.");
|
||||
}
|
||||
for (var index = 0; index < actual.Length; index++)
|
||||
{
|
||||
if (actual[index] != BizTalkPublicKeyToken[index])
|
||||
{
|
||||
throw new FileLoadException("Microsoft.BizTalk.ExplorerOM has an unexpected public key token.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets a required public instance property through reflection.</summary>
|
||||
/// <param name="instance">Object that owns the property.</param>
|
||||
/// <param name="name">Property name.</param>
|
||||
/// <returns>The property value.</returns>
|
||||
private static object GetProperty(object instance, string name)
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
throw new InvalidOperationException("ExplorerOM object was null while reading property '" + name + "'.");
|
||||
}
|
||||
var property = instance.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public);
|
||||
if (property == null)
|
||||
{
|
||||
throw new MissingMemberException(instance.GetType().FullName, name);
|
||||
}
|
||||
return property.GetValue(instance, null);
|
||||
}
|
||||
|
||||
/// <summary>Sets a required public instance property through reflection.</summary>
|
||||
/// <param name="instance">Object that owns the property.</param>
|
||||
/// <param name="name">Property name.</param>
|
||||
/// <param name="value">Value to assign.</param>
|
||||
private static void SetProperty(object instance, string name, object value)
|
||||
{
|
||||
var property = instance.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public);
|
||||
if (property == null || !property.CanWrite)
|
||||
{
|
||||
throw new MissingMemberException(instance.GetType().FullName, name);
|
||||
}
|
||||
property.SetValue(instance, value, null);
|
||||
}
|
||||
|
||||
/// <summary>Reads an optional string property from one ExplorerOM object.</summary>
|
||||
/// <param name="instance">ExplorerOM object.</param>
|
||||
/// <param name="name">Property name.</param>
|
||||
/// <returns>The trimmed string value, or null.</returns>
|
||||
private static string ReadString(object instance, string name)
|
||||
{
|
||||
var property = instance.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public);
|
||||
if (property == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var value = property.GetValue(instance, null) as string;
|
||||
return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
|
||||
}
|
||||
|
||||
/// <summary>Validates and enumerates one ExplorerOM collection.</summary>
|
||||
/// <param name="value">Collection object.</param>
|
||||
/// <param name="description">Property path used in diagnostics.</param>
|
||||
/// <returns>The collection as an enumerable sequence.</returns>
|
||||
private static IEnumerable Enumerate(object value, string description)
|
||||
{
|
||||
var enumerable = value as IEnumerable;
|
||||
if (enumerable == null)
|
||||
{
|
||||
throw new InvalidOperationException("ExplorerOM property '" + description + "' is not enumerable.");
|
||||
}
|
||||
return enumerable;
|
||||
}
|
||||
|
||||
/// <summary>Writes an informational message when logging is available.</summary>
|
||||
/// <param name="logger">Optional logger.</param>
|
||||
/// <param name="message">Message text.</param>
|
||||
private static void Info(OperationLogger logger, string message)
|
||||
{
|
||||
if (logger != null)
|
||||
{
|
||||
logger.Info(message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Stores one artifact-type mapping and rejects cross-application ambiguity.</summary>
|
||||
private sealed class ArtifactApplicationMap
|
||||
{
|
||||
/// <summary>Unambiguous name/application mappings.</summary>
|
||||
private readonly Dictionary<string, string> _applications = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>Names observed under more than one application.</summary>
|
||||
private readonly HashSet<string> _ambiguous = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>Gets the number of unambiguous artifact names.</summary>
|
||||
internal int Count { get { return _applications.Count; } }
|
||||
|
||||
/// <summary>Gets the represented application names.</summary>
|
||||
internal IEnumerable<string> ApplicationNames { get { return _applications.Values; } }
|
||||
|
||||
/// <summary>Adds one mapping unless the artifact name is blank or becomes ambiguous.</summary>
|
||||
/// <param name="application">Application name.</param>
|
||||
/// <param name="artifact">Artifact name.</param>
|
||||
internal void Add(string application, string artifact)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(application) || string.IsNullOrWhiteSpace(artifact))
|
||||
{
|
||||
return;
|
||||
}
|
||||
application = application.Trim();
|
||||
artifact = artifact.Trim();
|
||||
if (_ambiguous.Contains(artifact))
|
||||
{
|
||||
return;
|
||||
}
|
||||
string existing;
|
||||
if (_applications.TryGetValue(artifact, out existing)
|
||||
&& !string.Equals(existing, application, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_applications.Remove(artifact);
|
||||
_ambiguous.Add(artifact);
|
||||
return;
|
||||
}
|
||||
_applications[artifact] = application;
|
||||
}
|
||||
|
||||
/// <summary>Resolves one unambiguous artifact name.</summary>
|
||||
/// <param name="artifact">Artifact name.</param>
|
||||
/// <returns>The application name, or null.</returns>
|
||||
internal string Resolve(string artifact)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(artifact) || _ambiguous.Contains(artifact.Trim()))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
string application;
|
||||
return _applications.TryGetValue(artifact.Trim(), out application) ? application : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,10 +16,10 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
/// <summary>
|
||||
/// Current tool version written into generated snapshots.
|
||||
/// </summary>
|
||||
public const string Version = "2.3.2-net461";
|
||||
public const string Version = "2.3.3-net461";
|
||||
|
||||
/// <summary>
|
||||
/// Fallback application name used when WMI does not expose an application property.
|
||||
/// Fallback application name used when neither WMI nor the ExplorerOM catalog can resolve ownership.
|
||||
/// </summary>
|
||||
private const string UnknownApplication = "(Unknown Application)";
|
||||
|
||||
@@ -48,7 +48,17 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
var ports = client.Query("MSBTS_SendPort");
|
||||
try
|
||||
{
|
||||
_logger.Success("WMI/CIM diagnostic succeeded. Send ports visible: " + ports.Count);
|
||||
var applications = TryLoadApplicationResolver(client);
|
||||
var resolved = applications == null
|
||||
? 0
|
||||
: ports.Count(x => !string.IsNullOrWhiteSpace(applications.ResolveSendPort(BizTalkWmiClient.SafeGetString(x, "Name", null))));
|
||||
_logger.Success("WMI/CIM diagnostic succeeded. Send ports visible: " + ports.Count
|
||||
+ "; application associations resolved: " + resolved + ".");
|
||||
if (ports.Count > 0 && resolved != ports.Count)
|
||||
{
|
||||
_logger.Warning("Application association is incomplete. Unresolved send ports: "
|
||||
+ (ports.Count - resolved) + ". Check the preceding ExplorerOM and BizTalk Management database diagnostics.");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -74,18 +84,21 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
};
|
||||
|
||||
var apps = new Dictionary<string, ApplicationSnapshot>(StringComparer.OrdinalIgnoreCase);
|
||||
var applicationResolver = TryLoadApplicationResolver(client);
|
||||
var receiveLocations = client.Query("MSBTS_ReceiveLocation");
|
||||
try
|
||||
{
|
||||
foreach (var item in receiveLocations)
|
||||
{
|
||||
var appName = GetApplicationName(item);
|
||||
var name = BizTalkWmiClient.SafeGetString(item, "Name", string.Empty);
|
||||
var receivePortName = BizTalkWmiClient.SafeGetString(item, "ReceivePortName", string.Empty);
|
||||
var appName = GetApplicationName(item, applicationResolver, "ReceiveLocation", name, receivePortName);
|
||||
var app = GetApplication(apps, appName);
|
||||
app.ReceiveLocations.Add(new ReceiveLocationState
|
||||
{
|
||||
Application = app.Application,
|
||||
Name = BizTalkWmiClient.SafeGetString(item, "Name", string.Empty),
|
||||
ReceivePortName = BizTalkWmiClient.SafeGetString(item, "ReceivePortName", string.Empty),
|
||||
Name = name,
|
||||
ReceivePortName = receivePortName,
|
||||
Enabled = !BizTalkWmiClient.SafeGetBoolean(item, "IsDisabled", true),
|
||||
AdapterName = BizTalkWmiClient.SafeGetString(item, "AdapterName", string.Empty),
|
||||
Address = BizTalkWmiClient.SafeGetString(item, "InboundTransportURL", string.Empty)
|
||||
@@ -102,12 +115,13 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
{
|
||||
foreach (var item in sendPorts)
|
||||
{
|
||||
var appName = GetApplicationName(item);
|
||||
var name = BizTalkWmiClient.SafeGetString(item, "Name", string.Empty);
|
||||
var appName = GetApplicationName(item, applicationResolver, "SendPort", name, null);
|
||||
var app = GetApplication(apps, appName);
|
||||
app.SendPorts.Add(new SendPortState
|
||||
{
|
||||
Application = app.Application,
|
||||
Name = BizTalkWmiClient.SafeGetString(item, "Name", string.Empty),
|
||||
Name = name,
|
||||
Status = BizTalkWmiClient.SafeGetInt32(item, "Status", 0),
|
||||
PrimaryTransportType = BizTalkWmiClient.SafeGetString(item, "PTTransportType", string.Empty),
|
||||
PrimaryTransportAddress = BizTalkWmiClient.SafeGetString(item, "PTAddress", string.Empty)
|
||||
@@ -124,12 +138,13 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
{
|
||||
foreach (var item in orchestrations)
|
||||
{
|
||||
var appName = GetApplicationName(item);
|
||||
var name = BizTalkWmiClient.SafeGetString(item, "Name", string.Empty);
|
||||
var appName = GetApplicationName(item, applicationResolver, "Orchestration", name, null);
|
||||
var app = GetApplication(apps, appName);
|
||||
app.Orchestrations.Add(new OrchestrationState
|
||||
{
|
||||
Application = app.Application,
|
||||
Name = BizTalkWmiClient.SafeGetString(item, "Name", string.Empty),
|
||||
Name = name,
|
||||
OrchestrationStatus = BizTalkWmiClient.SafeGetInt32(item, "OrchestrationStatus", 0)
|
||||
});
|
||||
}
|
||||
@@ -170,6 +185,14 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
snapshot.HostInstances = snapshot.HostInstances.OrderBy(x => x.Server).ThenBy(x => x.InstanceName).ToList();
|
||||
|
||||
SnapshotValidator.Validate(snapshot);
|
||||
ApplicationSnapshot unresolved;
|
||||
if (apps.TryGetValue(UnknownApplication, out unresolved) && _logger != null)
|
||||
{
|
||||
_logger.Warning("Snapshot contains unresolved BizTalk application associations. ReceiveLocations="
|
||||
+ unresolved.ReceiveLocations.Count + "; SendPorts=" + unresolved.SendPorts.Count
|
||||
+ "; Orchestrations=" + unresolved.Orchestrations.Count
|
||||
+ ". Review ExplorerOM and BizTalk Management database diagnostics before plan approval.");
|
||||
}
|
||||
_logger.Success("Snapshot created. Applications: " + snapshot.Applications.Count + ", host instances: " + snapshot.HostInstances.Count);
|
||||
return snapshot;
|
||||
}
|
||||
@@ -873,24 +896,75 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the application name from the available WMI properties.
|
||||
/// Reads a directly exposed application name or resolves it through the
|
||||
/// ExplorerOM application hierarchy because the runtime WMI classes do not
|
||||
/// define a reliable application property.
|
||||
/// </summary>
|
||||
/// <param name="item">The WMI object being mapped into a snapshot item.</param>
|
||||
/// <param name="resolver">Optional ExplorerOM application resolver.</param>
|
||||
/// <param name="kind">Artifact kind used to select the catalog index.</param>
|
||||
/// <param name="name">Artifact name.</param>
|
||||
/// <param name="parentName">Optional parent receive-port name.</param>
|
||||
/// <returns>The discovered application name or a stable fallback.</returns>
|
||||
private static string GetApplicationName(ManagementObject item)
|
||||
private static string GetApplicationName(ManagementObject item, BizTalkApplicationResolver resolver, string kind, string name, string parentName)
|
||||
{
|
||||
var names = new[] { "ApplicationName", "Application", "BizTalkApplication" };
|
||||
foreach (var name in names)
|
||||
foreach (var propertyName in names)
|
||||
{
|
||||
var value = BizTalkWmiClient.SafeGetString(item, name, null);
|
||||
var value = BizTalkWmiClient.SafeGetString(item, propertyName, null);
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
if (resolver != null)
|
||||
{
|
||||
string application = null;
|
||||
if (string.Equals(kind, "SendPort", StringComparison.Ordinal))
|
||||
{
|
||||
application = resolver.ResolveSendPort(name);
|
||||
}
|
||||
else if (string.Equals(kind, "ReceiveLocation", StringComparison.Ordinal))
|
||||
{
|
||||
application = resolver.ResolveReceiveLocation(name, parentName);
|
||||
}
|
||||
else if (string.Equals(kind, "Orchestration", StringComparison.Ordinal))
|
||||
{
|
||||
application = resolver.ResolveOrchestration(name);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(application))
|
||||
{
|
||||
return application;
|
||||
}
|
||||
}
|
||||
return UnknownApplication;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the optional ExplorerOM application index while preserving WMI-only
|
||||
/// snapshot availability and emitting an actionable warning on failure.
|
||||
/// </summary>
|
||||
/// <param name="client">Connected BizTalk WMI client.</param>
|
||||
/// <returns>The populated resolver, or null when the catalog could not be read.</returns>
|
||||
private BizTalkApplicationResolver TryLoadApplicationResolver(BizTalkWmiClient client)
|
||||
{
|
||||
try
|
||||
{
|
||||
return BizTalkApplicationResolver.Load(client, _logger);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (_logger != null)
|
||||
{
|
||||
_logger.Warning("BizTalk application association could not be loaded. Runtime state collection remains available, "
|
||||
+ "but affected artifacts will be grouped under " + UnknownApplication + ". "
|
||||
+ ExceptionDiagnostics.Format(ex));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gibt alle von einer WMI-Abfrage übernommenen Objekte deterministisch frei.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="2.3.2.0" name="BizTalkPlatformManagementTool" />
|
||||
<assemblyIdentity version="2.3.3.0" name="BizTalkPlatformManagementTool" />
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
|
||||
Reference in New Issue
Block a user