namespace ZA.CoreService.ESBCertificateManager.Models; /// /// Verbindungskonfiguration für eine Progress Sonic ESB Management Instanz. /// Name = Verbindungs-Alias (hier oft gleich ContainerName, z.B. "DE-Test"). /// DomainName = Sonic-Domain (z.B. "proalpha-test"). /// Container wird in KnownContainers / DeploymentTarget.ContainerName geführt (z.B. "DE-Test"). /// public sealed class SonicConnection { /// /// Alias der Verbindung; referenziert von . /// public required string Name { get; init; } /// Sonic-Domain, z.B. "proalpha-test" (nicht der Containername). public required string DomainName { get; init; } /// /// Domain-Manager-/Broker-URL, z.B. "tcp://dekun-painwbdet:13070". /// public required string ConnectionUrl { get; init; } /// Sonic SMC / Domain Manager Login (nicht für WinRM). public required string Username { get; init; } /// Sonic SMC / Domain Manager Passwort (nicht für WinRM). public required string Password { get; init; } /// /// Windows-Konto für WinRM (Invoke-Command -Credential). /// Leer = aktueller Prozess-Benutzer ohne explizite Credentials. /// Nicht mit / (Sonic SMC) verwechseln. /// public string WinRmUsername { get; init; } = string.Empty; /// Windows-Passwort für WinRM; nur relevant wenn gesetzt ist. public string WinRmPassword { get; init; } = string.Empty; /// /// MfApi = Sonic MF Management Runtime API über ConnectionUrl + SMC-Logins (Standard); /// WinRm / LocalCmd = optionaler Fallback über stopcontainer/startcontainer; /// HttpApi = REST (falls vorhanden). /// public SonicManagementMode ManagementMode { get; init; } = SonicManagementMode.MfApi; /// /// Effektiver Modus: bei WinRm und lokalem ConnectionUrl-Host wird LocalCmd erzwungen. /// MfApi bleibt unverändert (nutzt Domain-Manager-Verbindung). /// public SonicManagementMode EffectiveManagementMode => ManagementMode == SonicManagementMode.WinRm && IsConnectionHostLocal(ConnectionUrl) ? SonicManagementMode.LocalCmd : ManagementMode; /// True wenn WinRm konfiguriert war, aber wegen lokalem Host auf LocalCmd umgestellt wurde. public bool IsLocalCmdAutoForced => ManagementMode == SonicManagementMode.WinRm && EffectiveManagementMode == SonicManagementMode.LocalCmd; /// Anzeigetext für UI (inkl. Auto-Erkennung). public string ManagementModeDisplay => IsLocalCmdAutoForced ? "LocalCmd (Host lokal erkannt)" : EffectiveManagementMode switch { SonicManagementMode.MfApi => "MfApi (Sonic Domain Manager)", _ => EffectiveManagementMode.ToString() }; /// /// True wenn der Host aus ConnectionUrl dieser Maschine entspricht /// (localhost / 127.0.0.1 / ::1 / Computername). /// public static bool IsConnectionHostLocal(string? connectionUrl) { if (string.IsNullOrWhiteSpace(connectionUrl)) { return false; } string host; try { host = new Uri(connectionUrl).Host; } catch { host = connectionUrl.Trim(); } if (string.IsNullOrWhiteSpace(host)) { return false; } if (host.Equals("localhost", StringComparison.OrdinalIgnoreCase) || host.Equals("127.0.0.1", StringComparison.OrdinalIgnoreCase) || host.Equals("::1", StringComparison.OrdinalIgnoreCase) || host.Equals("[::1]", StringComparison.OrdinalIgnoreCase)) { return true; } string machine = Environment.MachineName; if (host.Equals(machine, StringComparison.OrdinalIgnoreCase) || host.StartsWith(machine + ".", StringComparison.OrdinalIgnoreCase)) { return true; } try { string dnsName = System.Net.Dns.GetHostName(); if (!string.IsNullOrWhiteSpace(dnsName) && (host.Equals(dnsName, StringComparison.OrdinalIgnoreCase) || host.StartsWith(dnsName + ".", StringComparison.OrdinalIgnoreCase))) { return true; } } catch { // DNS optional } return false; } /// Sonic-Installationsroot, z.B. "C:\Sonic\MQ10.0" (enthält lib\*.jar für MfApi). public string SonicHome { get; init; } = @"C:\Sonic\MQ10.0"; /// /// Optionaler Pfad zu Sonic-Client-JARs für MfApi (mgmt_client.jar etc.). /// Leer = SonicHome\lib. /// public string MfClientLibPath { get; init; } = string.Empty; /// /// Container-Namen (kurz), z.B. ["DE-Test"]. Nicht die Domain. /// public List KnownContainers { get; init; } = []; public int ManagementHttpPort { get; init; } = 8080; public string ApiBasePath { get; init; } = "/api/v1"; public string ContainerListPath { get; init; } = string.Empty; public string ContainerRestartPath { get; init; } = string.Empty; public string ContainerStopPath { get; init; } = string.Empty; public string ContainerStartPath { get; init; } = string.Empty; public int WinRmPort { get; init; } = 5985; public string WinRmRestartScript { get; init; } = string.Empty; public string WinRmContainerListScript { get; init; } = string.Empty; public string WinRmXapiImportScript { get; init; } = string.Empty; public int TimeoutSeconds { get; init; } = 120; public int PostRestartDelaySeconds { get; init; } = 20; public string ResolveRestartScript() => string.IsNullOrWhiteSpace(WinRmRestartScript) ? DefaultRestartScript : WinRmRestartScript; public string ResolveContainerListScript() => string.IsNullOrWhiteSpace(WinRmContainerListScript) ? DefaultContainerListScript : WinRmContainerListScript; /// /// Offizieller MF-Container-Neustart laut Aurea CX Messenger Doku: /// SonicHome\bin\stopcontainer.bat Domain.Container /// SonicHome\bin\startcontainer.bat Domain.Container /// Danach Prozess-Verifikation (alte PIDs weg, neue PIDs da). /// public const string DefaultRestartScript = """ $ErrorActionPreference = 'Stop' $sonicHome = '{sonicHome}' $domain = '{domain}' $container = '{container}' $bin = Join-Path $sonicHome 'bin' $stopBat = Join-Path $bin 'stopcontainer.bat' $startBat = Join-Path $bin 'startcontainer.bat' # Kurzname = Container (z.B. DE-Test), Full = Domain.Container (z.B. proalpha-test.DE-Test) $shortName = if ($container -like '*.*') { ($container -split '\.', 2)[1] } else { $container } $fullName = if ($container -like '*.*') { $container } else { "$domain.$container" } $markers = @($shortName, $fullName, "$domain.$shortName") | Select-Object -Unique function Get-ContainerPids { $pids = @() Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object { $cmd = $_.CommandLine if (-not $cmd) { return $false } if ($_.Name -notmatch 'java|javaw|sonic') { return $false } foreach ($m in $markers) { if ($cmd -like "*$m*") { return $true } } return $false } | ForEach-Object { $pids += [int]$_.ProcessId } return @($pids | Select-Object -Unique) } function Wait-PidsGone([int[]]$pids, [int]$seconds) { $deadline = (Get-Date).AddSeconds($seconds) while ((Get-Date) -lt $deadline) { $alive = @($pids | Where-Object { Get-Process -Id $_ -ErrorAction SilentlyContinue }) if ($alive.Count -eq 0) { return $true } Start-Sleep -Seconds 2 } return $false } function Wait-NewPids([int[]]$oldPids, [int]$seconds) { $deadline = (Get-Date).AddSeconds($seconds) while ((Get-Date) -lt $deadline) { $now = @(Get-ContainerPids) $fresh = @($now | Where-Object { $oldPids -notcontains $_ }) if ($fresh.Count -gt 0) { return ,$fresh } Start-Sleep -Seconds 2 } return ,@() } function Invoke-ContainerBat([string]$bat, [string]$name) { if (-not (Test-Path -LiteralPath $bat)) { throw "Sonic BAT fehlt: $bat (SonicHome pruefen)" } $arg = '/c "' + $bat + '" "' + $name + '"' $p = Start-Process -FilePath 'cmd.exe' -ArgumentList $arg -Wait -PassThru -NoNewWindow return [int]$p.ExitCode } Write-Output "INFO:Domain=$domain Container=$shortName Full=$fullName" Write-Output "INFO:RestartVia=stopcontainer/startcontainer (MF Container)" $before = @(Get-ContainerPids) Write-Output "INFO:PIDsBefore=$($before -join ',')" if ($before.Count -eq 0) { Write-Output "WARN:No running Java/Sonic process for '$shortName' - starting anyway" } # 1) Offiziell: stopcontainer.bat Domain.Container (danach Kurzname als Fallback) $stopOk = $false foreach ($n in @($fullName, $shortName)) { Write-Output "INFO:stopcontainer $n" $code = Invoke-ContainerBat -bat $stopBat -name $n Write-Output "INFO:stopcontainer ExitCode=$code Name=$n" if ($code -eq 0) { $stopOk = $true; break } } if (-not $stopOk) { Write-Output "WARN:stopcontainer non-zero; will force-stop remaining PIDs if any" } Start-Sleep -Seconds 3 $still = @(Get-ContainerPids) # 2) Force-Stop falls BAT den Prozess nicht beendet (sonst kein echter Restart in SMC) if ($still.Count -gt 0) { Write-Output "INFO:Force-Stop PIDs=$($still -join ',')" foreach ($procId in $still) { Stop-Process -Id $procId -Force -ErrorAction SilentlyContinue } } if ($before.Count -gt 0) { if (-not (Wait-PidsGone -pids $before -seconds 45)) { throw "Container process still running after stop (PIDs=$($before -join ','))" } Write-Output "INFO:Old PIDs gone" } Start-Sleep -Seconds 3 # 3) Offiziell: startcontainer.bat Domain.Container $started = $false foreach ($n in @($fullName, $shortName)) { Write-Output "INFO:startcontainer $n" $code = Invoke-ContainerBat -bat $startBat -name $n Write-Output "INFO:startcontainer ExitCode=$code Name=$n" if ($code -eq 0) { $started = $true; break } } if (-not $started) { throw "startcontainer failed for $fullName / $shortName" } $after = @(Wait-NewPids -oldPids $before -seconds 60) if ($after.Count -eq 0) { $any = @(Get-ContainerPids) if ($any.Count -eq 0) { throw "After start no process for container '$shortName'. Check SMC / SonicHome / ContainerName." } Write-Output "INFO:PIDsAfter=$($any -join ',')" } else { Write-Output "INFO:PIDsAfter=$($after -join ',')" } Write-Output "OK:ContainerRestartVerified Domain=$domain Container=$shortName" """; public const string DefaultContainerListScript = """ $ErrorActionPreference = 'Continue' $sonicHome = '{sonicHome}' $domain = '{domain}' $names = New-Object System.Collections.Generic.List[string] function Add-Name([string]$n) { if ([string]::IsNullOrWhiteSpace($n)) { return } $n = $n.Trim() if (-not $names.Contains($n)) { [void]$names.Add($n) } } if (-not (Test-Path -LiteralPath $sonicHome)) { Write-Output "WARN:SonicHomeNichtGefunden:$sonicHome" } else { Write-Output "INFO:SonicHomeOk:$sonicHome Domain=$domain" Get-ChildItem -LiteralPath $sonicHome -Directory -ErrorAction SilentlyContinue | ForEach-Object { if ($_.Name -like '*.cache') { Add-Name ($_.Name -replace '\.cache$', '') } Get-ChildItem -LiteralPath $_.FullName -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -like '*.cache' } | ForEach-Object { Add-Name ($_.Name -replace '\.cache$', '') } } Get-ChildItem -LiteralPath $sonicHome -Recurse -Filter 'container.xml' -File -ErrorAction SilentlyContinue | Select-Object -First 40 | ForEach-Object { Add-Name (Split-Path $_.DirectoryName -Leaf) } } Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -match 'sonic|mf\.framework|container\.xml' } | ForEach-Object { if ($_.CommandLine -match '([A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+)\.cache') { Add-Name $Matches[1] } elseif ($_.CommandLine -match '\\([A-Za-z0-9_\-]+)\\container\.xml') { Add-Name $Matches[1] } } # Kurzform Domain.Container → Container foreach ($n in @($names.ToArray())) { if ($n -like '*.*') { $parts = $n -split '\.', 2 if ($parts.Count -eq 2) { Add-Name $parts[1] } } } if ($names.Count -eq 0) { Write-Output "WARN:KeineContainerGefunden Domain=$domain SonicHome=$sonicHome" } $names | Sort-Object -Unique """; } public enum SonicManagementMode { HttpApi = 0, WinRm = 1, LocalCmd = 2, /// Sonic MF Management API über Domain-Manager (ConnectionUrl + SMC-Credentials). MfApi = 3 }