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
+2
View File
@@ -6,6 +6,8 @@ Thumbs.db
.idea/ .idea/
*.user *.user
*.suo *.suo
bin/
obj/
# Logs / Outputs # Logs / Outputs
*.log *.log
+21
View File
@@ -0,0 +1,21 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 15
VisualStudioVersion = 15.0.28307.168
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizTalkPlatformManagementTool", "src\BizTalkPlatformManagementTool\BizTalkPlatformManagementTool.csproj", "{2C5B2C0A-F407-46C2-9E3B-1FA09FA8445A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2C5B2C0A-F407-46C2-9E3B-1FA09FA8445A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2C5B2C0A-F407-46C2-9E3B-1FA09FA8445A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2C5B2C0A-F407-46C2-9E3B-1FA09FA8445A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2C5B2C0A-F407-46C2-9E3B-1FA09FA8445A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
+6
View File
@@ -1,6 +1,12 @@
# Changelog # Changelog
## [Unreleased] - 2026-04-27
### Changed
- Projekt auf `BizTalkPlatformManagementTool` umbenannt.
- Solution, Projektdatei, Namespace, Assemblyname und Standard-Ausgabepfad angepasst.
- Dokumentation aktualisiert und `Installation.md` ergänzt.
## [1.3.0] - 2026-02-02 ## [1.3.0] - 2026-02-02
### Added ### Added
- HTML Report mit farblicher Kennzeichnung (grün/rot) - HTML Report mit farblicher Kennzeichnung (grün/rot)
+89
View File
@@ -0,0 +1,89 @@
# BizTalk Platform Management Tool Dokumentation
**Stand:** 2026-04-27
**Implementierung:** C# WinForms, .NET Framework 4.7.1
**Archivierte PowerShell-Version:** `archive/powershell/BizTalkPlatformManagementTool.ps1`
## Zweck
Das BizTalk Platform Management Tool unterstützt kontrollierte Wartungsfenster für Microsoft BizTalk Server 2020:
- Exakten Laufzeitstatus vor einer Wartung erfassen.
- Receive Locations, Orchestrations, Send Ports und Host Instances in definierter Reihenfolge herunterfahren.
- Den Laufzeitstatus aus einer gespeicherten `before.json` wiederherstellen.
- Den Status nach der Wartung erfassen und beide Snapshots vergleichen.
- Plan- und Ergebnisdateien für Review, Audit und Nachvollziehbarkeit schreiben.
## Projektstruktur
- Solution: `BizTalkPlatformManagementTool.sln`
- Projekt: `src/BizTalkPlatformManagementTool/BizTalkPlatformManagementTool.csproj`
- UI: `src/BizTalkPlatformManagementTool/Ui/MainForm.cs`
- WMI-Zugriff: `src/BizTalkPlatformManagementTool/Services/BizTalkWmiClient.cs`
- Operationslogik: `src/BizTalkPlatformManagementTool/Services/BizTalkOperationService.cs`
- PowerShell-Archiv: `archive/powershell/BizTalkPlatformManagementTool.ps1`
## UI Workflow
1. BizTalk-Servernamen eintragen.
2. Ausgabeverzeichnis wählen.
3. **Dry run** aktiviert lassen.
4. Mit **Diagnose** den Zugriff auf `root\MicrosoftBizTalkServer` prüfen.
5. Mit **Snapshot Before** die Datei `before.json` erzeugen.
6. Mit **Shutdown** den Plan `shutdown-plan.json` erzeugen und prüfen.
7. **Dry run** erst deaktivieren, wenn der Plan korrekt ist.
8. Nach der Wartung mit **Restore** aus `before.json` wiederherstellen.
9. Mit **Snapshot After** und **Compare** die Umgebung validieren.
## Sicherheitsdesign
- Dry-run ist standardmäßig aktiviert.
- Echte Shutdown-/Restore-Aktionen verlangen bei deaktiviertem Dry-run eine zusätzliche Bestätigung.
- Jede Operation schreibt Einträge in das sichtbare Operation Log.
- Operationspläne werden vor Laufzeitänderungen gespeichert.
- WMI-Methodenrückgaben werden geprüft.
- Wartezeiten nutzen konfigurierbare Timeout- und Polling-Werte.
- Host Instances auf anderen Servern werden übersprungen und als Warnung protokolliert.
## Shutdown-Reihenfolge
1. Receive Locations deaktivieren, die im Snapshot aktiviert waren.
2. Orchestrations stoppen, die gestartet waren.
3. Send Ports stoppen, die gestartet waren.
4. Host Instances stoppen, die auf dem ausgewählten Server gestartet waren.
## Restore-Reihenfolge
1. Host Instances starten, die auf dem ausgewählten Server zuvor gestartet waren.
2. Send Ports wiederherstellen.
3. Orchestrations wiederherstellen, soweit dies sicher möglich ist.
4. Receive Locations zuletzt wiederherstellen.
## Restore-Grenzen
- Send Ports werden auf `Started`, `Stopped` oder `Bound` zurückgesetzt.
- Orchestrations mit Status `Started`, `Stopped` oder `Unbound` werden direkt behandelt.
- Orchestrations mit Status `Bound` bleiben unverändert und werden zur manuellen Prüfung markiert, weil ein blindes Unenlist zu `Unbound` führen kann.
- Receive Locations werden zuletzt aktiviert, damit eingehender Verkehr erst nach Host Instances und abhängigen Artefakten wieder aufgenommen wird.
## Ausgabedateien
- Snapshots: `before.json`, `after.json`
- Operationspläne: `shutdown-plan.json`, `restore-plan.json`
- Nachher-Snapshots: `shutdown-after.json`, `restore-after.json`
- Diff: `diff.json`, `diff.csv`, `diff.html`
- Snapshot-Reports: `*.csv`, `*.hosts.csv`, `*.html`
## Status Mapping
- `MSBTS_SendPort.Status`: `1=Bound`, `2=Stopped`, `3=Started`
- `MSBTS_Orchestration.OrchestrationStatus`: `1=Unbound`, `2=Bound`, `3=Stopped`, `4=Started`
- `MSBTS_ReceiveLocation`: `Enabled = !IsDisabled`
- `MSBTS_HostInstance.ServiceState`: `1=Stopped`, `2=StartPending`, `3=StopPending`, `4=Started`
## Anforderungen
- Windows Server oder Administrationshost mit .NET Framework 4.7.1
- BizTalk Server 2020 oder installierte BizTalk Administration Tools
- Berechtigungen auf den WMI-Namespace `root\MicrosoftBizTalkServer`
- Visual Studio mit .NET Framework 4.7.1 Developer Pack für Builds
+37
View File
@@ -0,0 +1,37 @@
# Installation
## Voraussetzungen
- Windows Server 2019/2022 oder ein Windows-Administrationshost
- Microsoft BizTalk Server 2020 oder BizTalk Administration Tools
- .NET Framework 4.7.1 Runtime
- Für Builds: Visual Studio mit .NET Framework 4.7.1 Developer Pack
- Zugriff auf den WMI-Namespace `root\MicrosoftBizTalkServer`
- Ausreichende Rechte zum Lesen und Ändern von BizTalk-Artefakten
## Build
1. Repository öffnen.
2. `BizTalkPlatformManagementTool.sln` in Visual Studio öffnen.
3. Konfiguration `Release|Any CPU` auswählen.
4. Solution bauen.
5. Das Ergebnis liegt unter `src\BizTalkPlatformManagementTool\bin\Release\`.
## Deployment
1. Den Release-Ordner auf einen BizTalk-Server oder einen Administrationshost kopieren.
2. Sicherstellen, dass der ausführende Benutzer WMI-Zugriff auf `root\MicrosoftBizTalkServer` hat.
3. `BizTalkPlatformManagementTool.exe` starten.
4. Als Ausgabeverzeichnis einen Ordner wählen, in dem Plan-, Snapshot- und Report-Dateien abgelegt werden dürfen.
## Erster Funktionstest
1. Anwendung starten.
2. Zielserver eintragen oder den vorgeschlagenen lokalen Server verwenden.
3. **Dry run** aktiviert lassen.
4. **Diagnose** ausführen.
5. **Snapshot Before** ausführen und prüfen, ob `before.json` sowie CSV/HTML-Reports erzeugt wurden.
## Produktive Nutzung
Vor produktiven Änderungen immer zuerst einen Dry-run ausführen und die erzeugten `shutdown-plan.json` beziehungsweise `restore-plan.json` prüfen. Dry-run erst deaktivieren, wenn der Plan fachlich und technisch korrekt ist.
+60 -27
View File
@@ -1,37 +1,70 @@
# BizTalk Status Tool (PowerShell only) # BizTalk Platform Management Tool
**Version:** 1.3.0 WinForms tool for controlled Microsoft BizTalk Server 2020 platform operations during maintenance windows. The tool captures runtime snapshots, creates shutdown and restore plans, executes those plans in a safe order, and writes diff reports for validation.
**Kompatibel mit:** Windows Server 2019, PowerShell 5.1, BizTalk Server 2020
## Current State
- Application: C# WinForms
- Target framework: .NET Framework 4.7.1
- Solution: `BizTalkPlatformManagementTool.sln`
- Project: `src/BizTalkPlatformManagementTool/BizTalkPlatformManagementTool.csproj`
- Primary namespace and assembly: `BizTalkPlatformManagementTool`
- Legacy PowerShell archive: `archive/powershell/BizTalkPlatformManagementTool.ps1`
## Features ## Features
- Snapshots **vor**/**nach** Downtime als JSON
- **Diff** zwischen Snapshots (nur Änderungen)
- **HTMLReport** mit **Farben** (grün = Started/Enabled, rot = Stopped/Disabled/Unbound/Bound)
- **Remotefähig**: `-Server` Parameter (WMI unter `root\MicrosoftBizTalkServer`)
- **Logging**: Konsole + `BizTalkStatusTool.log`
## Verwendung (Beispiele) - Snapshot before and after downtime as JSON, CSV and HTML
```powershell - Diff between `before.json` and `after.json`
# Snapshot VOR Downtime vom RemoteServer - Controlled shutdown from the current runtime state
.\src\BizTalkStatusTool.ps1 -Before -Server "BTAPP01" -OutDir .\work - Controlled restore from `before.json`
- Host instance handling for the selected BizTalk server
- Dry-run mode enabled by default
- WMI access through `root\MicrosoftBizTalkServer`
- No compile-time dependency on BizTalk ExplorerOM assemblies
# Snapshot NACH Downtime ## Safe Usage
.\src\BizTalkStatusTool.ps1 -After -Server "BTAPP01" -OutDir .\work
# Diff erstellen (Text + HTML im gleichen Ordner) 1. Start the app on a BizTalk server or a management host with WMI access.
cd .\work 2. Keep **Dry run** enabled.
..\src\BizTalkStatusTool.ps1 -Compare 3. Click **Diagnose** to verify WMI access.
``` 4. Click **Snapshot Before**.
5. Click **Shutdown** and review `shutdown-plan.json`.
6. Disable **Dry run** only when the plan is correct.
7. After maintenance, click **Restore** using the saved `before.json`.
8. Click **Snapshot After** and **Compare**.
## StatusMapping ## Operation Order
- **SendPort.Status**: 1=Bound, 2=Stopped, 3=Started
- **Orchestration.OrchestrationStatus**: 1=Unbound, 2=Bound, 3=Stopped, 4=Started
- **ReceiveLocation**: `Enabled = -not IsDisabled`
**Hinweis:** Diese Mappings stammen aus der offiziellen BizTalkDokumentation (siehe `REFERENCES.md`). Shutdown:
- Disable receive locations that were enabled.
- Stop orchestrations that were started.
- Stop send ports that were started.
- Stop host instances that were started on the selected server.
## Releases Restore:
Fertige Skripte liegen im Ordner `/release`. - Start host instances that were previously started.
- Restore send ports to Started, Stopped or Bound.
- Restore orchestrations where safe.
- Restore receive locations last.
## Tags Orchestrations that were `Bound` are deliberately left unchanged during restore to avoid accidentally making them `Unbound`.
Mit `tools/Create-Tags.ps1` kannst du lokale GitTags (`v1.0.0`, `v1.1.0`, `v1.2.0`, `v1.3.0`) erstellen.
## Outputs
- `before.json`, `after.json`
- `shutdown-plan.json`, `restore-plan.json`
- `shutdown-after.json`, `restore-after.json`
- `diff.json`, `diff.csv`, `diff.html`
- Snapshot sidecars: `*.csv`, `*.hosts.csv`, `*.html`
## Build
Open `BizTalkPlatformManagementTool.sln` in Visual Studio on Windows with the .NET Framework 4.7.1 Developer Pack installed, then build the `Release|Any CPU` configuration.
The app targets .NET Framework 4.7.1 because BizTalk Server 2020 commonly runs in that Windows/.NET Framework operational environment.
## Documentation
- [Installation](Installation.md)
- [Dokumentation](Dokumentation.md)
- [References](REFERENCES.md)
+12
View File
@@ -3,5 +3,17 @@
- MSBTS_SendPort.Status (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-sendport-status-property-wmi - MSBTS_SendPort.Status (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-sendport-status-property-wmi
- MSBTS_SendPort (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-sendport-wmi - MSBTS_SendPort (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-sendport-wmi
- MSBTS_SendPort.Start (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-sendport-start-method-wmi
- MSBTS_SendPort.Stop (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-sendport-stop-method-wmi
- MSBTS_SendPort.Enlist (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-sendport-enlist-method-wmi
- MSBTS_SendPort.UnEnlist (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-sendport-unenlist-method-wmi
- MSBTS_Orchestration.OrchestrationStatus (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-orchestration-orchestrationstatus-property-wmi - MSBTS_Orchestration.OrchestrationStatus (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-orchestration-orchestrationstatus-property-wmi
- MSBTS_Orchestration.Start (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-orchestration-start-method-wmi
- MSBTS_Orchestration.Stop (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-orchestration-stop-method-wmi
- MSBTS_Orchestration.Unenlist (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-orchestration-unenlist-method-wmi
- MSBTS_ReceiveLocation (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-receivelocation-wmi - MSBTS_ReceiveLocation (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-receivelocation-wmi
- MSBTS_ReceiveLocation.Enable (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-receivelocation-enable-method-wmi
- MSBTS_ReceiveLocation.Disable (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-receivelocation-disable-method-wmi
- MSBTS_HostInstance (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-hostinstance-wmi
- MSBTS_HostInstance.Start (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-hostinstance-start-method-wmi
- MSBTS_HostInstance.Stop (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-hostinstance-stop-method-wmi
File diff suppressed because it is too large Load Diff
@@ -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"