Fix WinRM restart: avoid UTF-8 BOM PowerShell parse error.
Run scripts via -File (UTF-8 no BOM) and Base64 ScriptBlock, using official stopcontainer/startcontainer for MF restart. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -60,8 +60,10 @@ public sealed class SonicConnection
|
||||
: WinRmContainerListScript;
|
||||
|
||||
/// <summary>
|
||||
/// Echter Neustart mit Prozess-Verifikation (sichtbar in SMC):
|
||||
/// alte PIDs müssen weg, neue PIDs müssen erscheinen – sonst Fehler.
|
||||
/// 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).
|
||||
/// </summary>
|
||||
public const string DefaultRestartScript =
|
||||
"""
|
||||
@@ -69,14 +71,10 @@ public sealed class SonicConnection
|
||||
$sonicHome = '{sonicHome}'
|
||||
$domain = '{domain}'
|
||||
$container = '{container}'
|
||||
$connectionUrl = '{connectionUrl}'
|
||||
$username = '{username}'
|
||||
$password = '{password}'
|
||||
|
||||
$bin = Join-Path $sonicHome 'bin'
|
||||
$stopBat = Join-Path $bin 'stopcontainer.bat'
|
||||
$startBat = Join-Path $bin 'startcontainer.bat'
|
||||
$esbAdmin = Join-Path $bin 'esbadmin.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 }
|
||||
@@ -120,46 +118,40 @@ public sealed class SonicConnection
|
||||
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:PIDsVorher=$($before -join ',')"
|
||||
Write-Output "INFO:PIDsBefore=$($before -join ',')"
|
||||
|
||||
if ($before.Count -eq 0) {
|
||||
Write-Output "WARN:Kein laufender Java/Sonic-Prozess für '$shortName' gefunden – Stop ggf. schon offline; starte trotzdem."
|
||||
Write-Output "WARN:No running Java/Sonic process for '$shortName' - starting anyway"
|
||||
}
|
||||
|
||||
# 1) Optional: esbadmin (SMC-kompatibel über Domain Manager)
|
||||
if (Test-Path -LiteralPath $esbAdmin) {
|
||||
Write-Output "INFO:Versuche esbadmin Stop/Start"
|
||||
$scriptText = "connect $domain $connectionUrl $username $password`r`nstop container $shortName`r`nstart container $shortName`r`nexit`r`n"
|
||||
$tmp = [System.IO.Path]::GetTempFileName() + '.txt'
|
||||
Set-Content -LiteralPath $tmp -Value $scriptText -Encoding ASCII
|
||||
try {
|
||||
$p = Start-Process -FilePath 'cmd.exe' -ArgumentList @('/c', "`"$esbAdmin`" < `"$tmp`"") -Wait -PassThru -NoNewWindow
|
||||
Write-Output "INFO:esbadmin ExitCode=$($p.ExitCode)"
|
||||
}
|
||||
finally {
|
||||
Remove-Item -LiteralPath $tmp -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
Start-Sleep -Seconds 5
|
||||
}
|
||||
|
||||
# 2) stopcontainer.bat
|
||||
if (Test-Path -LiteralPath $stopBat) {
|
||||
# 1) Offiziell: stopcontainer.bat Domain.Container (danach Kurzname als Fallback)
|
||||
$stopOk = $false
|
||||
foreach ($n in @($fullName, $shortName)) {
|
||||
Write-Output "INFO:stopcontainer $n"
|
||||
$sp = Start-Process -FilePath 'cmd.exe' -ArgumentList @('/c', "`"$stopBat`" `"$n`"") -Wait -PassThru -NoNewWindow
|
||||
Write-Output "INFO:stopcontainer ExitCode=$($sp.ExitCode) Name=$n"
|
||||
$code = Invoke-ContainerBat -bat $stopBat -name $n
|
||||
Write-Output "INFO:stopcontainer ExitCode=$code Name=$n"
|
||||
if ($code -eq 0) { $stopOk = $true; break }
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Output "WARN:stopcontainer.bat fehlt: $stopBat"
|
||||
if (-not $stopOk) {
|
||||
Write-Output "WARN:stopcontainer non-zero; will force-stop remaining PIDs if any"
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds 3
|
||||
$still = @(Get-ContainerPids)
|
||||
|
||||
# 3) Harter Stop der alten PIDs – sonst sieht SMC oft keinen Neustart
|
||||
# 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) {
|
||||
@@ -169,41 +161,36 @@ public sealed class SonicConnection
|
||||
|
||||
if ($before.Count -gt 0) {
|
||||
if (-not (Wait-PidsGone -pids $before -seconds 45)) {
|
||||
throw "Container-Prozess läuft noch nach Stop (PIDs=$($before -join ',')). SMC würde keinen Stop sehen."
|
||||
throw "Container process still running after stop (PIDs=$($before -join ','))"
|
||||
}
|
||||
Write-Output "INFO:Alte PIDs beendet"
|
||||
Write-Output "INFO:Old PIDs gone"
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds 3
|
||||
|
||||
# 4) Start
|
||||
if (-not (Test-Path -LiteralPath $startBat)) {
|
||||
throw "startcontainer.bat nicht gefunden: $startBat (SonicHome prüfen)"
|
||||
}
|
||||
|
||||
# 3) Offiziell: startcontainer.bat Domain.Container
|
||||
$started = $false
|
||||
foreach ($n in @($fullName, $shortName)) {
|
||||
Write-Output "INFO:startcontainer $n"
|
||||
$st = Start-Process -FilePath 'cmd.exe' -ArgumentList @('/c', "`"$startBat`" `"$n`"") -Wait -PassThru -NoNewWindow
|
||||
Write-Output "INFO:startcontainer ExitCode=$($st.ExitCode) Name=$n"
|
||||
if ($st.ExitCode -eq 0) { $started = $true; break }
|
||||
$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 fehlgeschlagen für $fullName / $shortName"
|
||||
throw "startcontainer failed for $fullName / $shortName"
|
||||
}
|
||||
|
||||
$after = @(Wait-NewPids -oldPids $before -seconds 60)
|
||||
if ($after.Count -eq 0) {
|
||||
# falls Prozess mit gleicher PID-Liste zurückkam: mindestens irgendeinen Treffer verlangen
|
||||
$any = @(Get-ContainerPids)
|
||||
if ($any.Count -eq 0) {
|
||||
throw "Nach Start kein Prozess für Container '$shortName' sichtbar. In SMC prüfen / SonicHome & ContainerName prüfen."
|
||||
throw "After start no process for container '$shortName'. Check SMC / SonicHome / ContainerName."
|
||||
}
|
||||
Write-Output "INFO:PIDsNachher=$($any -join ',')"
|
||||
Write-Output "INFO:PIDsAfter=$($any -join ',')"
|
||||
}
|
||||
else {
|
||||
Write-Output "INFO:PIDsNachher=$($after -join ',')"
|
||||
Write-Output "INFO:PIDsAfter=$($after -join ',')"
|
||||
}
|
||||
|
||||
Write-Output "OK:ContainerRestartVerified Domain=$domain Container=$shortName"
|
||||
|
||||
@@ -6,9 +6,14 @@ namespace ZA.CoreService.ESBCertificateManager.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Führt PowerShell-Befehle lokal oder via WinRM (Invoke-Command) auf dem Sonic-Server aus.
|
||||
/// Schreibt Skripte als .ps1 (UTF-8 ohne BOM) und startet sie mit -File,
|
||||
/// um den bekannten stdin/BOM-Fehler zu vermeiden
|
||||
/// ("$ErrorActionPreference wurde nicht als Name eines Cmdlet erkannt").
|
||||
/// </summary>
|
||||
public sealed class WinRmExecutor
|
||||
{
|
||||
private static readonly Encoding Utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
|
||||
|
||||
private readonly SonicConnection _connection;
|
||||
|
||||
public WinRmExecutor(SonicConnection connection)
|
||||
@@ -62,16 +67,24 @@ public sealed class WinRmExecutor
|
||||
? BuildLocalScript(scriptBlock)
|
||||
: BuildRemoteScript(ExtractHost(_connection.ConnectionUrl), scriptBlock);
|
||||
|
||||
string tempFile = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
$"esb-winrm-{Guid.NewGuid():N}.ps1");
|
||||
|
||||
await File.WriteAllTextAsync(tempFile, fullScript, Utf8NoBom, cancellationToken);
|
||||
|
||||
try
|
||||
{
|
||||
ProcessStartInfo psi = new()
|
||||
{
|
||||
FileName = "powershell.exe",
|
||||
Arguments = "-NonInteractive -NoProfile -ExecutionPolicy Bypass -Command -",
|
||||
Arguments = "-NonInteractive -NoProfile -ExecutionPolicy Bypass -File \"" + tempFile + "\"",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardInput = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true,
|
||||
StandardInputEncoding = Encoding.UTF8
|
||||
StandardOutputEncoding = Encoding.UTF8,
|
||||
StandardErrorEncoding = Encoding.UTF8
|
||||
};
|
||||
|
||||
using Process process = new() { StartInfo = psi };
|
||||
@@ -81,9 +94,6 @@ public sealed class WinRmExecutor
|
||||
return (false, null, "PowerShell-Prozess konnte nicht gestartet werden.");
|
||||
}
|
||||
|
||||
await process.StandardInput.WriteAsync(fullScript.AsMemory(), cancellationToken);
|
||||
process.StandardInput.Close();
|
||||
|
||||
using CancellationTokenSource timeoutCts =
|
||||
CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
timeoutCts.CancelAfter(TimeSpan.FromSeconds(Math.Clamp(_connection.TimeoutSeconds, 10, 600)));
|
||||
@@ -112,23 +122,36 @@ public sealed class WinRmExecutor
|
||||
return (false, stdout.Length > 0 ? stdout : null,
|
||||
stderr.Length > 0 ? Truncate(stderr) : $"PowerShell ExitCode={process.ExitCode}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
try { File.Delete(tempFile); } catch { /* ignore */ }
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildLocalScript(string scriptBlock)
|
||||
=> "$ErrorActionPreference = 'Stop'\n" + scriptBlock;
|
||||
|
||||
/// <summary>
|
||||
/// Remote-Payload als Base64 einbetten und per [scriptblock]::Create ausführen.
|
||||
/// Vermeidet verschachteltes ScriptBlock-Brace-Nesting und lokale $-Expansion.
|
||||
/// </summary>
|
||||
private string BuildRemoteScript(string host, string scriptBlock)
|
||||
{
|
||||
string escapedPwd = _connection.Password.Replace("'", "''");
|
||||
string escapedUser = _connection.Username.Replace("'", "''");
|
||||
string escapedHost = host.Replace("'", "''");
|
||||
string remoteB64 = Convert.ToBase64String(Encoding.Unicode.GetBytes(scriptBlock));
|
||||
|
||||
return
|
||||
"$ErrorActionPreference = 'Stop'\n" +
|
||||
$"$secPwd = ConvertTo-SecureString '{escapedPwd}' -AsPlainText -Force\n" +
|
||||
$"$cred = New-Object System.Management.Automation.PSCredential('{escapedUser}', $secPwd)\n" +
|
||||
$"Invoke-Command -ComputerName '{escapedHost}' -Port {_connection.WinRmPort} -Credential $cred -ScriptBlock {{\n" +
|
||||
$" {scriptBlock}\n" +
|
||||
"} -ErrorAction Stop";
|
||||
$"$remoteB64 = '{remoteB64}'\n" +
|
||||
"$remoteScript = [System.Text.Encoding]::Unicode.GetString(" +
|
||||
"[System.Convert]::FromBase64String($remoteB64))\n" +
|
||||
"$sb = [scriptblock]::Create($remoteScript)\n" +
|
||||
$"Invoke-Command -ComputerName '{escapedHost}' -Port {_connection.WinRmPort} " +
|
||||
"-Credential $cred -ScriptBlock $sb -ErrorAction Stop";
|
||||
}
|
||||
|
||||
public static string ApplyScriptTemplate(
|
||||
|
||||
Reference in New Issue
Block a user