From cff972a0bf12f918d6dd3969e46f0b5c8c9c48c9 Mon Sep 17 00:00:00 2001 From: Johannes Rest Date: Mon, 2 Feb 2026 13:18:48 +0100 Subject: [PATCH] Initial version BizTalkStatus Tool. --- .gitattributes | 2 + .gitignore | 18 +++ CHANGELOG.md | 21 +++ LICENSE | 18 +++ README.md | 37 +++++ REFERENCES.md | 7 + src/BizTalkStatusTool.ps1 | 278 ++++++++++++++++++++++++++++++++++++++ tools/Create-Tags.ps1 | 14 ++ 8 files changed, 395 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 REFERENCES.md create mode 100644 src/BizTalkStatusTool.ps1 create mode 100644 tools/Create-Tags.ps1 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..7f5bd70 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ + +* text=auto eol=crlf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..51b6516 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ + +# OS / IDE +Thumbs.db +.DS_Store +.vscode/ +.idea/ +*.user +*.suo + +# Logs / Outputs +*.log +*.json +*.html +work/ +release/* + +# PowerShell specifics +*.ps1.orig diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..95b5c2e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,21 @@ + +# Changelog + +## [1.3.0] - 2026-02-02 +### Added +- HTML Report mit farblicher Kennzeichnung (grün/rot) +- Farbige Statusausgabe auf STDOUT +- Remote‑Server Parameter (-Server) und Ausgabeverzeichnis (-OutDir) +- Verbesserte Diff‑Erkennung (neu/entfernt) + +## [1.2.0] - 2026-02-02 +### Added +- Remote‑fähige Snapshot‑Funktion, Logging (Konsole + Datei) + +## [1.1.0] - 2026-02-02 +### Added +- Textuelle Diff‑Ausgabe auf STDOUT + HTML Report + +## [1.0.0] - 2026-02-02 +### Added +- Grundfunktion: Snapshot & Diff (Receive Locations, Send Ports, Orchestrations) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1ebc3cd --- /dev/null +++ b/LICENSE @@ -0,0 +1,18 @@ +MIT License + +Copyright (c) 2026 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0718f0e --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# BizTalk Status Tool (PowerShell only) + +**Version:** 1.3.0 +**Kompatibel mit:** Windows Server 2019, PowerShell 5.1, BizTalk Server 2020 + +## 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) +```powershell +# Snapshot VOR Downtime vom Remote‑Server +.\src\BizTalkStatusTool.ps1 -Before -Server "BTAPP01" -OutDir .\work + +# Snapshot NACH Downtime +.\src\BizTalkStatusTool.ps1 -After -Server "BTAPP01" -OutDir .\work + +# Diff erstellen (Text + HTML im gleichen Ordner) +cd .\work +..\src\BizTalkStatusTool.ps1 -Compare +``` + +## Status‑Mapping +- **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`). + +## Releases +Fertige Skripte liegen im Ordner `/release`. + +## Tags +Mit `tools/Create-Tags.ps1` kannst du lokale Git‑Tags (`v1.0.0`, `v1.1.0`, `v1.2.0`, `v1.3.0`) erstellen. diff --git a/REFERENCES.md b/REFERENCES.md new file mode 100644 index 0000000..0fe4950 --- /dev/null +++ b/REFERENCES.md @@ -0,0 +1,7 @@ + +# References + +- 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_Orchestration.OrchestrationStatus (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-orchestration-orchestrationstatus-property-wmi +- MSBTS_ReceiveLocation (WMI): https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-receivelocation-wmi diff --git a/src/BizTalkStatusTool.ps1 b/src/BizTalkStatusTool.ps1 new file mode 100644 index 0000000..123876e --- /dev/null +++ b/src/BizTalkStatusTool.ps1 @@ -0,0 +1,278 @@ +<#! +BizTalk Status Tool (PowerShell only, Remote Server Support) +Compatible with: +- Windows Server 2019 +- PowerShell 5.1 +- BizTalk Server 2020 (WMI under root\MicrosoftBizTalkServer) +Author: Copilot +Version: 1.3.0 +#> + +$Global:LogFile = Join-Path (Get-Location) "BizTalkStatusTool.log" + +function Write-Log { + param( + [string]$Message, + [ValidateSet('INFO','WARN','ERROR','DEBUG')] + [string]$Level = 'INFO' + ) + $timestamp = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss') + $line = "[$timestamp][$Level] $Message" + Write-Host $line + Add-Content -Path $Global:LogFile -Value $line +} + +# ----------------- Helpers: Status mapping & coloring ----------------- +function Format-Status { + param( + [Parameter(Mandatory=$true)][string]$ArtifactType, + [Parameter(Mandatory=$false)]$Value + ) + $result = [ordered]@{ Text=''; Class='neutral'; ConsoleColor='Yellow' } + + switch ($ArtifactType) { + 'ReceiveLocation' { + # Value is boolean (Enabled) + $enabled = [bool]$Value + if ($enabled) { $result.Text='Enabled'; $result.Class='ok'; $result.ConsoleColor='Green' } + else { $result.Text='Disabled'; $result.Class='bad'; $result.ConsoleColor='Red' } + } + 'SendPort' { + # MSBTS_SendPort.Status: 1=Bound, 2=Stopped, 3=Started + $code = [int]$Value + switch ($code) { + 3 { $result.Text='Started'; $result.Class='ok'; $result.ConsoleColor='Green' } + 2 { $result.Text='Stopped'; $result.Class='bad'; $result.ConsoleColor='Red' } + 1 { $result.Text='Bound'; $result.Class='bad'; $result.ConsoleColor='Red' } + default { $result.Text="Unknown($code)"; $result.Class='bad'; $result.ConsoleColor='Red' } + } + } + 'Orchestration' { + # MSBTS_Orchestration.OrchestrationStatus: 1=Unbound, 2=Bound, 3=Stopped, 4=Started + $code = [int]$Value + switch ($code) { + 4 { $result.Text='Started'; $result.Class='ok'; $result.ConsoleColor='Green' } + 3 { $result.Text='Stopped'; $result.Class='bad'; $result.ConsoleColor='Red' } + 2 { $result.Text='Bound'; $result.Class='bad'; $result.ConsoleColor='Red' } + 1 { $result.Text='Unbound'; $result.Class='bad'; $result.ConsoleColor='Red' } + default { $result.Text="Unknown($code)"; $result.Class='bad'; $result.ConsoleColor='Red' } + } + } + default { + $result.Text = [string]$Value + } + } + return [pscustomobject]$result +} + +function Write-ColoredKV { + param( + [string]$Key, + [psobject]$Formatted + ) + Write-Host ("{0,-15}" -f ($Key + ':')) -NoNewline + Write-Host $Formatted.Text -ForegroundColor $Formatted.ConsoleColor +} + +# ----------------- Snapshot ----------------- +function Get-BizTalkSnapshot { + param( + [string]$OutputFile = 'snapshot.json', + [string]$Server = $env:COMPUTERNAME + ) + Write-Log "Erzeuge Snapshot von Server '$Server' → Datei: $OutputFile" + try { + $apps = Get-WmiObject MSBTS_Application -Namespace "root\MicrosoftBizTalkServer" -ComputerName $Server -ErrorAction Stop + } catch { + Write-Log "FEHLER: Verbindung zu BizTalk-WMI auf '$Server' fehlgeschlagen: $($_.Exception.Message)" 'ERROR' + throw + } + + $snapshot = @() + foreach ($app in $apps) { + $appName = $app.Name + Write-Log "Lese Anwendung '$appName'" + + $rl = Get-WmiObject MSBTS_ReceiveLocation -Namespace root\MicrosoftBizTalkServer -ComputerName $Server | + Where-Object { $_.ApplicationName -eq $appName } | + Select-Object Name, + @{n='Enabled'; e={ -not $_.IsDisabled }}, + AdapterName, + @{n='Address'; e={$_.InboundTransportURL}} + + $sp = Get-WmiObject MSBTS_SendPort -Namespace root\MicrosoftBizTalkServer -ComputerName $Server | + Where-Object { $_.ApplicationName -eq $appName } | + Select-Object Name, + @{n='Status';e={$_.Status}}, + @{n='PrimaryTransportType';e={$_.PTTransportType}}, + @{n='PrimaryTransportAddress';e={$_.PTAddress}} + + $orch = Get-WmiObject MSBTS_Orchestration -Namespace root\MicrosoftBizTalkServer -ComputerName $Server | + Where-Object { $_.ApplicationName -eq $appName } | + Select-Object Name, @{n='OrchestrationStatus';e={$_.OrchestrationStatus}} + + $snapshot += [PSCustomObject]@{ + Application = $appName + ReceiveLocations = $rl + SendPorts = $sp + Orchestrations = $orch + } + } + + $snapshot | ConvertTo-Json -Depth 8 | Out-File $OutputFile -Encoding UTF8 + Write-Log "Snapshot gespeichert: $OutputFile" +} + +# ----------------- Diff + HTML + Console Output ----------------- +function Compare-BizTalkSnapshots { + param( + [Parameter(Mandatory=$true)][string]$Before, + [Parameter(Mandatory=$true)][string]$After, + [string]$HtmlReport = 'BizTalkDiff.html' + ) + Write-Log "Vergleiche Snapshots: $Before ↔ $After" + + $snapBefore = Get-Content $Before | ConvertFrom-Json + $snapAfter = Get-Content $After | ConvertFrom-Json + + $differences = @() + + # Helper: add diff row + function Add-Diff { + param($appName,$type,$name,$beforeVal,$afterVal) + $b = Format-Status -ArtifactType $type -Value $beforeVal + $a = Format-Status -ArtifactType $type -Value $afterVal + $differences += [PSCustomObject]@{ + Application = $appName + ArtifactType = $type + Name = $name + Before = $beforeVal + After = $afterVal + BeforeText = $b.Text + BeforeClass = $b.Class + AfterText = $a.Text + AfterClass = $a.Class + } + } + + foreach ($appAfter in $snapAfter) { + $appBefore = $snapBefore | Where-Object { $_.Application -eq $appAfter.Application } + + # Receive Locations + foreach ($rlAfter in $appAfter.ReceiveLocations) { + $rlBefore = $appBefore.ReceiveLocations | Where-Object { $_.Name -eq $rlAfter.Name } + if ($null -eq $rlBefore) { Add-Diff $appAfter.Application 'ReceiveLocation' $rlAfter.Name $null $rlAfter.Enabled; continue } + if ($rlBefore.Enabled -ne $rlAfter.Enabled) { Add-Diff $appAfter.Application 'ReceiveLocation' $rlAfter.Name $rlBefore.Enabled $rlAfter.Enabled } + } + # Detect removed RL + foreach ($rlBefore in $appBefore.ReceiveLocations) { + $rlAfter = $appAfter.ReceiveLocations | Where-Object { $_.Name -eq $rlBefore.Name } + if ($null -eq $rlAfter) { Add-Diff $appAfter.Application 'ReceiveLocation' $rlBefore.Name $rlBefore.Enabled $null } + } + + # Send Ports + foreach ($spAfter in $appAfter.SendPorts) { + $spBefore = $appBefore.SendPorts | Where-Object { $_.Name -eq $spAfter.Name } + if ($null -eq $spBefore) { Add-Diff $appAfter.Application 'SendPort' $spAfter.Name $null $spAfter.Status; continue } + if ($spBefore.Status -ne $spAfter.Status) { Add-Diff $appAfter.Application 'SendPort' $spAfter.Name $spBefore.Status $spAfter.Status } + } + foreach ($spBefore in $appBefore.SendPorts) { + $spAfter = $appAfter.SendPorts | Where-Object { $_.Name -eq $spBefore.Name } + if ($null -eq $spAfter) { Add-Diff $appAfter.Application 'SendPort' $spBefore.Name $spBefore.Status $null } + } + + # Orchestrations + foreach ($oAfter in $appAfter.Orchestrations) { + $oBefore = $appBefore.Orchestrations | Where-Object { $_.Name -eq $oAfter.Name } + if ($null -eq $oBefore) { Add-Diff $appAfter.Application 'Orchestration' $oAfter.Name $null $oAfter.OrchestrationStatus; continue } + if ($oBefore.OrchestrationStatus -ne $oAfter.OrchestrationStatus) { Add-Diff $appAfter.Application 'Orchestration' $oAfter.Name $oBefore.OrchestrationStatus $oAfter.OrchestrationStatus } + } + foreach ($oBefore in $appBefore.Orchestrations) { + $oAfter = $appAfter.Orchestrations | Where-Object { $_.Name -eq $oBefore.Name } + if ($null -eq $oAfter) { Add-Diff $appAfter.Application 'Orchestration' $oBefore.Name $oBefore.OrchestrationStatus $null } + } + } + + # ---------------- Console text summary with colors ---------------- + Write-Host ""; Write-Host "------------------------------------------------------------" -ForegroundColor Yellow + Write-Host " TEXTUELLE ÄNDERUNGSÜBERSICHT (StdOut)" -ForegroundColor Yellow + Write-Host "------------------------------------------------------------" -ForegroundColor Yellow + + if ($differences.Count -eq 0) { + Write-Host "Keine Unterschiede gefunden." -ForegroundColor Green + } else { + foreach ($d in $differences) { + Write-Host ""; Write-Host ("Application: {0}" -f $d.Application) -ForegroundColor Cyan + Write-Host ("Artifact: {0}" -f $d.ArtifactType) + Write-Host ("Name: {0}" -f $d.Name) + Write-ColoredKV -Key 'Before' -Formatted ([pscustomobject]@{ Text=$d.BeforeText; ConsoleColor=($(if($d.BeforeClass -eq 'ok'){'Green'}elseif($d.BeforeClass -eq 'bad'){'Red'}else{'Yellow'})) }) + Write-ColoredKV -Key 'After' -Formatted ([pscustomobject]@{ Text=$d.AfterText; ConsoleColor=($(if($d.AfterClass -eq 'ok'){'Green'}elseif($d.AfterClass -eq 'bad'){'Red'}else{'Yellow'})) }) + } + } + + # ---------------- HTML report ---------------- + Write-Log "Erzeuge HTML-Report: $HtmlReport" + + $htmlHeader = @" + + +BizTalk Differences Report + + + +

BizTalk Differences Report

+

Vergleich: $Before$After

+ + + + + + + + +"@ + + $rows = foreach ($d in $differences) { + "" + } + + $htmlFooter = "
ApplicationArtifactNameBeforeAfter
$($d.Application)$($d.ArtifactType)$($d.Name)$($d.BeforeText)$($d.AfterText)
" + + ($htmlHeader + ($rows -join "`n") + $htmlFooter) | Out-File $HtmlReport -Encoding UTF8 + + Write-Host ""; Write-Host ("HTML-Report erstellt: {0}" -f $HtmlReport) -ForegroundColor Green + Write-Host "→ Im selben Verzeichnis öffnen für die farbige Übersicht." -ForegroundColor DarkYellow + + return $differences +} + +# ---------------- Parameters (CLI) ---------------- +param( + [switch]$Before, + [switch]$After, + [switch]$Compare, + [string]$Server = $env:COMPUTERNAME, + [string]$OutDir = '.' +) + +if ($PSBoundParameters.ContainsKey('OutDir')) { New-Item -ItemType Directory -Force -Path $OutDir | Out-Null; Set-Location $OutDir } + +if ($Before) { Get-BizTalkSnapshot -OutputFile 'before.json' -Server $Server; exit } +elif ($After) { Get-BizTalkSnapshot -OutputFile 'after.json' -Server $Server; exit } +elif ($Compare) { Compare-BizTalkSnapshots -Before 'before.json' -After 'after.json' -HtmlReport 'BizTalkDiff.html'; exit } +else { + Write-Host "Verwendung:" -ForegroundColor Yellow + Write-Host " Snapshot vor Downtime: .\BizTalkStatusTool.ps1 -Before -Server -OutDir " + Write-Host " Snapshot nach Downtime: .\BizTalkStatusTool.ps1 -After -Server -OutDir " + Write-Host " Diff + HTML Report: .\BizTalkStatusTool.ps1 -Compare" +} diff --git a/tools/Create-Tags.ps1 b/tools/Create-Tags.ps1 new file mode 100644 index 0000000..4d34c36 --- /dev/null +++ b/tools/Create-Tags.ps1 @@ -0,0 +1,14 @@ +param( + [string]$TagPrefix = 'v' +) +if (!(Get-Command git -ErrorAction SilentlyContinue)) { Write-Host 'git ist nicht installiert oder nicht im PATH.'; exit 1 } + +# Initialisiert ein Repo, committet und erstellt Tags +& git init +& git add . +& git commit -m "Initial import (v1.3.0)" +& git tag "${TagPrefix}1.0.0" -m "Baseline" +& git tag "${TagPrefix}1.1.0" -m "Textdiff + HTML" +& git tag "${TagPrefix}1.2.0" -m "Remote + Logging" +& git tag "${TagPrefix}1.3.0" -m "Farben + Releases" +Write-Host "Tags erstellt: ${TagPrefix}1.0.0, ${TagPrefix}1.1.0, ${TagPrefix}1.2.0, ${TagPrefix}1.3.0"