diff --git a/ZA.CoreService.ESBCertificateManager/Form1.cs b/ZA.CoreService.ESBCertificateManager/Form1.cs
index ede2546..4b1ecbe 100644
--- a/ZA.CoreService.ESBCertificateManager/Form1.cs
+++ b/ZA.CoreService.ESBCertificateManager/Form1.cs
@@ -1827,17 +1827,18 @@ namespace ZA.CoreService.ESBCertificateManager
.FirstOrDefault(c => string.Equals(c.Name, selectedName, StringComparison.OrdinalIgnoreCase))
?? _sonicDiscovery.Connections[0];
- (string sonicHome, string? javaExe) = new SonicMfApiExecutor(conn).ResolveRuntimePaths();
- string javaDisplay = string.IsNullOrWhiteSpace(javaExe) ? "java=?" : javaExe;
+ string sonicHome = string.IsNullOrWhiteSpace(conn.SonicHome) ? "(leer)" : conn.SonicHome.Trim();
+ (bool binOk, string? binErr, _) = new SonicBinRestartExecutor(conn).Probe();
+ string binDisplay = binOk ? "stop/startcontainer=ok" : $"bin=fehlt ({binErr})";
string baseText =
- $"{conn.ManagementModeDisplay} | SonicHome={sonicHome} | {javaDisplay}";
+ $"{conn.ManagementModeDisplay} | SonicHome={sonicHome} | {binDisplay}";
if (!string.IsNullOrWhiteSpace(suffix))
{
baseText = $"{suffix} | {baseText}";
}
lblSonicStatus.Text = baseText;
- lblSonicStatus.ForeColor = string.IsNullOrWhiteSpace(javaExe) ? GoldColor : GreenColor;
+ lblSonicStatus.ForeColor = binOk ? GreenColor : GoldColor;
}
private static string TruncateStatus(string? text, int max = 120)
diff --git a/ZA.CoreService.ESBCertificateManager/Models/SonicConnection.cs b/ZA.CoreService.ESBCertificateManager/Models/SonicConnection.cs
index e85fadc..cd58cff 100644
--- a/ZA.CoreService.ESBCertificateManager/Models/SonicConnection.cs
+++ b/ZA.CoreService.ESBCertificateManager/Models/SonicConnection.cs
@@ -42,7 +42,11 @@ public sealed class SonicConnection
/// WinRm / LocalCmd = optionaler Fallback über stopcontainer/startcontainer;
/// HttpApi = REST (falls vorhanden).
///
- public SonicManagementMode ManagementMode { get; init; } = SonicManagementMode.MfApi;
+ ///
+ /// Standard: LocalCmd = stopcontainer/startcontainer unter SonicHome (laut Sonic-Doku, ohne extra Java).
+ /// Optional: MfApi (Domain Manager via Java-Client) oder WinRm.
+ ///
+ public SonicManagementMode ManagementMode { get; init; } = SonicManagementMode.LocalCmd;
///
/// Effektiver Modus: bei WinRm und lokalem ConnectionUrl-Host wird LocalCmd erzwungen.
diff --git a/ZA.CoreService.ESBCertificateManager/Services/SonicBinRestartExecutor.cs b/ZA.CoreService.ESBCertificateManager/Services/SonicBinRestartExecutor.cs
new file mode 100644
index 0000000..8c7b04c
--- /dev/null
+++ b/ZA.CoreService.ESBCertificateManager/Services/SonicBinRestartExecutor.cs
@@ -0,0 +1,150 @@
+using System.Diagnostics;
+using System.Text;
+using ZA.CoreService.ESBCertificateManager.Models;
+
+namespace ZA.CoreService.ESBCertificateManager.Services;
+
+///
+/// Neustart über die offiziellen Sonic-Bin-Scripts (laut Doku):
+/// SonicHome\bin\stopcontainer.bat Domain.Container
+/// SonicHome\bin\startcontainer.bat Domain.Container
+/// Die Scripts setzen selbst JAVA_HOME – kein separates System-Java nötig.
+///
+public sealed class SonicBinRestartExecutor
+{
+ private readonly SonicConnection _connection;
+
+ public SonicBinRestartExecutor(SonicConnection connection)
+ {
+ _connection = connection;
+ }
+
+ public (bool Ok, string? Error, string? Detail) Probe()
+ {
+ string sonicHome = _connection.SonicHome?.Trim() ?? string.Empty;
+ if (string.IsNullOrWhiteSpace(sonicHome))
+ {
+ return (false, "SonicHome ist leer – in appsettings setzen.", null);
+ }
+
+ if (!Directory.Exists(sonicHome))
+ {
+ return (false, $"SonicHome existiert nicht: '{sonicHome}'", null);
+ }
+
+ string stop = Path.Combine(sonicHome, "bin", "stopcontainer.bat");
+ string start = Path.Combine(sonicHome, "bin", "startcontainer.bat");
+ if (!File.Exists(stop) || !File.Exists(start))
+ {
+ return (false,
+ $"stopcontainer.bat/startcontainer.bat fehlen unter '{Path.Combine(sonicHome, "bin")}'.",
+ $"stop={File.Exists(stop)}; start={File.Exists(start)}");
+ }
+
+ return (true, null, $"SonicHome={sonicHome}");
+ }
+
+ public async Task<(bool Success, string Status, string? Detail)> RestartAsync(
+ string containerName,
+ CancellationToken cancellationToken = default)
+ {
+ (bool probeOk, string? probeError, string? probeDetail) = Probe();
+ if (!probeOk)
+ {
+ return (false, "Neustart fehlgeschlagen (SonicBin)",
+ $"{probeError}\n{probeDetail}");
+ }
+
+ string sonicHome = _connection.SonicHome.Trim();
+ string bin = Path.Combine(sonicHome, "bin");
+ string stopBat = Path.Combine(bin, "stopcontainer.bat");
+ string startBat = Path.Combine(bin, "startcontainer.bat");
+
+ string shortName = containerName.Contains('.')
+ ? containerName[(containerName.IndexOf('.') + 1)..]
+ : containerName;
+ string fullName = containerName.Contains('.')
+ ? containerName
+ : $"{_connection.DomainName}.{containerName}";
+
+ StringBuilder log = new();
+ log.AppendLine($"SonicHome={sonicHome}");
+ log.AppendLine($"Container={fullName} (kurz={shortName})");
+
+ (bool stopOk, string stopOut, string stopErr, int stopCode) =
+ await RunBatAsync(stopBat, fullName, bin, cancellationToken);
+ log.AppendLine($"STOP ExitCode={stopCode}");
+ if (stopOut.Length > 0) log.AppendLine("STOP out: " + Truncate(stopOut));
+ if (stopErr.Length > 0) log.AppendLine("STOP err: " + Truncate(stopErr));
+
+ // stopcontainer kann ExitCode!=0 liefern wenn Container schon down – trotzdem starten
+ await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
+
+ (bool startOk, string startOut, string startErr, int startCode) =
+ await RunBatAsync(startBat, fullName, bin, cancellationToken);
+ log.AppendLine($"START ExitCode={startCode}");
+ if (startOut.Length > 0) log.AppendLine("START out: " + Truncate(startOut));
+ if (startErr.Length > 0) log.AppendLine("START err: " + Truncate(startErr));
+
+ if (!startOk || startCode != 0)
+ {
+ // Fallback: Kurzname ohne Domain
+ if (!string.Equals(shortName, fullName, StringComparison.OrdinalIgnoreCase))
+ {
+ log.AppendLine($"Retry START mit Kurzname '{shortName}'…");
+ (startOk, startOut, startErr, startCode) =
+ await RunBatAsync(startBat, shortName, bin, cancellationToken);
+ log.AppendLine($"START(short) ExitCode={startCode}");
+ if (startOut.Length > 0) log.AppendLine("START out: " + Truncate(startOut));
+ if (startErr.Length > 0) log.AppendLine("START err: " + Truncate(startErr));
+ }
+ }
+
+ await Task.Delay(
+ TimeSpan.FromSeconds(Math.Clamp(_connection.PostRestartDelaySeconds, 0, 120)),
+ cancellationToken);
+
+ if (startCode != 0)
+ {
+ return (false, "Neustart fehlgeschlagen (SonicBin)",
+ $"startcontainer ExitCode={startCode} (stop ExitCode={stopCode}).\n{log}");
+ }
+
+ return (true, $"Container '{fullName}' neugestartet (SonicBin)", log.ToString());
+ }
+
+ private static async Task<(bool Started, string StdOut, string StdErr, int ExitCode)> RunBatAsync(
+ string batPath,
+ string argument,
+ string workingDirectory,
+ CancellationToken cancellationToken)
+ {
+ ProcessStartInfo psi = new()
+ {
+ FileName = "cmd.exe",
+ Arguments = $"/c \"\"{batPath}\" \"{argument}\"\"",
+ WorkingDirectory = workingDirectory,
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ CreateNoWindow = true,
+ StandardOutputEncoding = Encoding.UTF8,
+ StandardErrorEncoding = Encoding.UTF8
+ };
+
+ using Process process = new() { StartInfo = psi };
+ if (!process.Start())
+ {
+ return (false, string.Empty, "cmd.exe konnte nicht gestartet werden.", -1);
+ }
+
+ Task stdoutTask = process.StandardOutput.ReadToEndAsync(cancellationToken);
+ Task stderrTask = process.StandardError.ReadToEndAsync(cancellationToken);
+
+ await process.WaitForExitAsync(cancellationToken);
+ return (true, (await stdoutTask).Trim(), (await stderrTask).Trim(), process.ExitCode);
+ }
+
+ private static string Truncate(string value, int max = 500)
+ => value.Length <= max ? value : value[..max] + "…";
+}
diff --git a/ZA.CoreService.ESBCertificateManager/Services/SonicContainerDiscovery.cs b/ZA.CoreService.ESBCertificateManager/Services/SonicContainerDiscovery.cs
index edcdb81..d03fe4b 100644
--- a/ZA.CoreService.ESBCertificateManager/Services/SonicContainerDiscovery.cs
+++ b/ZA.CoreService.ESBCertificateManager/Services/SonicContainerDiscovery.cs
@@ -35,34 +35,47 @@ public sealed class SonicContainerDiscovery
using SonicManagementClient client = new(connection);
(bool reachable, string? pingError, string? resolvedPath) = await client.CheckConnectionAsync(cancellationToken);
- if (!reachable)
- {
- return SonicDiscoveryResult.Failed(connectionName,
- $"Management-Konsole nicht erreichbar: {pingError}");
- }
-
- (bool listOk, IReadOnlyList remoteNames, string? listError) =
- await client.GetContainersAsync(cancellationToken);
List diagnostics = [];
List remoteContainers = [];
+ bool listOk = false;
+ string? listError = null;
- foreach (string line in remoteNames)
+ if (!reachable)
{
- if (line.StartsWith("WARN:", StringComparison.OrdinalIgnoreCase)
- || line.StartsWith("INFO:", StringComparison.OrdinalIgnoreCase))
+ // Trotzdem KnownContainers nutzen – Neustart kann über SonicBin (stop/startcontainer) gehen.
+ diagnostics.Add($"Management-Ping: {pingError}");
+ if (connection.KnownContainers.Count == 0
+ && !knownTargets.Any(t =>
+ string.Equals(t.SonicConnectionName, connection.Name, StringComparison.OrdinalIgnoreCase)
+ && !string.IsNullOrWhiteSpace(t.ContainerName)))
{
- diagnostics.Add(line);
- continue;
+ return SonicDiscoveryResult.Failed(connectionName,
+ $"Management-Konsole nicht erreichbar: {pingError}");
+ }
+ }
+ else
+ {
+ (listOk, IReadOnlyList remoteNames, listError) =
+ await client.GetContainersAsync(cancellationToken);
+
+ foreach (string line in remoteNames)
+ {
+ if (line.StartsWith("WARN:", StringComparison.OrdinalIgnoreCase)
+ || line.StartsWith("INFO:", StringComparison.OrdinalIgnoreCase))
+ {
+ diagnostics.Add(line);
+ continue;
+ }
+
+ remoteContainers.Add(line);
}
- remoteContainers.Add(line);
- }
-
- if (!listOk)
- {
- diagnostics.Add($"Listen-Fehler: {listError}");
- remoteContainers = [];
+ if (!listOk)
+ {
+ diagnostics.Add($"Listen-Fehler: {listError}");
+ remoteContainers = [];
+ }
}
// Fallback: explizit in appsettings hinterlegte Container
diff --git a/ZA.CoreService.ESBCertificateManager/Services/SonicManagementClient.cs b/ZA.CoreService.ESBCertificateManager/Services/SonicManagementClient.cs
index 0160e5a..6144902 100644
--- a/ZA.CoreService.ESBCertificateManager/Services/SonicManagementClient.cs
+++ b/ZA.CoreService.ESBCertificateManager/Services/SonicManagementClient.cs
@@ -95,18 +95,49 @@ public sealed class SonicManagementClient : IDisposable
if (UsesMfApi)
{
(bool ok, string? error) = await _mfApi!.TestConnectionAsync(cancellationToken);
- string modeLabel =
- $"MfApi {_connection.ConnectionUrl} Domain={_connection.DomainName}";
- return (ok, error, ok ? modeLabel : null);
+ if (ok)
+ {
+ return (true, null,
+ $"MfApi {_connection.ConnectionUrl} Domain={_connection.DomainName}");
+ }
+
+ // Ohne lokales Java: Domain-Manager-TCP + KnownContainers reichen für Discovery.
+ bool javaMissing = error?.Contains("Java nicht gefunden", StringComparison.OrdinalIgnoreCase) == true;
+ if (javaMissing)
+ {
+ SonicBinRestartExecutor bin = new(_connection);
+ (bool binOk, _, string? binDetail) = bin.Probe();
+ if (binOk)
+ {
+ return (true, null,
+ $"SonicBin Fallback (kein Java für MfApi) {binDetail}; {_connection.ConnectionUrl}");
+ }
+
+ if (_connection.KnownContainers.Count > 0
+ || await IsTcpReachableAsync(_connection.ConnectionUrl, cancellationToken))
+ {
+ return (true, null,
+ $"MfApi ohne Java – KnownContainers/TCP; Hinweis: {Truncate(error ?? string.Empty, 180)}");
+ }
+ }
+
+ return (false, error, null);
}
if (UsesScripts)
{
+ if (Mode == SonicManagementMode.LocalCmd)
+ {
+ (bool binOk, string? binError, string? binDetail) = new SonicBinRestartExecutor(_connection).Probe();
+ return binOk
+ ? (true, null, $"LocalCmd/SonicBin {binDetail}")
+ : (false, binError, null);
+ }
+
(bool ok, string? error) = await _scriptRunner!.TestConnectionAsync(cancellationToken);
- string modeLabel = Mode == SonicManagementMode.LocalCmd
- ? $"LocalCmd (SonicHome={_connection.SonicHome})"
- : $"WinRM auf {ExtractHost(_connection.ConnectionUrl)}:{_connection.WinRmPort}";
- return (ok, error, ok ? modeLabel : null);
+ return ok
+ ? (true, null, $"WinRM auf {ExtractHost(_connection.ConnectionUrl)}:{_connection.WinRmPort}")
+ : (false, error, null);
}
try
@@ -169,6 +200,13 @@ public sealed class SonicManagementClient : IDisposable
if (UsesScripts)
{
+ // LocalCmd / WinRm: bevorzugter Doku-Weg über die .bat-Dateien (ohne extra Java-Suche).
+ if (Mode == SonicManagementMode.LocalCmd)
+ {
+ return await new SonicBinRestartExecutor(_connection)
+ .RestartAsync(containerName, cancellationToken);
+ }
+
return await RestartViaScriptAsync(containerName, cancellationToken);
}
@@ -260,22 +298,60 @@ public sealed class SonicManagementClient : IDisposable
(bool ok, string? output, string? error) =
await _mfApi!.RestartAsync(containerName, cancellationToken);
- if (!ok)
+ if (ok)
{
- return (false, "Neustart fehlgeschlagen (MfApi)",
- $"IAgentProxy.restart für '{containerName}' (Domain '{_connection.DomainName}') " +
- $"über {_connection.ConnectionUrl} fehlgeschlagen.\n" +
- $"Fehler: {error}\nAusgabe: {output}\n" +
- "Voraussetzungen: Domain Manager erreichbar, SMC-Logins korrekt, " +
- "JRE (JavaHome/SonicHome\\jre/JAVA_HOME) + Sonic-Client-JARs (SonicHome\\lib), Container online.");
+ await Task.Delay(
+ TimeSpan.FromSeconds(Math.Clamp(_connection.PostRestartDelaySeconds, 0, 120)),
+ cancellationToken);
+
+ return (true, $"Container '{containerName}' neugestartet (MfApi)",
+ $"Domain={_connection.DomainName}; ConnectionUrl={_connection.ConnectionUrl}\n{output}");
}
- await Task.Delay(
- TimeSpan.FromSeconds(Math.Clamp(_connection.PostRestartDelaySeconds, 0, 120)),
- cancellationToken);
+ // Kein Java / keine Client-JARs → offizieller Doku-Weg über stop/startcontainer.bat
+ bool softFail = error?.Contains("Java nicht gefunden", StringComparison.OrdinalIgnoreCase) == true
+ || error?.Contains("Client-JARs", StringComparison.OrdinalIgnoreCase) == true
+ || error?.Contains("lib", StringComparison.OrdinalIgnoreCase) == true;
- return (true, $"Container '{containerName}' neugestartet (MfApi)",
- $"Domain={_connection.DomainName}; ConnectionUrl={_connection.ConnectionUrl}\n{output}");
+ if (softFail)
+ {
+ SonicBinRestartExecutor bin = new(_connection);
+ (bool binOk, string binStatus, string? binDetail) =
+ await bin.RestartAsync(containerName, cancellationToken);
+ if (binOk)
+ {
+ return (true, binStatus,
+ $"MfApi nicht nutzbar ({Truncate(error ?? string.Empty, 160)}) – Fallback SonicBin:\n{binDetail}");
+ }
+
+ return (false, "Neustart fehlgeschlagen (MfApi→SonicBin)",
+ $"MfApi: {error}\n\nSonicBin-Fallback: {binDetail}\n" +
+ $"SonicHome='{_connection.SonicHome}' muss stopcontainer.bat enthalten.");
+ }
+
+ return (false, "Neustart fehlgeschlagen (MfApi)",
+ $"IAgentProxy.restart für '{containerName}' (Domain '{_connection.DomainName}') " +
+ $"über {_connection.ConnectionUrl} fehlgeschlagen.\n" +
+ $"Fehler: {error}\nAusgabe: {output}");
+ }
+
+ private static async Task IsTcpReachableAsync(string connectionUrl, CancellationToken cancellationToken)
+ {
+ try
+ {
+ Uri uri = new(connectionUrl);
+ string host = uri.Host;
+ int port = uri.Port > 0 ? uri.Port : 2506;
+ using System.Net.Sockets.TcpClient tcp = new();
+ using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+ cts.CancelAfter(TimeSpan.FromSeconds(5));
+ await tcp.ConnectAsync(host, port, cts.Token);
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
}
private static SonicConnection CloneAsLocalCmd(SonicConnection source)
diff --git a/ZA.CoreService.ESBCertificateManager/appsettings.json b/ZA.CoreService.ESBCertificateManager/appsettings.json
index 0e5a457..3b446f7 100644
--- a/ZA.CoreService.ESBCertificateManager/appsettings.json
+++ b/ZA.CoreService.ESBCertificateManager/appsettings.json
@@ -10,15 +10,9 @@
"Name": "DE-Test",
"DomainName": "proalpha-test",
"ConnectionUrl": "tcp://dekun-painwbdet:13070",
- // Sonic SMC / Domain Manager (für MfApi-Neustart):
"Username": "Administrator",
"Password": "Administrator",
- // Standard: Sonic MF Management API (IAgentProxy.restart) – kein WinRM.
- // Benötigt JRE + Client-JARs unter SonicHome\\lib (oder MfClientLibPath / MQ_HOME).
- // Java-Suche: JavaPath/JavaHome, JAVA_HOME/JRE_HOME, setenv.bat, rekursiv unter SonicHome
- // (nicht nur SonicHome\\jre\\bin\\java.exe), Registry, where.exe. Optional JavaHome/JavaPath setzen.
- // Optional-Fallback: "WinRm" / "LocalCmd" (stopcontainer/startcontainer).
- "ManagementMode": "MfApi",
+ "ManagementMode": "LocalCmd",
"SonicHome": "C:\\Sonic\\MQ10.0",
"JavaHome": "",
"JavaPath": "",
@@ -26,7 +20,6 @@
"KnownContainers": [
"DE-Test"
],
- // Nur für ManagementMode=WinRm relevant; leer lassen wenn nicht genutzt:
"WinRmUsername": "",
"WinRmPassword": "",
"WinRmPort": 5985,