Adding new .NET client code, replacing the powershell scripts.

This commit is contained in:
2026-04-27 10:52:08 +02:00
parent 016ce19229
commit 6c837f35f5
25 changed files with 3286 additions and 703 deletions
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.1" />
</startup>
</configuration>
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildToolsPath)\Microsoft.Common.props" Condition="Exists('$(MSBuildToolsPath)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{2C5B2C0A-F407-46C2-9E3B-1FA09FA8445A}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>BizTalkPlatformManagementTool</RootNamespace>
<AssemblyName>BizTalkPlatformManagementTool</AssemblyName>
<TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.Management" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Models\ArtifactStates.cs" />
<Compile Include="Models\BizTalkSnapshot.cs" />
<Compile Include="Models\DiffModels.cs" />
<Compile Include="Models\OperationModels.cs" />
<Compile Include="Services\BizTalkWmiClient.cs" />
<Compile Include="Services\CsvWriter.cs" />
<Compile Include="Services\HtmlReportWriter.cs" />
<Compile Include="Services\JsonFileStore.cs" />
<Compile Include="Services\OperationLogger.cs" />
<Compile Include="Services\SnapshotComparer.cs" />
<Compile Include="Services\SnapshotStore.cs" />
<Compile Include="Services\BizTalkOperationService.cs" />
<Compile Include="Ui\MainForm.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
@@ -0,0 +1,59 @@
namespace BizTalkPlatformManagementTool.Models
{
public static class ArtifactStates
{
public const int SendPortBound = 1;
public const int SendPortStopped = 2;
public const int SendPortStarted = 3;
public const int OrchestrationUnbound = 1;
public const int OrchestrationBound = 2;
public const int OrchestrationStopped = 3;
public const int OrchestrationStarted = 4;
public const int HostStopped = 1;
public const int HostStartPending = 2;
public const int HostStopPending = 3;
public const int HostStarted = 4;
public static string FormatReceiveLocation(bool enabled)
{
return enabled ? "Enabled" : "Disabled";
}
public static string FormatSendPort(int status)
{
switch (status)
{
case SendPortStarted: return "Started";
case SendPortStopped: return "Stopped";
case SendPortBound: return "Bound";
default: return "Unknown (" + status + ")";
}
}
public static string FormatOrchestration(int status)
{
switch (status)
{
case OrchestrationStarted: return "Started";
case OrchestrationStopped: return "Stopped";
case OrchestrationBound: return "Bound";
case OrchestrationUnbound: return "Unbound";
default: return "Unknown (" + status + ")";
}
}
public static string FormatHostInstance(int state)
{
switch (state)
{
case HostStarted: return "Started";
case HostStartPending: return "StartPending";
case HostStopPending: return "StopPending";
case HostStopped: return "Stopped";
default: return "Unknown (" + state + ")";
}
}
}
}
@@ -0,0 +1,126 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace BizTalkPlatformManagementTool.Models
{
[DataContract]
public sealed class BizTalkSnapshot
{
public BizTalkSnapshot()
{
Applications = new List<ApplicationSnapshot>();
HostInstances = new List<HostInstanceState>();
}
[DataMember(Order = 1)]
public string ToolVersion { get; set; }
[DataMember(Order = 2)]
public string CreatedAt { get; set; }
[DataMember(Order = 3)]
public string Server { get; set; }
[DataMember(Order = 4)]
public List<ApplicationSnapshot> Applications { get; set; }
[DataMember(Order = 5)]
public List<HostInstanceState> HostInstances { get; set; }
}
[DataContract]
public sealed class ApplicationSnapshot
{
public ApplicationSnapshot()
{
ReceiveLocations = new List<ReceiveLocationState>();
SendPorts = new List<SendPortState>();
Orchestrations = new List<OrchestrationState>();
}
[DataMember(Order = 1)]
public string Application { get; set; }
[DataMember(Order = 2)]
public List<ReceiveLocationState> ReceiveLocations { get; set; }
[DataMember(Order = 3)]
public List<SendPortState> SendPorts { get; set; }
[DataMember(Order = 4)]
public List<OrchestrationState> Orchestrations { get; set; }
}
[DataContract]
public sealed class ReceiveLocationState
{
[DataMember(Order = 1)]
public string Application { get; set; }
[DataMember(Order = 2)]
public string Name { get; set; }
[DataMember(Order = 3)]
public string ReceivePortName { get; set; }
[DataMember(Order = 4)]
public bool Enabled { get; set; }
[DataMember(Order = 5)]
public string AdapterName { get; set; }
[DataMember(Order = 6)]
public string Address { get; set; }
}
[DataContract]
public sealed class SendPortState
{
[DataMember(Order = 1)]
public string Application { get; set; }
[DataMember(Order = 2)]
public string Name { get; set; }
[DataMember(Order = 3)]
public int Status { get; set; }
[DataMember(Order = 4)]
public string PrimaryTransportType { get; set; }
[DataMember(Order = 5)]
public string PrimaryTransportAddress { get; set; }
}
[DataContract]
public sealed class OrchestrationState
{
[DataMember(Order = 1)]
public string Application { get; set; }
[DataMember(Order = 2)]
public string Name { get; set; }
[DataMember(Order = 3)]
public int OrchestrationStatus { get; set; }
}
[DataContract]
public sealed class HostInstanceState
{
[DataMember(Order = 1)]
public string InstanceName { get; set; }
[DataMember(Order = 2)]
public string HostName { get; set; }
[DataMember(Order = 3)]
public string Server { get; set; }
[DataMember(Order = 4)]
public int RawState { get; set; }
[DataMember(Order = 5)]
public string StateText { get; set; }
}
}
@@ -0,0 +1,59 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace BizTalkPlatformManagementTool.Models
{
[DataContract]
public sealed class SnapshotDiff
{
public SnapshotDiff()
{
ArtifactDifferences = new List<ArtifactDiffEntry>();
HostInstanceDifferences = new List<HostInstanceDiffEntry>();
}
[DataMember(Order = 1)]
public List<ArtifactDiffEntry> ArtifactDifferences { get; set; }
[DataMember(Order = 2)]
public List<HostInstanceDiffEntry> HostInstanceDifferences { get; set; }
}
[DataContract]
public sealed class ArtifactDiffEntry
{
[DataMember(Order = 1)]
public string Application { get; set; }
[DataMember(Order = 2)]
public string ArtifactType { get; set; }
[DataMember(Order = 3)]
public string Name { get; set; }
[DataMember(Order = 4)]
public string Before { get; set; }
[DataMember(Order = 5)]
public string After { get; set; }
}
[DataContract]
public sealed class HostInstanceDiffEntry
{
[DataMember(Order = 1)]
public string InstanceName { get; set; }
[DataMember(Order = 2)]
public string HostName { get; set; }
[DataMember(Order = 3)]
public string Server { get; set; }
[DataMember(Order = 4)]
public string Before { get; set; }
[DataMember(Order = 5)]
public string After { get; set; }
}
}
@@ -0,0 +1,94 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace BizTalkPlatformManagementTool.Models
{
public enum OperationMode
{
Shutdown,
Restore
}
public enum OperationStepKind
{
ReceiveLocation,
SendPort,
Orchestration,
HostInstance,
Note
}
[DataContract]
public sealed class OperationPlan
{
public OperationPlan()
{
Steps = new List<OperationStep>();
}
[DataMember(Order = 1)]
public string Mode { get; set; }
[DataMember(Order = 2)]
public string CreatedAt { get; set; }
[DataMember(Order = 3)]
public string Server { get; set; }
[DataMember(Order = 4)]
public List<OperationStep> Steps { get; set; }
}
[DataContract]
public sealed class OperationStep
{
[DataMember(Order = 1)]
public string Kind { get; set; }
[DataMember(Order = 2)]
public string Application { get; set; }
[DataMember(Order = 3)]
public string Name { get; set; }
[DataMember(Order = 4)]
public string Server { get; set; }
[DataMember(Order = 5)]
public string Action { get; set; }
[DataMember(Order = 6)]
public string WmiClass { get; set; }
[DataMember(Order = 7)]
public string KeyProperty { get; set; }
[DataMember(Order = 8)]
public string KeyValue { get; set; }
[DataMember(Order = 9)]
public string MethodName { get; set; }
[DataMember(Order = 10)]
public int[] Arguments { get; set; }
[DataMember(Order = 11)]
public int? TargetState { get; set; }
[DataMember(Order = 12)]
public bool Execute { get; set; }
[DataMember(Order = 13)]
public string Warning { get; set; }
}
public sealed class OperationOptions
{
public string Server { get; set; }
public string OutputDirectory { get; set; }
public string StateFile { get; set; }
public bool DryRun { get; set; }
public int WaitTimeoutSeconds { get; set; }
public int PollIntervalSeconds { get; set; }
}
}
@@ -0,0 +1,17 @@
using System;
using System.Windows.Forms;
using BizTalkPlatformManagementTool.Ui;
namespace BizTalkPlatformManagementTool
{
internal static class Program
{
[STAThread]
private static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
}
@@ -0,0 +1,415 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Management;
using BizTalkPlatformManagementTool.Models;
namespace BizTalkPlatformManagementTool.Services
{
public sealed class BizTalkOperationService
{
public const string Version = "2.0.0-net471";
private const string UnknownApplication = "(Unknown Application)";
private readonly OperationLogger _logger;
public BizTalkOperationService(OperationLogger logger)
{
_logger = logger;
}
public void Diagnose(string server)
{
using (var client = CreateClient(server))
{
var ports = client.Query("MSBTS_SendPort");
_logger.Success("WMI/CIM diagnostic succeeded. Send ports visible: " + ports.Count);
}
}
public BizTalkSnapshot CreateSnapshot(string server)
{
using (var client = CreateClient(server))
{
var snapshot = new BizTalkSnapshot
{
ToolVersion = Version,
CreatedAt = DateTime.Now.ToString("s"),
Server = client.Server
};
var apps = new Dictionary<string, ApplicationSnapshot>(StringComparer.OrdinalIgnoreCase);
foreach (var item in client.Query("MSBTS_ReceiveLocation"))
{
var appName = GetApplicationName(item);
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),
Enabled = !BizTalkWmiClient.SafeGetBoolean(item, "IsDisabled", true),
AdapterName = BizTalkWmiClient.SafeGetString(item, "AdapterName", string.Empty),
Address = BizTalkWmiClient.SafeGetString(item, "InboundTransportURL", string.Empty)
});
}
foreach (var item in client.Query("MSBTS_SendPort"))
{
var appName = GetApplicationName(item);
var app = GetApplication(apps, appName);
app.SendPorts.Add(new SendPortState
{
Application = app.Application,
Name = BizTalkWmiClient.SafeGetString(item, "Name", string.Empty),
Status = BizTalkWmiClient.SafeGetInt32(item, "Status", 0),
PrimaryTransportType = BizTalkWmiClient.SafeGetString(item, "PTTransportType", string.Empty),
PrimaryTransportAddress = BizTalkWmiClient.SafeGetString(item, "PTAddress", string.Empty)
});
}
foreach (var item in client.Query("MSBTS_Orchestration"))
{
var appName = GetApplicationName(item);
var app = GetApplication(apps, appName);
app.Orchestrations.Add(new OrchestrationState
{
Application = app.Application,
Name = BizTalkWmiClient.SafeGetString(item, "Name", string.Empty),
OrchestrationStatus = BizTalkWmiClient.SafeGetInt32(item, "OrchestrationStatus", 0)
});
}
foreach (var item in client.Query("MSBTS_HostInstance"))
{
var state = BizTalkWmiClient.SafeGetInt32(item, "ServiceState", 0);
snapshot.HostInstances.Add(new HostInstanceState
{
InstanceName = BizTalkWmiClient.SafeGetString(item, "InstanceName", BizTalkWmiClient.SafeGetString(item, "Name", string.Empty)),
HostName = BizTalkWmiClient.SafeGetString(item, "HostName", string.Empty),
Server = BizTalkWmiClient.SafeGetString(item, "RunningServer", string.Empty),
RawState = state,
StateText = ArtifactStates.FormatHostInstance(state)
});
}
foreach (var app in apps.Values.OrderBy(a => a.Application))
{
app.ReceiveLocations = app.ReceiveLocations.OrderBy(x => x.Name).ToList();
app.SendPorts = app.SendPorts.OrderBy(x => x.Name).ToList();
app.Orchestrations = app.Orchestrations.OrderBy(x => x.Name).ToList();
snapshot.Applications.Add(app);
}
snapshot.HostInstances = snapshot.HostInstances.OrderBy(x => x.Server).ThenBy(x => x.InstanceName).ToList();
_logger.Success("Snapshot created. Applications: " + snapshot.Applications.Count + ", host instances: " + snapshot.HostInstances.Count);
return snapshot;
}
}
public OperationPlan CreateShutdownPlan(BizTalkSnapshot snapshot, string server)
{
var plan = NewPlan(OperationMode.Shutdown, server);
foreach (var app in snapshot.Applications)
{
foreach (var item in app.ReceiveLocations.Where(x => x.Enabled))
{
plan.Steps.Add(Step("ReceiveLocation", app.Application, item.Name, null, "Disable receive location", "MSBTS_ReceiveLocation", "Name", item.Name, "Disable", null, false));
}
foreach (var item in app.Orchestrations.Where(x => x.OrchestrationStatus == ArtifactStates.OrchestrationStarted))
{
plan.Steps.Add(Step("Orchestration", app.Application, item.Name, null, "Stop orchestration", "MSBTS_Orchestration", "Name", item.Name, "Stop", new[] { 1, 1 }, ArtifactStates.OrchestrationStopped));
}
foreach (var item in app.SendPorts.Where(x => x.Status == ArtifactStates.SendPortStarted))
{
plan.Steps.Add(Step("SendPort", app.Application, item.Name, null, "Stop send port", "MSBTS_SendPort", "Name", item.Name, "Stop", null, ArtifactStates.SendPortStopped));
}
}
foreach (var item in snapshot.HostInstances.Where(x => x.RawState == ArtifactStates.HostStarted))
{
var step = Step("HostInstance", string.Empty, item.InstanceName, item.Server, "Stop host instance", "MSBTS_HostInstance", "InstanceName", item.InstanceName, "Stop", null, ArtifactStates.HostStopped);
if (!string.IsNullOrEmpty(item.Server) && !string.Equals(item.Server, server, StringComparison.OrdinalIgnoreCase))
{
step.Execute = false;
step.Warning = "Host instance belongs to server '" + item.Server + "'. Run this action on that server.";
}
plan.Steps.Add(step);
}
return plan;
}
public OperationPlan CreateRestorePlan(BizTalkSnapshot snapshot, string server)
{
var plan = NewPlan(OperationMode.Restore, server);
foreach (var item in snapshot.HostInstances.Where(x => x.RawState == ArtifactStates.HostStarted))
{
var step = Step("HostInstance", string.Empty, item.InstanceName, item.Server, "Start host instance", "MSBTS_HostInstance", "InstanceName", item.InstanceName, "Start", null, ArtifactStates.HostStarted);
if (!string.IsNullOrEmpty(item.Server) && !string.Equals(item.Server, server, StringComparison.OrdinalIgnoreCase))
{
step.Execute = false;
step.Warning = "Host instance belongs to server '" + item.Server + "'. Run this action on that server.";
}
plan.Steps.Add(step);
}
foreach (var app in snapshot.Applications)
{
foreach (var item in app.SendPorts)
{
if (item.Status == ArtifactStates.SendPortStarted)
{
plan.Steps.Add(Step("SendPort", app.Application, item.Name, null, "Start send port", "MSBTS_SendPort", "Name", item.Name, "Start", null, ArtifactStates.SendPortStarted));
}
else if (item.Status == ArtifactStates.SendPortStopped)
{
plan.Steps.Add(Step("SendPort", app.Application, item.Name, null, "Ensure send port is stopped", "MSBTS_SendPort", "Name", item.Name, "StopOrEnlist", null, ArtifactStates.SendPortStopped));
}
else if (item.Status == ArtifactStates.SendPortBound)
{
plan.Steps.Add(Step("SendPort", app.Application, item.Name, null, "Ensure send port is bound", "MSBTS_SendPort", "Name", item.Name, "UnEnlist", null, ArtifactStates.SendPortBound));
}
}
foreach (var item in app.Orchestrations)
{
if (item.OrchestrationStatus == ArtifactStates.OrchestrationStarted)
{
plan.Steps.Add(Step("Orchestration", app.Application, item.Name, null, "Start orchestration", "MSBTS_Orchestration", "Name", item.Name, "Start", new[] { 1, 1, 1 }, ArtifactStates.OrchestrationStarted));
}
else if (item.OrchestrationStatus == ArtifactStates.OrchestrationStopped)
{
plan.Steps.Add(Step("Orchestration", app.Application, item.Name, null, "Ensure orchestration is stopped", "MSBTS_Orchestration", "Name", item.Name, "StopIfStarted", new[] { 1, 1 }, ArtifactStates.OrchestrationStopped));
}
else if (item.OrchestrationStatus == ArtifactStates.OrchestrationBound)
{
plan.Steps.Add(new OperationStep
{
Kind = "Note",
Application = app.Application,
Name = item.Name,
Action = "Orchestration was Bound in the snapshot; leaving unchanged to avoid making it Unbound.",
Execute = false,
Warning = "Manual check recommended."
});
}
else if (item.OrchestrationStatus == ArtifactStates.OrchestrationUnbound)
{
plan.Steps.Add(Step("Orchestration", app.Application, item.Name, null, "Unenlist orchestration", "MSBTS_Orchestration", "Name", item.Name, "UnenlistService", new[] { 1 }, ArtifactStates.OrchestrationUnbound));
}
}
foreach (var item in app.ReceiveLocations)
{
plan.Steps.Add(Step("ReceiveLocation", app.Application, item.Name, null, item.Enabled ? "Enable receive location" : "Disable receive location", "MSBTS_ReceiveLocation", "Name", item.Name, item.Enabled ? "Enable" : "Disable", null, null));
}
}
return plan;
}
public void ExecutePlan(OperationPlan plan, OperationOptions options)
{
using (var client = CreateClient(options.Server))
{
foreach (var step in plan.Steps)
{
if (!step.Execute)
{
_logger.Warning(step.Action + (string.IsNullOrEmpty(step.Warning) ? string.Empty : " " + step.Warning));
continue;
}
if (options.DryRun)
{
_logger.Info("DRY RUN: " + step.Action + " '" + step.Name + "'");
continue;
}
using (var instance = client.FindByProperty(step.WmiClass, step.KeyProperty, step.KeyValue))
{
if (instance == null)
{
throw new InvalidOperationException(step.Kind + " not found: " + step.Name);
}
ExecuteStep(client, instance, step, options);
}
}
}
}
public string SaveSnapshot(string outputDirectory, string fileName, BizTalkSnapshot snapshot)
{
Directory.CreateDirectory(outputDirectory);
var path = Path.Combine(outputDirectory, fileName);
SnapshotStore.SaveSnapshotSet(path, snapshot);
_logger.Success("Saved snapshot: " + path);
return path;
}
public string SavePlan(string outputDirectory, string fileName, OperationPlan plan)
{
Directory.CreateDirectory(outputDirectory);
var path = Path.Combine(outputDirectory, fileName);
JsonFileStore.Save(path, plan);
_logger.Success("Saved operation plan: " + path);
return path;
}
public string SaveDiff(string outputDirectory, string fileName, SnapshotDiff diff)
{
Directory.CreateDirectory(outputDirectory);
var path = Path.Combine(outputDirectory, fileName);
SnapshotStore.SaveDiffSet(path, diff);
_logger.Success("Saved diff: " + path);
return path;
}
private void ExecuteStep(BizTalkWmiClient client, ManagementObject instance, OperationStep step, OperationOptions options)
{
if (string.Equals(step.MethodName, "StopOrEnlist", StringComparison.OrdinalIgnoreCase))
{
var status = BizTalkWmiClient.SafeGetInt32(instance, "Status", 0);
if (status == ArtifactStates.SendPortBound)
{
client.InvokeMethod(instance, "Enlist");
}
else if (status == ArtifactStates.SendPortStarted)
{
client.InvokeMethod(instance, "Stop");
}
else
{
_logger.Info("Already stopped: " + step.Name);
}
}
else if (string.Equals(step.MethodName, "StopIfStarted", StringComparison.OrdinalIgnoreCase))
{
var status = BizTalkWmiClient.SafeGetInt32(instance, "OrchestrationStatus", 0);
if (status == ArtifactStates.OrchestrationStarted)
{
client.InvokeMethod(instance, "Stop", ToObjects(step.Arguments));
}
else
{
_logger.Info("No stop required: " + step.Name);
}
}
else
{
client.InvokeMethod(instance, step.MethodName, ToObjects(step.Arguments));
}
WaitForTarget(client, step, options);
}
private void WaitForTarget(BizTalkWmiClient client, OperationStep step, OperationOptions options)
{
if (!step.TargetState.HasValue)
{
if (step.Kind == "ReceiveLocation")
{
var shouldBeEnabled = string.Equals(step.MethodName, "Enable", StringComparison.OrdinalIgnoreCase);
client.WaitForState(step.WmiClass, step.KeyProperty, step.KeyValue, o => !BizTalkWmiClient.SafeGetBoolean(o, "IsDisabled", true) == shouldBeEnabled, step.Action + " completed", options.WaitTimeoutSeconds, options.PollIntervalSeconds);
}
return;
}
if (step.Kind == "SendPort")
{
client.WaitForState(step.WmiClass, step.KeyProperty, step.KeyValue, o => BizTalkWmiClient.SafeGetInt32(o, "Status", 0) == step.TargetState.Value, step.Action + " completed", options.WaitTimeoutSeconds, options.PollIntervalSeconds);
}
else if (step.Kind == "Orchestration")
{
client.WaitForState(step.WmiClass, step.KeyProperty, step.KeyValue, o => BizTalkWmiClient.SafeGetInt32(o, "OrchestrationStatus", 0) == step.TargetState.Value, step.Action + " completed", options.WaitTimeoutSeconds, options.PollIntervalSeconds);
}
else if (step.Kind == "HostInstance")
{
client.WaitForState(step.WmiClass, step.KeyProperty, step.KeyValue, o => BizTalkWmiClient.SafeGetInt32(o, "ServiceState", 0) == step.TargetState.Value, step.Action + " completed", options.WaitTimeoutSeconds, options.PollIntervalSeconds);
}
}
private BizTalkWmiClient CreateClient(string server)
{
var client = new BizTalkWmiClient(server, _logger);
client.Connect();
return client;
}
private static OperationPlan NewPlan(OperationMode mode, string server)
{
return new OperationPlan
{
Mode = mode.ToString(),
CreatedAt = DateTime.Now.ToString("s"),
Server = server
};
}
private static OperationStep Step(string kind, string application, string name, string server, string action, string wmiClass, string keyProperty, string keyValue, string methodName, int[] arguments, int? targetState)
{
return new OperationStep
{
Kind = kind,
Application = application,
Name = name,
Server = server,
Action = action,
WmiClass = wmiClass,
KeyProperty = keyProperty,
KeyValue = keyValue,
MethodName = methodName,
Arguments = arguments,
TargetState = targetState,
Execute = true
};
}
private static object[] ToObjects(int[] values)
{
if (values == null || values.Length == 0)
{
return new object[0];
}
var result = new object[values.Length];
for (int i = 0; i < values.Length; i++)
{
result[i] = values[i];
}
return result;
}
private static ApplicationSnapshot GetApplication(Dictionary<string, ApplicationSnapshot> apps, string appName)
{
ApplicationSnapshot app;
if (!apps.TryGetValue(appName, out app))
{
app = new ApplicationSnapshot { Application = appName };
apps.Add(appName, app);
}
return app;
}
private static string GetApplicationName(ManagementObject item)
{
var names = new[] { "ApplicationName", "Application", "BizTalkApplication" };
foreach (var name in names)
{
var value = BizTalkWmiClient.SafeGetString(item, name, null);
if (!string.IsNullOrWhiteSpace(value))
{
return value;
}
}
return UnknownApplication;
}
}
}
@@ -0,0 +1,232 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Management;
using System.Threading;
namespace BizTalkPlatformManagementTool.Services
{
public sealed class BizTalkWmiClient : IDisposable
{
private const string NamespacePath = "root\\MicrosoftBizTalkServer";
private readonly string _server;
private readonly OperationLogger _logger;
private ManagementScope _scope;
public BizTalkWmiClient(string server, OperationLogger logger)
{
_server = string.IsNullOrWhiteSpace(server) ? Environment.MachineName : server.Trim();
_logger = logger;
}
public string Server
{
get { return _server; }
}
public void Connect()
{
var path = "\\\\" + _server + "\\" + NamespacePath;
_logger.Info("Connecting to WMI namespace " + path);
_scope = new ManagementScope(path);
_scope.Connect();
_logger.Success("WMI connection established.");
}
public List<ManagementObject> Query(string className)
{
EnsureConnected();
var result = new List<ManagementObject>();
var query = new ObjectQuery("SELECT * FROM " + className);
using (var searcher = new ManagementObjectSearcher(_scope, query))
using (var collection = searcher.Get())
{
foreach (ManagementObject item in collection)
{
result.Add(item);
}
}
return result;
}
public ManagementObject FindByProperty(string className, string propertyName, string value)
{
EnsureConnected();
var query = string.Format(
CultureInfo.InvariantCulture,
"SELECT * FROM {0} WHERE {1} = \"{2}\"",
className,
propertyName,
EscapeWql(value));
using (var searcher = new ManagementObjectSearcher(_scope, new ObjectQuery(query)))
using (var collection = searcher.Get())
{
foreach (ManagementObject item in collection)
{
return item;
}
}
return null;
}
public uint InvokeMethod(ManagementObject instance, string methodName, params object[] arguments)
{
if (instance == null)
{
throw new ArgumentNullException("instance");
}
var methodToCall = ResolveMethodName(instance, methodName);
_logger.Info("Calling " + instance.Path.ClassName + "." + methodToCall + " on " + SafeGetString(instance, "Name", SafeGetString(instance, "InstanceName", "<unknown>")));
object result = instance.InvokeMethod(methodToCall, arguments == null || arguments.Length == 0 ? null : arguments);
var returnCode = ExtractReturnCode(result);
if (returnCode != 0)
{
throw new InvalidOperationException(methodToCall + " returned HRESULT/ReturnValue " + returnCode.ToString(CultureInfo.InvariantCulture));
}
return returnCode;
}
public void WaitForState(string className, string keyProperty, string keyValue, Func<ManagementObject, bool> isReached, string description, int timeoutSeconds, int pollIntervalSeconds)
{
var deadline = DateTime.UtcNow.AddSeconds(Math.Max(1, timeoutSeconds));
var delay = Math.Max(1, pollIntervalSeconds);
while (DateTime.UtcNow <= deadline)
{
using (var current = FindByProperty(className, keyProperty, keyValue))
{
if (current != null && isReached(current))
{
_logger.Success("Reached: " + description);
return;
}
}
Thread.Sleep(TimeSpan.FromSeconds(delay));
}
throw new TimeoutException("Timeout while waiting for " + description);
}
public static string SafeGetString(ManagementBaseObject item, string propertyName, string fallback)
{
try
{
if (!HasProperty(item, propertyName))
{
return fallback;
}
var value = item[propertyName];
return value == null ? fallback : Convert.ToString(value, CultureInfo.InvariantCulture);
}
catch (ManagementException)
{
return fallback;
}
}
public static int SafeGetInt32(ManagementBaseObject item, string propertyName, int fallback)
{
try
{
if (!HasProperty(item, propertyName))
{
return fallback;
}
var value = item[propertyName];
return value == null ? fallback : Convert.ToInt32(value, CultureInfo.InvariantCulture);
}
catch
{
return fallback;
}
}
public static bool SafeGetBoolean(ManagementBaseObject item, string propertyName, bool fallback)
{
try
{
if (!HasProperty(item, propertyName))
{
return fallback;
}
var value = item[propertyName];
return value == null ? fallback : Convert.ToBoolean(value, CultureInfo.InvariantCulture);
}
catch
{
return fallback;
}
}
private void EnsureConnected()
{
if (_scope == null || !_scope.IsConnected)
{
Connect();
}
}
private static bool HasProperty(ManagementBaseObject item, string propertyName)
{
foreach (PropertyData property in item.Properties)
{
if (string.Equals(property.Name, propertyName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
private static string ResolveMethodName(ManagementObject instance, string requestedName)
{
foreach (MethodData method in instance.Methods)
{
if (string.Equals(method.Name, requestedName, StringComparison.OrdinalIgnoreCase))
{
return method.Name;
}
}
throw new MissingMethodException(instance.Path.ClassName, requestedName);
}
private static uint ExtractReturnCode(object result)
{
if (result == null)
{
return 0;
}
var outParams = result as ManagementBaseObject;
if (outParams != null && HasProperty(outParams, "ReturnValue"))
{
return Convert.ToUInt32(outParams["ReturnValue"], CultureInfo.InvariantCulture);
}
return Convert.ToUInt32(result, CultureInfo.InvariantCulture);
}
private static string EscapeWql(string value)
{
return (value ?? string.Empty).Replace("\\", "\\\\").Replace("\"", "\\\"");
}
public void Dispose()
{
}
}
}
@@ -0,0 +1,75 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
using BizTalkPlatformManagementTool.Models;
namespace BizTalkPlatformManagementTool.Services
{
public static class CsvWriter
{
public static void WriteSnapshotArtifacts(string path, BizTalkSnapshot snapshot)
{
var lines = new List<string> { "Application,Type,Name,Status" };
foreach (var app in snapshot.Applications)
{
foreach (var item in app.ReceiveLocations)
{
lines.Add(Row(app.Application, "ReceiveLocation", item.Name, ArtifactStates.FormatReceiveLocation(item.Enabled)));
}
foreach (var item in app.SendPorts)
{
lines.Add(Row(app.Application, "SendPort", item.Name, ArtifactStates.FormatSendPort(item.Status)));
}
foreach (var item in app.Orchestrations)
{
lines.Add(Row(app.Application, "Orchestration", item.Name, ArtifactStates.FormatOrchestration(item.OrchestrationStatus)));
}
}
File.WriteAllLines(path, lines, Encoding.UTF8);
}
public static void WriteSnapshotHosts(string path, BizTalkSnapshot snapshot)
{
var lines = new List<string> { "InstanceName,HostName,Server,State" };
foreach (var item in snapshot.HostInstances)
{
lines.Add(Row(item.InstanceName, item.HostName, item.Server, item.StateText));
}
File.WriteAllLines(path, lines, Encoding.UTF8);
}
public static void WriteDiff(string path, SnapshotDiff diff)
{
var lines = new List<string> { "Scope,Application,Type,Name,Server,Before,After" };
foreach (var item in diff.ArtifactDifferences)
{
lines.Add(Row("Artifact", item.Application, item.ArtifactType, item.Name, string.Empty, item.Before, item.After));
}
foreach (var item in diff.HostInstanceDifferences)
{
lines.Add(Row("HostInstance", string.Empty, "HostInstance", item.InstanceName, item.Server, item.Before, item.After));
}
File.WriteAllLines(path, lines, Encoding.UTF8);
}
private static string Row(params string[] values)
{
var escaped = new string[values.Length];
for (int i = 0; i < values.Length; i++)
{
escaped[i] = Escape(values[i]);
}
return string.Join(",", escaped);
}
private static string Escape(string value)
{
value = value ?? string.Empty;
if (value.IndexOfAny(new[] { ',', '"', '\r', '\n' }) < 0)
{
return value;
}
return "\"" + value.Replace("\"", "\"\"") + "\"";
}
}
}
@@ -0,0 +1,100 @@
using System.Net;
using System.Text;
using BizTalkPlatformManagementTool.Models;
namespace BizTalkPlatformManagementTool.Services
{
public static class HtmlReportWriter
{
public static void WriteSnapshot(string path, BizTalkSnapshot snapshot)
{
var html = new StringBuilder();
Header(html, "BizTalk Snapshot");
html.Append("<p><strong>Server:</strong> ").Append(Encode(snapshot.Server)).Append(" <strong>Created:</strong> ").Append(Encode(snapshot.CreatedAt)).Append("</p>");
foreach (var app in snapshot.Applications)
{
html.Append("<h2>").Append(Encode(app.Application)).Append("</h2>");
html.Append("<h3>Receive Locations</h3><table><tr><th>Name</th><th>Status</th><th>Adapter</th><th>Address</th></tr>");
foreach (var item in app.ReceiveLocations)
{
StatusRow(html, item.Name, ArtifactStates.FormatReceiveLocation(item.Enabled), item.Enabled, item.AdapterName, item.Address);
}
html.Append("</table>");
html.Append("<h3>Send Ports</h3><table><tr><th>Name</th><th>Status</th><th>Transport</th><th>Address</th></tr>");
foreach (var item in app.SendPorts)
{
StatusRow(html, item.Name, ArtifactStates.FormatSendPort(item.Status), item.Status == ArtifactStates.SendPortStarted, item.PrimaryTransportType, item.PrimaryTransportAddress);
}
html.Append("</table>");
html.Append("<h3>Orchestrations</h3><table><tr><th>Name</th><th>Status</th></tr>");
foreach (var item in app.Orchestrations)
{
html.Append("<tr><td>").Append(Encode(item.Name)).Append("</td><td class='").Append(item.OrchestrationStatus == ArtifactStates.OrchestrationStarted ? "ok" : "bad").Append("'>")
.Append(Encode(ArtifactStates.FormatOrchestration(item.OrchestrationStatus))).Append("</td></tr>");
}
html.Append("</table>");
}
html.Append("<h2>Host Instances</h2><table><tr><th>Instance</th><th>Host</th><th>Server</th><th>Status</th></tr>");
foreach (var item in snapshot.HostInstances)
{
html.Append("<tr><td>").Append(Encode(item.InstanceName)).Append("</td><td>").Append(Encode(item.HostName)).Append("</td><td>")
.Append(Encode(item.Server)).Append("</td><td class='").Append(item.RawState == ArtifactStates.HostStarted ? "ok" : "bad").Append("'>")
.Append(Encode(item.StateText)).Append("</td></tr>");
}
html.Append("</table>");
Footer(html);
System.IO.File.WriteAllText(path, html.ToString(), Encoding.UTF8);
}
public static void WriteDiff(string path, SnapshotDiff diff)
{
var html = new StringBuilder();
Header(html, "BizTalk Differences");
html.Append("<h2>Artifacts</h2><table><tr><th>Application</th><th>Type</th><th>Name</th><th>Before</th><th>After</th></tr>");
foreach (var item in diff.ArtifactDifferences)
{
html.Append("<tr><td>").Append(Encode(item.Application)).Append("</td><td>").Append(Encode(item.ArtifactType)).Append("</td><td>")
.Append(Encode(item.Name)).Append("</td><td>").Append(Encode(item.Before)).Append("</td><td>").Append(Encode(item.After)).Append("</td></tr>");
}
html.Append("</table>");
html.Append("<h2>Host Instances</h2><table><tr><th>Instance</th><th>Host</th><th>Server</th><th>Before</th><th>After</th></tr>");
foreach (var item in diff.HostInstanceDifferences)
{
html.Append("<tr><td>").Append(Encode(item.InstanceName)).Append("</td><td>").Append(Encode(item.HostName)).Append("</td><td>")
.Append(Encode(item.Server)).Append("</td><td>").Append(Encode(item.Before)).Append("</td><td>").Append(Encode(item.After)).Append("</td></tr>");
}
html.Append("</table>");
Footer(html);
System.IO.File.WriteAllText(path, html.ToString(), Encoding.UTF8);
}
private static void StatusRow(StringBuilder html, string name, string status, bool ok, string detail1, string detail2)
{
html.Append("<tr><td>").Append(Encode(name)).Append("</td><td class='").Append(ok ? "ok" : "bad").Append("'>").Append(Encode(status))
.Append("</td><td>").Append(Encode(detail1)).Append("</td><td>").Append(Encode(detail2)).Append("</td></tr>");
}
private static void Header(StringBuilder html, string title)
{
html.Append("<!doctype html><html><head><meta charset='utf-8'><title>").Append(Encode(title)).Append("</title><style>")
.Append("body{font-family:Segoe UI,Arial,sans-serif;font-size:13px;color:#222;margin:24px}table{border-collapse:collapse;width:100%;margin:8px 0 18px}th,td{border:1px solid #ccc;padding:6px 8px;text-align:left}th{background:#f3f3f3}.ok{color:#087a08;font-weight:600}.bad{color:#b00020;font-weight:600}h1{margin-top:0}h2{margin-top:24px}")
.Append("</style></head><body><h1>").Append(Encode(title)).Append("</h1>");
}
private static void Footer(StringBuilder html)
{
html.Append("</body></html>");
}
private static string Encode(string value)
{
return WebUtility.HtmlEncode(value ?? string.Empty);
}
}
}
@@ -0,0 +1,38 @@
using System.IO;
using System.Runtime.Serialization.Json;
using System.Text;
namespace BizTalkPlatformManagementTool.Services
{
public static class JsonFileStore
{
public static void Save<T>(string path, T value)
{
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path)));
var serializer = new DataContractJsonSerializer(typeof(T), new DataContractJsonSerializerSettings
{
UseSimpleDictionaryFormat = true
});
using (var stream = new MemoryStream())
{
serializer.WriteObject(stream, value);
var json = Encoding.UTF8.GetString(stream.ToArray());
File.WriteAllText(path, json, Encoding.UTF8);
}
}
public static T Load<T>(string path)
{
var serializer = new DataContractJsonSerializer(typeof(T), new DataContractJsonSerializerSettings
{
UseSimpleDictionaryFormat = true
});
using (var stream = File.OpenRead(path))
{
return (T)serializer.ReadObject(stream);
}
}
}
}
@@ -0,0 +1,64 @@
using System;
namespace BizTalkPlatformManagementTool.Services
{
public enum LogLevel
{
Info,
Warning,
Error,
Success
}
public sealed class LogEntry
{
public DateTime Timestamp { get; set; }
public LogLevel Level { get; set; }
public string Message { get; set; }
}
public sealed class OperationLogger
{
private readonly Action<LogEntry> _sink;
public OperationLogger(Action<LogEntry> sink)
{
_sink = sink;
}
public void Info(string message)
{
Write(LogLevel.Info, message);
}
public void Warning(string message)
{
Write(LogLevel.Warning, message);
}
public void Error(string message)
{
Write(LogLevel.Error, message);
}
public void Success(string message)
{
Write(LogLevel.Success, message);
}
private void Write(LogLevel level, string message)
{
if (_sink == null)
{
return;
}
_sink(new LogEntry
{
Timestamp = DateTime.Now,
Level = level,
Message = message
});
}
}
}
@@ -0,0 +1,180 @@
using System;
using System.Collections.Generic;
using BizTalkPlatformManagementTool.Models;
namespace BizTalkPlatformManagementTool.Services
{
public static class SnapshotComparer
{
public static SnapshotDiff Compare(BizTalkSnapshot before, BizTalkSnapshot after)
{
var diff = new SnapshotDiff();
CompareReceiveLocations(diff, FlattenReceiveLocations(before), FlattenReceiveLocations(after));
CompareSendPorts(diff, FlattenSendPorts(before), FlattenSendPorts(after));
CompareOrchestrations(diff, FlattenOrchestrations(before), FlattenOrchestrations(after));
CompareHostInstances(diff, before.HostInstances, after.HostInstances);
return diff;
}
private static void CompareReceiveLocations(SnapshotDiff diff, Dictionary<string, ReceiveLocationState> before, Dictionary<string, ReceiveLocationState> after)
{
foreach (var pair in after)
{
ReceiveLocationState old;
if (!before.TryGetValue(pair.Key, out old))
{
AddArtifact(diff, pair.Value.Application, "ReceiveLocation", pair.Value.Name, "<missing>", ArtifactStates.FormatReceiveLocation(pair.Value.Enabled));
}
else if (old.Enabled != pair.Value.Enabled)
{
AddArtifact(diff, pair.Value.Application, "ReceiveLocation", pair.Value.Name, ArtifactStates.FormatReceiveLocation(old.Enabled), ArtifactStates.FormatReceiveLocation(pair.Value.Enabled));
}
}
foreach (var pair in before)
{
if (!after.ContainsKey(pair.Key))
{
AddArtifact(diff, pair.Value.Application, "ReceiveLocation", pair.Value.Name, ArtifactStates.FormatReceiveLocation(pair.Value.Enabled), "<missing>");
}
}
}
private static void CompareSendPorts(SnapshotDiff diff, Dictionary<string, SendPortState> before, Dictionary<string, SendPortState> after)
{
foreach (var pair in after)
{
SendPortState old;
if (!before.TryGetValue(pair.Key, out old))
{
AddArtifact(diff, pair.Value.Application, "SendPort", pair.Value.Name, "<missing>", ArtifactStates.FormatSendPort(pair.Value.Status));
}
else if (old.Status != pair.Value.Status)
{
AddArtifact(diff, pair.Value.Application, "SendPort", pair.Value.Name, ArtifactStates.FormatSendPort(old.Status), ArtifactStates.FormatSendPort(pair.Value.Status));
}
}
foreach (var pair in before)
{
if (!after.ContainsKey(pair.Key))
{
AddArtifact(diff, pair.Value.Application, "SendPort", pair.Value.Name, ArtifactStates.FormatSendPort(pair.Value.Status), "<missing>");
}
}
}
private static void CompareOrchestrations(SnapshotDiff diff, Dictionary<string, OrchestrationState> before, Dictionary<string, OrchestrationState> after)
{
foreach (var pair in after)
{
OrchestrationState old;
if (!before.TryGetValue(pair.Key, out old))
{
AddArtifact(diff, pair.Value.Application, "Orchestration", pair.Value.Name, "<missing>", ArtifactStates.FormatOrchestration(pair.Value.OrchestrationStatus));
}
else if (old.OrchestrationStatus != pair.Value.OrchestrationStatus)
{
AddArtifact(diff, pair.Value.Application, "Orchestration", pair.Value.Name, ArtifactStates.FormatOrchestration(old.OrchestrationStatus), ArtifactStates.FormatOrchestration(pair.Value.OrchestrationStatus));
}
}
foreach (var pair in before)
{
if (!after.ContainsKey(pair.Key))
{
AddArtifact(diff, pair.Value.Application, "Orchestration", pair.Value.Name, ArtifactStates.FormatOrchestration(pair.Value.OrchestrationStatus), "<missing>");
}
}
}
private static void CompareHostInstances(SnapshotDiff diff, List<HostInstanceState> before, List<HostInstanceState> after)
{
var beforeMap = new Dictionary<string, HostInstanceState>(StringComparer.OrdinalIgnoreCase);
var afterMap = new Dictionary<string, HostInstanceState>(StringComparer.OrdinalIgnoreCase);
foreach (var item in before)
{
beforeMap[item.InstanceName ?? string.Empty] = item;
}
foreach (var item in after)
{
afterMap[item.InstanceName ?? string.Empty] = item;
}
foreach (var pair in afterMap)
{
HostInstanceState old;
if (!beforeMap.TryGetValue(pair.Key, out old))
{
diff.HostInstanceDifferences.Add(new HostInstanceDiffEntry { InstanceName = pair.Value.InstanceName, HostName = pair.Value.HostName, Server = pair.Value.Server, Before = "<missing>", After = pair.Value.StateText });
}
else if (old.RawState != pair.Value.RawState)
{
diff.HostInstanceDifferences.Add(new HostInstanceDiffEntry { InstanceName = pair.Value.InstanceName, HostName = pair.Value.HostName, Server = pair.Value.Server, Before = old.StateText, After = pair.Value.StateText });
}
}
foreach (var pair in beforeMap)
{
if (!afterMap.ContainsKey(pair.Key))
{
diff.HostInstanceDifferences.Add(new HostInstanceDiffEntry { InstanceName = pair.Value.InstanceName, HostName = pair.Value.HostName, Server = pair.Value.Server, Before = pair.Value.StateText, After = "<missing>" });
}
}
}
private static void AddArtifact(SnapshotDiff diff, string application, string type, string name, string before, string after)
{
diff.ArtifactDifferences.Add(new ArtifactDiffEntry
{
Application = application,
ArtifactType = type,
Name = name,
Before = before,
After = after
});
}
private static Dictionary<string, ReceiveLocationState> FlattenReceiveLocations(BizTalkSnapshot snapshot)
{
var map = new Dictionary<string, ReceiveLocationState>(StringComparer.OrdinalIgnoreCase);
foreach (var app in snapshot.Applications)
{
foreach (var item in app.ReceiveLocations)
{
map[item.Name ?? string.Empty] = item;
}
}
return map;
}
private static Dictionary<string, SendPortState> FlattenSendPorts(BizTalkSnapshot snapshot)
{
var map = new Dictionary<string, SendPortState>(StringComparer.OrdinalIgnoreCase);
foreach (var app in snapshot.Applications)
{
foreach (var item in app.SendPorts)
{
map[item.Name ?? string.Empty] = item;
}
}
return map;
}
private static Dictionary<string, OrchestrationState> FlattenOrchestrations(BizTalkSnapshot snapshot)
{
var map = new Dictionary<string, OrchestrationState>(StringComparer.OrdinalIgnoreCase);
foreach (var app in snapshot.Applications)
{
foreach (var item in app.Orchestrations)
{
map[item.Name ?? string.Empty] = item;
}
}
return map;
}
}
}
@@ -0,0 +1,25 @@
using System.IO;
using BizTalkPlatformManagementTool.Models;
namespace BizTalkPlatformManagementTool.Services
{
public static class SnapshotStore
{
public static void SaveSnapshotSet(string jsonPath, BizTalkSnapshot snapshot)
{
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(jsonPath)));
JsonFileStore.Save(jsonPath, snapshot);
CsvWriter.WriteSnapshotArtifacts(jsonPath + ".csv", snapshot);
CsvWriter.WriteSnapshotHosts(jsonPath + ".hosts.csv", snapshot);
HtmlReportWriter.WriteSnapshot(jsonPath + ".html", snapshot);
}
public static void SaveDiffSet(string jsonPath, SnapshotDiff diff)
{
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(jsonPath)));
JsonFileStore.Save(jsonPath, diff);
CsvWriter.WriteDiff(Path.ChangeExtension(jsonPath, ".csv"), diff);
HtmlReportWriter.WriteDiff(Path.ChangeExtension(jsonPath, ".html"), diff);
}
}
}
@@ -0,0 +1,462 @@
using System;
using System.Drawing;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Forms;
using BizTalkPlatformManagementTool.Models;
using BizTalkPlatformManagementTool.Services;
namespace BizTalkPlatformManagementTool.Ui
{
public sealed class MainForm : Form
{
private readonly TextBox _serverTextBox = new TextBox();
private readonly TextBox _outputTextBox = new TextBox();
private readonly TextBox _stateFileTextBox = new TextBox();
private readonly NumericUpDown _timeoutInput = new NumericUpDown();
private readonly NumericUpDown _pollInput = new NumericUpDown();
private readonly CheckBox _dryRunCheckBox = new CheckBox();
private readonly DataGridView _statusGrid = new DataGridView();
private readonly DataGridView _logGrid = new DataGridView();
private readonly StatusStrip _statusStrip = new StatusStrip();
private readonly ToolStripStatusLabel _statusLabel = new ToolStripStatusLabel();
private readonly BizTalkOperationService _service;
private Button _diagnoseButton;
private Button _beforeButton;
private Button _afterButton;
private Button _compareButton;
private Button _shutdownButton;
private Button _restoreButton;
public MainForm()
{
Text = "BizTalk Platform Management Tool";
Width = 1180;
Height = 760;
MinimumSize = new Size(980, 640);
StartPosition = FormStartPosition.CenterScreen;
_service = new BizTalkOperationService(new OperationLogger(AppendLog));
BuildUi();
}
private void BuildUi()
{
var root = new TableLayoutPanel
{
Dock = DockStyle.Fill,
ColumnCount = 1,
RowCount = 4,
Padding = new Padding(10)
};
root.RowStyles.Add(new RowStyle(SizeType.Absolute, 88));
root.RowStyles.Add(new RowStyle(SizeType.Absolute, 54));
root.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
root.RowStyles.Add(new RowStyle(SizeType.Absolute, 24));
Controls.Add(root);
root.Controls.Add(BuildSettingsPanel(), 0, 0);
root.Controls.Add(BuildActionPanel(), 0, 1);
root.Controls.Add(BuildTabs(), 0, 2);
_statusStrip.Items.Add(_statusLabel);
_statusLabel.Text = "Ready. Dry run is enabled.";
root.Controls.Add(_statusStrip, 0, 3);
}
private Control BuildSettingsPanel()
{
var panel = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 8, RowCount = 2 };
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 70));
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 30));
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 76));
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 35));
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 88));
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 82));
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 72));
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 88));
panel.RowStyles.Add(new RowStyle(SizeType.Absolute, 36));
panel.RowStyles.Add(new RowStyle(SizeType.Absolute, 36));
_serverTextBox.Text = Environment.MachineName;
_serverTextBox.Dock = DockStyle.Fill;
_outputTextBox.Text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "BizTalkPlatformManagementTool");
_outputTextBox.Dock = DockStyle.Fill;
_stateFileTextBox.Text = "before.json";
_stateFileTextBox.Dock = DockStyle.Fill;
_timeoutInput.Minimum = 30;
_timeoutInput.Maximum = 3600;
_timeoutInput.Value = 300;
_timeoutInput.Dock = DockStyle.Fill;
_pollInput.Minimum = 1;
_pollInput.Maximum = 60;
_pollInput.Value = 5;
_pollInput.Dock = DockStyle.Fill;
_dryRunCheckBox.Text = "Dry run";
_dryRunCheckBox.Checked = true;
_dryRunCheckBox.Dock = DockStyle.Fill;
var browseButton = new Button { Text = "Browse...", Dock = DockStyle.Fill };
browseButton.Click += BrowseButtonClick;
panel.Controls.Add(Label("Server"), 0, 0);
panel.Controls.Add(_serverTextBox, 1, 0);
panel.Controls.Add(Label("Output"), 2, 0);
panel.Controls.Add(_outputTextBox, 3, 0);
panel.Controls.Add(browseButton, 4, 0);
panel.Controls.Add(Label("Timeout"), 5, 0);
panel.Controls.Add(_timeoutInput, 6, 0);
panel.Controls.Add(Label("State"), 0, 1);
panel.Controls.Add(_stateFileTextBox, 1, 1);
panel.SetColumnSpan(_stateFileTextBox, 3);
panel.Controls.Add(_dryRunCheckBox, 4, 1);
panel.Controls.Add(Label("Poll sec."), 5, 1);
panel.Controls.Add(_pollInput, 6, 1);
return panel;
}
private Control BuildActionPanel()
{
var panel = new FlowLayoutPanel { Dock = DockStyle.Fill, FlowDirection = FlowDirection.LeftToRight, Padding = new Padding(0, 8, 0, 0) };
_diagnoseButton = ActionButton("Diagnose", DiagnoseClick);
_beforeButton = ActionButton("Snapshot Before", BeforeClick);
_afterButton = ActionButton("Snapshot After", AfterClick);
_compareButton = ActionButton("Compare", CompareClick);
_shutdownButton = ActionButton("Shutdown", ShutdownClick);
_restoreButton = ActionButton("Restore", RestoreClick);
panel.Controls.Add(_diagnoseButton);
panel.Controls.Add(_beforeButton);
panel.Controls.Add(_afterButton);
panel.Controls.Add(_compareButton);
panel.Controls.Add(_shutdownButton);
panel.Controls.Add(_restoreButton);
return panel;
}
private Control BuildTabs()
{
var tabs = new TabControl { Dock = DockStyle.Fill };
var statusPage = new TabPage("Status / Results");
var logPage = new TabPage("Operation Log");
ConfigureGrid(_statusGrid);
_statusGrid.Columns.Add("Scope", "Scope");
_statusGrid.Columns.Add("Application", "Application");
_statusGrid.Columns.Add("Name", "Name");
_statusGrid.Columns.Add("Status", "Status");
_statusGrid.Columns.Add("Details", "Details");
statusPage.Controls.Add(_statusGrid);
ConfigureGrid(_logGrid);
_logGrid.Columns.Add("Time", "Time");
_logGrid.Columns.Add("Level", "Level");
_logGrid.Columns.Add("Message", "Message");
_logGrid.Columns[2].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
logPage.Controls.Add(_logGrid);
tabs.TabPages.Add(statusPage);
tabs.TabPages.Add(logPage);
return tabs;
}
private void DiagnoseClick(object sender, EventArgs e)
{
var server = _serverTextBox.Text.Trim();
RunAsync("Diagnosing WMI access...", () => _service.Diagnose(server));
}
private void BeforeClick(object sender, EventArgs e)
{
var options = GetOptions();
RunAsync("Creating before snapshot...", () =>
{
var snapshot = _service.CreateSnapshot(options.Server);
_service.SaveSnapshot(options.OutputDirectory, "before.json", snapshot);
ShowSnapshot(snapshot);
});
}
private void AfterClick(object sender, EventArgs e)
{
var options = GetOptions();
RunAsync("Creating after snapshot...", () =>
{
var snapshot = _service.CreateSnapshot(options.Server);
_service.SaveSnapshot(options.OutputDirectory, "after.json", snapshot);
ShowSnapshot(snapshot);
});
}
private void CompareClick(object sender, EventArgs e)
{
var options = GetOptions();
RunAsync("Comparing before and after snapshots...", () =>
{
var before = JsonFileStore.Load<BizTalkSnapshot>(Path.Combine(options.OutputDirectory, "before.json"));
var after = JsonFileStore.Load<BizTalkSnapshot>(Path.Combine(options.OutputDirectory, "after.json"));
var diff = SnapshotComparer.Compare(before, after);
_service.SaveDiff(options.OutputDirectory, "diff.json", diff);
ShowDiff(diff);
});
}
private void ShutdownClick(object sender, EventArgs e)
{
if (!ConfirmDangerousAction("Shutdown"))
{
return;
}
var options = GetOptions();
RunAsync("Preparing shutdown...", () =>
{
var snapshot = _service.CreateSnapshot(options.Server);
_service.SaveSnapshot(options.OutputDirectory, "before.json", snapshot);
var plan = _service.CreateShutdownPlan(snapshot, options.Server);
_service.SavePlan(options.OutputDirectory, "shutdown-plan.json", plan);
ShowPlan(plan);
_service.ExecutePlan(plan, options);
if (!options.DryRun)
{
var after = _service.CreateSnapshot(options.Server);
_service.SaveSnapshot(options.OutputDirectory, "shutdown-after.json", after);
ShowSnapshot(after);
}
});
}
private void RestoreClick(object sender, EventArgs e)
{
if (!ConfirmDangerousAction("Restore"))
{
return;
}
var options = GetOptions();
RunAsync("Preparing restore...", () =>
{
var snapshot = JsonFileStore.Load<BizTalkSnapshot>(ResolveStateFile(options));
var plan = _service.CreateRestorePlan(snapshot, options.Server);
_service.SavePlan(options.OutputDirectory, "restore-plan.json", plan);
ShowPlan(plan);
_service.ExecutePlan(plan, options);
if (!options.DryRun)
{
var after = _service.CreateSnapshot(options.Server);
_service.SaveSnapshot(options.OutputDirectory, "restore-after.json", after);
ShowSnapshot(after);
}
});
}
private void RunAsync(string status, Action work)
{
SetBusy(true, status);
Task.Run(() =>
{
try
{
work();
AppendLog(new LogEntry { Timestamp = DateTime.Now, Level = LogLevel.Success, Message = "Operation completed." });
SetBusy(false, "Ready.");
}
catch (Exception ex)
{
AppendLog(new LogEntry { Timestamp = DateTime.Now, Level = LogLevel.Error, Message = ex.Message });
SetBusy(false, "Failed: " + ex.Message);
}
});
}
private OperationOptions GetOptions()
{
var server = string.IsNullOrWhiteSpace(_serverTextBox.Text) ? Environment.MachineName : _serverTextBox.Text.Trim();
var output = string.IsNullOrWhiteSpace(_outputTextBox.Text)
? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "BizTalkPlatformManagementTool")
: _outputTextBox.Text.Trim();
var stateFile = string.IsNullOrWhiteSpace(_stateFileTextBox.Text) ? "before.json" : _stateFileTextBox.Text.Trim();
return new OperationOptions
{
Server = server,
OutputDirectory = output,
StateFile = stateFile,
DryRun = _dryRunCheckBox.Checked,
WaitTimeoutSeconds = (int)_timeoutInput.Value,
PollIntervalSeconds = (int)_pollInput.Value
};
}
private string ResolveStateFile(OperationOptions options)
{
return Path.IsPathRooted(options.StateFile) ? options.StateFile : Path.Combine(options.OutputDirectory, options.StateFile);
}
private bool ConfirmDangerousAction(string actionName)
{
if (_dryRunCheckBox.Checked)
{
return true;
}
var result = MessageBox.Show(
actionName + " will change the BizTalk runtime state on server '" + _serverTextBox.Text + "'. Continue?",
"Confirm BizTalk Runtime Change",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning,
MessageBoxDefaultButton.Button2);
return result == DialogResult.Yes;
}
private void ShowSnapshot(BizTalkSnapshot snapshot)
{
InvokeIfRequired(() =>
{
_statusGrid.Rows.Clear();
foreach (var app in snapshot.Applications)
{
foreach (var item in app.ReceiveLocations)
{
_statusGrid.Rows.Add("ReceiveLocation", app.Application, item.Name, ArtifactStates.FormatReceiveLocation(item.Enabled), item.AdapterName + " " + item.Address);
}
foreach (var item in app.SendPorts)
{
_statusGrid.Rows.Add("SendPort", app.Application, item.Name, ArtifactStates.FormatSendPort(item.Status), item.PrimaryTransportType + " " + item.PrimaryTransportAddress);
}
foreach (var item in app.Orchestrations)
{
_statusGrid.Rows.Add("Orchestration", app.Application, item.Name, ArtifactStates.FormatOrchestration(item.OrchestrationStatus), string.Empty);
}
}
foreach (var item in snapshot.HostInstances)
{
_statusGrid.Rows.Add("HostInstance", string.Empty, item.InstanceName, item.StateText, item.HostName + " on " + item.Server);
}
});
}
private void ShowDiff(SnapshotDiff diff)
{
InvokeIfRequired(() =>
{
_statusGrid.Rows.Clear();
foreach (var item in diff.ArtifactDifferences)
{
_statusGrid.Rows.Add("Diff", item.Application, item.Name, item.Before + " -> " + item.After, item.ArtifactType);
}
foreach (var item in diff.HostInstanceDifferences)
{
_statusGrid.Rows.Add("HostDiff", string.Empty, item.InstanceName, item.Before + " -> " + item.After, item.Server);
}
});
}
private void ShowPlan(OperationPlan plan)
{
InvokeIfRequired(() =>
{
_statusGrid.Rows.Clear();
foreach (var step in plan.Steps)
{
_statusGrid.Rows.Add(step.Kind, step.Application, step.Name, step.Execute ? step.Action : "Skipped", step.Warning ?? step.MethodName);
}
});
}
private void AppendLog(LogEntry entry)
{
InvokeIfRequired(() =>
{
var index = _logGrid.Rows.Add(entry.Timestamp.ToString("HH:mm:ss"), entry.Level.ToString(), entry.Message);
var row = _logGrid.Rows[index];
if (entry.Level == LogLevel.Error)
{
row.DefaultCellStyle.ForeColor = Color.DarkRed;
}
else if (entry.Level == LogLevel.Warning)
{
row.DefaultCellStyle.ForeColor = Color.DarkOrange;
}
else if (entry.Level == LogLevel.Success)
{
row.DefaultCellStyle.ForeColor = Color.DarkGreen;
}
_logGrid.FirstDisplayedScrollingRowIndex = Math.Max(0, _logGrid.Rows.Count - 1);
});
}
private void SetBusy(bool busy, string status)
{
InvokeIfRequired(() =>
{
_diagnoseButton.Enabled = !busy;
_beforeButton.Enabled = !busy;
_afterButton.Enabled = !busy;
_compareButton.Enabled = !busy;
_shutdownButton.Enabled = !busy;
_restoreButton.Enabled = !busy;
_statusLabel.Text = status;
});
}
private void BrowseButtonClick(object sender, EventArgs e)
{
using (var dialog = new FolderBrowserDialog())
{
dialog.SelectedPath = Directory.Exists(_outputTextBox.Text) ? _outputTextBox.Text : Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
if (dialog.ShowDialog(this) == DialogResult.OK)
{
_outputTextBox.Text = dialog.SelectedPath;
}
}
}
private void InvokeIfRequired(Action action)
{
if (IsDisposed)
{
return;
}
if (InvokeRequired)
{
BeginInvoke(action);
}
else
{
action();
}
}
private static Label Label(string text)
{
return new Label { Text = text, Dock = DockStyle.Fill, TextAlign = ContentAlignment.MiddleLeft };
}
private static Button ActionButton(string text, EventHandler handler)
{
var button = new Button { Text = text, Width = 148, Height = 34, Margin = new Padding(0, 0, 8, 0) };
button.Click += handler;
return button;
}
private static void ConfigureGrid(DataGridView grid)
{
grid.Dock = DockStyle.Fill;
grid.AllowUserToAddRows = false;
grid.AllowUserToDeleteRows = false;
grid.ReadOnly = true;
grid.RowHeadersVisible = false;
grid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
grid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
grid.BackgroundColor = SystemColors.Window;
}
}
}
-676
View File
@@ -1,676 +0,0 @@
<#
BizTalk Status Tool Extended Version
Includes:
- Applications Snapshot (RL/SP/ORCH)
- Host Instances Snapshot
- JSON / CSV / HTML / Console
- Compare (Before / After)
- HostInstance Diff (New / Removed / Changed)
#>
# ======================================================
# PARAMETERS
# ======================================================
param(
[switch]$Before,
[switch]$After,
[switch]$Compare,
[switch]$Diag,
[string]$Server,
[string]$MgmtDbServer,
[string]$MgmtDbName,
[string]$OutDir
)
if (-not $Server) { $Server = $env:COMPUTERNAME }
if (-not $OutDir) { $OutDir = "." }
$ErrorActionPreference = "Stop"
$Global:LogFile = Join-Path (Get-Location) "BizTalkStatusTool.log"
# ======================================================
# LOG
# ======================================================
function Write-Log {
param([string]$Message,[ValidateSet('INFO','WARN','ERROR','DEBUG')]$Level='INFO')
$ts = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
$line = "[$ts][$Level] $Message"
$fg = switch ($Level) {
INFO {"Gray"} WARN {"Yellow"} ERROR {"Red"} DEBUG {"DarkCyan"}
}
Write-Host $line -ForegroundColor $fg
Add-Content -Path $Global:LogFile -Value $line
}
# ======================================================
# STATUS MAPPING
# ======================================================
function Format-Status {
param([string]$ArtifactType, $Value)
$r = [ordered]@{
Text = ""
Class = "neutral"
ConsoleColor = "Yellow"
}
switch ($ArtifactType) {
"ReceiveLocation" {
if ($Value -eq $true) {
$r.Text="Enabled"; $r.Class="ok"; $r.ConsoleColor="Green"
}
elseif ($Value -eq $false) {
$r.Text="Disabled"; $r.Class="bad"; $r.ConsoleColor="Red"
}
}
"SendPort" {
switch ([int]$Value) {
3 { $r.Text="Started"; $r.Class="ok"; $r.ConsoleColor="Green" }
2 { $r.Text="Stopped"; $r.Class="bad"; $r.ConsoleColor="Red" }
1 { $r.Text="Bound"; $r.Class="bad"; $r.ConsoleColor="Red" }
default { $r.Text="Unknown"; $r.Class="bad"; $r.ConsoleColor="Red" }
}
}
"Orchestration" {
switch ([int]$Value) {
4 { $r.Text="Started"; $r.Class="ok"; $r.ConsoleColor="Green" }
3 { $r.Text="Stopped"; $r.Class="bad"; $r.ConsoleColor="Red" }
2 { $r.Text="Bound"; $r.Class="bad"; $r.ConsoleColor="Red" }
default { $r.Text="Unknown"; $r.Class="bad"; $r.ConsoleColor="Red" }
}
}
"HostInstance" {
switch ([int]$Value) {
4 { $r.Text="Started"; $r.Class="ok"; $r.ConsoleColor="Green" }
2 { $r.Text="StartPending"; $r.Class="warn"; $r.ConsoleColor="Yellow" }
3 { $r.Text="StopPending"; $r.Class="warn"; $r.ConsoleColor="Yellow" }
1 { $r.Text="Stopped"; $r.Class="bad"; $r.ConsoleColor="Red" }
default { $r.Text="Unknown"; $r.Class="bad"; $r.ConsoleColor="Red" }
}
}
}
return [pscustomobject]$r
}
# ======================================================
# MGMT DB
# ======================================================
function Get-BizTalkMgmtDbInfo {
param([string]$MgmtDbServer,[string]$MgmtDbName)
$reg = "HKLM:\SOFTWARE\Microsoft\BizTalk Server\3.0\Administration"
try {
$p = Get-ItemProperty $reg
if (-not $MgmtDbServer) { $MgmtDbServer = $p.MgmtDBServer }
if (-not $MgmtDbName) { $MgmtDbName = $p.MgmtDBName }
}
catch {
if (-not $MgmtDbServer) { $MgmtDbServer = "localhost" }
if (-not $MgmtDbName) { $MgmtDbName = "BizTalkMgmtDb" }
}
return @{ Server=$MgmtDbServer; Name=$MgmtDbName }
}
# ======================================================
# HOST INSTANCES
# ======================================================
function Get-BizTalkHostInstances {
param([string]$Server)
$wmi = @{
Namespace = "root\MicrosoftBizTalkServer"
ComputerName = $Server
ErrorAction = 'Stop'
}
$list = Get-WmiObject MSBTS_HostInstance @wmi
$result = foreach ($hi in $list) {
$st = Format-Status -ArtifactType "HostInstance" -Value $hi.ServiceState
[pscustomobject]@{
InstanceName = $hi.InstanceName
HostName = $hi.HostName
Server = $hi.RunningServer
RawState = $hi.ServiceState
StateText = $st.Text
StateClass = $st.Class
ConsoleColor = $st.ConsoleColor
}
}
return $result
}
function Show-HostInstancesConsole {
param($HostInstances)
Write-Host "`n=== Host Instances ===" -ForegroundColor Yellow
foreach ($hi in $HostInstances) {
Write-Host ("{0,-35} {1,-20} {2,-10}" -f $hi.InstanceName,$hi.HostName,$hi.StateText) `
-ForegroundColor $hi.ConsoleColor
}
}
function Export-HostInstancesHtml {
param($HostInstances)
$html = @()
$html += "<h3>Host Instances</h3>"
$html += "<table>"
$html += "<tr><th>Instance</th><th>Host</th><th>Server</th><th>Status</th></tr>"
foreach ($hi in $HostInstances) {
$html += "<tr>
<td>$($hi.InstanceName)</td>
<td>$($hi.HostName)</td>
<td>$($hi.Server)</td>
<td class='$($hi.StateClass)'>$($hi.StateText)</td>
</tr>"
}
$html += "</table>"
return ($html -join "`n")
}
function Export-HostInstancesCsv {
param($HostInstances, [string]$CsvFile)
$HostInstances |
Select InstanceName,HostName,Server,StateText |
Export-Csv -NoTypeInformation -Encoding UTF8 -Path $CsvFile
}
# ======================================================
# SNAPSHOT
# ======================================================
function Get-BizTalkSnapshot {
param(
[string]$OutputFile,
[string]$Server,
[string]$MgmtDbServer,
[string]$MgmtDbName
)
Write-Log "Snapshot: Server=$Server File=$OutputFile"
[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.BizTalk.ExplorerOM")
$db = Get-BizTalkMgmtDbInfo -MgmtDbServer $MgmtDbServer -MgmtDbName $MgmtDbName
$cat = New-Object Microsoft.BizTalk.ExplorerOM.BtsCatalogExplorer
$cat.ConnectionString = "Server={0};Database={1};Integrated Security=SSPI" -f $db.Server,$db.Name
$apps = $cat.Applications
# WMI lookup tables
$wmi = @{ Namespace='root\MicrosoftBizTalkServer'; ComputerName=$Server; ErrorAction='Stop' }
$rlW = Get-WmiObject MSBTS_ReceiveLocation @wmi |
Select Name, @{n='Enabled';e={-not $_.IsDisabled}}, AdapterName, @{n='Address';e={$_.InboundTransportURL}}
$spW = Get-WmiObject MSBTS_SendPort @wmi |
Select Name, @{n='Status';e={$_.Status}}, @{n='PrimaryTransportType';e={$_.PTTransportType}},
@{n='PrimaryTransportAddress';e={$_.PTAddress}}
$orW = Get-WmiObject MSBTS_Orchestration @wmi |
Select Name, @{n='OrchestrationStatus';e={$_.OrchestrationStatus}}
$mapRL=@{}; foreach($i in $rlW){$mapRL[$i.Name]=$i}
$mapSP=@{}; foreach($i in $spW){$mapSP[$i.Name]=$i}
$mapOR=@{}; foreach($i in $orW){$mapOR[$i.Name]=$i}
$snapshot=@()
foreach ($app in $apps) {
$rlItems=@()
foreach ($rp in $app.ReceivePorts) {
foreach ($rl in $rp.ReceiveLocations) {
if ($mapRL.ContainsKey($rl.Name)) {
$w = $mapRL[$rl.Name]
$rlItems += [pscustomobject]@{
Name=$rl.Name
Enabled=$w.Enabled
AdapterName=$w.AdapterName
Address=$w.Address
}
}
}
}
$spItems=@()
foreach ($sp in $app.SendPorts) {
if ($mapSP.ContainsKey($sp.Name)) {
$w = $mapSP[$sp.Name]
$spItems += [pscustomobject]@{
Name=$sp.Name
Status=$w.Status
PrimaryTransportType=$w.PrimaryTransportType
PrimaryTransportAddress=$w.PrimaryTransportAddress
}
}
}
$orItems=@()
foreach ($o in $app.Orchestrations) {
$key = if ($mapOR.ContainsKey($o.FullName)) { $o.FullName } else { $o.Name }
if ($mapOR.ContainsKey($key)) {
$w = $mapOR[$key]
$orItems += [pscustomobject]@{
Name=$o.FullName
OrchestrationStatus=$w.OrchestrationStatus
}
}
}
$snapshot += [pscustomobject]@{
Application=$app.Name
ReceiveLocations=$rlItems
SendPorts=$spItems
Orchestrations=$orItems
}
}
# Host Instances
$hostInstances = Get-BizTalkHostInstances -Server $Server
# Export JSON (Applications + HostInstances)
$full = [pscustomobject]@{
Applications = $snapshot
HostInstances = $hostInstances
}
$full | ConvertTo-Json -Depth 8 | Out-File $OutputFile -Encoding UTF8
# Export CSV
$csvArtifact = foreach ($app in $snapshot) {
foreach ($rl in $app.ReceiveLocations) {
$st = Format-Status -ArtifactType "ReceiveLocation" -Value $rl.Enabled
[pscustomobject]@{
Application=$app.Application
Type="ReceiveLocation"
Name=$rl.Name
Status=$st.Text
}
}
foreach ($sp in $app.SendPorts) {
$st = Format-Status -ArtifactType "SendPort" -Value $sp.Status
[pscustomobject]@{
Application=$app.Application
Type="SendPort"
Name=$sp.Name
Status=$st.Text
}
}
foreach ($o in $app.Orchestrations) {
$st = Format-Status -ArtifactType "Orchestration" -Value $o.OrchestrationStatus
[pscustomobject]@{
Application=$app.Application
Type="Orchestration"
Name=$o.Name
Status=$st.Text
}
}
}
$csvArtifact |
Export-Csv -Encoding UTF8 -NoTypeInformation -Path ($OutputFile + ".csv")
Export-HostInstancesCsv -HostInstances $hostInstances -CsvFile ($OutputFile + ".hosts.csv")
# HTML
$html=@()
$html+="<html><head><style>
table{border-collapse:collapse;margin-bottom:20px;}
td,th{border:1px solid #999;padding:4px 8px;}
.ok{color:green;font-weight:bold;}
.bad{color:red;font-weight:bold;}
.warn{color:orange;font-weight:bold;}
</style></head><body>"
$html+="<h2>BizTalk Snapshot</h2>"
foreach ($app in $snapshot) {
$html+="<h3>Application: $($app.Application)</h3>"
if ($app.ReceiveLocations.Count -gt 0) {
$html+="<h4>Receive Locations</h4><table><tr><th>Name</th><th>Status</th></tr>"
foreach ($rl in $app.ReceiveLocations) {
$st = Format-Status -ArtifactType "ReceiveLocation" -Value $rl.Enabled
$html+="<tr><td>$($rl.Name)</td><td class='$($st.Class)'>$($st.Text)</td></tr>"
}
$html+="</table>"
}
if ($app.SendPorts.Count -gt 0) {
$html+="<h4>Send Ports</h4><table><tr><th>Name</th><th>Status</th></tr>"
foreach ($sp in $app.SendPorts) {
$st = Format-Status -ArtifactType "SendPort" -Value $sp.Status
$html+="<tr><td>$($sp.Name)</td><td class='$($st.Class)'>$($st.Text)</td></tr>"
}
$html+="</table>"
}
if ($app.Orchestrations.Count -gt 0) {
$html+="<h4>Orchestrations</h4><table><tr><th>Name</th><th>Status</th></tr>"
foreach ($o in $app.Orchestrations) {
$st = Format-Status -ArtifactType "Orchestration" -Value $o.OrchestrationStatus
$html+="<tr><td>$($o.Name)</td><td class='$($st.Class)'>$($st.Text)</td></tr>"
}
$html+="</table>"
}
}
# Add Host Instances
$html += Export-HostInstancesHtml -HostInstances $hostInstances
$html+="</body></html>"
$html -join "`n" | Out-File ($OutputFile + ".html") -Encoding UTF8
# Console Output
Write-Host "`n===== Snapshot =====" -ForegroundColor Yellow
foreach ($app in $snapshot) {
Write-Host "`nApplication: $($app.Application)" -ForegroundColor Cyan
foreach ($rl in $app.ReceiveLocations) {
$st = Format-Status -ArtifactType "ReceiveLocation" -Value $rl.Enabled
Write-Host (" RL {0,-60} {1}" -f $rl.Name,$st.Text) -ForegroundColor $st.ConsoleColor
}
foreach ($sp in $app.SendPorts) {
$st = Format-Status -ArtifactType "SendPort" -Value $sp.Status
Write-Host (" SP {0,-60} {1}" -f $sp.Name,$st.Text) -ForegroundColor $st.ConsoleColor
}
foreach ($o in $app.Orchestrations) {
$st = Format-Status -ArtifactType "Orchestration" -Value $o.OrchestrationStatus
Write-Host (" OR {0,-60} {1}" -f $o.Name,$st.Text) -ForegroundColor $st.ConsoleColor
}
}
Show-HostInstancesConsole -HostInstances $hostInstances
Write-Host "Snapshot erstellt: $(Resolve-Path $OutputFile)" -ForegroundColor Green
}
# ======================================================
# COMPARE (Before/After)
# ======================================================
function Compare-BizTalkSnapshots {
param(
[string]$Before,
[string]$After,
[string]$HtmlReport = "BizTalkDiff.html"
)
$b = Get-Content $Before | ConvertFrom-Json
$a = Get-Content $After | ConvertFrom-Json
$diff = @()
function AddDiff {
param($app,$type,$name,$before,$after)
$bf = Format-Status -ArtifactType $type -Value $before
$af = Format-Status -ArtifactType $type -Value $after
$script:diff += [pscustomobject]@{
Application = $app
ArtifactType= $type
Name = $name
BeforeText = $bf.Text
BeforeClass = $bf.Class
AfterText = $af.Text
AfterClass = $af.Class
}
}
foreach ($appAfter in $a.Applications) {
$appBefore = $b.Applications | Where-Object {$_.Application -eq $appAfter.Application}
# RECEIVE LOCATIONS
foreach ($x in $appAfter.ReceiveLocations) {
$old = $appBefore.ReceiveLocations | Where-Object {$_.Name -eq $x.Name}
if (-not $old) { AddDiff $appAfter.Application "ReceiveLocation" $x.Name $null $x.Enabled; continue }
if ($old.Enabled -ne $x.Enabled) { AddDiff $appAfter.Application "ReceiveLocation" $x.Name $old.Enabled $x.Enabled }
}
foreach ($x in $appBefore.ReceiveLocations) {
if (-not ($appAfter.ReceiveLocations | Where-Object {$_.Name -eq $x.Name})) {
AddDiff $appBefore.Application "ReceiveLocation" $x.Name $x.Enabled $null
}
}
# SEND PORTS
foreach ($x in $appAfter.SendPorts) {
$old = $appBefore.SendPorts | Where-Object {$_.Name -eq $x.Name}
if (-not $old) { AddDiff $appAfter.Application "SendPort" $x.Name $null $x.Status; continue }
if ($old.Status -ne $x.Status) { AddDiff $appAfter.Application "SendPort" $x.Name $old.Status $x.Status }
}
foreach ($x in $appBefore.SendPorts) {
if (-not ($appAfter.SendPorts | Where-Object {$_.Name -eq $x.Name})) {
AddDiff $appBefore.Application "SendPort" $x.Name $x.Status $null
}
}
# ORCHESTRATIONS
foreach ($x in $appAfter.Orchestrations) {
$old = $appBefore.Orchestrations | Where-Object {$_.Name -eq $x.Name}
if (-not $old) { AddDiff $appAfter.Application "Orchestration" $x.Name $null $x.OrchestrationStatus; continue }
if ($old.OrchestrationStatus -ne $x.OrchestrationStatus) {
AddDiff $appAfter.Application "Orchestration" $x.Name $old.OrchestrationStatus $x.OrchestrationStatus
}
}
foreach ($x in $appBefore.Orchestrations) {
if (-not ($appAfter.Orchestrations | Where-Object {$_.Name -eq $x.Name})) {
AddDiff $appBefore.Application "Orchestration" $x.Name $x.OrchestrationStatus $null
}
}
}
# ======================================================
# HOST INSTANCE DIFF (Option H-DIFF2)
# ======================================================
$beforeHI = $b.HostInstances
$afterHI = $a.HostInstances
$hiDiff_New=@()
$hiDiff_Removed=@()
$hiDiff_Changed=@()
# New
foreach ($hi in $afterHI) {
if (-not ($beforeHI | Where-Object {$_.InstanceName -eq $hi.InstanceName})) {
$hiDiff_New += $hi
}
}
# Removed
foreach ($hi in $beforeHI) {
if (-not ($afterHI | Where-Object {$_.InstanceName -eq $hi.InstanceName})) {
$hiDiff_Removed += $hi
}
}
# Changed
foreach ($hiAfter in $afterHI) {
$hiBefore = $beforeHI | Where-Object {$_.InstanceName -eq $hiAfter.InstanceName}
if ($hiBefore) {
if ($hiBefore.StateText -ne $hiAfter.StateText) {
$hiDiff_Changed += [pscustomobject]@{
Instance = $hiAfter.InstanceName
Before = $hiBefore.StateText
After = $hiAfter.StateText
}
}
}
}
# ======================================================
# JSON DIFF EXPORT
# ======================================================
$jsonDiff = [pscustomobject]@{
ArtifactsDiff = $diff
HostInstances = [pscustomobject]@{
New = $hiDiff_New
Removed = $hiDiff_Removed
Changed = $hiDiff_Changed
}
}
$jsonDiff | ConvertTo-Json -Depth 8 | Out-File "diff.json" -Encoding UTF8
# ======================================================
# CSV DIFF EXPORT
# ======================================================
$diff | Export-Csv -Path "diff.csv" -NoTypeInformation -Encoding UTF8
# ======================================================
# HTML DIFF EXPORT
# ======================================================
$html=@()
$html+="<html><head><style>
table{border-collapse:collapse;margin-bottom:20px;}
td,th{border:1px solid #999;padding:4px 8px;}
.ok{color:green;font-weight:bold;}
.bad{color:red;font-weight:bold;}
.warn{color:orange;font-weight:bold;}
</style></head><body>"
$html+="<h2>BizTalk Differences</h2>"
# Artifacts
$html+="<h3>Artifact Differences</h3>"
$html+="<table><tr><th>Application</th><th>Type</th><th>Name</th><th>Before</th><th>After</th></tr>"
foreach ($d in $diff) {
$html+="<tr>
<td>$($d.Application)</td>
<td>$($d.ArtifactType)</td>
<td>$($d.Name)</td>
<td class='$($d.BeforeClass)'>$($d.BeforeText)</td>
<td class='$($d.AfterClass)'>$($d.AfterText)</td>
</tr>"
}
$html+="</table>"
# HostInstance DIFF
$html+="<h3>Host Instance Changes</h3>"
# NEW
$html+="<h4>New</h4><table><tr><th>Instance</th><th>Host</th><th>Server</th><th>Status</th></tr>"
foreach ($hi in $hiDiff_New) {
$html+="<tr>
<td>$($hi.InstanceName)</td>
<td>$($hi.HostName)</td>
<td>$($hi.Server)</td>
<td class='$($hi.StateClass)'>$($hi.StateText)</td>
</tr>"
}
$html+="</table>"
# REMOVED
$html+="<h4>Removed</h4><table><tr><th>Instance</th><th>Host</th><th>Server</th><th>Status</th></tr>"
foreach ($hi in $hiDiff_Removed) {
$html+="<tr>
<td>$($hi.InstanceName)</td>
<td>$($hi.HostName)</td>
<td>$($hi.Server)</td>
<td class='bad'>$($hi.StateText)</td>
</tr>"
}
$html+="</table>"
# CHANGED
$html+="<h4>Changed</h4><table><tr><th>Instance</th><th>Before</th><th>After</th></tr>"
foreach ($hi in $hiDiff_Changed) {
$before = Format-Status -ArtifactType "HostInstance" -Value $hi.Before
$after = Format-Status -ArtifactType "HostInstance" -Value $hi.After
$html+="<tr>
<td>$($hi.Instance)</td>
<td class='$($before.Class)'>$($hi.Before)</td>
<td class='$($after.Class)'>$($hi.After)</td>
</tr>"
}
$html+="</table>"
$html+="</body></html>"
$html -join "`n" | Out-File $HtmlReport -Encoding UTF8
# ======================================================
# CONSOLE OUTPUT
# ======================================================
Write-Host "`n===== BizTalk DIFF =====" -ForegroundColor Yellow
if ($diff.Count -eq 0 -and
$hiDiff_New.Count -eq 0 -and
$hiDiff_Removed.Count -eq 0 -and
$hiDiff_Changed.Count -eq 0)
{
Write-Host "Keine Unterschiede." -ForegroundColor Green
}
else {
Write-Host "`n--- Artifacts ---" -ForegroundColor Cyan
foreach ($d in $diff) {
Write-Host "`nApplication: $($d.Application)" -ForegroundColor Cyan
Write-Host "Artifact: $($d.ArtifactType)"
Write-Host "Name: $($d.Name)"
Write-Host ("Before: {0}" -f $d.BeforeText) -ForegroundColor $(
if ($d.BeforeClass -eq "ok") {"Green"} elseif ($d.BeforeClass -eq "warn") {"Yellow"} else {"Red"})
Write-Host ("After: {0}" -f $d.AfterText) -ForegroundColor $(
if ($d.AfterClass -eq "ok") {"Green"} elseif ($d.AfterClass -eq "warn") {"Yellow"} else {"Red"})
}
Write-Host "`n--- Host Instances (New) ---" -ForegroundColor Cyan
foreach ($hi in $hiDiff_New) {
Write-Host ("NEW: {0} {1}" -f $hi.InstanceName,$hi.StateText) -ForegroundColor $hi.ConsoleColor
}
Write-Host "`n--- Host Instances (Removed) ---" -ForegroundColor Cyan
foreach ($hi in $hiDiff_Removed) {
Write-Host ("REMOVED: {0} {1}" -f $hi.InstanceName,$hi.StateText) -ForegroundColor Red
}
Write-Host "`n--- Host Instances (Changed) ---" -ForegroundColor Cyan
foreach ($hi in $hiDiff_Changed) {
Write-Host ("CHANGED: {0} {1} -> {2}" -f $hi.Instance,$hi.Before,$hi.After) -ForegroundColor Yellow
}
}
Write-Host "HTML Diff exportiert: $HtmlReport" -ForegroundColor Green
}
# ======================================================
# DISPATCH
# ======================================================
if (-not (Test-Path $OutDir)) {
New-Item -ItemType Directory -Path $OutDir | Out-Null
}
Set-Location $OutDir
Write-Log "Working dir: $(Get-Location)"
if ($Diag) {
Write-Host "DIAG → Server: $Server" -ForegroundColor Yellow
try {
$sp = Get-WmiObject MSBTS_SendPort -Namespace root\MicrosoftBizTalkServer -ComputerName $Server |
Select -First 3 Name,Status
Write-Host "WMI OK:" -ForegroundColor Green
$sp | Format-Table
}
catch {
Write-Host "ERROR: $($_.Exception.Message)" -ForegroundColor Red
}
exit
}
if ($Before) { Get-BizTalkSnapshot -OutputFile "before.json" -Server $Server -MgmtDbServer $MgmtDbServer -MgmtDbName $MgmtDbName; exit }
if ($After) { Get-BizTalkSnapshot -OutputFile "after.json" -Server $Server -MgmtDbServer $MgmtDbServer -MgmtDbName $MgmtDbName; exit }
if ($Compare){ Compare-BizTalkSnapshots -Before "before.json" -After "after.json"; exit }
Write-Host "Usage:"
Write-Host " .\BizTalkStatusTool.ps1 -Before"
Write-Host " .\BizTalkStatusTool.ps1 -After"
Write-Host " .\BizTalkStatusTool.ps1 -Compare"
Write-Host " .\BizTalkStatusTool.ps1 -Diag"