Add PowerShell-free Windows installer
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.AccessControl;
|
||||
using System.Security.Principal;
|
||||
using System.Xml;
|
||||
|
||||
namespace BizTalkCheckmkPulse.Setup
|
||||
{
|
||||
internal sealed class InstallerEngine
|
||||
{
|
||||
internal const string TaskName = "BizTalk Checkmk Pulse Provider";
|
||||
private readonly string packageDirectory;
|
||||
private readonly string installDirectory;
|
||||
private readonly string runtimeDirectory;
|
||||
private readonly string checkmkLocalDirectory;
|
||||
|
||||
public InstallerEngine(string packageDirectory)
|
||||
{
|
||||
this.packageDirectory = Path.GetFullPath(packageDirectory);
|
||||
installDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "BizTalkCheckmkPulse");
|
||||
runtimeDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "BizTalkCheckmkPulse");
|
||||
checkmkLocalDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "checkmk", "agent", "local");
|
||||
}
|
||||
|
||||
public void Install(string account, string password, bool isGmsa, string environmentName, Action<string> report)
|
||||
{
|
||||
Validate(account, password, isGmsa, environmentName);
|
||||
var accountSid = (SecurityIdentifier)new NTAccount(account).Translate(typeof(SecurityIdentifier));
|
||||
report("Konto aufgeloest: " + account + " (" + accountSid.Value + ")");
|
||||
|
||||
var sourceApplication = Path.Combine(packageDirectory, "application");
|
||||
var sourceExe = Path.Combine(sourceApplication, "BizTalkCheckmkPulse.exe");
|
||||
var sourceConfig = sourceExe + ".config";
|
||||
var sourceWrapper = Path.Combine(packageDirectory, "biztalk_checkmk_pulse.cmd");
|
||||
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 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.");
|
||||
|
||||
Directory.CreateDirectory(checkmkLocalDirectory);
|
||||
File.Copy(sourceWrapper, Path.Combine(checkmkLocalDirectory, "biztalk_checkmk_pulse.cmd"), true);
|
||||
report("Checkmk Local Check installiert: " + checkmkLocalDirectory);
|
||||
|
||||
RunSelfTest(targetExe);
|
||||
report("Self-Test erfolgreich: acht Checkmk-Services.");
|
||||
|
||||
new TaskSchedulerService().RegisterAndStart(
|
||||
TaskName,
|
||||
targetExe,
|
||||
installDirectory,
|
||||
account,
|
||||
isGmsa ? null : password,
|
||||
isGmsa);
|
||||
report("Scheduled Task registriert: " + TaskName);
|
||||
}
|
||||
|
||||
public void Uninstall(bool keepRuntimeData, Action<string> report)
|
||||
{
|
||||
new TaskSchedulerService().DeleteIfExists(TaskName);
|
||||
report("Scheduled Task entfernt.");
|
||||
|
||||
var wrapper = Path.Combine(checkmkLocalDirectory, "biztalk_checkmk_pulse.cmd");
|
||||
if (File.Exists(wrapper)) File.Delete(wrapper);
|
||||
if (Directory.Exists(installDirectory)) Directory.Delete(installDirectory, true);
|
||||
if (!keepRuntimeData && Directory.Exists(runtimeDirectory)) Directory.Delete(runtimeDirectory, true);
|
||||
}
|
||||
|
||||
private static void Validate(string account, string password, bool isGmsa, string environmentName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(account)) throw new ArgumentException("Collector-Konto fehlt.");
|
||||
if (account.IndexOf('\\') <= 0 || account.EndsWith("\\", StringComparison.Ordinal))
|
||||
throw new ArgumentException(@"Collector-Konto im Format DOMAIN\Benutzer eingeben, z.B. BEW\t231bizmon.");
|
||||
if (isGmsa && !account.EndsWith("$", StringComparison.Ordinal))
|
||||
throw new ArgumentException(@"Ein gMSA-Konto muss mit '$' enden, z.B. BEW\svc_biztalk_cmk$.");
|
||||
if (!isGmsa && string.IsNullOrEmpty(password)) throw new ArgumentException("Kennwort fehlt.");
|
||||
if (!new[] { "", "ACC", "DEV", "TST", "PRD" }.Contains(environmentName))
|
||||
throw new ArgumentException("Ungueltige Umgebung.");
|
||||
}
|
||||
|
||||
private static void RequireFile(string path)
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
throw new FileNotFoundException("Installationspaket ist unvollstaendig. Datei fehlt: " + path, path);
|
||||
}
|
||||
|
||||
private static void SetEnvironment(string configPath, string environmentName)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
private static void ApplyDirectoryAcl(
|
||||
string path,
|
||||
SecurityIdentifier collectorSid,
|
||||
FileSystemRights collectorRights,
|
||||
FileSystemRights systemRights)
|
||||
{
|
||||
var inheritance = InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit;
|
||||
var security = new DirectorySecurity();
|
||||
security.SetAccessRuleProtection(true, false);
|
||||
security.AddAccessRule(new FileSystemAccessRule(
|
||||
new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null),
|
||||
FileSystemRights.FullControl,
|
||||
inheritance,
|
||||
PropagationFlags.None,
|
||||
AccessControlType.Allow));
|
||||
security.AddAccessRule(new FileSystemAccessRule(
|
||||
new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null),
|
||||
systemRights,
|
||||
inheritance,
|
||||
PropagationFlags.None,
|
||||
AccessControlType.Allow));
|
||||
security.AddAccessRule(new FileSystemAccessRule(
|
||||
collectorSid,
|
||||
collectorRights,
|
||||
inheritance,
|
||||
PropagationFlags.None,
|
||||
AccessControlType.Allow));
|
||||
new DirectoryInfo(path).SetAccessControl(security);
|
||||
}
|
||||
|
||||
private static void RunSelfTest(string executable)
|
||||
{
|
||||
var start = new ProcessStartInfo(executable, "--self-test")
|
||||
{
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true
|
||||
};
|
||||
using (var process = Process.Start(start))
|
||||
{
|
||||
if (process == null) throw new InvalidOperationException("Self-Test konnte nicht gestartet werden.");
|
||||
var output = process.StandardOutput.ReadToEnd();
|
||||
var error = process.StandardError.ReadToEnd();
|
||||
if (!process.WaitForExit(30000))
|
||||
{
|
||||
process.Kill();
|
||||
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)))
|
||||
throw new InvalidOperationException("Self-Test fehlgeschlagen. Exitcode=" + process.ExitCode + ", Zeilen=" + lines.Length + ". " + error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user