Fix app startup and enable Sonic ESB restart via remote CMD.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,7 +7,20 @@ public static class AppSettingsLoader
|
||||
{
|
||||
public static AppSettings Load()
|
||||
{
|
||||
string basePath = AppContext.BaseDirectory;
|
||||
// Zuerst Output-Verzeichnis, dann aktuelles Arbeitsverzeichnis (z.B. VS Debug).
|
||||
string basePath = Directory.Exists(AppContext.BaseDirectory)
|
||||
? AppContext.BaseDirectory
|
||||
: Directory.GetCurrentDirectory();
|
||||
|
||||
string settingsPath = Path.Combine(basePath, "appsettings.json");
|
||||
if (!File.Exists(settingsPath))
|
||||
{
|
||||
string cwdPath = Path.Combine(Directory.GetCurrentDirectory(), "appsettings.json");
|
||||
if (File.Exists(cwdPath))
|
||||
{
|
||||
basePath = Directory.GetCurrentDirectory();
|
||||
}
|
||||
}
|
||||
|
||||
IConfigurationRoot configuration = new ConfigurationBuilder()
|
||||
.SetBasePath(basePath)
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
[
|
||||
{
|
||||
"Id": 1,
|
||||
"Name": "ESB Local A",
|
||||
"Environment": "DEV",
|
||||
"Name": "DE-Test ESB Container",
|
||||
"Environment": "TEST",
|
||||
"IsActive": true,
|
||||
"TargetDirectory": "DeploySandbox/target-a",
|
||||
"TargetDirectory": "DeploySandbox/target-esb",
|
||||
"CertificateFileName": "esb-cert.cer",
|
||||
"ContainerName": "sonic-container-a",
|
||||
"RestartType": "None",
|
||||
"ContainerName": "ESB",
|
||||
"RestartType": "SonicContainer",
|
||||
"RestartCommand": "",
|
||||
"RestartArguments": "",
|
||||
"RestartTimeoutSeconds": 30,
|
||||
"SonicConnectionName": "",
|
||||
"RestartTimeoutSeconds": 90,
|
||||
"SonicConnectionName": "DE-Test",
|
||||
"XapiSourcePath": "",
|
||||
"TlsHost": "",
|
||||
"TlsPort": null,
|
||||
@@ -21,15 +21,15 @@
|
||||
},
|
||||
{
|
||||
"Id": 2,
|
||||
"Name": "ESB Local B",
|
||||
"Name": "Lokaler CMD-Test (Echo)",
|
||||
"Environment": "DEV",
|
||||
"IsActive": true,
|
||||
"TargetDirectory": "DeploySandbox/target-b",
|
||||
"TargetDirectory": "DeploySandbox/target-cmd",
|
||||
"CertificateFileName": "esb-cert.cer",
|
||||
"ContainerName": "sonic-container-b",
|
||||
"ContainerName": "",
|
||||
"RestartType": "Command",
|
||||
"RestartCommand": "cmd.exe",
|
||||
"RestartArguments": "/c echo Restart simulated for sonic-container-b",
|
||||
"RestartArguments": "/c echo Neustart-Simulation OK",
|
||||
"RestartTimeoutSeconds": 15,
|
||||
"SonicConnectionName": "",
|
||||
"XapiSourcePath": "",
|
||||
@@ -38,45 +38,5 @@
|
||||
"TlsServerName": "",
|
||||
"ExpectedFingerprint": null,
|
||||
"SortOrder": 20
|
||||
},
|
||||
{
|
||||
"Id": 3,
|
||||
"Name": "DE-Test Container A (Sonic)",
|
||||
"Environment": "TEST",
|
||||
"IsActive": false,
|
||||
"TargetDirectory": "DeploySandbox/target-c",
|
||||
"CertificateFileName": "esb-cert.cer",
|
||||
"ContainerName": "sonic-container-a",
|
||||
"RestartType": "SonicContainer",
|
||||
"RestartCommand": "",
|
||||
"RestartArguments": "",
|
||||
"RestartTimeoutSeconds": 60,
|
||||
"SonicConnectionName": "DE-Test",
|
||||
"XapiSourcePath": "",
|
||||
"TlsHost": "dekun-painwbdet",
|
||||
"TlsPort": 443,
|
||||
"TlsServerName": "esb-test.firma.local",
|
||||
"ExpectedFingerprint": null,
|
||||
"SortOrder": 30
|
||||
},
|
||||
{
|
||||
"Id": 4,
|
||||
"Name": "DE-Test Container B (Sonic + XApi)",
|
||||
"Environment": "TEST",
|
||||
"IsActive": false,
|
||||
"TargetDirectory": "DeploySandbox/target-d",
|
||||
"CertificateFileName": "esb-cert.cer",
|
||||
"ContainerName": "sonic-container-b",
|
||||
"RestartType": "SonicContainerWithXapi",
|
||||
"RestartCommand": "",
|
||||
"RestartArguments": "",
|
||||
"RestartTimeoutSeconds": 60,
|
||||
"SonicConnectionName": "DE-Test",
|
||||
"XapiSourcePath": "Assets/xapi-resources.xml",
|
||||
"TlsHost": "dekun-painwbdet",
|
||||
"TlsPort": 443,
|
||||
"TlsServerName": "esb-test.firma.local",
|
||||
"ExpectedFingerprint": null,
|
||||
"SortOrder": 40
|
||||
}
|
||||
]
|
||||
|
||||
@@ -52,6 +52,7 @@ namespace ZA.CoreService.ESBCertificateManager
|
||||
private Button btnClose = null!;
|
||||
private Button btnValidate = null!;
|
||||
private Button btnDeploy = null!;
|
||||
private Button btnRestartOnly = null!;
|
||||
private Button btnCancelRun = null!;
|
||||
|
||||
private bool isMaximized = false;
|
||||
@@ -1231,6 +1232,11 @@ namespace ZA.CoreService.ESBCertificateManager
|
||||
btnValidate.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
||||
btnValidate.Click += async (_, _) => await RunValidationAsync();
|
||||
|
||||
btnRestartOnly = CreateSecondaryButton("ESB neu starten");
|
||||
btnRestartOnly.Size = new Size(150, 42);
|
||||
btnRestartOnly.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
||||
btnRestartOnly.Click += async (_, _) => await RunRestartOnlyAsync();
|
||||
|
||||
btnDeploy = CreatePrimaryButton("Deployment starten");
|
||||
btnDeploy.Size = new Size(170, 42);
|
||||
btnDeploy.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
||||
@@ -1238,12 +1244,14 @@ namespace ZA.CoreService.ESBCertificateManager
|
||||
|
||||
panel.Controls.Add(btnCancelRun);
|
||||
panel.Controls.Add(btnValidate);
|
||||
panel.Controls.Add(btnRestartOnly);
|
||||
panel.Controls.Add(btnDeploy);
|
||||
|
||||
panel.Resize += (_, _) =>
|
||||
{
|
||||
btnDeploy.Left = panel.Width - btnDeploy.Width;
|
||||
btnValidate.Left = btnDeploy.Left - btnValidate.Width - 12;
|
||||
btnRestartOnly.Left = btnDeploy.Left - btnRestartOnly.Width - 12;
|
||||
btnValidate.Left = btnRestartOnly.Left - btnValidate.Width - 12;
|
||||
btnCancelRun.Left = btnValidate.Left - btnCancelRun.Width - 12;
|
||||
};
|
||||
|
||||
@@ -1377,11 +1385,20 @@ namespace ZA.CoreService.ESBCertificateManager
|
||||
btnCancelRun.Enabled = _isOperationRunning;
|
||||
btnValidate.Enabled = idle && hasCertificate && hasTargets;
|
||||
btnDeploy.Enabled = idle && hasCertificate && hasSelection;
|
||||
if (btnRestartOnly is not null)
|
||||
{
|
||||
btnRestartOnly.Enabled = idle && hasSelection;
|
||||
}
|
||||
|
||||
if (dgvTargets is not null)
|
||||
{
|
||||
dgvTargets.Enabled = idle;
|
||||
}
|
||||
|
||||
if (btnLoadFromSonic is not null)
|
||||
{
|
||||
btnLoadFromSonic.Enabled = idle && _sonicDiscovery.HasConnections;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetStatus(string message, bool isError)
|
||||
@@ -1464,6 +1481,86 @@ namespace ZA.CoreService.ESBCertificateManager
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task RunRestartOnlyAsync()
|
||||
{
|
||||
if (_isOperationRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<DeploymentTarget> selected = GetSelectedTargets();
|
||||
PreflightValidationResult preflight = _orchestrator.ValidateRestartOnly(selected);
|
||||
|
||||
if (!preflight.IsValid)
|
||||
{
|
||||
ShowPreflightIssues(
|
||||
preflight,
|
||||
"Neustart blockiert – Vorabprüfung fehlgeschlagen.",
|
||||
"ESB neu starten");
|
||||
return;
|
||||
}
|
||||
|
||||
DialogResult confirm = MessageBox.Show(
|
||||
this,
|
||||
$"ESB/Container für {selected.Count} Ziel(e) wirklich neu starten?\n\n" +
|
||||
"Es wird stopcontainer/startcontainer über die Sonic-Verbindung ausgeführt\n" +
|
||||
"(WinRM Remote-CMD oder LocalCmd laut appsettings).",
|
||||
"ESB neu starten",
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Warning);
|
||||
|
||||
if (confirm != DialogResult.Yes)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_runCts?.Dispose();
|
||||
_runCts = new CancellationTokenSource();
|
||||
SetOperationRunning(true);
|
||||
UpdateToNextStep(3);
|
||||
SetStatus($"ESB-Neustart läuft für {selected.Count} Ziel(e)…", isError: false);
|
||||
|
||||
Progress<TargetProgressUpdate> progress = new(update =>
|
||||
{
|
||||
SetTargetRowStatus(update.TargetId, update.StatusText);
|
||||
SetStatus(update.StatusText, isError: update.SuccessHint == false);
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
DeploymentRunResult runResult = await _orchestrator.RestartOnlyAsync(
|
||||
selected,
|
||||
progress,
|
||||
_runCts.Token);
|
||||
|
||||
foreach (TargetStepResult targetResult in runResult.TargetResults)
|
||||
{
|
||||
SetTargetRowStatus(targetResult.TargetId, targetResult.StatusText);
|
||||
}
|
||||
|
||||
SetStatus(
|
||||
runResult.OverallSuccess
|
||||
? $"Neustart erfolgreich ({runResult.TargetResults.Count} Ziel(e))."
|
||||
: "Neustart mit Fehlern beendet. Details in Status-Spalte / Log.",
|
||||
isError: !runResult.OverallSuccess);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
SetStatus("Neustart abgebrochen.", isError: true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SetStatus($"Neustart fehlgeschlagen: {ex.Message}", isError: true);
|
||||
MessageBox.Show(this, ex.Message, "ESB neu starten", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
SetOperationRunning(false);
|
||||
_runCts?.Dispose();
|
||||
_runCts = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RunDeploymentAsync()
|
||||
{
|
||||
if (_isOperationRunning || _loadedCertificateInfo is null)
|
||||
@@ -1680,9 +1777,6 @@ namespace ZA.CoreService.ESBCertificateManager
|
||||
|
||||
lblSonicStatus = new Label
|
||||
{
|
||||
Text = _sonicDiscovery.HasConnections
|
||||
? "Keine Verbindung konfiguriert"
|
||||
: "Keine Sonic-Verbindung konfiguriert",
|
||||
ForeColor = MutedTextColor,
|
||||
Font = new Font("Segoe UI", 8.5f),
|
||||
AutoSize = true,
|
||||
@@ -1696,7 +1790,10 @@ namespace ZA.CoreService.ESBCertificateManager
|
||||
}
|
||||
else
|
||||
{
|
||||
lblSonicStatus.Text = $"{_sonicDiscovery.Connections.Count} Verbindung(en) konfiguriert – noch nicht geladen";
|
||||
SonicConnection first = _sonicDiscovery.Connections[0];
|
||||
lblSonicStatus.Text =
|
||||
$"{_sonicDiscovery.Connections.Count} Verb. | {first.ManagementMode} | {first.DomainName} | SonicHome={first.SonicHome}";
|
||||
lblSonicStatus.ForeColor = GreenColor;
|
||||
}
|
||||
|
||||
card.Controls.Add(sonicLabel);
|
||||
|
||||
@@ -15,93 +15,148 @@ public sealed class SonicConnection
|
||||
|
||||
/// <summary>
|
||||
/// Sonic-Broker-/Management-URL im Sonic-Format, z.B. "tcp://dekun-painwbdet:13070".
|
||||
/// Der Hostname wird daraus extrahiert (für HTTP-Modus: Basis-URL, für WinRM-Modus: Zielrechner).
|
||||
/// Der Hostname wird daraus extrahiert (WinRM-Ziel / HTTP-Basis).
|
||||
/// </summary>
|
||||
public required string ConnectionUrl { get; init; }
|
||||
|
||||
/// <summary>Benutzername für die Management-Konsole.</summary>
|
||||
/// <summary>Benutzername für WinRM / Management-Konsole.</summary>
|
||||
public required string Username { get; init; }
|
||||
|
||||
/// <summary>Passwort für die Management-Konsole.</summary>
|
||||
/// <summary>Passwort für WinRM / Management-Konsole.</summary>
|
||||
public required string Password { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Management-Modus: HttpApi (REST) oder WinRm (PowerShell Remoting).
|
||||
/// Standard: WinRm – da Sonic 10.x kein HTTP REST API bereitstellt.
|
||||
/// Management-Modus:
|
||||
/// - WinRm: PowerShell Remoting auf dem Sonic-Server (Standard)
|
||||
/// - LocalCmd: CMD/PowerShell lokal (zum Testen direkt auf dem Sonic-PC)
|
||||
/// - HttpApi: REST, falls vorhanden
|
||||
/// </summary>
|
||||
public SonicManagementMode ManagementMode { get; init; } = SonicManagementMode.WinRm;
|
||||
|
||||
/// <summary>
|
||||
/// Installationsroot von Sonic MQ / Management Console, z.B. "C:\Sonic\MQ10.0".
|
||||
/// Wird für Default-Scripts (stopcontainer/startcontainer) benötigt.
|
||||
/// </summary>
|
||||
public string SonicHome { get; init; } = @"C:\Sonic\MQ10.0";
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// HTTP REST API (ManagementMode = HttpApi)
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
/// <summary>HTTP-Port der Sonic Management Console REST-API (Standard: 8080).</summary>
|
||||
public int ManagementHttpPort { get; init; } = 8080;
|
||||
|
||||
/// <summary>Präfix für alle REST-API-Pfade (Standard: "/api/v1").</summary>
|
||||
public string ApiBasePath { get; init; } = "/api/v1";
|
||||
|
||||
/// <summary>Konfigurierter Pfad zum Auflisten aller Container. Leer = automatische Erkennung.</summary>
|
||||
public string ContainerListPath { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>Konfigurierter Pfad zum Neustarten. Platzhalter: {domain}, {container}.</summary>
|
||||
public string ContainerRestartPath { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>Konfigurierter Pfad zum Stoppen. Platzhalter: {domain}, {container}.</summary>
|
||||
public string ContainerStopPath { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>Konfigurierter Pfad zum Starten. Platzhalter: {domain}, {container}.</summary>
|
||||
public string ContainerStartPath { get; init; } = string.Empty;
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// WinRM / PowerShell Remoting (ManagementMode = WinRm)
|
||||
// WinRM / LocalCmd
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// WinRM-Port auf dem Zielrechner (Standard: 5985 = HTTP, 5986 = HTTPS).
|
||||
/// </summary>
|
||||
public int WinRmPort { get; init; } = 5985;
|
||||
|
||||
/// <summary>
|
||||
/// PowerShell-Scriptblock zum Neustarten eines Containers.
|
||||
/// Platzhalter: {container} = Container-Name (nicht enkodiert), {domain} = Domain-Name.
|
||||
/// Beispiel für Windows-Service: "Restart-Service -Name 'CT-ZADBService' -Force"
|
||||
/// Beispiel für Sonic-Skript: "& 'C:\\Sonic\\bin\\stopContainer.bat' '{container}'; Start-Sleep 5; & 'C:\\Sonic\\bin\\startContainer.bat' '{container}'"
|
||||
/// PowerShell-Script zum Neustarten. Leer = Default über Sonic bin\stop/startcontainer.bat.
|
||||
/// Platzhalter: {container}, {domain}, {sonicHome}
|
||||
/// </summary>
|
||||
public string WinRmRestartScript { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// PowerShell-Scriptblock zum Auflisten aller Container der Domain.
|
||||
/// Ausgabe: eine Zeile pro Container-Name.
|
||||
/// Beispiel: "Get-Service -DisplayName 'Sonic*' | Select-Object -ExpandProperty Name"
|
||||
/// PowerShell-Script zum Auflisten der Container. Leer = Default (Services + SonicHome).
|
||||
/// </summary>
|
||||
public string WinRmContainerListScript { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// PowerShell-Scriptblock für XApi-Import.
|
||||
/// Platzhalter: {container}, {xapiPath}.
|
||||
/// </summary>
|
||||
public string WinRmXapiImportScript { get; init; } = string.Empty;
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Gemeinsame Einstellungen
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
/// <summary>Timeout in Sekunden für einzelne API-/Script-Aufrufe (Standard: 60).</summary>
|
||||
public int TimeoutSeconds { get; init; } = 60;
|
||||
|
||||
/// <summary>Wartezeit in Sekunden nach einem Container-Neustart (Standard: 15).</summary>
|
||||
public int PostRestartDelaySeconds { get; init; } = 15;
|
||||
|
||||
/// <summary>Effektives Restart-Script inkl. Default, wenn leer.</summary>
|
||||
public string ResolveRestartScript()
|
||||
=> string.IsNullOrWhiteSpace(WinRmRestartScript)
|
||||
? DefaultRestartScript
|
||||
: WinRmRestartScript;
|
||||
|
||||
/// <summary>Effektives List-Script inkl. Default, wenn leer.</summary>
|
||||
public string ResolveContainerListScript()
|
||||
=> string.IsNullOrWhiteSpace(WinRmContainerListScript)
|
||||
? DefaultContainerListScript
|
||||
: WinRmContainerListScript;
|
||||
|
||||
/// <summary>
|
||||
/// Default: Sonic Management Console Tools per CMD auf dem Ziel-PC.
|
||||
/// stopcontainer.bat / startcontainer.bat unter SonicHome\bin.
|
||||
/// </summary>
|
||||
public const string DefaultRestartScript =
|
||||
"""
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$sonicHome = '{sonicHome}'
|
||||
$domain = '{domain}'
|
||||
$container = '{container}'
|
||||
$bin = Join-Path $sonicHome 'bin'
|
||||
$stop = Join-Path $bin 'stopcontainer.bat'
|
||||
$start = Join-Path $bin 'startcontainer.bat'
|
||||
$fullName = if ($container -like '*.*') { $container } else { "$domain.$container" }
|
||||
|
||||
if (-not (Test-Path -LiteralPath $stop)) {
|
||||
throw "Sonic stopcontainer.bat nicht gefunden: $stop (SonicHome prüfen)"
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $start)) {
|
||||
throw "Sonic startcontainer.bat nicht gefunden: $start (SonicHome prüfen)"
|
||||
}
|
||||
|
||||
Write-Output "STOP $fullName via $stop"
|
||||
$stopProc = Start-Process -FilePath 'cmd.exe' -ArgumentList @('/c', "`"$stop`" `"$fullName`"") -Wait -PassThru -NoNewWindow
|
||||
if ($stopProc.ExitCode -ne 0) {
|
||||
Write-Warning "stopcontainer ExitCode=$($stopProc.ExitCode) – starte trotzdem neu"
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds 5
|
||||
|
||||
Write-Output "START $fullName via $start"
|
||||
$startProc = Start-Process -FilePath 'cmd.exe' -ArgumentList @('/c', "`"$start`" `"$fullName`"") -Wait -PassThru -NoNewWindow
|
||||
if ($startProc.ExitCode -ne 0) {
|
||||
throw "startcontainer fehlgeschlagen, ExitCode=$($startProc.ExitCode)"
|
||||
}
|
||||
|
||||
Write-Output "Neustart ok: $fullName"
|
||||
""";
|
||||
|
||||
public const string DefaultContainerListScript =
|
||||
"""
|
||||
$ErrorActionPreference = 'Continue'
|
||||
$sonicHome = '{sonicHome}'
|
||||
$names = @()
|
||||
|
||||
# Windows-Dienste mit Sonic/ESB im Namen
|
||||
$names += @(Get-Service -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.DisplayName -match 'Sonic|ESB|MQ' -or $_.Name -match 'Sonic|ESB|MQ' } |
|
||||
Select-Object -ExpandProperty Name)
|
||||
|
||||
# Container-Cache-Ordner unter SonicHome (Domain.Container.cache)
|
||||
if (Test-Path -LiteralPath $sonicHome) {
|
||||
$names += @(Get-ChildItem -LiteralPath $sonicHome -Directory -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.Name -like '*.cache' } |
|
||||
ForEach-Object { $_.Name -replace '\.cache$', '' })
|
||||
}
|
||||
|
||||
$names | Where-Object { $_ } | Sort-Object -Unique
|
||||
""";
|
||||
}
|
||||
|
||||
public enum SonicManagementMode
|
||||
{
|
||||
/// <summary>HTTP REST API (wenn vom Sonic-Server bereitgestellt).</summary>
|
||||
HttpApi,
|
||||
HttpApi = 0,
|
||||
|
||||
/// <summary>
|
||||
/// PowerShell Remoting (WinRM) – Standard für Sonic 10.x auf Windows.
|
||||
/// Führt konfigurierte Scriptblöcke via Invoke-Command auf dem Sonic-Server aus.
|
||||
/// PowerShell Remoting (WinRM) – führt Scripts auf dem Sonic-Server aus.
|
||||
/// </summary>
|
||||
WinRm
|
||||
WinRm = 1,
|
||||
|
||||
/// <summary>
|
||||
/// CMD/PowerShell lokal auf diesem PC – zum Testen direkt auf dem Sonic-Rechner.
|
||||
/// </summary>
|
||||
LocalCmd = 2
|
||||
}
|
||||
|
||||
@@ -26,6 +26,89 @@ public sealed class DeploymentOrchestrator
|
||||
IReadOnlyList<DeploymentTarget> selectedTargets)
|
||||
=> _preflightValidator.Validate(certificatePath, certificateInfo, selectedTargets);
|
||||
|
||||
public PreflightValidationResult ValidateRestartOnly(IReadOnlyList<DeploymentTarget> selectedTargets)
|
||||
=> _preflightValidator.ValidateRestartOnly(selectedTargets);
|
||||
|
||||
/// <summary>
|
||||
/// Nur ESB-/Container-Neustart – ohne Zertifikatskopieren und ohne TLS-Probe.
|
||||
/// </summary>
|
||||
public async Task<DeploymentRunResult> RestartOnlyAsync(
|
||||
IReadOnlyList<DeploymentTarget> selectedTargets,
|
||||
IProgress<TargetProgressUpdate>? progress,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
DateTimeOffset startedAt = DateTimeOffset.Now;
|
||||
List<TargetStepResult> results = [];
|
||||
|
||||
using RunLogger logger = new(_settings.LogDirectory);
|
||||
logger.Write($"Neustart-only gestartet für {selectedTargets.Count} Ziel(e).");
|
||||
|
||||
foreach (DeploymentTarget target in selectedTargets)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
results.Add(await RestartTargetOnlyAsync(target, logger, progress, cancellationToken));
|
||||
}
|
||||
|
||||
DateTimeOffset finishedAt = DateTimeOffset.Now;
|
||||
DeploymentRunResult runResult = new()
|
||||
{
|
||||
TargetResults = results,
|
||||
StartedAt = startedAt,
|
||||
FinishedAt = finishedAt,
|
||||
CertificateFilePath = string.Empty,
|
||||
CertificateFingerprint = string.Empty
|
||||
};
|
||||
|
||||
logger.Write(
|
||||
$"Neustart-only beendet. Erfolg={runResult.OverallSuccess}; Dauer={(finishedAt - startedAt).TotalSeconds:F1}s; Log={logger.LogFilePath}");
|
||||
|
||||
try
|
||||
{
|
||||
await _sqlRunLogger.PersistRunResultAsync(runResult, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Write($"SQL-Protokollierung fehlgeschlagen (nicht kritisch): {ex.Message}");
|
||||
}
|
||||
|
||||
return runResult;
|
||||
}
|
||||
|
||||
private async Task<TargetStepResult> RestartTargetOnlyAsync(
|
||||
DeploymentTarget target,
|
||||
RunLogger logger,
|
||||
IProgress<TargetProgressUpdate>? progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
List<string> steps = [];
|
||||
DateTimeOffset targetStart = DateTimeOffset.Now;
|
||||
progress?.Report(new TargetProgressUpdate(target.Id, "Neustart…", false));
|
||||
|
||||
(bool restartOk, string restartStatus, string? restartDetail) =
|
||||
await _restartExecutor.ExecuteAsync(target, cancellationToken);
|
||||
|
||||
RecordStep(steps, logger, target.Name, "Restart", restartStatus, restartDetail);
|
||||
|
||||
TargetStepResult targetResult = new()
|
||||
{
|
||||
TargetId = target.Id,
|
||||
TargetName = target.Name,
|
||||
Success = restartOk,
|
||||
StatusText = restartStatus,
|
||||
Detail = restartDetail,
|
||||
Steps = steps,
|
||||
StartedAt = targetStart,
|
||||
FinishedAt = DateTimeOffset.Now,
|
||||
CopySucceeded = true,
|
||||
RestartSucceeded = restartOk,
|
||||
TlsSucceeded = true,
|
||||
ObservedFingerprint = null
|
||||
};
|
||||
|
||||
progress?.Report(new TargetProgressUpdate(target.Id, restartStatus, restartOk));
|
||||
return targetResult;
|
||||
}
|
||||
|
||||
public async Task<DeploymentRunResult> RunAsync(
|
||||
string certificatePath,
|
||||
CertificateInfo certificateInfo,
|
||||
|
||||
@@ -32,13 +32,45 @@ public sealed class PreflightValidator
|
||||
|
||||
foreach (DeploymentTarget target in selectedTargets)
|
||||
{
|
||||
ValidateTarget(result, target);
|
||||
ValidateTarget(result, target, requireDeployPaths: true);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void ValidateTarget(PreflightValidationResult result, DeploymentTarget target)
|
||||
/// <summary>
|
||||
/// Vorabprüfung nur für ESB-Neustart (ohne Zertifikat / Kopierpfade).
|
||||
/// </summary>
|
||||
public PreflightValidationResult ValidateRestartOnly(IReadOnlyList<DeploymentTarget> selectedTargets)
|
||||
{
|
||||
PreflightValidationResult result = new();
|
||||
|
||||
if (selectedTargets.Count == 0)
|
||||
{
|
||||
AddIssue(result, "Bitte mindestens ein Ziel anhaken.");
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (DeploymentTarget target in selectedTargets)
|
||||
{
|
||||
if (target.RestartType is RestartType.None)
|
||||
{
|
||||
AddIssue(result, $"Ziel '{target.Name}': RestartType=None – kein Neustart konfiguriert.", target.Id);
|
||||
continue;
|
||||
}
|
||||
|
||||
ValidateTarget(result, target, requireDeployPaths: false);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void ValidateTarget(
|
||||
PreflightValidationResult result,
|
||||
DeploymentTarget target,
|
||||
bool requireDeployPaths)
|
||||
{
|
||||
if (requireDeployPaths)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(target.TargetDirectory))
|
||||
{
|
||||
@@ -49,6 +81,7 @@ public sealed class PreflightValidator
|
||||
{
|
||||
AddIssue(result, $"Ziel '{target.Name}': CertificateFileName fehlt.", target.Id);
|
||||
}
|
||||
}
|
||||
|
||||
if (target.RestartType == RestartType.Command
|
||||
&& string.IsNullOrWhiteSpace(target.RestartCommand))
|
||||
@@ -75,7 +108,7 @@ public sealed class PreflightValidator
|
||||
AddIssue(result, $"Ziel '{target.Name}': XapiSourcePath fehlt bei RestartType=SonicContainerWithXapi.", target.Id);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(target.TargetDirectory))
|
||||
if (!requireDeployPaths || string.IsNullOrWhiteSpace(target.TargetDirectory))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -9,19 +9,20 @@ namespace ZA.CoreService.ESBCertificateManager.Services;
|
||||
/// <summary>
|
||||
/// Verwaltet Sonic ESB Container über die Management Console.
|
||||
///
|
||||
/// Modus = WinRm (Standard, Sonic 10.x):
|
||||
/// PowerShell Remoting (Invoke-Command) auf dem Sonic-Server.
|
||||
/// Erfordert WinRM auf dem Zielrechner: Enable-PSRemoting -Force
|
||||
/// Modus = WinRm (Standard):
|
||||
/// PowerShell Remoting (Invoke-Command) → CMD stop/startcontainer auf dem Sonic-Server.
|
||||
///
|
||||
/// Modus = LocalCmd:
|
||||
/// Dieselbe CMD/PowerShell-Logik lokal (Test direkt auf dem Sonic-PC).
|
||||
///
|
||||
/// Modus = HttpApi:
|
||||
/// HTTP REST API mit automatischer Pfad-Erkennung.
|
||||
/// Probiert: /mf/rest/v1, /api/v1, /sonic/management, /containers
|
||||
/// </summary>
|
||||
public sealed class SonicManagementClient : IDisposable
|
||||
{
|
||||
private readonly SonicConnection _connection;
|
||||
private readonly HttpClient? _http;
|
||||
private readonly WinRmExecutor? _winRm;
|
||||
private readonly WinRmExecutor? _scriptRunner;
|
||||
|
||||
// Gecachter HTTP-Basis-Pfad (nur im HttpApi-Modus)
|
||||
private string? _resolvedContainerBasePath;
|
||||
@@ -34,13 +35,16 @@ public sealed class SonicManagementClient : IDisposable
|
||||
"/containers"
|
||||
];
|
||||
|
||||
private bool UsesScripts =>
|
||||
_connection.ManagementMode is SonicManagementMode.WinRm or SonicManagementMode.LocalCmd;
|
||||
|
||||
public SonicManagementClient(SonicConnection connection)
|
||||
{
|
||||
_connection = connection;
|
||||
|
||||
if (connection.ManagementMode == SonicManagementMode.WinRm)
|
||||
if (UsesScripts)
|
||||
{
|
||||
_winRm = new WinRmExecutor(connection);
|
||||
_scriptRunner = new WinRmExecutor(connection);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -77,10 +81,13 @@ public sealed class SonicManagementClient : IDisposable
|
||||
public async Task<(bool Success, string? Error, string? ResolvedPath)> CheckConnectionAsync(
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_connection.ManagementMode == SonicManagementMode.WinRm)
|
||||
if (UsesScripts)
|
||||
{
|
||||
(bool ok, string? error) = await _winRm!.TestConnectionAsync(cancellationToken);
|
||||
return (ok, error, ok ? $"WinRM auf {ExtractHost(_connection.ConnectionUrl)}:{_connection.WinRmPort}" : null);
|
||||
(bool ok, string? error) = await _scriptRunner!.TestConnectionAsync(cancellationToken);
|
||||
string modeLabel = _connection.ManagementMode == SonicManagementMode.LocalCmd
|
||||
? $"LocalCmd (SonicHome={_connection.SonicHome})"
|
||||
: $"WinRM auf {ExtractHost(_connection.ConnectionUrl)}:{_connection.WinRmPort}";
|
||||
return (ok, error, ok ? modeLabel : null);
|
||||
}
|
||||
|
||||
try
|
||||
@@ -116,9 +123,9 @@ public sealed class SonicManagementClient : IDisposable
|
||||
public async Task<(bool Success, IReadOnlyList<string> ContainerNames, string? Error)> GetContainersAsync(
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_connection.ManagementMode == SonicManagementMode.WinRm)
|
||||
if (UsesScripts)
|
||||
{
|
||||
return await GetContainersViaWinRmAsync(cancellationToken);
|
||||
return await GetContainersViaScriptAsync(cancellationToken);
|
||||
}
|
||||
|
||||
return await GetContainersViaHttpAsync(cancellationToken);
|
||||
@@ -131,9 +138,9 @@ public sealed class SonicManagementClient : IDisposable
|
||||
string containerName,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_connection.ManagementMode == SonicManagementMode.WinRm)
|
||||
if (UsesScripts)
|
||||
{
|
||||
return await RestartViaWinRmAsync(containerName, cancellationToken);
|
||||
return await RestartViaScriptAsync(containerName, cancellationToken);
|
||||
}
|
||||
|
||||
return await RestartViaHttpAsync(containerName, cancellationToken);
|
||||
@@ -147,83 +154,72 @@ public sealed class SonicManagementClient : IDisposable
|
||||
string xapiSourcePath,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_connection.ManagementMode == SonicManagementMode.WinRm)
|
||||
if (UsesScripts)
|
||||
{
|
||||
return await ImportXapiViaWinRmAsync(containerName, xapiSourcePath, cancellationToken);
|
||||
return await ImportXapiViaScriptAsync(containerName, xapiSourcePath, cancellationToken);
|
||||
}
|
||||
|
||||
return await ImportXapiViaHttpAsync(containerName, xapiSourcePath, cancellationToken);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// WinRM-Implementierungen
|
||||
// Script-Implementierungen (WinRM / LocalCmd)
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
private async Task<(bool, IReadOnlyList<string>, string?)> GetContainersViaWinRmAsync(
|
||||
private async Task<(bool, IReadOnlyList<string>, string?)> GetContainersViaScriptAsync(
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_connection.WinRmContainerListScript))
|
||||
{
|
||||
return (false, [],
|
||||
"WinRmContainerListScript ist nicht konfiguriert.\n" +
|
||||
"Beispiel: \"Get-Service -DisplayName 'Sonic*' | Select-Object -ExpandProperty DisplayName\"");
|
||||
}
|
||||
|
||||
string script = WinRmExecutor.ApplyScriptTemplate(
|
||||
_connection.WinRmContainerListScript,
|
||||
_connection.ResolveContainerListScript(),
|
||||
containerName: string.Empty,
|
||||
domainName: _connection.DomainName);
|
||||
domainName: _connection.DomainName,
|
||||
sonicHome: _connection.SonicHome);
|
||||
|
||||
(bool ok, string? output, string? error) =
|
||||
await _winRm!.RunScriptAsync(script, cancellationToken);
|
||||
await _scriptRunner!.RunScriptAsync(script, cancellationToken);
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
return (false, [], $"WinRM Container-Liste fehlgeschlagen: {error}");
|
||||
return (false, [], $"Container-Liste fehlgeschlagen ({_connection.ManagementMode}): {error}");
|
||||
}
|
||||
|
||||
List<string> names = (output ?? string.Empty)
|
||||
.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||
.Where(l => l.Length > 0)
|
||||
.Where(l => l.Length > 0 && !l.StartsWith("WARNING", StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
return (true, names, null);
|
||||
}
|
||||
|
||||
private async Task<(bool, string, string?)> RestartViaWinRmAsync(
|
||||
private async Task<(bool, string, string?)> RestartViaScriptAsync(
|
||||
string containerName, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_connection.WinRmRestartScript))
|
||||
{
|
||||
return (false, "Neustart fehlgeschlagen",
|
||||
"WinRmRestartScript ist nicht konfiguriert.\n" +
|
||||
"Beispiel für Windows-Service: \"Restart-Service -Name 'CT-ZADBService' -Force\"\n" +
|
||||
"Platzhalter {container} wird durch den Container-Namen ersetzt.");
|
||||
}
|
||||
|
||||
string script = WinRmExecutor.ApplyScriptTemplate(
|
||||
_connection.WinRmRestartScript,
|
||||
_connection.ResolveRestartScript(),
|
||||
containerName,
|
||||
_connection.DomainName);
|
||||
_connection.DomainName,
|
||||
_connection.SonicHome);
|
||||
|
||||
(bool ok, string? output, string? error) =
|
||||
await _winRm!.RunScriptAsync(script, cancellationToken);
|
||||
await _scriptRunner!.RunScriptAsync(script, cancellationToken);
|
||||
|
||||
string mode = _connection.ManagementMode.ToString();
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
return (false, "Neustart fehlgeschlagen (WinRM)",
|
||||
$"Fehler: {error}\nAusgabe: {output}");
|
||||
return (false, $"Neustart fehlgeschlagen ({mode})",
|
||||
$"Fehler: {error}\nAusgabe: {output}\nSonicHome={_connection.SonicHome}");
|
||||
}
|
||||
|
||||
await Task.Delay(
|
||||
TimeSpan.FromSeconds(Math.Clamp(_connection.PostRestartDelaySeconds, 0, 120)),
|
||||
cancellationToken);
|
||||
|
||||
return (true, "Container neugestartet (WinRM)",
|
||||
return (true, $"ESB-Container neugestartet ({mode})",
|
||||
$"Ausgabe: {output ?? "(keine)"}");
|
||||
}
|
||||
|
||||
private async Task<(bool, string, string?)> ImportXapiViaWinRmAsync(
|
||||
private async Task<(bool, string, string?)> ImportXapiViaScriptAsync(
|
||||
string containerName, string xapiSourcePath, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_connection.WinRmXapiImportScript))
|
||||
@@ -236,21 +232,22 @@ public sealed class SonicManagementClient : IDisposable
|
||||
_connection.WinRmXapiImportScript,
|
||||
containerName,
|
||||
_connection.DomainName,
|
||||
_connection.SonicHome,
|
||||
xapiSourcePath);
|
||||
|
||||
(bool importOk, string? importOut, string? importErr) =
|
||||
await _winRm!.RunScriptAsync(script, cancellationToken);
|
||||
await _scriptRunner!.RunScriptAsync(script, cancellationToken);
|
||||
|
||||
if (!importOk)
|
||||
{
|
||||
return (false, "XApi-Import fehlgeschlagen (WinRM)", importErr ?? importOut);
|
||||
return (false, "XApi-Import fehlgeschlagen", importErr ?? importOut);
|
||||
}
|
||||
|
||||
(bool restartOk, string restartStatus, string? restartDetail) =
|
||||
await RestartViaWinRmAsync(containerName, cancellationToken);
|
||||
await RestartViaScriptAsync(containerName, cancellationToken);
|
||||
|
||||
return restartOk
|
||||
? (true, "XApi importiert + Container neugestartet (WinRM)",
|
||||
? (true, "XApi importiert + Container neugestartet",
|
||||
$"Import: {importOut} | Restart: {restartDetail}")
|
||||
: (false, restartStatus, restartDetail);
|
||||
}
|
||||
@@ -473,8 +470,14 @@ public sealed class SonicManagementClient : IDisposable
|
||||
? root.EnumerateArray()
|
||||
: root.ValueKind == JsonValueKind.Object
|
||||
? new[] { "containers", "data", "items", "result" }
|
||||
.Where(root.TryGetProperty)
|
||||
.SelectMany(k => { root.TryGetProperty(k, out JsonElement a); return a.EnumerateArray(); })
|
||||
.Where(k => root.TryGetProperty(k, out _))
|
||||
.SelectMany(k =>
|
||||
{
|
||||
root.TryGetProperty(k, out JsonElement a);
|
||||
return a.ValueKind == JsonValueKind.Array
|
||||
? a.EnumerateArray()
|
||||
: Enumerable.Empty<JsonElement>();
|
||||
})
|
||||
: [];
|
||||
|
||||
foreach (JsonElement el in elements)
|
||||
|
||||
@@ -5,14 +5,7 @@ using ZA.CoreService.ESBCertificateManager.Models;
|
||||
namespace ZA.CoreService.ESBCertificateManager.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Führt PowerShell-Befehle via WinRM (Invoke-Command) auf dem Sonic-Server aus.
|
||||
///
|
||||
/// Voraussetzungen auf dem Zielrechner:
|
||||
/// - WinRM muss aktiviert sein: Enable-PSRemoting -Force
|
||||
/// - Ausführungsrichtlinie: Set-ExecutionPolicy RemoteSigned
|
||||
///
|
||||
/// Voraussetzungen auf dem App-Rechner (einmalig, als Admin):
|
||||
/// - Set-Item WSMan:\localhost\Client\TrustedHosts -Value "dekun-painwbdet"
|
||||
/// Führt PowerShell-Befehle lokal oder via WinRM (Invoke-Command) auf dem Sonic-Server aus.
|
||||
/// </summary>
|
||||
public sealed class WinRmExecutor
|
||||
{
|
||||
@@ -23,15 +16,20 @@ public sealed class WinRmExecutor
|
||||
_connection = connection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prüft die WinRM-Konnektivität und ob der Sonic-Server per TCP erreichbar ist.
|
||||
/// </summary>
|
||||
public async Task<(bool Success, string? Error)> TestConnectionAsync(
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_connection.ManagementMode == SonicManagementMode.LocalCmd)
|
||||
{
|
||||
(bool ok, string? output, string? error) = await RunScriptAsync(
|
||||
"$env:COMPUTERNAME", cancellationToken);
|
||||
return ok
|
||||
? (true, null)
|
||||
: (false, $"LocalCmd fehlgeschlagen: {error ?? output}");
|
||||
}
|
||||
|
||||
string host = ExtractHost(_connection.ConnectionUrl);
|
||||
|
||||
// TCP-Ping auf WinRM-Port
|
||||
try
|
||||
{
|
||||
using System.Net.Sockets.TcpClient tcp = new();
|
||||
@@ -44,30 +42,25 @@ public sealed class WinRmExecutor
|
||||
{
|
||||
return (false,
|
||||
$"WinRM-Port {_connection.WinRmPort} auf '{host}' nicht erreichbar: {ex.Message}\n" +
|
||||
$"Auf dem Zielrechner ausführen: Enable-PSRemoting -Force");
|
||||
"Auf dem Zielrechner: Enable-PSRemoting -Force\n" +
|
||||
"Oder ManagementMode=LocalCmd setzen und die App auf dem Sonic-PC starten.");
|
||||
}
|
||||
|
||||
// Kurztest: Hostname zurückgeben
|
||||
(bool ok, string? output, string? error) = await RunScriptAsync(
|
||||
(bool sessionOk, _, string? sessionError) = await RunScriptAsync(
|
||||
"$env:COMPUTERNAME", cancellationToken);
|
||||
|
||||
return ok
|
||||
return sessionOk
|
||||
? (true, null)
|
||||
: (false, $"WinRM-Verbindung fehlgeschlagen: {error}");
|
||||
: (false, $"WinRM-Verbindung fehlgeschlagen: {sessionError}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Führt einen Scriptblock auf dem Remote-Rechner aus und gibt Stdout zurück.
|
||||
/// </summary>
|
||||
public async Task<(bool Success, string? Output, string? Error)> RunScriptAsync(
|
||||
string scriptBlock,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
string host = ExtractHost(_connection.ConnectionUrl);
|
||||
|
||||
// Passwort als SecureString – bleibt im PowerShell-Prozess, wird nicht als Argument übergeben
|
||||
// Stattdessen: Scriptblock über stdin senden
|
||||
string fullScript = BuildScript(host, scriptBlock);
|
||||
string fullScript = _connection.ManagementMode == SonicManagementMode.LocalCmd
|
||||
? BuildLocalScript(scriptBlock)
|
||||
: BuildRemoteScript(ExtractHost(_connection.ConnectionUrl), scriptBlock);
|
||||
|
||||
ProcessStartInfo psi = new()
|
||||
{
|
||||
@@ -88,8 +81,7 @@ public sealed class WinRmExecutor
|
||||
return (false, null, "PowerShell-Prozess konnte nicht gestartet werden.");
|
||||
}
|
||||
|
||||
// Skript über stdin – Credentials gehen NICHT als sichtbares Argument durch
|
||||
await process.StandardInput.WriteAsync(fullScript);
|
||||
await process.StandardInput.WriteAsync(fullScript.AsMemory(), cancellationToken);
|
||||
process.StandardInput.Close();
|
||||
|
||||
using CancellationTokenSource timeoutCts =
|
||||
@@ -106,7 +98,7 @@ public sealed class WinRmExecutor
|
||||
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try { process.Kill(entireProcessTree: true); } catch { /* ignore */ }
|
||||
return (false, null, $"WinRM-Ausführung Timeout nach {_connection.TimeoutSeconds}s.");
|
||||
return (false, null, $"Ausführung Timeout nach {_connection.TimeoutSeconds}s.");
|
||||
}
|
||||
|
||||
string stdout = (await stdoutTask).Trim();
|
||||
@@ -121,9 +113,11 @@ public sealed class WinRmExecutor
|
||||
stderr.Length > 0 ? Truncate(stderr) : $"PowerShell ExitCode={process.ExitCode}");
|
||||
}
|
||||
|
||||
private string BuildScript(string host, string scriptBlock)
|
||||
private static string BuildLocalScript(string scriptBlock)
|
||||
=> "$ErrorActionPreference = 'Stop'\n" + scriptBlock;
|
||||
|
||||
private string BuildRemoteScript(string host, string scriptBlock)
|
||||
{
|
||||
// Passwort über Variable, nicht als Argument – verhindert Sichtbarkeit in Prozessliste
|
||||
string escapedPwd = _connection.Password.Replace("'", "''");
|
||||
string escapedUser = _connection.Username.Replace("'", "''");
|
||||
string escapedHost = host.Replace("'", "''");
|
||||
@@ -137,21 +131,17 @@ public sealed class WinRmExecutor
|
||||
"} -ErrorAction Stop";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ersetzt Platzhalter in einem konfigurierten WinRM-Script.
|
||||
/// {container} → Container-Name (einfache Hochkommas werden verdoppelt)
|
||||
/// {domain} → Domain-Name
|
||||
/// {xapiPath} → Pfad zur XApi-Quelldatei
|
||||
/// </summary>
|
||||
public static string ApplyScriptTemplate(string template, string containerName,
|
||||
string domainName = "", string xapiPath = "")
|
||||
public static string ApplyScriptTemplate(
|
||||
string template,
|
||||
string containerName,
|
||||
string domainName = "",
|
||||
string sonicHome = "",
|
||||
string xapiPath = "")
|
||||
=> template
|
||||
.Replace("{container}", containerName.Replace("'", "''"),
|
||||
StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("{domain}", domainName.Replace("'", "''"),
|
||||
StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("{xapiPath}", xapiPath.Replace("'", "''"),
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
.Replace("{container}", containerName.Replace("'", "''"), StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("{domain}", domainName.Replace("'", "''"), StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("{sonicHome}", sonicHome.Replace("'", "''"), StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("{xapiPath}", xapiPath.Replace("'", "''"), StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private static string ExtractHost(string connectionUrl)
|
||||
{
|
||||
|
||||
@@ -10,17 +10,22 @@
|
||||
"Name": "DE-Test",
|
||||
"DomainName": "proalpha-test",
|
||||
"ConnectionUrl": "tcp://dekun-painwbdet:13070",
|
||||
"ManagementHttpPort": 8080,
|
||||
"ApiBasePath": "/api/v1",
|
||||
"Username": "Administrator",
|
||||
"Password": "Administrator",
|
||||
"TimeoutSeconds": 30,
|
||||
"ManagementMode": "WinRm",
|
||||
"SonicHome": "C:\\Sonic\\MQ10.0",
|
||||
"WinRmPort": 5985,
|
||||
"TimeoutSeconds": 90,
|
||||
"PostRestartDelaySeconds": 15,
|
||||
|
||||
"ManagementHttpPort": 8080,
|
||||
"ApiBasePath": "/api/v1",
|
||||
"ContainerListPath": "",
|
||||
"ContainerRestartPath": "",
|
||||
"ContainerStopPath": "",
|
||||
"ContainerStartPath": ""
|
||||
"ContainerStartPath": "",
|
||||
"WinRmRestartScript": "",
|
||||
"WinRmContainerListScript": "",
|
||||
"WinRmXapiImportScript": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user