Make installer runtime acceptance transactional

This commit is contained in:
2026-08-10 18:28:49 +02:00
parent 8fe1b57173
commit 448be8dc49
20 changed files with 874 additions and 65 deletions
@@ -7,6 +7,7 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Text;
using System.Xml;
namespace BizTalkCheckmkPulse.Setup
@@ -14,6 +15,7 @@ namespace BizTalkCheckmkPulse.Setup
internal sealed class InstallerEngine
{
internal const string TaskName = "BizTalk Checkmk Pulse Provider";
private static readonly TimeSpan PostInstallValidationTimeout = TimeSpan.FromMinutes(4);
private readonly string packageDirectory;
private readonly string installDirectory;
private readonly string runtimeDirectory;
@@ -54,7 +56,8 @@ namespace BizTalkCheckmkPulse.Setup
var targetWrapper = Path.Combine(checkmkLocalDirectory, "biztalk_checkmk_pulse.cmd");
var previousWrapper = File.Exists(targetWrapper) ? File.ReadAllBytes(targetWrapper) : null;
var hadExistingInstallation = Directory.Exists(installDirectory);
var taskRemoved = false;
var hadExistingExecutable = File.Exists(targetExe);
var mutationStarted = false;
var backupCreated = false;
var filesActivated = false;
var scheduler = new TaskSchedulerService();
@@ -94,8 +97,8 @@ namespace BizTalkCheckmkPulse.Setup
}
// Erst nach vollstaendiger Staging-Pruefung wird der laufende Provider angehalten.
mutationStarted = true;
scheduler.DeleteIfExists(TaskName);
taskRemoved = true;
report("Vorhandener Scheduled Task angehalten und fuer das Update entfernt.");
if (hadExistingInstallation)
@@ -129,28 +132,53 @@ namespace BizTalkCheckmkPulse.Setup
RunSelfTest(targetExe);
report("Installierter Self-Test erfolgreich: neun Checkmk-Services.");
scheduler.RegisterAndStart(
var runtimeValidationStartedUtc = DateTime.UtcNow;
scheduler.RegisterValidationAndStart(
TaskName,
targetExe,
installDirectory,
account,
isGmsa ? null : password,
isGmsa);
taskRemoved = false;
report("Scheduled Task registriert und einmalig gestartet: " + TaskName);
report("Einmaliger Provider-Abnahmelauf mit erzwungenem Endpoint-Katalogabgleich gestartet.");
var completedRun = scheduler.WaitForSuccessfulRun(
TaskName,
runtimeValidationStartedUtc,
PostInstallValidationTimeout,
report);
report("Provider-Abnahmelauf erfolgreich: LastTaskResult=" + completedRun.ExitCode + ".");
var runtimeValidation = RunRuntimeValidation(
targetExe,
runtimeValidationStartedUtc,
account);
report("Post-Install-Runtime-Abnahme erfolgreich: " + runtimeValidation);
scheduler.RegisterRecurring(
TaskName,
targetExe,
installDirectory,
account,
isGmsa ? null : password,
isGmsa);
report("Validierter minuetlicher Scheduled Task registriert: " + TaskName);
TryDeleteDirectory(backupDirectory, report);
}
catch (Exception installException)
{
var rollbackFailures = new List<string>();
try
if (mutationStarted)
{
scheduler.DeleteIfExists(TaskName);
}
catch (Exception ex)
{
rollbackFailures.Add("Task stoppen: " + ex.Message);
try
{
scheduler.DeleteIfExists(TaskName);
}
catch (Exception ex)
{
rollbackFailures.Add("Task stoppen: " + ex.Message);
}
}
if (filesActivated || backupCreated)
@@ -167,27 +195,36 @@ namespace BizTalkCheckmkPulse.Setup
}
}
try
{
RestoreWrapper(targetWrapper, previousWrapper);
}
catch (Exception ex)
{
rollbackFailures.Add("Checkmk-Wrapper wiederherstellen: " + ex.Message);
}
if (taskRemoved && hadExistingInstallation && File.Exists(targetExe))
if (mutationStarted)
{
try
{
scheduler.RegisterAndStart(
RestoreWrapper(targetWrapper, previousWrapper);
}
catch (Exception ex)
{
rollbackFailures.Add("Checkmk-Wrapper wiederherstellen: " + ex.Message);
}
}
if (mutationStarted && hadExistingExecutable && File.Exists(targetExe))
{
try
{
var rollbackRunStartedUtc = DateTime.UtcNow;
scheduler.RegisterRecurringAndStart(
TaskName,
targetExe,
installDirectory,
account,
isGmsa ? null : password,
isGmsa);
report("Scheduled Task fuer die vorherige Version wiederhergestellt.");
scheduler.WaitForSuccessfulRun(
TaskName,
rollbackRunStartedUtc,
PostInstallValidationTimeout,
report);
report("Scheduled Task und frischer Snapshot der vorherigen Version wiederhergestellt.");
}
catch (Exception ex)
{
@@ -445,6 +482,45 @@ namespace BizTalkCheckmkPulse.Setup
}
}
private static string RunRuntimeValidation(
string executable,
DateTime notBeforeUtc,
string expectedIdentity)
{
var identityBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(expectedIdentity));
var arguments = "--validate-runtime --validation-not-before-utc "
+ notBeforeUtc.ToUniversalTime().ToString("o")
+ " --expected-identity-base64 " + identityBase64;
var start = new ProcessStartInfo(executable, arguments)
{
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true
};
using (var process = Process.Start(start))
{
if (process == null) throw new InvalidOperationException("Runtime-Abnahme konnte nicht gestartet werden.");
var output = process.StandardOutput.ReadToEnd().Trim();
var error = process.StandardError.ReadToEnd().Trim();
if (!process.WaitForExit(30000))
{
process.Kill();
throw new TimeoutException("Runtime-Abnahme hat das Zeitlimit ueberschritten.");
}
if (process.ExitCode != 0
|| !output.StartsWith("RUNTIME_VALIDATION_V1 ", StringComparison.Ordinal))
{
throw new InvalidOperationException(
"Runtime-Abnahme fehlgeschlagen. Exitcode=" + process.ExitCode
+ ". " + (error.Length == 0 ? output : error));
}
return output;
}
}
private static string ExtractServiceName(string line)
{
var firstQuote = line.IndexOf('"');