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;
|
: WinRmContainerListScript;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Echter Neustart mit Prozess-Verifikation (sichtbar in SMC):
|
/// Offizieller MF-Container-Neustart laut Aurea CX Messenger Doku:
|
||||||
/// alte PIDs müssen weg, neue PIDs müssen erscheinen – sonst Fehler.
|
/// SonicHome\bin\stopcontainer.bat Domain.Container
|
||||||
|
/// SonicHome\bin\startcontainer.bat Domain.Container
|
||||||
|
/// Danach Prozess-Verifikation (alte PIDs weg, neue PIDs da).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string DefaultRestartScript =
|
public const string DefaultRestartScript =
|
||||||
"""
|
"""
|
||||||
@@ -69,14 +71,10 @@ public sealed class SonicConnection
|
|||||||
$sonicHome = '{sonicHome}'
|
$sonicHome = '{sonicHome}'
|
||||||
$domain = '{domain}'
|
$domain = '{domain}'
|
||||||
$container = '{container}'
|
$container = '{container}'
|
||||||
$connectionUrl = '{connectionUrl}'
|
|
||||||
$username = '{username}'
|
|
||||||
$password = '{password}'
|
|
||||||
|
|
||||||
$bin = Join-Path $sonicHome 'bin'
|
$bin = Join-Path $sonicHome 'bin'
|
||||||
$stopBat = Join-Path $bin 'stopcontainer.bat'
|
$stopBat = Join-Path $bin 'stopcontainer.bat'
|
||||||
$startBat = Join-Path $bin 'startcontainer.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)
|
# 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 }
|
$shortName = if ($container -like '*.*') { ($container -split '\.', 2)[1] } else { $container }
|
||||||
@@ -120,46 +118,40 @@ public sealed class SonicConnection
|
|||||||
return ,@()
|
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:Domain=$domain Container=$shortName Full=$fullName"
|
||||||
|
Write-Output "INFO:RestartVia=stopcontainer/startcontainer (MF Container)"
|
||||||
$before = @(Get-ContainerPids)
|
$before = @(Get-ContainerPids)
|
||||||
Write-Output "INFO:PIDsVorher=$($before -join ',')"
|
Write-Output "INFO:PIDsBefore=$($before -join ',')"
|
||||||
|
|
||||||
if ($before.Count -eq 0) {
|
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)
|
# 1) Offiziell: stopcontainer.bat Domain.Container (danach Kurzname als Fallback)
|
||||||
if (Test-Path -LiteralPath $esbAdmin) {
|
$stopOk = $false
|
||||||
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) {
|
|
||||||
foreach ($n in @($fullName, $shortName)) {
|
foreach ($n in @($fullName, $shortName)) {
|
||||||
Write-Output "INFO:stopcontainer $n"
|
Write-Output "INFO:stopcontainer $n"
|
||||||
$sp = Start-Process -FilePath 'cmd.exe' -ArgumentList @('/c', "`"$stopBat`" `"$n`"") -Wait -PassThru -NoNewWindow
|
$code = Invoke-ContainerBat -bat $stopBat -name $n
|
||||||
Write-Output "INFO:stopcontainer ExitCode=$($sp.ExitCode) Name=$n"
|
Write-Output "INFO:stopcontainer ExitCode=$code Name=$n"
|
||||||
|
if ($code -eq 0) { $stopOk = $true; break }
|
||||||
}
|
}
|
||||||
}
|
if (-not $stopOk) {
|
||||||
else {
|
Write-Output "WARN:stopcontainer non-zero; will force-stop remaining PIDs if any"
|
||||||
Write-Output "WARN:stopcontainer.bat fehlt: $stopBat"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Start-Sleep -Seconds 3
|
Start-Sleep -Seconds 3
|
||||||
$still = @(Get-ContainerPids)
|
$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) {
|
if ($still.Count -gt 0) {
|
||||||
Write-Output "INFO:Force-Stop PIDs=$($still -join ',')"
|
Write-Output "INFO:Force-Stop PIDs=$($still -join ',')"
|
||||||
foreach ($procId in $still) {
|
foreach ($procId in $still) {
|
||||||
@@ -169,41 +161,36 @@ public sealed class SonicConnection
|
|||||||
|
|
||||||
if ($before.Count -gt 0) {
|
if ($before.Count -gt 0) {
|
||||||
if (-not (Wait-PidsGone -pids $before -seconds 45)) {
|
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
|
Start-Sleep -Seconds 3
|
||||||
|
|
||||||
# 4) Start
|
# 3) Offiziell: startcontainer.bat Domain.Container
|
||||||
if (-not (Test-Path -LiteralPath $startBat)) {
|
|
||||||
throw "startcontainer.bat nicht gefunden: $startBat (SonicHome prüfen)"
|
|
||||||
}
|
|
||||||
|
|
||||||
$started = $false
|
$started = $false
|
||||||
foreach ($n in @($fullName, $shortName)) {
|
foreach ($n in @($fullName, $shortName)) {
|
||||||
Write-Output "INFO:startcontainer $n"
|
Write-Output "INFO:startcontainer $n"
|
||||||
$st = Start-Process -FilePath 'cmd.exe' -ArgumentList @('/c', "`"$startBat`" `"$n`"") -Wait -PassThru -NoNewWindow
|
$code = Invoke-ContainerBat -bat $startBat -name $n
|
||||||
Write-Output "INFO:startcontainer ExitCode=$($st.ExitCode) Name=$n"
|
Write-Output "INFO:startcontainer ExitCode=$code Name=$n"
|
||||||
if ($st.ExitCode -eq 0) { $started = $true; break }
|
if ($code -eq 0) { $started = $true; break }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (-not $started) {
|
if (-not $started) {
|
||||||
throw "startcontainer fehlgeschlagen für $fullName / $shortName"
|
throw "startcontainer failed for $fullName / $shortName"
|
||||||
}
|
}
|
||||||
|
|
||||||
$after = @(Wait-NewPids -oldPids $before -seconds 60)
|
$after = @(Wait-NewPids -oldPids $before -seconds 60)
|
||||||
if ($after.Count -eq 0) {
|
if ($after.Count -eq 0) {
|
||||||
# falls Prozess mit gleicher PID-Liste zurückkam: mindestens irgendeinen Treffer verlangen
|
|
||||||
$any = @(Get-ContainerPids)
|
$any = @(Get-ContainerPids)
|
||||||
if ($any.Count -eq 0) {
|
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 {
|
else {
|
||||||
Write-Output "INFO:PIDsNachher=$($after -join ',')"
|
Write-Output "INFO:PIDsAfter=$($after -join ',')"
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Output "OK:ContainerRestartVerified Domain=$domain Container=$shortName"
|
Write-Output "OK:ContainerRestartVerified Domain=$domain Container=$shortName"
|
||||||
|
|||||||
@@ -6,9 +6,14 @@ namespace ZA.CoreService.ESBCertificateManager.Services;
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Führt PowerShell-Befehle lokal oder via WinRM (Invoke-Command) auf dem Sonic-Server aus.
|
/// 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>
|
/// </summary>
|
||||||
public sealed class WinRmExecutor
|
public sealed class WinRmExecutor
|
||||||
{
|
{
|
||||||
|
private static readonly Encoding Utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
|
||||||
|
|
||||||
private readonly SonicConnection _connection;
|
private readonly SonicConnection _connection;
|
||||||
|
|
||||||
public WinRmExecutor(SonicConnection connection)
|
public WinRmExecutor(SonicConnection connection)
|
||||||
@@ -62,16 +67,24 @@ public sealed class WinRmExecutor
|
|||||||
? BuildLocalScript(scriptBlock)
|
? BuildLocalScript(scriptBlock)
|
||||||
: BuildRemoteScript(ExtractHost(_connection.ConnectionUrl), 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()
|
ProcessStartInfo psi = new()
|
||||||
{
|
{
|
||||||
FileName = "powershell.exe",
|
FileName = "powershell.exe",
|
||||||
Arguments = "-NonInteractive -NoProfile -ExecutionPolicy Bypass -Command -",
|
Arguments = "-NonInteractive -NoProfile -ExecutionPolicy Bypass -File \"" + tempFile + "\"",
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
RedirectStandardInput = true,
|
|
||||||
RedirectStandardOutput = true,
|
RedirectStandardOutput = true,
|
||||||
RedirectStandardError = true,
|
RedirectStandardError = true,
|
||||||
CreateNoWindow = true,
|
CreateNoWindow = true,
|
||||||
StandardInputEncoding = Encoding.UTF8
|
StandardOutputEncoding = Encoding.UTF8,
|
||||||
|
StandardErrorEncoding = Encoding.UTF8
|
||||||
};
|
};
|
||||||
|
|
||||||
using Process process = new() { StartInfo = psi };
|
using Process process = new() { StartInfo = psi };
|
||||||
@@ -81,9 +94,6 @@ public sealed class WinRmExecutor
|
|||||||
return (false, null, "PowerShell-Prozess konnte nicht gestartet werden.");
|
return (false, null, "PowerShell-Prozess konnte nicht gestartet werden.");
|
||||||
}
|
}
|
||||||
|
|
||||||
await process.StandardInput.WriteAsync(fullScript.AsMemory(), cancellationToken);
|
|
||||||
process.StandardInput.Close();
|
|
||||||
|
|
||||||
using CancellationTokenSource timeoutCts =
|
using CancellationTokenSource timeoutCts =
|
||||||
CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||||
timeoutCts.CancelAfter(TimeSpan.FromSeconds(Math.Clamp(_connection.TimeoutSeconds, 10, 600)));
|
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,
|
return (false, stdout.Length > 0 ? stdout : null,
|
||||||
stderr.Length > 0 ? Truncate(stderr) : $"PowerShell ExitCode={process.ExitCode}");
|
stderr.Length > 0 ? Truncate(stderr) : $"PowerShell ExitCode={process.ExitCode}");
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try { File.Delete(tempFile); } catch { /* ignore */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static string BuildLocalScript(string scriptBlock)
|
private static string BuildLocalScript(string scriptBlock)
|
||||||
=> "$ErrorActionPreference = 'Stop'\n" + 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)
|
private string BuildRemoteScript(string host, string scriptBlock)
|
||||||
{
|
{
|
||||||
string escapedPwd = _connection.Password.Replace("'", "''");
|
string escapedPwd = _connection.Password.Replace("'", "''");
|
||||||
string escapedUser = _connection.Username.Replace("'", "''");
|
string escapedUser = _connection.Username.Replace("'", "''");
|
||||||
string escapedHost = host.Replace("'", "''");
|
string escapedHost = host.Replace("'", "''");
|
||||||
|
string remoteB64 = Convert.ToBase64String(Encoding.Unicode.GetBytes(scriptBlock));
|
||||||
|
|
||||||
return
|
return
|
||||||
"$ErrorActionPreference = 'Stop'\n" +
|
"$ErrorActionPreference = 'Stop'\n" +
|
||||||
$"$secPwd = ConvertTo-SecureString '{escapedPwd}' -AsPlainText -Force\n" +
|
$"$secPwd = ConvertTo-SecureString '{escapedPwd}' -AsPlainText -Force\n" +
|
||||||
$"$cred = New-Object System.Management.Automation.PSCredential('{escapedUser}', $secPwd)\n" +
|
$"$cred = New-Object System.Management.Automation.PSCredential('{escapedUser}', $secPwd)\n" +
|
||||||
$"Invoke-Command -ComputerName '{escapedHost}' -Port {_connection.WinRmPort} -Credential $cred -ScriptBlock {{\n" +
|
$"$remoteB64 = '{remoteB64}'\n" +
|
||||||
$" {scriptBlock}\n" +
|
"$remoteScript = [System.Text.Encoding]::Unicode.GetString(" +
|
||||||
"} -ErrorAction Stop";
|
"[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(
|
public static string ApplyScriptTemplate(
|
||||||
|
|||||||
Reference in New Issue
Block a user