1041 lines
45 KiB
PowerShell
1041 lines
45 KiB
PowerShell
|
||
<#
|
||
BizTalk Platform Management Tool – Refactored Level B (Single File)
|
||
Resilienz-Level 1 • HTML-Level B • PS 5.1 & 7.x kompatibel
|
||
|
||
Beinhaltet:
|
||
- Snapshot: Applications (ReceiveLocations / SendPorts / Orchestrations) + Host Instances
|
||
- Exporte: JSON, CSV, HTML (modernisiert)
|
||
- Compare: Before/After (Artifacts + HostInstances) → JSON, CSV, HTML, Console
|
||
- Farbiges Console-Logging (wie Original)
|
||
- Stabilere WMI-Zugriffe (Fallback auf CIM für PS 7.x)
|
||
- Fix: HostInstance-Status (int ↔ text) robust
|
||
#>
|
||
|
||
# ============================= PARAMETERS =============================
|
||
param(
|
||
[switch]$Before,
|
||
[switch]$After,
|
||
[switch]$Compare,
|
||
[switch]$Diag,
|
||
[switch]$Shutdown,
|
||
[switch]$Restore,
|
||
[switch]$PlanOnly,
|
||
[string]$Server,
|
||
[string]$MgmtDbServer,
|
||
[string]$MgmtDbName,
|
||
[string]$OutDir = ".",
|
||
[string]$StateFile = "before.json",
|
||
[int]$WaitTimeoutSec = 300,
|
||
[int]$PollIntervalSec = 5
|
||
)
|
||
|
||
if (-not $Server) { $Server = $env:COMPUTERNAME }
|
||
$ErrorActionPreference = "Stop"
|
||
$Global:LogFile = Join-Path (Get-Location) "BizTalkPlatformManagementTool.log"
|
||
|
||
# ============================= LOGGING =============================
|
||
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
|
||
}
|
||
|
||
# ============================= UTILITIES =============================
|
||
function Test-CommandExists {
|
||
param([Parameter(Mandatory)][string]$Name)
|
||
try { return [bool](Get-Command -Name $Name -ErrorAction Stop) } catch { return $false }
|
||
}
|
||
|
||
function Invoke-BizTalkWmiQuery {
|
||
<#
|
||
Cross-PS-Version Wrapper.
|
||
- In PS 5.1 nutzt Get-WmiObject
|
||
- In PS 7.x (und falls WMI nicht verfügbar) nutzt Get-CimInstance
|
||
#>
|
||
param(
|
||
[Parameter(Mandatory)][string]$ClassName,
|
||
[Parameter()][string]$Namespace = 'root\MicrosoftBizTalkServer',
|
||
[Parameter()][string]$ComputerName = $env:COMPUTERNAME,
|
||
[switch]$ThrowOnError
|
||
)
|
||
$useWmi = Test-CommandExists -Name 'Get-WmiObject'
|
||
try {
|
||
if ($useWmi) {
|
||
return Get-WmiObject -Class $ClassName -Namespace $Namespace -ComputerName $ComputerName -ErrorAction Stop
|
||
} else {
|
||
return Get-CimInstance -ClassName $ClassName -Namespace $Namespace -ComputerName $ComputerName -ErrorAction Stop
|
||
}
|
||
}
|
||
catch {
|
||
Write-Log "WMI/CIM query failed for ${ClassName}: $($_.Exception.Message)" 'ERROR'
|
||
if ($ThrowOnError) { throw }
|
||
return @()
|
||
}
|
||
}
|
||
|
||
# ============================= STATUS MAPPING =============================
|
||
function Convert-HostState {
|
||
param($Value)
|
||
if ($Value -is [int]) { return $Value }
|
||
switch ($Value.ToString().ToLower()) {
|
||
'started' { return 4 }
|
||
'startpending' { return 2 }
|
||
'stoppending' { return 3 }
|
||
'stopped' { return 1 }
|
||
default { return 0 }
|
||
}
|
||
}
|
||
|
||
function Format-Status {
|
||
param([string]$ArtifactType, $Value)
|
||
|
||
$r = [ordered]@{Text='Unknown'; Class='bad'; ConsoleColor='Red'}
|
||
|
||
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' }
|
||
1 { $r.Text='Unbound'; $r.Class='bad'; $r.ConsoleColor='Red' }
|
||
default { $r.Text='Unknown'; $r.Class='bad'; $r.ConsoleColor='Red' }
|
||
}
|
||
}
|
||
'HostInstance' {
|
||
$v = Convert-HostState $Value
|
||
switch ($v) {
|
||
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($MgmtDbServer,$MgmtDbName)
|
||
try {
|
||
$p = Get-ItemProperty 'HKLM:SOFTWARE\\Microsoft\\BizTalk Server\\3.0\\Administration'
|
||
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)
|
||
|
||
$list = Invoke-BizTalkWmiQuery -ClassName 'MSBTS_HostInstance' -ComputerName $Server
|
||
|
||
$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
|
||
}
|
||
|
||
# ============================= CONTROL HELPERS =============================
|
||
function Get-BizTalkStateFile {
|
||
param([Parameter(Mandatory)][string]$Path)
|
||
if (-not (Test-Path $Path)) {
|
||
throw "State file not found: $Path"
|
||
}
|
||
return (Get-Content $Path -Raw | ConvertFrom-Json)
|
||
}
|
||
|
||
function Get-ReturnCode {
|
||
param($Result)
|
||
if ($null -eq $Result) { return $null }
|
||
if ($Result.PSObject.Properties.Name -contains 'ReturnValue') { return [int]$Result.ReturnValue }
|
||
if ($Result -is [int] -or $Result -is [uint32]) { return [int]$Result }
|
||
return $null
|
||
}
|
||
|
||
function Assert-BizTalkMethodResult {
|
||
param(
|
||
[Parameter(Mandatory)][string]$Action,
|
||
$Result
|
||
)
|
||
|
||
$code = Get-ReturnCode -Result $Result
|
||
if ($null -ne $code -and $code -ne 0) {
|
||
throw "$Action failed with HRESULT/ReturnValue $code"
|
||
}
|
||
}
|
||
|
||
function Invoke-BizTalkObjectMethod {
|
||
param(
|
||
[Parameter(Mandatory)]$InputObject,
|
||
[Parameter(Mandatory)][string]$MethodName,
|
||
[hashtable]$CimArguments = @{},
|
||
[object[]]$WmiArguments = @(),
|
||
[Parameter(Mandatory)][string]$Action,
|
||
[switch]$PlanOnly
|
||
)
|
||
|
||
if ($PlanOnly) {
|
||
Write-Log "PLAN: $Action"
|
||
return
|
||
}
|
||
|
||
Write-Log $Action
|
||
|
||
try {
|
||
if ($InputObject -is [Microsoft.Management.Infrastructure.CimInstance]) {
|
||
if ($CimArguments.Count -gt 0) {
|
||
$result = Invoke-CimMethod -InputObject $InputObject -MethodName $MethodName -Arguments $CimArguments -ErrorAction Stop
|
||
} else {
|
||
$result = Invoke-CimMethod -InputObject $InputObject -MethodName $MethodName -ErrorAction Stop
|
||
}
|
||
} else {
|
||
$args = if ($WmiArguments.Count -gt 0) { [object[]]$WmiArguments } else { $null }
|
||
$result = $InputObject.InvokeMethod($MethodName, $args)
|
||
}
|
||
|
||
Assert-BizTalkMethodResult -Action $Action -Result $result
|
||
Write-Log "OK: $Action"
|
||
}
|
||
catch {
|
||
Write-Log "FAILED: $Action - $($_.Exception.Message)" 'ERROR'
|
||
throw
|
||
}
|
||
}
|
||
|
||
function New-NameMap {
|
||
param(
|
||
[Parameter(Mandatory)][AllowEmptyCollection()]$Items,
|
||
[Parameter(Mandatory)][string]$KeyProperty
|
||
)
|
||
|
||
$map = @{}
|
||
foreach ($item in $Items) {
|
||
$key = $item.$KeyProperty
|
||
if (-not $key) { continue }
|
||
if ($map.ContainsKey($key)) {
|
||
Write-Log "Duplicate WMI key '$key' for property '$KeyProperty'; keeping first object." 'WARN'
|
||
continue
|
||
}
|
||
$map[$key] = $item
|
||
}
|
||
return $map
|
||
}
|
||
|
||
function Wait-BizTalkCondition {
|
||
param(
|
||
[Parameter(Mandatory)][scriptblock]$Condition,
|
||
[Parameter(Mandatory)][string]$Description,
|
||
[int]$TimeoutSec = 300,
|
||
[int]$PollIntervalSec = 5,
|
||
[switch]$PlanOnly
|
||
)
|
||
|
||
if ($PlanOnly) { return }
|
||
|
||
$deadline = (Get-Date).AddSeconds($TimeoutSec)
|
||
do {
|
||
if (& $Condition) {
|
||
Write-Log "Reached: $Description"
|
||
return
|
||
}
|
||
Start-Sleep -Seconds $PollIntervalSec
|
||
} while ((Get-Date) -lt $deadline)
|
||
|
||
throw "Timeout after ${TimeoutSec}s while waiting for: $Description"
|
||
}
|
||
|
||
function Get-CurrentBizTalkObjectMaps {
|
||
param([Parameter(Mandatory)][string]$Server)
|
||
|
||
$receiveLocations = Invoke-BizTalkWmiQuery -ClassName 'MSBTS_ReceiveLocation' -ComputerName $Server -ThrowOnError
|
||
$sendPorts = Invoke-BizTalkWmiQuery -ClassName 'MSBTS_SendPort' -ComputerName $Server -ThrowOnError
|
||
$orchestrations = Invoke-BizTalkWmiQuery -ClassName 'MSBTS_Orchestration' -ComputerName $Server -ThrowOnError
|
||
$hostInstances = Invoke-BizTalkWmiQuery -ClassName 'MSBTS_HostInstance' -ComputerName $Server -ThrowOnError
|
||
|
||
return [pscustomobject]@{
|
||
ReceiveLocations = New-NameMap -Items $receiveLocations -KeyProperty 'Name'
|
||
SendPorts = New-NameMap -Items $sendPorts -KeyProperty 'Name'
|
||
Orchestrations = New-NameMap -Items $orchestrations -KeyProperty 'Name'
|
||
HostInstances = New-NameMap -Items $hostInstances -KeyProperty 'InstanceName'
|
||
}
|
||
}
|
||
|
||
function Get-SnapshotArtifacts {
|
||
param([Parameter(Mandatory)]$Snapshot)
|
||
|
||
$receiveLocations = @()
|
||
$sendPorts = @()
|
||
$orchestrations = @()
|
||
|
||
foreach ($app in $Snapshot.Applications) {
|
||
foreach ($item in $app.ReceiveLocations) {
|
||
$receiveLocations += [pscustomobject]@{ Application=$app.Application; Name=$item.Name; Enabled=$item.Enabled }
|
||
}
|
||
foreach ($item in $app.SendPorts) {
|
||
$sendPorts += [pscustomobject]@{ Application=$app.Application; Name=$item.Name; Status=[int]$item.Status }
|
||
}
|
||
foreach ($item in $app.Orchestrations) {
|
||
$orchestrations += [pscustomobject]@{ Application=$app.Application; Name=$item.Name; Status=[int]$item.OrchestrationStatus }
|
||
}
|
||
}
|
||
|
||
return [pscustomobject]@{
|
||
ReceiveLocations = $receiveLocations
|
||
SendPorts = $sendPorts
|
||
Orchestrations = $orchestrations
|
||
HostInstances = @($Snapshot.HostInstances)
|
||
}
|
||
}
|
||
|
||
function Export-BizTalkOperationPlan {
|
||
param(
|
||
[Parameter(Mandatory)][string]$Path,
|
||
[Parameter(Mandatory)][string]$Mode,
|
||
[Parameter(Mandatory)]$Artifacts
|
||
)
|
||
|
||
$plan = [pscustomobject]@{
|
||
Mode = $Mode
|
||
CreatedAt = (Get-Date).ToString('s')
|
||
ReceiveLocations = @($Artifacts.ReceiveLocations)
|
||
SendPorts = @($Artifacts.SendPorts)
|
||
Orchestrations = @($Artifacts.Orchestrations)
|
||
HostInstances = @($Artifacts.HostInstances)
|
||
}
|
||
|
||
$plan | ConvertTo-Json -Depth 8 | Out-File $Path -Encoding UTF8
|
||
Write-Log "Operation plan exported: $Path"
|
||
}
|
||
|
||
function Stop-BizTalkRuntimeFromSnapshot {
|
||
param(
|
||
[Parameter(Mandatory)]$Snapshot,
|
||
[Parameter(Mandatory)][string]$Server,
|
||
[int]$WaitTimeoutSec,
|
||
[int]$PollIntervalSec,
|
||
[switch]$PlanOnly
|
||
)
|
||
|
||
$artifacts = Get-SnapshotArtifacts -Snapshot $Snapshot
|
||
Export-BizTalkOperationPlan -Path 'shutdown-plan.json' -Mode 'Shutdown' -Artifacts $artifacts
|
||
|
||
$maps = Get-CurrentBizTalkObjectMaps -Server $Server
|
||
|
||
foreach ($rl in $artifacts.ReceiveLocations | Where-Object { $_.Enabled -eq $true }) {
|
||
if (-not $maps.ReceiveLocations.ContainsKey($rl.Name)) { Write-Log "Receive location not found: $($rl.Name)" 'WARN'; continue }
|
||
$obj = $maps.ReceiveLocations[$rl.Name]
|
||
Invoke-BizTalkObjectMethod -InputObject $obj -MethodName 'Disable' -Action "Disable receive location '$($rl.Name)'" -PlanOnly:$PlanOnly
|
||
Wait-BizTalkCondition -Description "Receive location '$($rl.Name)' disabled" -TimeoutSec $WaitTimeoutSec -PollIntervalSec $PollIntervalSec -PlanOnly:$PlanOnly -Condition {
|
||
$current = Invoke-BizTalkWmiQuery -ClassName 'MSBTS_ReceiveLocation' -ComputerName $Server | Where-Object { $_.Name -eq $rl.Name } | Select-Object -First 1
|
||
$current -and $current.IsDisabled -eq $true
|
||
}
|
||
}
|
||
|
||
foreach ($orch in $artifacts.Orchestrations | Where-Object { $_.Status -eq 4 }) {
|
||
if (-not $maps.Orchestrations.ContainsKey($orch.Name)) { Write-Log "Orchestration not found: $($orch.Name)" 'WARN'; continue }
|
||
$obj = $maps.Orchestrations[$orch.Name]
|
||
Invoke-BizTalkObjectMethod -InputObject $obj -MethodName 'Stop' -CimArguments @{ AutoDisableReceiveLocationFlag=[uint32]1; AutoSuspendServiceInstanceFlag=[uint32]1 } -WmiArguments @(1,1) -Action "Stop orchestration '$($orch.Name)'" -PlanOnly:$PlanOnly
|
||
Wait-BizTalkCondition -Description "Orchestration '$($orch.Name)' stopped" -TimeoutSec $WaitTimeoutSec -PollIntervalSec $PollIntervalSec -PlanOnly:$PlanOnly -Condition {
|
||
$current = Invoke-BizTalkWmiQuery -ClassName 'MSBTS_Orchestration' -ComputerName $Server | Where-Object { $_.Name -eq $orch.Name } | Select-Object -First 1
|
||
$current -and [int]$current.OrchestrationStatus -eq 3
|
||
}
|
||
}
|
||
|
||
foreach ($sp in $artifacts.SendPorts | Where-Object { $_.Status -eq 3 }) {
|
||
if (-not $maps.SendPorts.ContainsKey($sp.Name)) { Write-Log "Send port not found: $($sp.Name)" 'WARN'; continue }
|
||
$obj = $maps.SendPorts[$sp.Name]
|
||
Invoke-BizTalkObjectMethod -InputObject $obj -MethodName 'Stop' -Action "Stop send port '$($sp.Name)'" -PlanOnly:$PlanOnly
|
||
Wait-BizTalkCondition -Description "Send port '$($sp.Name)' stopped" -TimeoutSec $WaitTimeoutSec -PollIntervalSec $PollIntervalSec -PlanOnly:$PlanOnly -Condition {
|
||
$current = Invoke-BizTalkWmiQuery -ClassName 'MSBTS_SendPort' -ComputerName $Server | Where-Object { $_.Name -eq $sp.Name } | Select-Object -First 1
|
||
$current -and [int]$current.Status -eq 2
|
||
}
|
||
}
|
||
|
||
foreach ($hi in $artifacts.HostInstances | Where-Object { (Convert-HostState $_.RawState) -eq 4 }) {
|
||
if ($hi.Server -and $hi.Server -ne $Server) {
|
||
Write-Log "Skipping host instance '$($hi.InstanceName)' on server '$($hi.Server)'. Run the shutdown on that server to stop it safely." 'WARN'
|
||
continue
|
||
}
|
||
if (-not $maps.HostInstances.ContainsKey($hi.InstanceName)) { Write-Log "Host instance not found: $($hi.InstanceName)" 'WARN'; continue }
|
||
$obj = $maps.HostInstances[$hi.InstanceName]
|
||
Invoke-BizTalkObjectMethod -InputObject $obj -MethodName 'Stop' -Action "Stop host instance '$($hi.InstanceName)'" -PlanOnly:$PlanOnly
|
||
Wait-BizTalkCondition -Description "Host instance '$($hi.InstanceName)' stopped" -TimeoutSec $WaitTimeoutSec -PollIntervalSec $PollIntervalSec -PlanOnly:$PlanOnly -Condition {
|
||
$current = Invoke-BizTalkWmiQuery -ClassName 'MSBTS_HostInstance' -ComputerName $Server | Where-Object { $_.InstanceName -eq $hi.InstanceName } | Select-Object -First 1
|
||
$current -and (Convert-HostState $current.ServiceState) -eq 1
|
||
}
|
||
}
|
||
}
|
||
|
||
function Restore-BizTalkRuntimeFromSnapshot {
|
||
param(
|
||
[Parameter(Mandatory)]$Snapshot,
|
||
[Parameter(Mandatory)][string]$Server,
|
||
[int]$WaitTimeoutSec,
|
||
[int]$PollIntervalSec,
|
||
[switch]$PlanOnly
|
||
)
|
||
|
||
$artifacts = Get-SnapshotArtifacts -Snapshot $Snapshot
|
||
Export-BizTalkOperationPlan -Path 'restore-plan.json' -Mode 'Restore' -Artifacts $artifacts
|
||
|
||
$maps = Get-CurrentBizTalkObjectMaps -Server $Server
|
||
|
||
foreach ($hi in $artifacts.HostInstances | Where-Object { (Convert-HostState $_.RawState) -eq 4 }) {
|
||
if ($hi.Server -and $hi.Server -ne $Server) {
|
||
Write-Log "Skipping host instance '$($hi.InstanceName)' on server '$($hi.Server)'. Run the restore on that server to start it safely." 'WARN'
|
||
continue
|
||
}
|
||
if (-not $maps.HostInstances.ContainsKey($hi.InstanceName)) { Write-Log "Host instance not found: $($hi.InstanceName)" 'WARN'; continue }
|
||
$obj = $maps.HostInstances[$hi.InstanceName]
|
||
Invoke-BizTalkObjectMethod -InputObject $obj -MethodName 'Start' -Action "Start host instance '$($hi.InstanceName)'" -PlanOnly:$PlanOnly
|
||
Wait-BizTalkCondition -Description "Host instance '$($hi.InstanceName)' started" -TimeoutSec $WaitTimeoutSec -PollIntervalSec $PollIntervalSec -PlanOnly:$PlanOnly -Condition {
|
||
$current = Invoke-BizTalkWmiQuery -ClassName 'MSBTS_HostInstance' -ComputerName $Server | Where-Object { $_.InstanceName -eq $hi.InstanceName } | Select-Object -First 1
|
||
$current -and (Convert-HostState $current.ServiceState) -eq 4
|
||
}
|
||
}
|
||
|
||
foreach ($sp in $artifacts.SendPorts) {
|
||
if (-not $maps.SendPorts.ContainsKey($sp.Name)) { Write-Log "Send port not found: $($sp.Name)" 'WARN'; continue }
|
||
$obj = $maps.SendPorts[$sp.Name]
|
||
$currentStatus = [int]$obj.Status
|
||
switch ($sp.Status) {
|
||
3 {
|
||
Invoke-BizTalkObjectMethod -InputObject $obj -MethodName 'Start' -Action "Start send port '$($sp.Name)'" -PlanOnly:$PlanOnly
|
||
}
|
||
2 {
|
||
if ($currentStatus -eq 1) {
|
||
Invoke-BizTalkObjectMethod -InputObject $obj -MethodName 'Enlist' -Action "Enlist send port '$($sp.Name)' to stopped state" -PlanOnly:$PlanOnly
|
||
} elseif ($currentStatus -eq 3) {
|
||
Invoke-BizTalkObjectMethod -InputObject $obj -MethodName 'Stop' -Action "Stop send port '$($sp.Name)'" -PlanOnly:$PlanOnly
|
||
} else {
|
||
Write-Log "Send port already stopped: $($sp.Name)"
|
||
}
|
||
}
|
||
1 {
|
||
Invoke-BizTalkObjectMethod -InputObject $obj -MethodName 'UnEnlist' -Action "Unenlist send port '$($sp.Name)'" -PlanOnly:$PlanOnly
|
||
}
|
||
}
|
||
Wait-BizTalkCondition -Description "Send port '$($sp.Name)' restored to status $($sp.Status)" -TimeoutSec $WaitTimeoutSec -PollIntervalSec $PollIntervalSec -PlanOnly:$PlanOnly -Condition {
|
||
$current = Invoke-BizTalkWmiQuery -ClassName 'MSBTS_SendPort' -ComputerName $Server | Where-Object { $_.Name -eq $sp.Name } | Select-Object -First 1
|
||
$current -and [int]$current.Status -eq $sp.Status
|
||
}
|
||
}
|
||
|
||
foreach ($orch in $artifacts.Orchestrations) {
|
||
if (-not $maps.Orchestrations.ContainsKey($orch.Name)) { Write-Log "Orchestration not found: $($orch.Name)" 'WARN'; continue }
|
||
$obj = $maps.Orchestrations[$orch.Name]
|
||
$currentStatus = [int]$obj.OrchestrationStatus
|
||
switch ($orch.Status) {
|
||
4 {
|
||
if ($currentStatus -eq 4) {
|
||
Write-Log "Orchestration already started: $($orch.Name)"
|
||
} else {
|
||
Invoke-BizTalkObjectMethod -InputObject $obj -MethodName 'Start' -CimArguments @{ AutoEnableReceiveLocationFlag=[uint32]1; AutoResumeOrchestrationInstanceFlag=[uint32]1; AutoStartSendPortsFlag=[uint32]1 } -WmiArguments @(1,1,1) -Action "Start orchestration '$($orch.Name)'" -PlanOnly:$PlanOnly
|
||
}
|
||
}
|
||
3 {
|
||
if ($currentStatus -eq 4) {
|
||
Invoke-BizTalkObjectMethod -InputObject $obj -MethodName 'Stop' -CimArguments @{ AutoDisableReceiveLocationFlag=[uint32]1; AutoSuspendServiceInstanceFlag=[uint32]1 } -WmiArguments @(1,1) -Action "Stop orchestration '$($orch.Name)'" -PlanOnly:$PlanOnly
|
||
} elseif ($currentStatus -eq 3) {
|
||
Write-Log "Orchestration already stopped: $($orch.Name)"
|
||
} else {
|
||
Write-Log "Orchestration '$($orch.Name)' target is Stopped, current status is $currentStatus. Leaving unchanged." 'WARN'
|
||
}
|
||
}
|
||
2 {
|
||
Write-Log "Orchestration '$($orch.Name)' was Bound in the snapshot. Bound restore is left unchanged to avoid accidentally changing it to Unbound." 'WARN'
|
||
}
|
||
1 {
|
||
if ($currentStatus -eq 1) {
|
||
Write-Log "Orchestration already unbound: $($orch.Name)"
|
||
} else {
|
||
Invoke-BizTalkObjectMethod -InputObject $obj -MethodName 'UnenlistService' -CimArguments @{ AutoTerminateOrchestrationInstanceFlag=[uint32]1 } -WmiArguments @(1) -Action "Unenlist orchestration '$($orch.Name)'" -PlanOnly:$PlanOnly
|
||
}
|
||
}
|
||
}
|
||
if ($orch.Status -ne 2) {
|
||
Wait-BizTalkCondition -Description "Orchestration '$($orch.Name)' restored to status $($orch.Status)" -TimeoutSec $WaitTimeoutSec -PollIntervalSec $PollIntervalSec -PlanOnly:$PlanOnly -Condition {
|
||
$current = Invoke-BizTalkWmiQuery -ClassName 'MSBTS_Orchestration' -ComputerName $Server | Where-Object { $_.Name -eq $orch.Name } | Select-Object -First 1
|
||
$current -and [int]$current.OrchestrationStatus -eq $orch.Status
|
||
}
|
||
}
|
||
}
|
||
|
||
foreach ($rl in $artifacts.ReceiveLocations) {
|
||
if (-not $maps.ReceiveLocations.ContainsKey($rl.Name)) { Write-Log "Receive location not found: $($rl.Name)" 'WARN'; continue }
|
||
$obj = $maps.ReceiveLocations[$rl.Name]
|
||
if ($rl.Enabled -eq $true) {
|
||
Invoke-BizTalkObjectMethod -InputObject $obj -MethodName 'Enable' -Action "Enable receive location '$($rl.Name)'" -PlanOnly:$PlanOnly
|
||
} else {
|
||
Invoke-BizTalkObjectMethod -InputObject $obj -MethodName 'Disable' -Action "Keep receive location disabled '$($rl.Name)'" -PlanOnly:$PlanOnly
|
||
}
|
||
Wait-BizTalkCondition -Description "Receive location '$($rl.Name)' restored" -TimeoutSec $WaitTimeoutSec -PollIntervalSec $PollIntervalSec -PlanOnly:$PlanOnly -Condition {
|
||
$current = Invoke-BizTalkWmiQuery -ClassName 'MSBTS_ReceiveLocation' -ComputerName $Server | Where-Object { $_.Name -eq $rl.Name } | Select-Object -First 1
|
||
$current -and ((-not $current.IsDisabled) -eq [bool]$rl.Enabled)
|
||
}
|
||
}
|
||
}
|
||
|
||
# ============================= HTML HELPERS =============================
|
||
function New-Html-Header {
|
||
param([string]$Title)
|
||
@"
|
||
<html>
|
||
<head>
|
||
<meta charset='utf-8'/>
|
||
<style>
|
||
body{font-family:Segoe UI,Arial,sans-serif;font-size:13px;color:#222;margin:20px;}
|
||
h2{margin-top:0}
|
||
section{margin-bottom:24px}
|
||
table{border-collapse:collapse;width:100%;margin:8px 0 16px 0}
|
||
th,td{border:1px solid #ccc;padding:6px 8px;text-align:left}
|
||
th{background:#f8f8f8}
|
||
tr:nth-child(even){background:#fcfcfc}
|
||
.ok{color:#0a7a0a;font-weight:600}
|
||
.bad{color:#c12727;font-weight:600}
|
||
.warn{color:#c88719;font-weight:600}
|
||
.summary{display:flex;gap:16px;margin:8px 0 12px 0}
|
||
.summary .card{border:1px solid #ddd;border-radius:6px;padding:8px 12px;background:#fafafa}
|
||
.summary .value{font-size:18px;font-weight:700;margin-top:4px}
|
||
.app{margin-top:18px}
|
||
.small{color:#666}
|
||
</style>
|
||
<title>$Title</title>
|
||
</head>
|
||
<body>
|
||
<h2>$Title</h2>
|
||
"@
|
||
}
|
||
|
||
function New-Html-Footer { "</body></html>" }
|
||
|
||
# ============================= 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
|
||
$rlW = Invoke-BizTalkWmiQuery -ClassName 'MSBTS_ReceiveLocation' -ComputerName $Server |
|
||
Select-Object Name, @{n='Enabled';e={-not $_.IsDisabled}}, AdapterName, @{n='Address';e={$_.InboundTransportURL}}
|
||
|
||
$spW = Invoke-BizTalkWmiQuery -ClassName 'MSBTS_SendPort' -ComputerName $Server |
|
||
Select-Object Name, @{n='Status';e={$_.Status}}, @{n='PrimaryTransportType';e={$_.PTTransportType}}, @{n='PrimaryTransportAddress';e={$_.PTAddress}}
|
||
|
||
$orW = Invoke-BizTalkWmiQuery -ClassName 'MSBTS_Orchestration' -ComputerName $Server |
|
||
Select-Object 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 (Artifacts)
|
||
$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 CSV (HostInstances)
|
||
$hostInstances |
|
||
Select-Object InstanceName,HostName,Server,StateText |
|
||
Export-Csv -NoTypeInformation -Encoding UTF8 -Path ($OutputFile + '.hosts.csv')
|
||
|
||
# HTML Snapshot (modernisiert)
|
||
$html = @()
|
||
$html += New-Html-Header -Title 'BizTalk Snapshot'
|
||
|
||
# Summary
|
||
$totalApps = ($snapshot | Measure-Object).Count
|
||
$totalRL = ($snapshot.ReceiveLocations | Measure-Object).Count
|
||
$totalSP = ($snapshot.SendPorts | Measure-Object).Count
|
||
$totalOR = ($snapshot.Orchestrations | Measure-Object).Count
|
||
$totalHI = ($hostInstances | Measure-Object).Count
|
||
|
||
$html += "<section class='summary'>"
|
||
$html += "<div class='card'><div>Applications</div><div class='value'>$totalApps</div></div>"
|
||
$html += "<div class='card'><div>Receive Locations</div><div class='value'>$totalRL</div></div>"
|
||
$html += "<div class='card'><div>Send Ports</div><div class='value'>$totalSP</div></div>"
|
||
$html += "<div class='card'><div>Orchestrations</div><div class='value'>$totalOR</div></div>"
|
||
$html += "<div class='card'><div>Host Instances</div><div class='value'>$totalHI</div></div>"
|
||
$html += "</section>"
|
||
|
||
foreach ($app in $snapshot) {
|
||
$html += "<section class='app'>"
|
||
$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><th class='small'>Adapter</th><th class='small'>Address</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><td class='small'>$($rl.AdapterName)</td><td class='small'>$($rl.Address)</td></tr>"
|
||
}
|
||
$html += "</table>"
|
||
}
|
||
|
||
if ($app.SendPorts.Count -gt 0) {
|
||
$html += "<h4>Send Ports</h4><table><tr><th>Name</th><th>Status</th><th class='small'>PrimaryTransportType</th><th class='small'>PrimaryTransportAddress</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><td class='small'>$($sp.PrimaryTransportType)</td><td class='small'>$($sp.PrimaryTransportAddress)</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>"
|
||
}
|
||
$html += "</section>"
|
||
}
|
||
|
||
# Host Instances
|
||
$html += "<section>"
|
||
$html += "<h3>Host Instances</h3>"
|
||
$html += "<table><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>"
|
||
$html += "</section>"
|
||
|
||
$html += New-Html-Footer
|
||
$html -join "`n" | Out-File ($OutputFile + '.html') -Encoding UTF8
|
||
|
||
# Console Output (wie Original)
|
||
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
|
||
}
|
||
}
|
||
|
||
# Host Instances (Konsole)
|
||
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
|
||
}
|
||
|
||
Write-Host "Snapshot erstellt: $(Resolve-Path $OutputFile)" -ForegroundColor Green
|
||
}
|
||
|
||
# ============================= COMPARE =============================
|
||
function Compare-BizTalkSnapshots {
|
||
param(
|
||
[string]$Before,
|
||
[string]$After,
|
||
[string]$HtmlReport = 'BizTalkDiff.html'
|
||
)
|
||
|
||
Write-Log "Vergleiche Snapshots: $Before ↔ $After"
|
||
|
||
$b = Get-Content $Before | ConvertFrom-Json
|
||
$a = Get-Content $After | ConvertFrom-Json
|
||
|
||
$diff = New-Object System.Collections.ArrayList
|
||
|
||
function AddDiff {
|
||
param($app,$type,$name,$before,$after)
|
||
$bf = Format-Status -ArtifactType $type -Value $before
|
||
$af = Format-Status -ArtifactType $type -Value $after
|
||
[void]$diff.Add([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 ========================
|
||
$beforeHI = $b.HostInstances
|
||
$afterHI = $a.HostInstances
|
||
|
||
$hiDiff_New=@()
|
||
$hiDiff_Removed=@()
|
||
$hiDiff_Changed=@()
|
||
|
||
foreach ($hi in $afterHI) {
|
||
if (-not ($beforeHI | Where-Object {$_.InstanceName -eq $hi.InstanceName})) {
|
||
$hiDiff_New += $hi
|
||
}
|
||
}
|
||
|
||
foreach ($hi in $beforeHI) {
|
||
if (-not ($afterHI | Where-Object {$_.InstanceName -eq $hi.InstanceName})) {
|
||
$hiDiff_Removed += $hi
|
||
}
|
||
}
|
||
|
||
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
|
||
# HostInstance CSVs separat bereitstellen
|
||
$hiDiff_New | Export-Csv -Path 'diff.hosts.new.csv' -NoTypeInformation -Encoding UTF8
|
||
$hiDiff_Removed | Export-Csv -Path 'diff.hosts.removed.csv' -NoTypeInformation -Encoding UTF8
|
||
$hiDiff_Changed | Export-Csv -Path 'diff.hosts.changed.csv' -NoTypeInformation -Encoding UTF8
|
||
|
||
# ======================== HTML DIFF EXPORT ========================
|
||
$html=@()
|
||
$html+= New-Html-Header -Title 'BizTalk Differences'
|
||
|
||
# Summary-Karten
|
||
$artCount = ($diff | Measure-Object).Count
|
||
$hiNew = ($hiDiff_New | Measure-Object).Count
|
||
$hiRem = ($hiDiff_Removed | Measure-Object).Count
|
||
$hiChg = ($hiDiff_Changed | Measure-Object).Count
|
||
|
||
$html += "<section class='summary'>"
|
||
$html += "<div class='card'><div>Artifact Differences</div><div class='value'>$artCount</div></div>"
|
||
$html += "<div class='card'><div>Hosts New</div><div class='value'>$hiNew</div></div>"
|
||
$html += "<div class='card'><div>Hosts Removed</div><div class='value'>$hiRem</div></div>"
|
||
$html += "<div class='card'><div>Hosts Changed</div><div class='value'>$hiChg</div></div>"
|
||
$html += "</section>"
|
||
|
||
# Artifacts
|
||
$html += "<section>"
|
||
$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>"
|
||
$html += "</section>"
|
||
|
||
# HostInstance: NEW
|
||
$html += "<section>"
|
||
$html += "<h3>Host Instance Changes</h3>"
|
||
$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>"
|
||
|
||
# HostInstance: REMOVED
|
||
$html += "<h4>Removed</h4><table><tr><th>Instance</th><th>Host</th><th>Server</th><th>Last 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>"
|
||
|
||
# HostInstance: 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 += "</section>"
|
||
|
||
$html += New-Html-Footer
|
||
$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 =============================
|
||
try {
|
||
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 = Invoke-BizTalkWmiQuery -ClassName 'MSBTS_SendPort' -ComputerName $Server | Select-Object -First 3 Name,Status
|
||
Write-Host "WMI/CIM OK:" -ForegroundColor Green
|
||
$sp | Format-Table
|
||
} catch {
|
||
Write-Host "ERROR: $($_.Exception.Message)" -ForegroundColor Red
|
||
}
|
||
exit
|
||
}
|
||
|
||
if ($Shutdown) {
|
||
Get-BizTalkSnapshot -OutputFile $StateFile -Server $Server -MgmtDbServer $MgmtDbServer -MgmtDbName $MgmtDbName
|
||
$snapshot = Get-BizTalkStateFile -Path $StateFile
|
||
Stop-BizTalkRuntimeFromSnapshot -Snapshot $snapshot -Server $Server -WaitTimeoutSec $WaitTimeoutSec -PollIntervalSec $PollIntervalSec -PlanOnly:$PlanOnly
|
||
if (-not $PlanOnly) {
|
||
Get-BizTalkSnapshot -OutputFile 'shutdown-after.json' -Server $Server -MgmtDbServer $MgmtDbServer -MgmtDbName $MgmtDbName
|
||
}
|
||
exit
|
||
}
|
||
|
||
if ($Restore) {
|
||
$snapshot = Get-BizTalkStateFile -Path $StateFile
|
||
Restore-BizTalkRuntimeFromSnapshot -Snapshot $snapshot -Server $Server -WaitTimeoutSec $WaitTimeoutSec -PollIntervalSec $PollIntervalSec -PlanOnly:$PlanOnly
|
||
if (-not $PlanOnly) {
|
||
Get-BizTalkSnapshot -OutputFile 'restore-after.json' -Server $Server -MgmtDbServer $MgmtDbServer -MgmtDbName $MgmtDbName
|
||
}
|
||
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 " .\\BizTalkPlatformManagementTool.ps1 -Before"
|
||
Write-Host " .\\BizTalkPlatformManagementTool.ps1 -After"
|
||
Write-Host " .\\BizTalkPlatformManagementTool.ps1 -Compare"
|
||
Write-Host " .\\BizTalkPlatformManagementTool.ps1 -Diag"
|
||
Write-Host " .\\BizTalkPlatformManagementTool.ps1 -Shutdown [-PlanOnly] [-StateFile before.json]"
|
||
Write-Host " .\\BizTalkPlatformManagementTool.ps1 -Restore [-PlanOnly] [-StateFile before.json]"
|
||
}
|
||
catch {
|
||
Write-Log "Fatal: $($_.Exception.Message)" 'ERROR'
|
||
throw
|
||
}
|