Add recovery preflight and ACC restart runbook
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user