Adding new .NET client code, replacing the powershell scripts.
This commit is contained in:
@@ -6,6 +6,8 @@ Thumbs.db
|
|||||||
.idea/
|
.idea/
|
||||||
*.user
|
*.user
|
||||||
*.suo
|
*.suo
|
||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
|
||||||
# Logs / Outputs
|
# Logs / Outputs
|
||||||
*.log
|
*.log
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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.
|
||||||
@@ -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)
|
|
||||||
- **HTML‑Report** mit **Farben** (grün = Started/Enabled, rot = Stopped/Disabled/Unbound/Bound)
|
|
||||||
- **Remote‑fä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 Remote‑Server
|
- 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**.
|
||||||
|
|
||||||
## Status‑Mapping
|
## 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 BizTalk‑Dokumentation (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 Git‑Tags (`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)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
|
||||||
Reference in New Issue
Block a user