Harden ACC installer update and endpoint performance
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.AccessControl;
|
||||
using System.Security.Principal;
|
||||
using System.Xml;
|
||||
@@ -27,6 +30,7 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
public void Install(string account, string password, bool isGmsa, string environmentName, Action<string> report)
|
||||
{
|
||||
Validate(account, password, isGmsa, environmentName);
|
||||
report = report ?? delegate { };
|
||||
var accountSid = (SecurityIdentifier)new NTAccount(account).Translate(typeof(SecurityIdentifier));
|
||||
report("Konto aufgeloest: " + account + " (" + accountSid.Value + ")");
|
||||
|
||||
@@ -37,46 +41,153 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
RequireFile(sourceExe);
|
||||
RequireFile(sourceConfig);
|
||||
RequireFile(sourceWrapper);
|
||||
|
||||
// Ein laufender Provider kann die installierte EXE waehrend eines Updates sperren.
|
||||
new TaskSchedulerService().DeleteIfExists(TaskName);
|
||||
report("Vorhandener Scheduled Task angehalten beziehungsweise fuer das Update entfernt.");
|
||||
|
||||
Directory.CreateDirectory(installDirectory);
|
||||
var targetExe = Path.Combine(installDirectory, "BizTalkCheckmkPulse.exe");
|
||||
var targetConfig = targetExe + ".config";
|
||||
File.Copy(sourceExe, targetExe, true);
|
||||
File.Copy(sourceConfig, targetConfig, true);
|
||||
SetEnvironment(targetConfig, environmentName);
|
||||
report("Programmdateien installiert: " + installDirectory);
|
||||
var stagingDirectory = installDirectory + ".staging." + Guid.NewGuid().ToString("N");
|
||||
var backupDirectory = installDirectory + ".backup." + Guid.NewGuid().ToString("N");
|
||||
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 backupCreated = false;
|
||||
var filesActivated = false;
|
||||
var scheduler = new TaskSchedulerService();
|
||||
|
||||
var dataDirectory = Path.Combine(runtimeDirectory, "data");
|
||||
var logDirectory = Path.Combine(runtimeDirectory, "logs");
|
||||
Directory.CreateDirectory(runtimeDirectory);
|
||||
Directory.CreateDirectory(dataDirectory);
|
||||
Directory.CreateDirectory(logDirectory);
|
||||
try
|
||||
{
|
||||
RunSelfTest(sourceExe);
|
||||
report("Paket-Vorpruefung erfolgreich: neun Checkmk-Services.");
|
||||
if (!isGmsa)
|
||||
{
|
||||
ValidateBatchLogon(account, password);
|
||||
report("Collector-Anmeldung und 'Log on as a batch job' vor dem Update bestaetigt.");
|
||||
}
|
||||
|
||||
ApplyDirectoryAcl(installDirectory, accountSid, FileSystemRights.ReadAndExecute, FileSystemRights.ReadAndExecute);
|
||||
ApplyDirectoryAcl(runtimeDirectory, accountSid, FileSystemRights.ReadAndExecute, FileSystemRights.ReadAndExecute);
|
||||
ApplyDirectoryAcl(dataDirectory, accountSid, FileSystemRights.Modify, FileSystemRights.ReadAndExecute);
|
||||
ApplyDirectoryAcl(logDirectory, accountSid, FileSystemRights.Modify, FileSystemRights.Modify);
|
||||
report("Least-Privilege-Verzeichnisrechte gesetzt.");
|
||||
Directory.CreateDirectory(stagingDirectory);
|
||||
var stagedExe = Path.Combine(stagingDirectory, "BizTalkCheckmkPulse.exe");
|
||||
var stagedConfig = stagedExe + ".config";
|
||||
File.Copy(sourceExe, stagedExe, false);
|
||||
var effectiveEnvironment = PrepareConfig(
|
||||
sourceConfig,
|
||||
stagedConfig,
|
||||
File.Exists(targetConfig) ? targetConfig : null,
|
||||
environmentName);
|
||||
RunSelfTest(stagedExe);
|
||||
report("Update-Staging validiert. Umgebung=" + (effectiveEnvironment.Length == 0 ? "(keine)" : effectiveEnvironment) + ".");
|
||||
|
||||
Directory.CreateDirectory(checkmkLocalDirectory);
|
||||
File.Copy(sourceWrapper, Path.Combine(checkmkLocalDirectory, "biztalk_checkmk_pulse.cmd"), true);
|
||||
report("Checkmk Local Check installiert: " + checkmkLocalDirectory);
|
||||
// Erst nach vollstaendiger Staging-Pruefung wird der laufende Provider angehalten.
|
||||
scheduler.DeleteIfExists(TaskName);
|
||||
taskRemoved = true;
|
||||
report("Vorhandener Scheduled Task angehalten und fuer das Update entfernt.");
|
||||
|
||||
RunSelfTest(targetExe);
|
||||
report("Self-Test erfolgreich: acht Checkmk-Services.");
|
||||
if (hadExistingInstallation)
|
||||
{
|
||||
Directory.Move(installDirectory, backupDirectory);
|
||||
backupCreated = true;
|
||||
}
|
||||
|
||||
new TaskSchedulerService().RegisterAndStart(
|
||||
TaskName,
|
||||
targetExe,
|
||||
installDirectory,
|
||||
account,
|
||||
isGmsa ? null : password,
|
||||
isGmsa);
|
||||
report("Scheduled Task registriert: " + TaskName);
|
||||
Directory.Move(stagingDirectory, installDirectory);
|
||||
filesActivated = true;
|
||||
report(hadExistingInstallation
|
||||
? "Programmdateien atomar auf die neue Version umgestellt."
|
||||
: "Programmdateien installiert: " + installDirectory);
|
||||
|
||||
var dataDirectory = Path.Combine(runtimeDirectory, "data");
|
||||
var logDirectory = Path.Combine(runtimeDirectory, "logs");
|
||||
Directory.CreateDirectory(runtimeDirectory);
|
||||
Directory.CreateDirectory(dataDirectory);
|
||||
Directory.CreateDirectory(logDirectory);
|
||||
|
||||
ApplyDirectoryAcl(installDirectory, accountSid, FileSystemRights.ReadAndExecute, FileSystemRights.ReadAndExecute);
|
||||
ApplyDirectoryAcl(runtimeDirectory, accountSid, FileSystemRights.ReadAndExecute, FileSystemRights.ReadAndExecute);
|
||||
ApplyDirectoryAcl(dataDirectory, accountSid, FileSystemRights.Modify, FileSystemRights.ReadAndExecute);
|
||||
ApplyDirectoryAcl(logDirectory, accountSid, FileSystemRights.Modify, FileSystemRights.Modify);
|
||||
report("Least-Privilege-Verzeichnisrechte gesetzt; vorhandene Runtime-Daten bleiben erhalten.");
|
||||
|
||||
Directory.CreateDirectory(checkmkLocalDirectory);
|
||||
File.Copy(sourceWrapper, targetWrapper, true);
|
||||
report("Checkmk Local Check installiert: " + checkmkLocalDirectory);
|
||||
|
||||
RunSelfTest(targetExe);
|
||||
report("Installierter Self-Test erfolgreich: neun Checkmk-Services.");
|
||||
|
||||
scheduler.RegisterAndStart(
|
||||
TaskName,
|
||||
targetExe,
|
||||
installDirectory,
|
||||
account,
|
||||
isGmsa ? null : password,
|
||||
isGmsa);
|
||||
taskRemoved = false;
|
||||
report("Scheduled Task registriert und einmalig gestartet: " + TaskName);
|
||||
|
||||
TryDeleteDirectory(backupDirectory, report);
|
||||
}
|
||||
catch (Exception installException)
|
||||
{
|
||||
var rollbackFailures = new List<string>();
|
||||
try
|
||||
{
|
||||
scheduler.DeleteIfExists(TaskName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
rollbackFailures.Add("Task stoppen: " + ex.Message);
|
||||
}
|
||||
|
||||
if (filesActivated || backupCreated)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(installDirectory)) Directory.Delete(installDirectory, true);
|
||||
if (backupCreated && Directory.Exists(backupDirectory)) Directory.Move(backupDirectory, installDirectory);
|
||||
report("Vorherige Programmversion wiederhergestellt.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
rollbackFailures.Add("Programmdateien wiederherstellen: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
RestoreWrapper(targetWrapper, previousWrapper);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
rollbackFailures.Add("Checkmk-Wrapper wiederherstellen: " + ex.Message);
|
||||
}
|
||||
|
||||
if (taskRemoved && hadExistingInstallation && File.Exists(targetExe))
|
||||
{
|
||||
try
|
||||
{
|
||||
scheduler.RegisterAndStart(
|
||||
TaskName,
|
||||
targetExe,
|
||||
installDirectory,
|
||||
account,
|
||||
isGmsa ? null : password,
|
||||
isGmsa);
|
||||
report("Scheduled Task fuer die vorherige Version wiederhergestellt.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
rollbackFailures.Add("Scheduled Task wiederherstellen: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
var rollback = rollbackFailures.Count == 0
|
||||
? "Rollback erfolgreich."
|
||||
: "Rollback unvollstaendig: " + string.Join(" | ", rollbackFailures);
|
||||
throw new InvalidOperationException(
|
||||
"Installation/Update fehlgeschlagen. " + rollback + " Ursache: " + installException.Message,
|
||||
installException);
|
||||
}
|
||||
finally
|
||||
{
|
||||
TryDeleteDirectory(stagingDirectory, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void Uninstall(bool keepRuntimeData, Action<string> report)
|
||||
@@ -108,14 +219,152 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
throw new FileNotFoundException("Installationspaket ist unvollstaendig. Datei fehlt: " + path, path);
|
||||
}
|
||||
|
||||
private static void SetEnvironment(string configPath, string environmentName)
|
||||
internal string GetInstalledEnvironment()
|
||||
{
|
||||
var document = new XmlDocument { PreserveWhitespace = true };
|
||||
document.Load(configPath);
|
||||
var setting = document.SelectSingleNode("/configuration/appSettings/add[@key='EnvironmentName']") as XmlElement;
|
||||
if (setting == null) throw new InvalidDataException("EnvironmentName fehlt in " + configPath + ".");
|
||||
setting.SetAttribute("value", environmentName);
|
||||
document.Save(configPath);
|
||||
var config = Path.Combine(installDirectory, "BizTalkCheckmkPulse.exe.config");
|
||||
if (!File.Exists(config)) return string.Empty;
|
||||
try
|
||||
{
|
||||
var document = LoadXml(config);
|
||||
var setting = FindAppSetting(document, "EnvironmentName");
|
||||
return setting == null ? string.Empty : setting.GetAttribute("value").Trim();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
internal bool IsInstalled
|
||||
{
|
||||
get { return File.Exists(Path.Combine(installDirectory, "BizTalkCheckmkPulse.exe")); }
|
||||
}
|
||||
|
||||
internal static string PrepareConfig(
|
||||
string sourceConfig,
|
||||
string stagedConfig,
|
||||
string existingConfig,
|
||||
string requestedEnvironment)
|
||||
{
|
||||
var document = LoadXml(sourceConfig);
|
||||
if (!string.IsNullOrWhiteSpace(existingConfig) && File.Exists(existingConfig))
|
||||
{
|
||||
var existing = LoadXml(existingConfig);
|
||||
var existingSettings = existing.SelectNodes("/configuration/appSettings/add[@key]");
|
||||
if (existingSettings != null)
|
||||
{
|
||||
foreach (XmlNode node in existingSettings)
|
||||
{
|
||||
var element = node as XmlElement;
|
||||
if (element == null) continue;
|
||||
var key = element.GetAttribute("key");
|
||||
var value = element.GetAttribute("value");
|
||||
if (IsSupersededDefault(key, value)) continue;
|
||||
var target = FindAppSetting(document, key);
|
||||
if (target != null) target.SetAttribute("value", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var environmentSetting = FindAppSetting(document, "EnvironmentName");
|
||||
if (environmentSetting == null)
|
||||
throw new InvalidDataException("EnvironmentName fehlt in " + sourceConfig + ".");
|
||||
if (!string.IsNullOrWhiteSpace(requestedEnvironment))
|
||||
environmentSetting.SetAttribute("value", requestedEnvironment.Trim());
|
||||
|
||||
document.Save(stagedConfig);
|
||||
return environmentSetting.GetAttribute("value").Trim();
|
||||
}
|
||||
|
||||
private static XmlDocument LoadXml(string path)
|
||||
{
|
||||
var document = new XmlDocument { PreserveWhitespace = true, XmlResolver = null };
|
||||
using (var reader = XmlReader.Create(path, new XmlReaderSettings
|
||||
{
|
||||
DtdProcessing = DtdProcessing.Prohibit,
|
||||
XmlResolver = null
|
||||
}))
|
||||
{
|
||||
document.Load(reader);
|
||||
}
|
||||
return document;
|
||||
}
|
||||
|
||||
private static XmlElement FindAppSetting(XmlDocument document, string key)
|
||||
{
|
||||
var nodes = document.SelectNodes("/configuration/appSettings/add[@key]");
|
||||
if (nodes == null) return null;
|
||||
foreach (XmlNode node in nodes)
|
||||
{
|
||||
var element = node as XmlElement;
|
||||
if (element != null && string.Equals(element.GetAttribute("key"), key, StringComparison.Ordinal))
|
||||
return element;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool IsSupersededDefault(string key, string value)
|
||||
{
|
||||
return string.Equals(key, "EndpointProbeMaxConcurrency", StringComparison.Ordinal)
|
||||
&& string.Equals(value, "12", StringComparison.Ordinal)
|
||||
|| string.Equals(key, "EndpointMaxCount", StringComparison.Ordinal)
|
||||
&& string.Equals(value, "500", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static void ValidateBatchLogon(string account, string password)
|
||||
{
|
||||
var separator = account.IndexOf('\\');
|
||||
var domain = account.Substring(0, separator);
|
||||
var user = account.Substring(separator + 1);
|
||||
IntPtr token;
|
||||
if (!LogonUser(user, domain, password, 4, 0, out token))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Collector-Anmeldung als Batch fehlgeschlagen: "
|
||||
+ new Win32Exception(Marshal.GetLastWin32Error()).Message
|
||||
+ ". Kennwort und lokales Recht 'Log on as a batch job' pruefen.");
|
||||
}
|
||||
|
||||
CloseHandle(token);
|
||||
}
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool LogonUser(
|
||||
string userName,
|
||||
string domain,
|
||||
string password,
|
||||
int logonType,
|
||||
int logonProvider,
|
||||
out IntPtr token);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool CloseHandle(IntPtr handle);
|
||||
|
||||
private static void RestoreWrapper(string path, byte[] previousContent)
|
||||
{
|
||||
if (previousContent == null)
|
||||
{
|
||||
if (File.Exists(path)) File.Delete(path);
|
||||
return;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
File.WriteAllBytes(path, previousContent);
|
||||
}
|
||||
|
||||
private static void TryDeleteDirectory(string path, Action<string> report)
|
||||
{
|
||||
if (!Directory.Exists(path)) return;
|
||||
try
|
||||
{
|
||||
Directory.Delete(path, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (report != null) report("Hinweis: temporaeres Verzeichnis konnte nicht entfernt werden: " + path + " (" + ex.Message + ")");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyDirectoryAcl(
|
||||
@@ -168,7 +417,7 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
throw new InvalidOperationException("Self-Test hat das Zeitlimit ueberschritten.");
|
||||
}
|
||||
var lines = output.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (process.ExitCode != 0 || lines.Length != 8 || lines.Any(x => !x.StartsWith("0 ", StringComparison.Ordinal)))
|
||||
if (process.ExitCode != 0 || lines.Length != 9 || lines.Any(x => !x.StartsWith("0 ", StringComparison.Ordinal)))
|
||||
throw new InvalidOperationException("Self-Test fehlgeschlagen. Exitcode=" + process.ExitCode + ", Zeilen=" + lines.Length + ". " + error);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user