diff --git a/src/BizTalkStatusTool.ps1 b/src/BizTalkStatusTool.ps1 index 123876e..388aec2 100644 --- a/src/BizTalkStatusTool.ps1 +++ b/src/BizTalkStatusTool.ps1 @@ -1,278 +1,676 @@ -<#! -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 +<# +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) #> -$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) ---------------- +# ====================================================== +# PARAMETERS +# ====================================================== param( [switch]$Before, [switch]$After, [switch]$Compare, - [string]$Server = $env:COMPUTERNAME, - [string]$OutDir = '.' + [switch]$Diag, + [string]$Server, + [string]$MgmtDbServer, + [string]$MgmtDbName, + [string]$OutDir ) -if ($PSBoundParameters.ContainsKey('OutDir')) { New-Item -ItemType Directory -Force -Path $OutDir | Out-Null; Set-Location $OutDir } +if (-not $Server) { $Server = $env:COMPUTERNAME } +if (-not $OutDir) { $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" +$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 += "

Host Instances

" + $html += "" + $html += "" + + foreach ($hi in $HostInstances) { + $html += " + + + + + " + } + + $html += "
InstanceHostServerStatus
$($hi.InstanceName)$($hi.HostName)$($hi.Server)$($hi.StateText)
" + 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+="

BizTalk Snapshot

" + + foreach ($app in $snapshot) { + $html+="

Application: $($app.Application)

" + + if ($app.ReceiveLocations.Count -gt 0) { + $html+="

Receive Locations

" + foreach ($rl in $app.ReceiveLocations) { + $st = Format-Status -ArtifactType "ReceiveLocation" -Value $rl.Enabled + $html+="" + } + $html+="
NameStatus
$($rl.Name)$($st.Text)
" + } + + if ($app.SendPorts.Count -gt 0) { + $html+="

Send Ports

" + foreach ($sp in $app.SendPorts) { + $st = Format-Status -ArtifactType "SendPort" -Value $sp.Status + $html+="" + } + $html+="
NameStatus
$($sp.Name)$($st.Text)
" + } + + if ($app.Orchestrations.Count -gt 0) { + $html+="

Orchestrations

" + foreach ($o in $app.Orchestrations) { + $st = Format-Status -ArtifactType "Orchestration" -Value $o.OrchestrationStatus + $html+="" + } + $html+="
NameStatus
$($o.Name)$($st.Text)
" + } + } + + # Add Host Instances + $html += Export-HostInstancesHtml -HostInstances $hostInstances + + $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+="

BizTalk Differences

" + + # Artifacts + $html+="

Artifact Differences

" + $html+="" + foreach ($d in $diff) { + $html+=" + + + + + + " + } + $html+="
ApplicationTypeNameBeforeAfter
$($d.Application)$($d.ArtifactType)$($d.Name)$($d.BeforeText)$($d.AfterText)
" + + # HostInstance DIFF + $html+="

Host Instance Changes

" + + # NEW + $html+="

New

" + foreach ($hi in $hiDiff_New) { + $html+=" + + + + + " + } + $html+="
InstanceHostServerStatus
$($hi.InstanceName)$($hi.HostName)$($hi.Server)$($hi.StateText)
" + + # REMOVED + $html+="

Removed

" + foreach ($hi in $hiDiff_Removed) { + $html+=" + + + + + " + } + $html+="
InstanceHostServerStatus
$($hi.InstanceName)$($hi.HostName)$($hi.Server)$($hi.StateText)
" + + # CHANGED + $html+="

Changed

" + foreach ($hi in $hiDiff_Changed) { + $before = Format-Status -ArtifactType "HostInstance" -Value $hi.Before + $after = Format-Status -ArtifactType "HostInstance" -Value $hi.After + $html+=" + + + + " + } + $html+="
InstanceBeforeAfter
$($hi.Instance)$($hi.Before)$($hi.After)
" + + $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" \ No newline at end of file