Add recovery preflight and ACC restart runbook

This commit is contained in:
2026-08-19 18:13:03 +02:00
parent 9ce7e8d45a
commit 1228f71765
16 changed files with 296 additions and 24 deletions
@@ -23,7 +23,7 @@ namespace BizTalkPlatformManagementTool.Setup
private const string ProductName = "BizTalk Platform Management Tool";
/// <summary>Aktuelle Produktversion des Installers und Uninstall-Eintrags.</summary>
private const string ProductVersion = "2.2.0";
private const string ProductVersion = "2.2.1";
/// <summary>
/// Wartezeiten zwischen Wiederholungen atomarer Verzeichnisverschiebungen.
@@ -65,7 +65,7 @@ namespace BizTalkPlatformManagementTool.Setup
{
AutoSize = true,
Font = new Font(Font.FontFamily, 14, FontStyle.Bold),
Text = "BizTalk Platform Management Tool 2.2.0"
Text = "BizTalk Platform Management Tool 2.2.1"
});
root.Controls.Add(new Label
{
@@ -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.2.0.0")]
[assembly: AssemblyFileVersion("2.2.0.0")]
[assembly: AssemblyVersion("2.2.1.0")]
[assembly: AssemblyFileVersion("2.2.1.0")]
[assembly: InternalsVisibleTo("BizTalkPlatformManagementTool.Tests")]
@@ -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.2.0.0" name="BizTalkPlatformManagementTool.Setup" />
<assemblyIdentity version="2.2.1.0" name="BizTalkPlatformManagementTool.Setup" />
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security><requestedPrivileges><requestedExecutionLevel level="requireAdministrator" uiAccess="false" /></requestedPrivileges></security>
</trustInfo>
@@ -9,6 +9,6 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: ComVisible(false)]
[assembly: Guid("2c5b2c0a-f407-46c2-9e3b-1fa09fa8445a")]
[assembly: AssemblyVersion("2.2.0.0")]
[assembly: AssemblyFileVersion("2.2.0.0")]
[assembly: AssemblyVersion("2.2.1.0")]
[assembly: AssemblyFileVersion("2.2.1.0")]
[assembly: InternalsVisibleTo("BizTalkPlatformManagementTool.Tests")]
@@ -16,7 +16,7 @@ namespace BizTalkPlatformManagementTool.Services
/// <summary>
/// Current tool version written into generated snapshots.
/// </summary>
public const string Version = "2.2.0-net461";
public const string Version = "2.2.1-net461";
/// <summary>
/// Fallback application name used when WMI does not expose an application property.
@@ -110,6 +110,11 @@ namespace BizTalkPlatformManagementTool.Ui
/// </summary>
private Button _restoreButton;
/// <summary>
/// Button that validates the selected recovery snapshot without contacting WMI.
/// </summary>
private Button _validateStateButton;
/// <summary>
/// Button that performs a state-aware best-effort recovery from an existing snapshot.
/// </summary>
@@ -220,6 +225,8 @@ namespace BizTalkPlatformManagementTool.Ui
var browseButton = new Button { Text = "Browse...", Dock = DockStyle.Fill, Margin = new Padding(8, 5, 8, 5) };
browseButton.Click += BrowseButtonClick;
var stateBrowseButton = new Button { Text = "State...", Dock = DockStyle.Fill, Margin = new Padding(8, 5, 8, 5) };
stateBrowseButton.Click += StateBrowseButtonClick;
_environmentStatusLabel.Dock = DockStyle.Fill;
_environmentStatusLabel.Margin = new Padding(12, 5, 0, 5);
@@ -239,6 +246,7 @@ namespace BizTalkPlatformManagementTool.Ui
panel.Controls.Add(_stateFileTextBox, 1, 1);
panel.SetColumnSpan(_stateFileTextBox, 2);
panel.Controls.Add(_dryRunCheckBox, 3, 1);
panel.Controls.Add(stateBrowseButton, 4, 1);
panel.Controls.Add(Label("Poll sec."), 7, 1);
panel.Controls.Add(_pollInput, 8, 1);
panel.Controls.Add(_environmentStatusLabel, 9, 0);
@@ -260,6 +268,7 @@ namespace BizTalkPlatformManagementTool.Ui
_compareButton = ActionButton("Compare", CompareClick);
_shutdownButton = ActionButton("Shutdown", ShutdownClick);
_restoreButton = ActionButton("Restore", RestoreClick);
_validateStateButton = ActionButton("Validate State", ValidateStateClick);
_emergencyRestoreButton = ActionButton("Emergency Restore", EmergencyRestoreClick);
_emergencyRestoreButton.Width = 142;
_clearButton = ActionButton("Clear", ClearClick);
@@ -271,6 +280,7 @@ namespace BizTalkPlatformManagementTool.Ui
panel.Controls.Add(_compareButton);
panel.Controls.Add(_shutdownButton);
panel.Controls.Add(_restoreButton);
panel.Controls.Add(_validateStateButton);
panel.Controls.Add(_emergencyRestoreButton);
panel.Controls.Add(_clearButton);
panel.Controls.Add(_closeButton);
@@ -425,6 +435,39 @@ namespace BizTalkPlatformManagementTool.Ui
});
}
/// <summary>
/// Validates the selected state file and target server without opening a WMI connection
/// or changing any runtime state.
/// </summary>
/// <param name="sender">The control that raised the event.</param>
/// <param name="e">The event arguments.</param>
private void ValidateStateClick(object sender, EventArgs e)
{
var options = GetOptions();
RunAsync("Validating recovery state file...", () =>
{
var sourcePath = ResolveStateFile(options);
var snapshot = JsonFileStore.Load<BizTalkSnapshot>(sourcePath);
SnapshotValidator.EnsureServerMatches(snapshot, options.Server);
ShowSnapshot(snapshot);
var receiveLocationCount = snapshot.Applications.Sum(x => x.ReceiveLocations.Count);
var sendPortCount = snapshot.Applications.Sum(x => x.SendPorts.Count);
var orchestrationCount = snapshot.Applications.Sum(x => x.Orchestrations.Count);
var summary = "Recovery state is valid. Source=" + sourcePath
+ "; SnapshotVersion=" + (snapshot.ToolVersion ?? "(not recorded)")
+ "; CreatedAt=" + (snapshot.CreatedAt ?? "(not recorded)")
+ "; Server=" + snapshot.Server
+ "; Applications=" + snapshot.Applications.Count
+ "; HostInstances=" + snapshot.HostInstances.Count
+ "; ReceiveLocations=" + receiveLocationCount
+ "; SendPorts=" + sendPortCount
+ "; Orchestrations=" + orchestrationCount + ".";
_logger.Success(summary);
ShowStateValidationSummary(summary);
});
}
/// <summary>
/// Handles emergency recovery from the preserved state file without creating
/// or overwriting <c>before.json</c>.
@@ -454,7 +497,24 @@ namespace BizTalkPlatformManagementTool.Ui
}
var report = _service.ExecutePlan(plan, options);
CapturePostOperationSnapshot(options, report, "emergency-restore-after-" + stamp + ".json");
var after = CapturePostOperationSnapshot(options, report, "emergency-restore-after-" + stamp + ".json");
if (after != null)
{
try
{
var diff = SnapshotComparer.Compare(snapshot, after);
var diffPath = _service.SaveDiff(options.OutputDirectory, "emergency-restore-diff-" + stamp + ".json", diff);
_logger.Success("Saved emergency recovery target/actual comparison: " + diffPath);
}
catch (Exception ex)
{
var error = "Emergency recovery comparison failed: " + FormatException(ex);
report.PostSnapshotError = string.IsNullOrWhiteSpace(report.PostSnapshotError)
? error
: report.PostSnapshotError + " | " + error;
_logger.Error(error + " The execution report will still be saved.");
}
}
var reportPath = _service.SaveExecutionReport(options.OutputDirectory, "emergency-restore-result-" + stamp + ".json", report);
ShowExecutionReport(report);
ThrowWhenOperatorReviewIsRequired(report, reportPath);
@@ -467,22 +527,24 @@ namespace BizTalkPlatformManagementTool.Ui
/// <param name="options">The operation options.</param>
/// <param name="report">The report that receives a snapshot error.</param>
/// <param name="fileName">The snapshot file name.</param>
private void CapturePostOperationSnapshot(OperationOptions options, OperationExecutionReport report, string fileName)
private BizTalkSnapshot CapturePostOperationSnapshot(OperationOptions options, OperationExecutionReport report, string fileName)
{
if (options.DryRun)
{
return;
return null;
}
try
{
var after = _service.CreateSnapshot(options.Server);
_service.SaveSnapshot(options.OutputDirectory, fileName, after);
ShowSnapshot(after);
return after;
}
catch (Exception ex)
{
report.PostSnapshotError = FormatException(ex);
_logger.Error("Post-operation snapshot failed, but the execution report will still be saved. Error: " + report.PostSnapshotError);
return null;
}
}
@@ -788,6 +850,7 @@ namespace BizTalkPlatformManagementTool.Ui
_compareButton.Enabled = !busy;
_shutdownButton.Enabled = !busy;
_restoreButton.Enabled = !busy;
_validateStateButton.Enabled = !busy;
_emergencyRestoreButton.Enabled = !busy;
_clearButton.Enabled = !busy;
_closeButton.Enabled = !busy;
@@ -812,6 +875,54 @@ namespace BizTalkPlatformManagementTool.Ui
}
}
/// <summary>
/// Handles selection of an existing JSON recovery snapshot.
/// </summary>
/// <param name="sender">The control that raised the event.</param>
/// <param name="e">The event arguments.</param>
private void StateBrowseButtonClick(object sender, EventArgs e)
{
using (var dialog = new OpenFileDialog())
{
dialog.Title = "Select preserved BizTalk state snapshot";
dialog.Filter = "BizTalk snapshot (*.json)|*.json|All files (*.*)|*.*";
dialog.CheckFileExists = true;
dialog.Multiselect = false;
var currentPath = ResolveStateFile(GetOptions());
if (File.Exists(currentPath))
{
dialog.InitialDirectory = Path.GetDirectoryName(currentPath);
dialog.FileName = Path.GetFileName(currentPath);
}
if (dialog.ShowDialog(this) == DialogResult.OK)
{
_stateFileTextBox.Text = dialog.FileName;
}
}
}
/// <summary>
/// Shows the successful WMI-free state-file validation result to the operator.
/// </summary>
/// <param name="summary">The complete validation summary.</param>
private void ShowStateValidationSummary(string summary)
{
Action show = () => MessageBox.Show(
this,
summary + "\n\nNo runtime state was changed and no WMI connection was opened.",
"Recovery State Valid",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
if (InvokeRequired)
{
Invoke(show);
}
else
{
show();
}
}
/// <summary>
/// Executes a UI update on the UI thread when required.
/// </summary>
@@ -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.2.0.0" name="BizTalkPlatformManagementTool" />
<assemblyIdentity version="2.2.1.0" name="BizTalkPlatformManagementTool" />
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>