Improve installer diagnostics and rollback reporting
This commit is contained in:
@@ -15,13 +15,14 @@ namespace BizTalkPlatformManagementTool.Setup
|
||||
{
|
||||
internal const string ApplicationExeName = "BizTalkPlatformManagementTool.exe";
|
||||
private const string ProductName = "BizTalk Platform Management Tool";
|
||||
private const string ProductVersion = "2.1.0";
|
||||
private const string ProductVersion = "2.1.1";
|
||||
private const string UninstallKeyPath = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\BizTalkPlatformManagementTool";
|
||||
private readonly string packageDirectory;
|
||||
private readonly string installDirectory;
|
||||
private readonly string dataDirectory;
|
||||
private readonly bool registerWindowsIntegration;
|
||||
private readonly Func<string, bool> selfTestRunner;
|
||||
private string lastInstallerLogDirectory;
|
||||
|
||||
private sealed class WindowsIntegrationSnapshot
|
||||
{
|
||||
@@ -38,7 +39,7 @@ namespace BizTalkPlatformManagementTool.Setup
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "BizTalkPlatformManagementTool"),
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "BizTalkPlatformManagementTool"),
|
||||
true,
|
||||
RunApplicationSelfTest)
|
||||
null)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -48,11 +49,16 @@ namespace BizTalkPlatformManagementTool.Setup
|
||||
this.installDirectory = Path.GetFullPath(installDirectory);
|
||||
this.dataDirectory = Path.GetFullPath(dataDirectory);
|
||||
this.registerWindowsIntegration = registerWindowsIntegration;
|
||||
this.selfTestRunner = selfTestRunner ?? throw new ArgumentNullException("selfTestRunner");
|
||||
this.selfTestRunner = selfTestRunner;
|
||||
}
|
||||
|
||||
/// <summary>Gets the fixed machine-wide application installation directory.</summary>
|
||||
public string InstallDirectory { get { return installDirectory; } }
|
||||
/// <summary>Gets the directory containing persistent setup diagnostic logs.</summary>
|
||||
public string InstallerLogDirectory
|
||||
{
|
||||
get { return lastInstallerLogDirectory ?? Path.Combine(dataDirectory, "InstallerLogs"); }
|
||||
}
|
||||
/// <summary>Gets whether this setup copy contains a complete install/update payload.</summary>
|
||||
public bool HasInstallPayload { get { return Directory.Exists(Path.Combine(packageDirectory, "application")) && File.Exists(Path.Combine(packageDirectory, "application.manifest")); } }
|
||||
/// <summary>Gets whether the application executable is present at the install target.</summary>
|
||||
@@ -61,47 +67,94 @@ namespace BizTalkPlatformManagementTool.Setup
|
||||
/// <summary>Validates, stages and transactionally installs or updates the application.</summary>
|
||||
public void Install(bool createDesktopShortcut, Action<string> report)
|
||||
{
|
||||
report = report ?? delegate { };
|
||||
Directory.CreateDirectory(dataDirectory);
|
||||
var uiReport = report ?? delegate { };
|
||||
var log = SetupOperationLog.Create(dataDirectory, "install-update");
|
||||
Action<string> write = message => { log.Write("INFO", message); report(message); };
|
||||
write("Diagnoselog: " + (log.FilePath.Length == 0 ? "nicht verfuegbar" : log.FilePath));
|
||||
Action<string> write = message => ReportSafely(log, uiReport, "INFO", message);
|
||||
if (!string.IsNullOrEmpty(log.CreationError))
|
||||
write((log.IsFallback ? "WARNUNG: Primaeres Diagnoselog nicht verfuegbar; Temp-Fallback wird verwendet: " : "WARNUNG: Diagnoselog konnte nicht angelegt werden: ") + log.CreationError);
|
||||
if (log.FilePath.Length != 0)
|
||||
{
|
||||
lastInstallerLogDirectory = Path.GetDirectoryName(log.FilePath);
|
||||
write("Diagnoselog: " + log.FilePath);
|
||||
}
|
||||
|
||||
var sourceApplication = Path.Combine(packageDirectory, "application");
|
||||
var manifestPath = Path.Combine(packageDirectory, "application.manifest");
|
||||
var stagingDirectory = installDirectory + ".staging." + Guid.NewGuid().ToString("N");
|
||||
var backupDirectory = installDirectory + ".backup." + Guid.NewGuid().ToString("N");
|
||||
var hadExistingInstallation = Directory.Exists(installDirectory);
|
||||
var integrationSnapshot = registerWindowsIntegration ? CaptureWindowsIntegration() : null;
|
||||
WindowsIntegrationSnapshot integrationSnapshot = null;
|
||||
var backupCreated = false;
|
||||
var activated = false;
|
||||
var integrationMutationStarted = false;
|
||||
var phaseCode = "SETUP-INITIALIZATION";
|
||||
var phase = "Initialisierung";
|
||||
|
||||
try
|
||||
{
|
||||
write("Phase 1/6: Paketmanifest und SHA-256 pruefen.");
|
||||
Directory.CreateDirectory(dataDirectory);
|
||||
log.Write(
|
||||
"INFO",
|
||||
"event=setup_context package_directory=\"" + packageDirectory
|
||||
+ "\" source_application=\"" + sourceApplication
|
||||
+ "\" manifest=\"" + manifestPath
|
||||
+ "\" install_directory=\"" + installDirectory
|
||||
+ "\" data_directory=\"" + dataDirectory
|
||||
+ "\" staging_directory=\"" + stagingDirectory
|
||||
+ "\" backup_directory=\"" + backupDirectory
|
||||
+ "\" existing_installation=" + hadExistingInstallation
|
||||
+ " desktop_shortcut_requested=" + createDesktopShortcut
|
||||
+ " windows_integration=" + registerWindowsIntegration);
|
||||
LogDriveSpace(log, installDirectory);
|
||||
log.WriteFileDetails("setup_executable", Assembly.GetExecutingAssembly().Location);
|
||||
log.WriteFileDetails("existing_application", Path.Combine(installDirectory, ApplicationExeName));
|
||||
integrationSnapshot = registerWindowsIntegration ? CaptureWindowsIntegration() : null;
|
||||
if (integrationSnapshot != null)
|
||||
log.Write("INFO", "event=integration_snapshot registry_key_existed=" + integrationSnapshot.RegistryKeyExisted
|
||||
+ " registry_value_count=" + integrationSnapshot.RegistryValues.Count
|
||||
+ " desktop_shortcut=" + (integrationSnapshot.DesktopShortcut != null)
|
||||
+ " start_menu_shortcut=" + (integrationSnapshot.StartMenuShortcut != null)
|
||||
+ " uninstaller=" + (integrationSnapshot.Uninstaller != null));
|
||||
|
||||
phaseCode = "SETUP-PACKAGE-VALIDATION";
|
||||
phase = "Paketmanifest und SHA-256 pruefen";
|
||||
write("Phase 1/6: " + phase + ".");
|
||||
var files = PackageManifest.ValidateAndRead(sourceApplication, manifestPath);
|
||||
RequirePayload(files, ApplicationExeName);
|
||||
RequirePayload(files, ApplicationExeName + ".config");
|
||||
EnsureApplicationNotRunning();
|
||||
log.WriteFileDetails("package_manifest", manifestPath);
|
||||
foreach (var file in files)
|
||||
log.Write("INFO", "event=payload_validated relative_path=\"" + file.RelativePath + "\" size_bytes=" + file.Length + " sha256=" + file.Sha256);
|
||||
EnsureApplicationNotRunning(log);
|
||||
|
||||
write("Phase 2/6: Update in isoliertes Staging kopieren.");
|
||||
phaseCode = "SETUP-STAGING-VALIDATION";
|
||||
phase = "Update in isoliertes Staging kopieren und pruefen";
|
||||
write("Phase 2/6: " + phase + ".");
|
||||
CopyPayload(sourceApplication, stagingDirectory, files);
|
||||
var stagedExe = Path.Combine(stagingDirectory, ApplicationExeName);
|
||||
if (!selfTestRunner(stagedExe)) throw new InvalidOperationException("Der WMI-freie Self-Test der Staging-Version ist fehlgeschlagen.");
|
||||
RunAndValidateSelfTest(stagedExe, "staging", log);
|
||||
write("Staging-Self-Test erfolgreich.");
|
||||
|
||||
write("Phase 3/6: Vorhandene Version sichern und Staging atomar aktivieren.");
|
||||
phaseCode = "SETUP-ACTIVATION";
|
||||
phase = "Vorhandene Version sichern und Staging atomar aktivieren";
|
||||
write("Phase 3/6: " + phase + ". Ab hier beginnt die Systemaenderung.");
|
||||
if (hadExistingInstallation)
|
||||
{
|
||||
log.Write("INFO", "event=directory_move role=backup source=\"" + installDirectory + "\" target=\"" + backupDirectory + "\"");
|
||||
Directory.Move(installDirectory, backupDirectory);
|
||||
backupCreated = true;
|
||||
log.Write("INFO", "event=directory_move_complete role=backup");
|
||||
}
|
||||
log.Write("INFO", "event=directory_move role=activate source=\"" + stagingDirectory + "\" target=\"" + installDirectory + "\"");
|
||||
Directory.Move(stagingDirectory, installDirectory);
|
||||
activated = true;
|
||||
log.Write("INFO", "event=directory_move_complete role=activate");
|
||||
|
||||
write("Phase 4/6: Aktivierte Version erneut pruefen.");
|
||||
phaseCode = "SETUP-ACTIVATED-SELFTEST";
|
||||
phase = "Aktivierte Version erneut pruefen";
|
||||
write("Phase 4/6: " + phase + ".");
|
||||
var targetExe = Path.Combine(installDirectory, ApplicationExeName);
|
||||
if (!selfTestRunner(targetExe)) throw new InvalidOperationException("Der Self-Test der aktivierten Version ist fehlgeschlagen.");
|
||||
RunAndValidateSelfTest(targetExe, "activated", log);
|
||||
File.WriteAllText(
|
||||
Path.Combine(installDirectory, "install-state.txt"),
|
||||
"ProductVersion=" + ProductVersion + Environment.NewLine
|
||||
@@ -109,88 +162,186 @@ namespace BizTalkPlatformManagementTool.Setup
|
||||
+ "ManifestSha256=" + PackageManifest.Sha256(manifestPath) + Environment.NewLine,
|
||||
new UTF8Encoding(false));
|
||||
|
||||
write("Phase 5/6: Windows-Integration registrieren.");
|
||||
phaseCode = "SETUP-WINDOWS-INTEGRATION";
|
||||
phase = "Windows-Integration registrieren und verifizieren";
|
||||
write("Phase 5/6: " + phase + ".");
|
||||
if (registerWindowsIntegration)
|
||||
{
|
||||
integrationMutationStarted = true;
|
||||
RegisterWindowsIntegration(targetExe, createDesktopShortcut);
|
||||
ValidateWindowsIntegration(targetExe, createDesktopShortcut, log);
|
||||
}
|
||||
|
||||
write("Phase 6/6: Backup bereinigen.");
|
||||
TryDeleteDirectory(backupDirectory, write);
|
||||
phaseCode = "SETUP-CLEANUP";
|
||||
phase = "Backup bereinigen";
|
||||
write("Phase 6/6: " + phase + ".");
|
||||
var backupCleanupSucceeded = TryDeleteDirectory(backupDirectory, write);
|
||||
log.Write(
|
||||
backupCleanupSucceeded ? "INFO" : "WARN",
|
||||
"event=setup_completed result=" + (backupCleanupSucceeded ? "success" : "success_with_backup_cleanup_warning")
|
||||
+ " backup_directory_preserved=" + Directory.Exists(backupDirectory)
|
||||
+ " install_directory=\"" + installDirectory + "\"");
|
||||
write("Installation/Update erfolgreich abgeschlossen: " + installDirectory);
|
||||
write("Technische Installationsabnahme erfolgreich. BizTalk-WMI wurde bewusst nicht als Installerkriterium verwendet; bitte anschliessend in der Anwendung 'Diagnose' und einen Dry-run ausfuehren.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Write("ERROR", ex.ToString());
|
||||
log.WriteException(phaseCode, phase, ex);
|
||||
var rollbackErrors = new List<string>();
|
||||
try
|
||||
var mutationStarted = backupCreated || activated || integrationMutationStarted;
|
||||
var fileRollbackSucceeded = true;
|
||||
if (!mutationStarted)
|
||||
{
|
||||
if (activated && Directory.Exists(installDirectory)) Directory.Delete(installDirectory, true);
|
||||
if (backupCreated && Directory.Exists(backupDirectory)) Directory.Move(backupDirectory, installDirectory);
|
||||
write(backupCreated ? "Rollback: vorherige Programmversion wiederhergestellt." : "Rollback: unvollstaendige Neuinstallation entfernt.");
|
||||
write("Kein Rollback erforderlich: Der Fehler trat vor der ersten Systemaenderung auf.");
|
||||
log.Write("ERROR", "event=rollback_summary result=not_required mutation_started=false");
|
||||
}
|
||||
catch (Exception rollbackException)
|
||||
else
|
||||
{
|
||||
rollbackErrors.Add(rollbackException.Message);
|
||||
log.Write("ERROR", "Rollback files: " + rollbackException);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (registerWindowsIntegration)
|
||||
log.Write("WARN", "event=rollback_started activated=" + activated + " backup_created=" + backupCreated + " integration_mutation_started=" + integrationMutationStarted);
|
||||
try
|
||||
{
|
||||
RestoreWindowsIntegration(integrationSnapshot);
|
||||
if (activated && Directory.Exists(installDirectory))
|
||||
{
|
||||
log.Write("WARN", "event=rollback_step step=remove_activated_directory path=\"" + installDirectory + "\"");
|
||||
Directory.Delete(installDirectory, true);
|
||||
}
|
||||
if (backupCreated && Directory.Exists(backupDirectory))
|
||||
{
|
||||
log.Write("WARN", "event=rollback_step step=restore_backup source=\"" + backupDirectory + "\" target=\"" + installDirectory + "\"");
|
||||
Directory.Move(backupDirectory, installDirectory);
|
||||
}
|
||||
write(backupCreated ? "Rollback: vorherige Programmversion wiederhergestellt." : "Rollback: unvollstaendige Neuinstallation entfernt.");
|
||||
log.Write("WARN", "event=rollback_step_complete step=files result=success");
|
||||
}
|
||||
catch (Exception rollbackException)
|
||||
{
|
||||
fileRollbackSucceeded = false;
|
||||
rollbackErrors.Add("Programmdateien: " + rollbackException.Message);
|
||||
log.WriteException("SETUP-ROLLBACK-FILES", "Rollback Programmdateien", rollbackException);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (registerWindowsIntegration && integrationSnapshot != null)
|
||||
{
|
||||
log.Write("WARN", "event=rollback_step step=restore_windows_integration");
|
||||
RestoreWindowsIntegration(integrationSnapshot);
|
||||
log.Write("WARN", "event=rollback_step_complete step=windows_integration result=success");
|
||||
}
|
||||
}
|
||||
catch (Exception rollbackException)
|
||||
{
|
||||
rollbackErrors.Add("Windows-Integration: " + rollbackException.Message);
|
||||
log.WriteException("SETUP-ROLLBACK-INTEGRATION", "Rollback Windows-Integration", rollbackException);
|
||||
}
|
||||
}
|
||||
catch (Exception rollbackException)
|
||||
{
|
||||
rollbackErrors.Add(rollbackException.Message);
|
||||
log.Write("ERROR", "Rollback registration: " + rollbackException);
|
||||
}
|
||||
|
||||
TryDeleteDirectory(stagingDirectory, write);
|
||||
TryDeleteDirectory(backupDirectory, write);
|
||||
var suffix = rollbackErrors.Count == 0 ? " Rollback erfolgreich." : " Rollback-Fehler: " + string.Join(" | ", rollbackErrors);
|
||||
throw new InvalidOperationException("Installation/Update fehlgeschlagen." + suffix + " Ursache: " + ex.Message, ex);
|
||||
if (fileRollbackSucceeded && !Directory.Exists(installDirectory) && Directory.Exists(backupDirectory))
|
||||
{
|
||||
fileRollbackSucceeded = false;
|
||||
rollbackErrors.Add("Programmdateien: Backup blieb ohne aktives Installationsverzeichnis bestehen: " + backupDirectory);
|
||||
log.Write("ERROR", "event=rollback_invariant_failed active_directory=false backup_directory=true backup_path=\"" + backupDirectory + "\"");
|
||||
}
|
||||
var rollback = !mutationStarted
|
||||
? "Kein Rollback erforderlich."
|
||||
: rollbackErrors.Count == 0
|
||||
? "Rollback erfolgreich."
|
||||
: "Rollback unvollstaendig: " + string.Join(" | ", rollbackErrors);
|
||||
log.Write("ERROR", "event=rollback_summary result=\"" + rollback + "\" backup_preserved=" + Directory.Exists(backupDirectory));
|
||||
throw new InvalidOperationException(
|
||||
"Installation/Update fehlgeschlagen. Fehlercode=" + phaseCode + ". Phase='" + phase + "'. "
|
||||
+ rollback + " Ursache: " + ex.Message + " Diagnoselog: " + (log.FilePath.Length == 0 ? "nicht verfuegbar" : log.FilePath),
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes the active program directory and registered Windows integration.</summary>
|
||||
public void Uninstall(Action<string> report)
|
||||
{
|
||||
report = report ?? delegate { };
|
||||
Directory.CreateDirectory(dataDirectory);
|
||||
var uiReport = report ?? delegate { };
|
||||
var log = SetupOperationLog.Create(dataDirectory, "uninstall");
|
||||
Action<string> write = message => { log.Write("INFO", message); report(message); };
|
||||
var integrationSnapshot = registerWindowsIntegration ? CaptureWindowsIntegration() : null;
|
||||
Action<string> write = message => ReportSafely(log, uiReport, "INFO", message);
|
||||
if (!string.IsNullOrEmpty(log.CreationError))
|
||||
write((log.IsFallback ? "WARNUNG: Primaeres Diagnoselog nicht verfuegbar; Temp-Fallback wird verwendet: " : "WARNUNG: Diagnoselog konnte nicht angelegt werden: ") + log.CreationError);
|
||||
if (log.FilePath.Length != 0)
|
||||
{
|
||||
lastInstallerLogDirectory = Path.GetDirectoryName(log.FilePath);
|
||||
write("Diagnoselog: " + log.FilePath);
|
||||
}
|
||||
WindowsIntegrationSnapshot integrationSnapshot = null;
|
||||
var removalDirectory = installDirectory + ".removed." + Guid.NewGuid().ToString("N");
|
||||
var filesMoved = false;
|
||||
var phaseCode = "UNINSTALL-INITIALIZATION";
|
||||
var phase = "Initialisierung";
|
||||
try
|
||||
{
|
||||
EnsureApplicationNotRunning();
|
||||
Directory.CreateDirectory(dataDirectory);
|
||||
log.Write("INFO", "event=uninstall_context install_directory=\"" + installDirectory + "\" removal_directory=\"" + removalDirectory + "\" data_directory=\"" + dataDirectory + "\"");
|
||||
log.WriteFileDetails("installed_application", Path.Combine(installDirectory, ApplicationExeName));
|
||||
integrationSnapshot = registerWindowsIntegration ? CaptureWindowsIntegration() : null;
|
||||
|
||||
phaseCode = "UNINSTALL-RUNNING-APPLICATION";
|
||||
phase = "Laufende Anwendung ausschliessen";
|
||||
write("Phase 1/3: " + phase + ".");
|
||||
EnsureApplicationNotRunning(log);
|
||||
|
||||
phaseCode = "UNINSTALL-QUARANTINE";
|
||||
phase = "Programmverzeichnis deaktivieren";
|
||||
write("Phase 2/3: " + phase + ".");
|
||||
if (Directory.Exists(installDirectory))
|
||||
{
|
||||
log.Write("INFO", "event=directory_move role=uninstall_quarantine source=\"" + installDirectory + "\" target=\"" + removalDirectory + "\"");
|
||||
Directory.Move(installDirectory, removalDirectory);
|
||||
filesMoved = true;
|
||||
log.Write("INFO", "event=directory_move_complete role=uninstall_quarantine");
|
||||
}
|
||||
if (registerWindowsIntegration) RemoveWindowsIntegration();
|
||||
TryDeleteDirectory(removalDirectory, write);
|
||||
|
||||
phaseCode = "UNINSTALL-WINDOWS-INTEGRATION";
|
||||
phase = "Windows-Integration entfernen";
|
||||
write("Phase 3/3: " + phase + ".");
|
||||
if (registerWindowsIntegration) RemoveWindowsIntegration(log);
|
||||
var cleanupSucceeded = TryDeleteDirectory(removalDirectory, write);
|
||||
log.Write("INFO", "event=uninstall_completed result=" + (cleanupSucceeded ? "success" : "success_with_quarantine_cleanup_warning")
|
||||
+ " removal_directory_preserved=" + Directory.Exists(removalDirectory));
|
||||
write("Deinstallation erfolgreich. Installer-Logs bleiben erhalten: " + Path.Combine(dataDirectory, "InstallerLogs"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Write("ERROR", ex.ToString());
|
||||
log.WriteException(phaseCode, phase, ex);
|
||||
var rollbackErrors = new List<string>();
|
||||
if (filesMoved && !Directory.Exists(installDirectory) && Directory.Exists(removalDirectory))
|
||||
{
|
||||
try { Directory.Move(removalDirectory, installDirectory); }
|
||||
catch (Exception rollbackException) { log.Write("ERROR", "Uninstall rollback files: " + rollbackException); }
|
||||
try
|
||||
{
|
||||
Directory.Move(removalDirectory, installDirectory);
|
||||
log.Write("WARN", "event=uninstall_rollback_step step=restore_program_directory result=success");
|
||||
}
|
||||
catch (Exception rollbackException)
|
||||
{
|
||||
rollbackErrors.Add("Programmverzeichnis: " + rollbackException.Message);
|
||||
log.WriteException("UNINSTALL-ROLLBACK-FILES", "Deinstallationsrollback Programmverzeichnis", rollbackException);
|
||||
}
|
||||
}
|
||||
if (registerWindowsIntegration)
|
||||
if (registerWindowsIntegration && integrationSnapshot != null)
|
||||
{
|
||||
try { RestoreWindowsIntegration(integrationSnapshot); }
|
||||
catch (Exception rollbackException) { log.Write("ERROR", "Uninstall rollback registration: " + rollbackException); }
|
||||
try
|
||||
{
|
||||
RestoreWindowsIntegration(integrationSnapshot);
|
||||
log.Write("WARN", "event=uninstall_rollback_step step=restore_windows_integration result=success");
|
||||
}
|
||||
catch (Exception rollbackException)
|
||||
{
|
||||
rollbackErrors.Add("Windows-Integration: " + rollbackException.Message);
|
||||
log.WriteException("UNINSTALL-ROLLBACK-INTEGRATION", "Deinstallationsrollback Windows-Integration", rollbackException);
|
||||
}
|
||||
}
|
||||
throw;
|
||||
var rollback = rollbackErrors.Count == 0 ? "Rollback erfolgreich." : "Rollback unvollstaendig: " + string.Join(" | ", rollbackErrors);
|
||||
log.Write("ERROR", "event=uninstall_rollback_summary result=\"" + rollback + "\"");
|
||||
throw new InvalidOperationException(
|
||||
"Deinstallation fehlgeschlagen. Fehlercode=" + phaseCode + ". Phase='" + phase + "'. " + rollback
|
||||
+ " Ursache: " + ex.Message + " Diagnoselog: " + (log.FilePath.Length == 0 ? "nicht verfuegbar" : log.FilePath),
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,13 +373,19 @@ namespace BizTalkPlatformManagementTool.Setup
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveWindowsIntegration()
|
||||
private void RemoveWindowsIntegration(SetupOperationLog log)
|
||||
{
|
||||
TryDeleteFile(DesktopShortcutPath);
|
||||
DeleteFileIfExists(DesktopShortcutPath);
|
||||
var programsDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonPrograms), ProductName);
|
||||
TryDeleteFile(Path.Combine(programsDirectory, ProductName + ".lnk"));
|
||||
DeleteFileIfExists(Path.Combine(programsDirectory, ProductName + ".lnk"));
|
||||
if (Directory.Exists(programsDirectory) && Directory.GetFileSystemEntries(programsDirectory).Length == 0) Directory.Delete(programsDirectory);
|
||||
Registry.LocalMachine.DeleteSubKeyTree(UninstallKeyPath, false);
|
||||
using (var remainingKey = Registry.LocalMachine.OpenSubKey(UninstallKeyPath, false))
|
||||
{
|
||||
if (File.Exists(DesktopShortcutPath) || File.Exists(StartMenuShortcutPath) || remainingKey != null)
|
||||
throw new InvalidOperationException("Windows integration removal verification failed.");
|
||||
}
|
||||
log.Write("INFO", "event=windows_integration_removed registry_key=\"HKLM\\" + UninstallKeyPath + "\"");
|
||||
}
|
||||
|
||||
private static void CreateShortcut(string shortcutPath, string targetPath, string workingDirectory, string description)
|
||||
@@ -309,22 +466,47 @@ namespace BizTalkPlatformManagementTool.Setup
|
||||
{
|
||||
if (content == null)
|
||||
{
|
||||
TryDeleteFile(path);
|
||||
DeleteFileIfExists(path);
|
||||
return;
|
||||
}
|
||||
if (File.Exists(path) && File.ReadAllBytes(path).SequenceEqual(content)) return;
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
File.WriteAllBytes(path, content);
|
||||
}
|
||||
|
||||
private void EnsureApplicationNotRunning()
|
||||
private void EnsureApplicationNotRunning(SetupOperationLog log)
|
||||
{
|
||||
var target = Path.Combine(installDirectory, ApplicationExeName);
|
||||
if (!File.Exists(target)) return;
|
||||
if (!File.Exists(target))
|
||||
{
|
||||
log.Write("INFO", "event=running_application_check target_exists=false target=\"" + target + "\"");
|
||||
return;
|
||||
}
|
||||
var candidates = 0;
|
||||
foreach (var process in Process.GetProcessesByName(Path.GetFileNameWithoutExtension(ApplicationExeName)))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.Equals(Path.GetFullPath(process.MainModule.FileName), Path.GetFullPath(target), StringComparison.OrdinalIgnoreCase))
|
||||
candidates++;
|
||||
var processId = process.Id;
|
||||
log.Write("INFO", "event=running_application_candidate_detected pid=" + processId.ToString(CultureInfo.InvariantCulture));
|
||||
string processPath;
|
||||
try
|
||||
{
|
||||
processPath = process.MainModule.FileName;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.WriteException("SETUP-RUNNING-APPLICATION-INSPECTION", "Prozesspfad fuer PID " + processId + " pruefen", ex);
|
||||
throw new InvalidOperationException(
|
||||
"Ein gleichnamiger Prozess (PID " + processId + ") konnte nicht sicher geprueft werden. Beenden Sie ihn oder pruefen Sie Berechtigungen und versuchen Sie es erneut.",
|
||||
ex);
|
||||
}
|
||||
var matches = string.Equals(Path.GetFullPath(processPath), Path.GetFullPath(target), StringComparison.OrdinalIgnoreCase);
|
||||
log.Write("INFO", "event=running_application_candidate pid=" + processId.ToString(CultureInfo.InvariantCulture)
|
||||
+ " session_id=" + process.SessionId.ToString(CultureInfo.InvariantCulture)
|
||||
+ " path=\"" + processPath + "\" matches_target=" + matches);
|
||||
if (matches)
|
||||
throw new InvalidOperationException(ProductName + " is still running. Close it before installation or removal.");
|
||||
}
|
||||
finally
|
||||
@@ -332,28 +514,170 @@ namespace BizTalkPlatformManagementTool.Setup
|
||||
process.Dispose();
|
||||
}
|
||||
}
|
||||
log.Write("INFO", "event=running_application_check target_exists=true candidate_count=" + candidates.ToString(CultureInfo.InvariantCulture) + " result=not_running");
|
||||
}
|
||||
|
||||
private static bool RunApplicationSelfTest(string executable)
|
||||
private void RunAndValidateSelfTest(string executable, string label, SetupOperationLog log)
|
||||
{
|
||||
log.WriteFileDetails(label + "_self_test_executable", executable);
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
if (selfTestRunner != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var successful = selfTestRunner(executable);
|
||||
stopwatch.Stop();
|
||||
log.Write(
|
||||
successful ? "INFO" : "ERROR",
|
||||
"event=self_test_completed label=" + label
|
||||
+ " runner=injected elapsed_ms=" + stopwatch.ElapsedMilliseconds.ToString(CultureInfo.InvariantCulture)
|
||||
+ " result=" + (successful ? "success" : "failure"));
|
||||
if (!successful) throw new InvalidOperationException("Der WMI-freie Self-Test '" + label + "' ist fehlgeschlagen.");
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
stopwatch.Stop();
|
||||
log.WriteException("SETUP-SELFTEST-INJECTED", "Self-Test " + label, ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
var startInfo = new ProcessStartInfo(executable, "--self-test")
|
||||
{
|
||||
WorkingDirectory = Path.GetDirectoryName(executable),
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true
|
||||
};
|
||||
using (var process = Process.Start(startInfo))
|
||||
{
|
||||
if (process == null) return false;
|
||||
if (process == null) throw new InvalidOperationException("Self-Test '" + label + "' konnte nicht gestartet werden.");
|
||||
var outputRead = process.StandardOutput.ReadToEndAsync();
|
||||
var errorRead = process.StandardError.ReadToEndAsync();
|
||||
if (!process.WaitForExit(60000))
|
||||
{
|
||||
try { process.Kill(); } catch { }
|
||||
return false;
|
||||
var killResult = "sent";
|
||||
try { process.Kill(); }
|
||||
catch (Exception killException)
|
||||
{
|
||||
killResult = "failed";
|
||||
log.WriteException("SETUP-SELFTEST-KILL", "Self-Test " + label + " nach Timeout beenden", killException);
|
||||
}
|
||||
var exitedAfterKill = false;
|
||||
try { exitedAfterKill = process.WaitForExit(5000); } catch { }
|
||||
var streamsCompleted = false;
|
||||
try
|
||||
{
|
||||
streamsCompleted = System.Threading.Tasks.Task.WaitAll(new System.Threading.Tasks.Task[] { outputRead, errorRead }, 5000);
|
||||
}
|
||||
catch (Exception captureException)
|
||||
{
|
||||
log.WriteException("SETUP-SELFTEST-CAPTURE", "Self-Test " + label + " Ausgabe nach Timeout erfassen", captureException);
|
||||
}
|
||||
stopwatch.Stop();
|
||||
log.Write(
|
||||
"ERROR",
|
||||
"event=self_test_timeout label=" + label
|
||||
+ " timeout_ms=60000 elapsed_ms=" + stopwatch.ElapsedMilliseconds.ToString(CultureInfo.InvariantCulture)
|
||||
+ " kill_result=" + killResult
|
||||
+ " exited_after_kill=" + exitedAfterKill
|
||||
+ " streams_completed=" + streamsCompleted
|
||||
+ " stdout=\"" + (outputRead.Status == System.Threading.Tasks.TaskStatus.RanToCompletion ? outputRead.Result : "[capture incomplete]")
|
||||
+ "\" stderr=\"" + (errorRead.Status == System.Threading.Tasks.TaskStatus.RanToCompletion ? errorRead.Result : "[capture incomplete]") + "\"");
|
||||
throw new TimeoutException("Self-Test '" + label + "' hat das Zeitlimit von 60 Sekunden ueberschritten.");
|
||||
}
|
||||
System.Threading.Tasks.Task.WaitAll(outputRead, errorRead);
|
||||
stopwatch.Stop();
|
||||
var output = outputRead.Result;
|
||||
var error = errorRead.Result;
|
||||
log.Write(
|
||||
process.ExitCode == 0 ? "INFO" : "ERROR",
|
||||
"event=self_test_completed label=" + label
|
||||
+ " runner=process elapsed_ms=" + stopwatch.ElapsedMilliseconds.ToString(CultureInfo.InvariantCulture)
|
||||
+ " exit_code=" + process.ExitCode.ToString(CultureInfo.InvariantCulture)
|
||||
+ " exit_code_hex=0x" + process.ExitCode.ToString("X8", CultureInfo.InvariantCulture)
|
||||
+ " stdout=\"" + output + "\" stderr=\"" + error + "\"");
|
||||
if (process.ExitCode != 0 || output.IndexOf("SELF_TEST_OK", StringComparison.Ordinal) < 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Self-Test '" + label + "' fehlgeschlagen. Exitcode=" + process.ExitCode
|
||||
+ " (0x" + process.ExitCode.ToString("X8", CultureInfo.InvariantCulture) + ")"
|
||||
+ ", Stdout=" + CompactProcessText(output)
|
||||
+ ", Stderr=" + CompactProcessText(error) + ".");
|
||||
}
|
||||
return process.ExitCode == 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateWindowsIntegration(string targetExe, bool desktopRequested, SetupOperationLog log)
|
||||
{
|
||||
if (!File.Exists(StartMenuShortcutPath)) throw new FileNotFoundException("Start menu shortcut was not created.", StartMenuShortcutPath);
|
||||
if (desktopRequested != File.Exists(DesktopShortcutPath))
|
||||
throw new InvalidOperationException("Desktop shortcut state does not match the setup selection. Requested=" + desktopRequested + ".");
|
||||
if (!File.Exists(UninstallerPath)) throw new FileNotFoundException("Uninstaller was not installed.", UninstallerPath);
|
||||
|
||||
using (var key = Registry.LocalMachine.OpenSubKey(UninstallKeyPath, false))
|
||||
{
|
||||
if (key == null) throw new InvalidOperationException("Windows uninstall registry key is missing after registration.");
|
||||
RequireRegistryValue(key, "DisplayName", ProductName);
|
||||
RequireRegistryValue(key, "DisplayVersion", ProductVersion);
|
||||
RequireRegistryValue(key, "InstallLocation", installDirectory);
|
||||
RequireRegistryValue(key, "DisplayIcon", targetExe);
|
||||
RequireRegistryValue(key, "UninstallString", "\"" + UninstallerPath + "\" --uninstall");
|
||||
}
|
||||
|
||||
log.WriteFileDetails("registered_uninstaller", UninstallerPath);
|
||||
log.WriteFileDetails("start_menu_shortcut", StartMenuShortcutPath);
|
||||
if (desktopRequested) log.WriteFileDetails("desktop_shortcut", DesktopShortcutPath);
|
||||
log.Write("INFO", "event=windows_integration_validated registry_key=\"HKLM\\" + UninstallKeyPath + "\" desktop_shortcut=" + desktopRequested);
|
||||
}
|
||||
|
||||
private static void RequireRegistryValue(RegistryKey key, string name, string expected)
|
||||
{
|
||||
var actual = Convert.ToString(key.GetValue(name, null, RegistryValueOptions.DoNotExpandEnvironmentNames), CultureInfo.InvariantCulture);
|
||||
if (!string.Equals(actual, expected, StringComparison.OrdinalIgnoreCase))
|
||||
throw new InvalidOperationException("Uninstall registry value '" + name + "' is invalid. Expected='" + expected + "', actual='" + actual + "'.");
|
||||
}
|
||||
|
||||
private static void ReportSafely(SetupOperationLog log, Action<string> report, string level, string message)
|
||||
{
|
||||
log.Write(level, message);
|
||||
try
|
||||
{
|
||||
report(message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Write("WARN", "event=ui_report_failed " + SetupOperationLog.FormatException(ex));
|
||||
}
|
||||
}
|
||||
|
||||
private static void LogDriveSpace(SetupOperationLog log, string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
var root = Path.GetPathRoot(Path.GetFullPath(path));
|
||||
var drive = new DriveInfo(root);
|
||||
log.Write(
|
||||
"INFO",
|
||||
"event=drive_space root=\"" + root + "\" format=\"" + drive.DriveFormat + "\""
|
||||
+ " available_bytes=" + drive.AvailableFreeSpace.ToString(CultureInfo.InvariantCulture)
|
||||
+ " total_bytes=" + drive.TotalSize.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Write("WARN", "event=drive_space_failed path=\"" + path + "\" " + SetupOperationLog.FormatException(ex));
|
||||
}
|
||||
}
|
||||
|
||||
private static string CompactProcessText(string value)
|
||||
{
|
||||
var text = (value ?? string.Empty).Replace("\r", string.Empty).Replace("\n", " | ").Trim();
|
||||
const int maxLength = 2000;
|
||||
return text.Length <= maxLength ? text : text.Substring(0, maxLength) + "...[truncated]";
|
||||
}
|
||||
|
||||
private static void CopyPayload(string sourceRoot, string targetRoot, IEnumerable<PackageFile> files)
|
||||
{
|
||||
Directory.CreateDirectory(targetRoot);
|
||||
@@ -374,15 +698,23 @@ namespace BizTalkPlatformManagementTool.Setup
|
||||
throw new InvalidDataException("Required payload file is missing from the manifest: " + relativePath);
|
||||
}
|
||||
|
||||
private static void TryDeleteDirectory(string path, Action<string> report)
|
||||
private static bool TryDeleteDirectory(string path, Action<string> report)
|
||||
{
|
||||
try { if (Directory.Exists(path)) Directory.Delete(path, true); }
|
||||
catch (Exception ex) { report("WARNUNG: Verzeichnis konnte nicht bereinigt werden: " + path + " - " + ex.Message); }
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(path)) Directory.Delete(path, true);
|
||||
return !Directory.Exists(path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
report("WARNUNG: Verzeichnis konnte nicht bereinigt werden: " + path + " - " + ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryDeleteFile(string path)
|
||||
private static void DeleteFileIfExists(string path)
|
||||
{
|
||||
try { if (File.Exists(path)) File.Delete(path); } catch { }
|
||||
if (File.Exists(path)) File.Delete(path);
|
||||
}
|
||||
|
||||
private static string DesktopShortcutPath
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
@@ -41,7 +43,7 @@ namespace BizTalkPlatformManagementTool.Setup
|
||||
{
|
||||
AutoSize = true,
|
||||
Font = new Font(Font.FontFamily, 14, FontStyle.Bold),
|
||||
Text = "BizTalk Platform Management Tool 2.1.0"
|
||||
Text = "BizTalk Platform Management Tool 2.1.1"
|
||||
});
|
||||
root.Controls.Add(new Label
|
||||
{
|
||||
@@ -66,6 +68,8 @@ namespace BizTalkPlatformManagementTool.Setup
|
||||
var buttons = new FlowLayoutPanel { AutoSize = true, Dock = DockStyle.Fill, FlowDirection = FlowDirection.RightToLeft };
|
||||
var closeButton = new Button { Text = "Schliessen", AutoSize = true };
|
||||
closeButton.Click += (sender, args) => Close();
|
||||
var logsButton = new Button { Text = "Diagnoselogs oeffnen", AutoSize = true };
|
||||
logsButton.Click += OpenLogs;
|
||||
installButton.Text = engine.IsInstalled ? "Update installieren" : "Installieren";
|
||||
installButton.AutoSize = true;
|
||||
installButton.Enabled = engine.HasInstallPayload && !uninstallMode;
|
||||
@@ -75,6 +79,7 @@ namespace BizTalkPlatformManagementTool.Setup
|
||||
uninstallButton.Enabled = engine.IsInstalled;
|
||||
uninstallButton.Click += (sender, args) => Run(true);
|
||||
buttons.Controls.Add(closeButton);
|
||||
buttons.Controls.Add(logsButton);
|
||||
buttons.Controls.Add(uninstallButton);
|
||||
buttons.Controls.Add(installButton);
|
||||
root.Controls.Add(buttons);
|
||||
@@ -84,6 +89,19 @@ namespace BizTalkPlatformManagementTool.Setup
|
||||
else if (!engine.HasInstallPayload) Append("Kein Installationspayload neben Setup.exe gefunden. Dieser Aufruf erlaubt nur die Deinstallation.");
|
||||
}
|
||||
|
||||
private void OpenLogs(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(engine.InstallerLogDirectory);
|
||||
Process.Start("explorer.exe", engine.InstallerLogDirectory);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(this, "Diagnoseordner konnte nicht geoeffnet werden: " + ex.Message + "\n\n" + engine.InstallerLogDirectory, "Diagnoselogs", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
private void Run(bool uninstall)
|
||||
{
|
||||
if (uninstall && MessageBox.Show(this, "BizTalk Platform Management Tool wirklich deinstallieren?", "Deinstallation bestaetigen", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) != DialogResult.Yes)
|
||||
|
||||
@@ -8,6 +8,6 @@ using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyProduct("BizTalk Platform Management Tool")]
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: Guid("675b68a9-bd80-46a5-b8c5-3b11b0b374e2")]
|
||||
[assembly: AssemblyVersion("2.1.0.0")]
|
||||
[assembly: AssemblyFileVersion("2.1.0.0")]
|
||||
[assembly: AssemblyVersion("2.1.1.0")]
|
||||
[assembly: AssemblyFileVersion("2.1.1.0")]
|
||||
[assembly: InternalsVisibleTo("BizTalkPlatformManagementTool.Tests")]
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Security.Principal;
|
||||
using System.Text;
|
||||
|
||||
namespace BizTalkPlatformManagementTool.Setup
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes a durable, single-line diagnostic trace for one setup operation.
|
||||
/// No credential or other secret is accepted by this component.
|
||||
/// </summary>
|
||||
internal sealed class SetupOperationLog
|
||||
{
|
||||
private readonly object sync = new object();
|
||||
@@ -15,33 +21,182 @@ namespace BizTalkPlatformManagementTool.Setup
|
||||
}
|
||||
|
||||
public string FilePath { get; private set; }
|
||||
public string CreationError { get; private set; }
|
||||
public bool IsFallback { get; private set; }
|
||||
|
||||
public static SetupOperationLog Create(string dataDirectory, string operation)
|
||||
{
|
||||
try
|
||||
{
|
||||
var directory = Path.Combine(dataDirectory, "InstallerLogs");
|
||||
Directory.CreateDirectory(directory);
|
||||
var path = Path.Combine(directory, "setup-" + DateTime.Now.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture) + "-" + operation + ".log");
|
||||
return new SetupOperationLog(path);
|
||||
return CreateInDirectory(Path.Combine(dataDirectory, "InstallerLogs"), operation);
|
||||
}
|
||||
catch
|
||||
catch (Exception primaryException)
|
||||
{
|
||||
return new SetupOperationLog(string.Empty);
|
||||
try
|
||||
{
|
||||
var fallback = CreateInDirectory(
|
||||
Path.Combine(Path.GetTempPath(), "BizTalkPlatformManagementTool", "InstallerLogs"),
|
||||
operation);
|
||||
fallback.CreationError = FormatException(primaryException);
|
||||
fallback.IsFallback = true;
|
||||
fallback.Write(
|
||||
"WARN",
|
||||
"event=primary_log_creation_failed requested_data_directory=\"" + dataDirectory + "\" "
|
||||
+ fallback.CreationError);
|
||||
return fallback;
|
||||
}
|
||||
catch (Exception fallbackException)
|
||||
{
|
||||
return new SetupOperationLog(string.Empty)
|
||||
{
|
||||
CreationError = "primary={" + FormatException(primaryException) + "} fallback={" + FormatException(fallbackException) + "}"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static SetupOperationLog CreateInDirectory(string directory, string operation)
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
CleanupOldLogs(directory);
|
||||
var path = Path.Combine(
|
||||
directory,
|
||||
"setup-" + DateTime.UtcNow.ToString("yyyyMMdd-HHmmssfff", CultureInfo.InvariantCulture)
|
||||
+ "-" + Process.GetCurrentProcess().Id.ToString(CultureInfo.InvariantCulture)
|
||||
+ "-" + operation + ".log");
|
||||
using (new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.Read)) { }
|
||||
var log = new SetupOperationLog(path);
|
||||
log.Write(
|
||||
"INFO",
|
||||
"event=setup_started operation=" + operation
|
||||
+ " setup_version=" + typeof(SetupOperationLog).Assembly.GetName().Version
|
||||
+ " os=\"" + Environment.OSVersion.VersionString + "\""
|
||||
+ " process_bitness=" + (Environment.Is64BitProcess ? "64" : "32")
|
||||
+ " os_bitness=" + (Environment.Is64BitOperatingSystem ? "64" : "32")
|
||||
+ " clr=" + Environment.Version
|
||||
+ " machine=\"" + Environment.MachineName + "\""
|
||||
+ " identity=\"" + CurrentIdentity() + "\""
|
||||
+ " elevated=" + IsElevated()
|
||||
+ " base_directory=\"" + AppDomain.CurrentDomain.BaseDirectory + "\"");
|
||||
return log;
|
||||
}
|
||||
|
||||
public void Write(string level, string message)
|
||||
{
|
||||
if (string.IsNullOrEmpty(FilePath)) return;
|
||||
var line = "[" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture) + "][" + level + "] " + (message ?? string.Empty) + Environment.NewLine;
|
||||
var line = DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture)
|
||||
+ " level=" + (level ?? "INFO").ToUpperInvariant()
|
||||
+ " " + SingleLine(message)
|
||||
+ Environment.NewLine;
|
||||
try
|
||||
{
|
||||
lock (sync) File.AppendAllText(FilePath, line, new UTF8Encoding(false));
|
||||
lock (sync)
|
||||
{
|
||||
File.AppendAllText(FilePath, line, new UTF8Encoding(false));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Setup logging must not hide the actual installation result.
|
||||
// Diagnostic logging must never replace the actual setup outcome.
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteException(string errorCode, string phase, Exception exception)
|
||||
{
|
||||
Write("ERROR", "event=exception error_code=" + errorCode + " phase=\"" + phase + "\" " + FormatException(exception));
|
||||
}
|
||||
|
||||
public void WriteFileDetails(string label, string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
Write("INFO", "event=file_details label=\"" + label + "\" path=\"" + path + "\" exists=false");
|
||||
return;
|
||||
}
|
||||
var info = new FileInfo(path);
|
||||
var version = FileVersionInfo.GetVersionInfo(path).FileVersion ?? "(unknown)";
|
||||
Write(
|
||||
"INFO",
|
||||
"event=file_details label=\"" + label + "\" path=\"" + path + "\" exists=true"
|
||||
+ " size_bytes=" + info.Length.ToString(CultureInfo.InvariantCulture)
|
||||
+ " modified_utc=" + info.LastWriteTimeUtc.ToString("o", CultureInfo.InvariantCulture)
|
||||
+ " file_version=\"" + version + "\""
|
||||
+ " sha256=" + PackageManifest.Sha256(path));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Write("WARN", "event=file_details_failed label=\"" + label + "\" path=\"" + path + "\" " + FormatException(ex));
|
||||
}
|
||||
}
|
||||
|
||||
internal static string FormatException(Exception exception)
|
||||
{
|
||||
var result = new StringBuilder();
|
||||
var current = exception;
|
||||
var depth = 0;
|
||||
while (current != null && depth < 12)
|
||||
{
|
||||
if (depth > 0) result.Append(" | inner[").Append(depth).Append("] ");
|
||||
result.Append("exception_type=").Append(current.GetType().FullName)
|
||||
.Append(" hresult=0x").Append(current.HResult.ToString("X8", CultureInfo.InvariantCulture))
|
||||
.Append(" message=\"").Append(current.Message).Append('"');
|
||||
if (!string.IsNullOrWhiteSpace(current.StackTrace)) result.Append(" stack=\"").Append(current.StackTrace).Append('"');
|
||||
current = current.InnerException;
|
||||
depth++;
|
||||
}
|
||||
return SingleLine(result.ToString());
|
||||
}
|
||||
|
||||
private static string CurrentIdentity()
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var identity = WindowsIdentity.GetCurrent())
|
||||
{
|
||||
return identity == null ? "(unknown)" : identity.Name;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "(unknown)";
|
||||
}
|
||||
}
|
||||
|
||||
private static string IsElevated()
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var identity = WindowsIdentity.GetCurrent())
|
||||
{
|
||||
return identity != null && new WindowsPrincipal(identity).IsInRole(WindowsBuiltInRole.Administrator) ? "true" : "false";
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
private static string SingleLine(string value)
|
||||
{
|
||||
return (value ?? string.Empty).Replace("\r", "\\r").Replace("\n", "\\n").Replace("\t", "\\t");
|
||||
}
|
||||
|
||||
private static void CleanupOldLogs(string directory)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cutoff = DateTime.UtcNow.AddDays(-90);
|
||||
foreach (var file in Directory.GetFiles(directory, "setup-*.log"))
|
||||
{
|
||||
if (File.GetLastWriteTimeUtc(file) < cutoff) File.Delete(file);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Retention cleanup is best-effort.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="2.1.0.0" name="BizTalkPlatformManagementTool.Setup" />
|
||||
<assemblyIdentity version="2.1.1.0" name="BizTalkPlatformManagementTool.Setup" />
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security><requestedPrivileges><requestedExecutionLevel level="requireAdministrator" uiAccess="false" /></requestedPrivileges></security>
|
||||
</trustInfo>
|
||||
|
||||
@@ -8,5 +8,5 @@ using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyCopyright("Copyright © 2026")]
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: Guid("2c5b2c0a-f407-46c2-9e3b-1fa09fa8445a")]
|
||||
[assembly: AssemblyVersion("2.1.0.0")]
|
||||
[assembly: AssemblyFileVersion("2.1.0.0")]
|
||||
[assembly: AssemblyVersion("2.1.1.0")]
|
||||
[assembly: AssemblyFileVersion("2.1.1.0")]
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
/// <summary>
|
||||
/// Current tool version written into generated snapshots.
|
||||
/// </summary>
|
||||
public const string Version = "2.1.0-net461";
|
||||
public const string Version = "2.1.1-net461";
|
||||
|
||||
/// <summary>
|
||||
/// Fallback application name used when WMI does not expose an application property.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="2.1.0.0" name="BizTalkPlatformManagementTool" />
|
||||
<assemblyIdentity version="2.1.1.0" name="BizTalkPlatformManagementTool" />
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
|
||||
Reference in New Issue
Block a user