Fixed host instances WMI query error when dealing with shutdown / restart
This commit is contained in:
@@ -13,6 +13,9 @@ obj/
|
||||
*.log
|
||||
*.json
|
||||
*.html
|
||||
*.zip
|
||||
*.txt
|
||||
*.png
|
||||
work/
|
||||
release/*
|
||||
|
||||
|
||||
+11
-3
@@ -1,7 +1,7 @@
|
||||
# BizTalk Platform Management Tool Dokumentation
|
||||
|
||||
**Stand:** 2026-04-27
|
||||
**Implementierung:** C# WinForms, .NET Framework 4.7.1
|
||||
**Implementierung:** C# WinForms, .NET Framework 4.6.1
|
||||
**Archivierte PowerShell-Version:** `archive/powershell/BizTalkPlatformManagementTool.ps1`
|
||||
|
||||
## Zweck
|
||||
@@ -40,10 +40,13 @@ Das BizTalk Platform Management Tool unterstützt kontrollierte Wartungsfenster
|
||||
- Dry-run ist standardmäßig aktiviert.
|
||||
- Echte Shutdown-/Restore-Aktionen verlangen bei deaktiviertem Dry-run eine zusätzliche Bestätigung.
|
||||
- Jede Operation schreibt Einträge in das sichtbare Operation Log.
|
||||
- Zusätzlich wird neben der EXE eine tägliche Logdatei `BizTalkPlatformManagementTool-yyyy-MM-dd.log` geschrieben.
|
||||
- Logdateien werden rollierend für den aktuellen Tag plus vier vorherige Tage vorgehalten.
|
||||
- Operationspläne werden vor Laufzeitänderungen gespeichert.
|
||||
- WMI-Methodenrückgaben werden geprüft.
|
||||
- Wartezeiten nutzen konfigurierbare Timeout- und Polling-Werte.
|
||||
- Host Instances auf anderen Servern werden übersprungen und als Warnung protokolliert.
|
||||
- Echte Shutdown-/Restore-Schritte protokollieren WMI-Klasse, Schlüssel, Zielobjekt und Methode, damit Fehler wie WMI-Query- oder Methodenfehler eindeutig zugeordnet werden können.
|
||||
|
||||
## Shutdown-Reihenfolge
|
||||
|
||||
@@ -73,6 +76,11 @@ Das BizTalk Platform Management Tool unterstützt kontrollierte Wartungsfenster
|
||||
- Nachher-Snapshots: `shutdown-after.json`, `restore-after.json`
|
||||
- Diff: `diff.json`, `diff.csv`, `diff.html`
|
||||
- Snapshot-Reports: `*.csv`, `*.hosts.csv`, `*.html`
|
||||
- Laufzeitlogs neben der EXE: `BizTalkPlatformManagementTool-yyyy-MM-dd.log`
|
||||
|
||||
## Fehleranalyse
|
||||
|
||||
Bei echten Shutdown- und Restore-Aktionen wird jeder Schritt vor der Ausführung mit Artefakttyp, WMI-Klasse, Schlüsselproperty, Schlüsselwert und Methodenname protokolliert. Die Objektauflösung verwendet eine breite `SELECT * FROM <class>`-Abfrage und filtert danach im Prozess auf den Schlüsselwert. Dadurch können Host-Instance-Namen und andere BizTalk-Namen mit Sonderzeichen keine ungültige WMI-WQL-`WHERE`-Query mehr erzeugen.
|
||||
|
||||
## Status Mapping
|
||||
|
||||
@@ -83,7 +91,7 @@ Das BizTalk Platform Management Tool unterstützt kontrollierte Wartungsfenster
|
||||
|
||||
## Anforderungen
|
||||
|
||||
- Windows Server oder Administrationshost mit .NET Framework 4.7.1
|
||||
- Windows Server oder Administrationshost mit .NET Framework 4.6.1
|
||||
- BizTalk Server 2020 oder installierte BizTalk Administration Tools
|
||||
- Berechtigungen auf den WMI-Namespace `root\MicrosoftBizTalkServer`
|
||||
- Visual Studio mit .NET Framework 4.7.1 Developer Pack für Builds
|
||||
- Visual Studio mit .NET Framework 4.6.1 Developer Pack für Builds
|
||||
|
||||
+4
-2
@@ -4,10 +4,11 @@
|
||||
|
||||
- Windows Server 2019/2022 oder ein Windows-Administrationshost
|
||||
- Microsoft BizTalk Server 2020 oder BizTalk Administration Tools
|
||||
- .NET Framework 4.7.1 Runtime
|
||||
- Für Builds: Visual Studio mit .NET Framework 4.7.1 Developer Pack
|
||||
- .NET Framework 4.6.1 Runtime
|
||||
- Für Builds: Visual Studio mit .NET Framework 4.6.1 Developer Pack
|
||||
- Zugriff auf den WMI-Namespace `root\MicrosoftBizTalkServer`
|
||||
- Ausreichende Rechte zum Lesen und Ändern von BizTalk-Artefakten
|
||||
- Schreibrechte im Verzeichnis der EXE für die tägliche Logdatei
|
||||
|
||||
## Build
|
||||
|
||||
@@ -23,6 +24,7 @@
|
||||
2. Sicherstellen, dass der ausführende Benutzer WMI-Zugriff auf `root\MicrosoftBizTalkServer` hat.
|
||||
3. `BizTalkPlatformManagementTool.exe` starten.
|
||||
4. Als Ausgabeverzeichnis einen Ordner wählen, in dem Plan-, Snapshot- und Report-Dateien abgelegt werden dürfen.
|
||||
5. Prüfen, dass im EXE-Verzeichnis `BizTalkPlatformManagementTool-yyyy-MM-dd.log` geschrieben werden kann. Logs werden für maximal fünf Tage vorgehalten.
|
||||
|
||||
## Erster Funktionstest
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ WinForms tool for controlled Microsoft BizTalk Server 2020 platform operations d
|
||||
## Current State
|
||||
|
||||
- Application: C# WinForms
|
||||
- Target framework: .NET Framework 4.7.1
|
||||
- Target framework: .NET Framework 4.6.1
|
||||
- Solution: `BizTalkPlatformManagementTool.sln`
|
||||
- Project: `src/BizTalkPlatformManagementTool/BizTalkPlatformManagementTool.csproj`
|
||||
- Primary namespace and assembly: `BizTalkPlatformManagementTool`
|
||||
@@ -20,6 +20,7 @@ WinForms tool for controlled Microsoft BizTalk Server 2020 platform operations d
|
||||
- Host instance handling for the selected BizTalk server
|
||||
- Dry-run mode enabled by default
|
||||
- WMI access through `root\MicrosoftBizTalkServer`
|
||||
- Detailed operation logging in the GUI and daily rolling log files next to the executable
|
||||
- No compile-time dependency on BizTalk ExplorerOM assemblies
|
||||
|
||||
## Safe Usage
|
||||
@@ -56,12 +57,19 @@ Orchestrations that were `Bound` are deliberately left unchanged during restore
|
||||
- `shutdown-after.json`, `restore-after.json`
|
||||
- `diff.json`, `diff.csv`, `diff.html`
|
||||
- Snapshot sidecars: `*.csv`, `*.hosts.csv`, `*.html`
|
||||
- Runtime logs next to the executable: `BizTalkPlatformManagementTool-yyyy-MM-dd.log`
|
||||
|
||||
Log files are retained for the current day plus the previous four days. Older `BizTalkPlatformManagementTool-*.log` files are removed on startup.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
The Operation Log shows the WMI class, key property, key value and method for real shutdown and restore steps. WMI objects are resolved with a broad `SELECT * FROM <class>` query and a client-side key filter so names containing special characters do not break the WMI query parser.
|
||||
|
||||
## Build
|
||||
|
||||
Open `BizTalkPlatformManagementTool.sln` in Visual Studio on Windows with the .NET Framework 4.7.1 Developer Pack installed, then build the `Release|Any CPU` configuration.
|
||||
Open `BizTalkPlatformManagementTool.sln` in Visual Studio on Windows with the .NET Framework 4.6.1 Developer Pack installed, then build the `Release|Any CPU` configuration.
|
||||
|
||||
The app targets .NET Framework 4.7.1 because BizTalk Server 2020 commonly runs in that Windows/.NET Framework operational environment.
|
||||
The app targets .NET Framework 4.6.1 for compatibility with customer environments that do not have newer .NET Framework developer packs installed.
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.1" />
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
|
||||
</startup>
|
||||
</configuration>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>BizTalkPlatformManagementTool</RootNamespace>
|
||||
<AssemblyName>BizTalkPlatformManagementTool</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<Deterministic>true</Deterministic>
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
{
|
||||
public sealed class BizTalkOperationService
|
||||
{
|
||||
public const string Version = "2.0.0-net471";
|
||||
public const string Version = "2.0.0-net461";
|
||||
private const string UnknownApplication = "(Unknown Application)";
|
||||
|
||||
private readonly OperationLogger _logger;
|
||||
@@ -116,7 +116,7 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
{
|
||||
foreach (var item in app.ReceiveLocations.Where(x => x.Enabled))
|
||||
{
|
||||
plan.Steps.Add(Step("ReceiveLocation", app.Application, item.Name, null, "Disable receive location", "MSBTS_ReceiveLocation", "Name", item.Name, "Disable", null, false));
|
||||
plan.Steps.Add(Step("ReceiveLocation", app.Application, item.Name, null, "Disable receive location", "MSBTS_ReceiveLocation", "Name", item.Name, "Disable", null, null));
|
||||
}
|
||||
|
||||
foreach (var item in app.Orchestrations.Where(x => x.OrchestrationStatus == ArtifactStates.OrchestrationStarted))
|
||||
@@ -232,6 +232,9 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Info("Executing step: " + DescribeStep(step));
|
||||
using (var instance = client.FindByProperty(step.WmiClass, step.KeyProperty, step.KeyValue))
|
||||
{
|
||||
if (instance == null)
|
||||
@@ -242,6 +245,11 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
ExecuteStep(client, instance, step, options);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException("Step failed: " + DescribeStep(step) + ". Error: " + ex.Message, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,6 +395,23 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string DescribeStep(OperationStep step)
|
||||
{
|
||||
if (step == null)
|
||||
{
|
||||
return "<unknown step>";
|
||||
}
|
||||
|
||||
return step.Action
|
||||
+ " '" + step.Name + "'"
|
||||
+ " [kind=" + step.Kind
|
||||
+ ", class=" + step.WmiClass
|
||||
+ ", key=" + step.KeyProperty + "=" + (step.KeyValue ?? string.Empty)
|
||||
+ ", method=" + step.MethodName
|
||||
+ (string.IsNullOrWhiteSpace(step.Server) ? string.Empty : ", server=" + step.Server)
|
||||
+ "]";
|
||||
}
|
||||
|
||||
private static ApplicationSnapshot GetApplication(Dictionary<string, ApplicationSnapshot> apps, string appName)
|
||||
{
|
||||
ApplicationSnapshot app;
|
||||
|
||||
@@ -34,11 +34,24 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
}
|
||||
|
||||
public List<ManagementObject> Query(string className)
|
||||
{
|
||||
return Query(className, true);
|
||||
}
|
||||
|
||||
private List<ManagementObject> Query(string className, bool logQuery)
|
||||
{
|
||||
EnsureConnected();
|
||||
|
||||
var result = new List<ManagementObject>();
|
||||
var query = new ObjectQuery("SELECT * FROM " + className);
|
||||
var queryText = "SELECT * FROM " + className;
|
||||
if (logQuery)
|
||||
{
|
||||
_logger.Info("Executing WMI query: " + queryText);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var query = new ObjectQuery(queryText);
|
||||
using (var searcher = new ManagementObjectSearcher(_scope, query))
|
||||
using (var collection = searcher.Get())
|
||||
{
|
||||
@@ -47,31 +60,62 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
result.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ManagementException ex)
|
||||
{
|
||||
throw new InvalidOperationException("WMI query failed. Query: " + queryText + ". WMI error: " + ex.Message, ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public ManagementObject FindByProperty(string className, string propertyName, string value)
|
||||
{
|
||||
return FindByProperty(className, propertyName, value, true);
|
||||
}
|
||||
|
||||
private ManagementObject FindByProperty(string className, string propertyName, string value, bool logLookup)
|
||||
{
|
||||
EnsureConnected();
|
||||
|
||||
var query = string.Format(
|
||||
var queryText = "SELECT * FROM " + className;
|
||||
var filter = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"SELECT * FROM {0} WHERE {1} = \"{2}\"",
|
||||
className,
|
||||
"{0} = \"{1}\"",
|
||||
propertyName,
|
||||
EscapeWql(value));
|
||||
value ?? string.Empty);
|
||||
if (logLookup)
|
||||
{
|
||||
_logger.Info("Resolving WMI object: " + queryText + " with client-side filter " + filter);
|
||||
}
|
||||
|
||||
using (var searcher = new ManagementObjectSearcher(_scope, new ObjectQuery(query)))
|
||||
using (var collection = searcher.Get())
|
||||
try
|
||||
{
|
||||
foreach (ManagementObject item in collection)
|
||||
ManagementObject match = null;
|
||||
var items = Query(className, false);
|
||||
foreach (var item in items)
|
||||
{
|
||||
return item;
|
||||
if (MatchesProperty(item, className, propertyName, value))
|
||||
{
|
||||
match = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (!object.ReferenceEquals(item, match))
|
||||
{
|
||||
item.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
catch (ManagementException ex)
|
||||
{
|
||||
throw new InvalidOperationException("WMI lookup failed. Query: " + queryText + ". Filter: " + filter + ". WMI error: " + ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public uint InvokeMethod(ManagementObject instance, string methodName, params object[] arguments)
|
||||
@@ -84,11 +128,20 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
var methodToCall = ResolveMethodName(instance, methodName);
|
||||
_logger.Info("Calling " + instance.Path.ClassName + "." + methodToCall + " on " + SafeGetString(instance, "Name", SafeGetString(instance, "InstanceName", "<unknown>")));
|
||||
|
||||
object result = instance.InvokeMethod(methodToCall, arguments == null || arguments.Length == 0 ? null : arguments);
|
||||
object result;
|
||||
try
|
||||
{
|
||||
result = instance.InvokeMethod(methodToCall, arguments == null || arguments.Length == 0 ? null : arguments);
|
||||
}
|
||||
catch (ManagementException ex)
|
||||
{
|
||||
throw new InvalidOperationException("WMI method failed. Class: " + instance.Path.ClassName + ", method: " + methodToCall + ", object: " + SafeObjectName(instance) + ". WMI error: " + ex.Message, ex);
|
||||
}
|
||||
|
||||
var returnCode = ExtractReturnCode(result);
|
||||
if (returnCode != 0)
|
||||
{
|
||||
throw new InvalidOperationException(methodToCall + " returned HRESULT/ReturnValue " + returnCode.ToString(CultureInfo.InvariantCulture));
|
||||
throw new InvalidOperationException("WMI method returned an error. Class: " + instance.Path.ClassName + ", method: " + methodToCall + ", object: " + SafeObjectName(instance) + ", ReturnValue: " + returnCode.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
return returnCode;
|
||||
@@ -101,7 +154,7 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
|
||||
while (DateTime.UtcNow <= deadline)
|
||||
{
|
||||
using (var current = FindByProperty(className, keyProperty, keyValue))
|
||||
using (var current = FindByProperty(className, keyProperty, keyValue, false))
|
||||
{
|
||||
if (current != null && isReached(current))
|
||||
{
|
||||
@@ -113,7 +166,7 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
Thread.Sleep(TimeSpan.FromSeconds(delay));
|
||||
}
|
||||
|
||||
throw new TimeoutException("Timeout while waiting for " + description);
|
||||
throw new TimeoutException("Timeout while waiting for " + description + " [" + className + "." + keyProperty + "=" + keyValue + "]");
|
||||
}
|
||||
|
||||
public static string SafeGetString(ManagementBaseObject item, string propertyName, string fallback)
|
||||
@@ -191,15 +244,60 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool MatchesProperty(ManagementBaseObject item, string className, string propertyName, string expectedValue)
|
||||
{
|
||||
foreach (var candidate in CandidatePropertyNames(className, propertyName))
|
||||
{
|
||||
if (!HasProperty(item, candidate))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var actual = SafeGetString(item, candidate, string.Empty);
|
||||
if (string.Equals(actual, expectedValue ?? string.Empty, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> CandidatePropertyNames(string className, string propertyName)
|
||||
{
|
||||
yield return propertyName;
|
||||
|
||||
if (string.Equals(className, "MSBTS_HostInstance", StringComparison.OrdinalIgnoreCase)
|
||||
&& string.Equals(propertyName, "InstanceName", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
yield return "Name";
|
||||
}
|
||||
}
|
||||
|
||||
private static string SafeObjectName(ManagementObject instance)
|
||||
{
|
||||
try
|
||||
{
|
||||
return instance.Path == null ? "<unknown>" : instance.Path.RelativePath;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return SafeGetString(instance, "Name", SafeGetString(instance, "InstanceName", "<unknown>"));
|
||||
}
|
||||
}
|
||||
|
||||
private static string ResolveMethodName(ManagementObject instance, string requestedName)
|
||||
{
|
||||
foreach (MethodData method in instance.Methods)
|
||||
using (var managementClass = new ManagementClass(instance.Scope, new ManagementPath(instance.Path.ClassName), null))
|
||||
{
|
||||
foreach (MethodData method in managementClass.Methods)
|
||||
{
|
||||
if (string.Equals(method.Name, requestedName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return method.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new MissingMethodException(instance.Path.ClassName, requestedName);
|
||||
}
|
||||
@@ -220,11 +318,6 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
return Convert.ToUInt32(result, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private static string EscapeWql(string value)
|
||||
{
|
||||
return (value ?? string.Empty).Replace("\\", "\\\\").Replace("\"", "\\\"");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace BizTalkPlatformManagementTool.Services
|
||||
{
|
||||
@@ -19,11 +22,28 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
|
||||
public sealed class OperationLogger
|
||||
{
|
||||
private const string LogFilePrefix = "BizTalkPlatformManagementTool-";
|
||||
private const string LogFileExtension = ".log";
|
||||
private const int RetentionDays = 5;
|
||||
private static readonly object FileLock = new object();
|
||||
private static int _cleanupDone;
|
||||
|
||||
private readonly Action<LogEntry> _sink;
|
||||
private readonly string _logDirectory;
|
||||
|
||||
public OperationLogger(Action<LogEntry> sink)
|
||||
{
|
||||
_sink = sink;
|
||||
_logDirectory = AppDomain.CurrentDomain.BaseDirectory;
|
||||
CleanupOldLogs();
|
||||
}
|
||||
|
||||
public string LogFilePath
|
||||
{
|
||||
get
|
||||
{
|
||||
return Path.Combine(_logDirectory, LogFilePrefix + DateTime.Now.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture) + LogFileExtension);
|
||||
}
|
||||
}
|
||||
|
||||
public void Info(string message)
|
||||
@@ -48,17 +68,69 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
|
||||
private void Write(LogLevel level, string message)
|
||||
{
|
||||
var entry = new LogEntry
|
||||
{
|
||||
Timestamp = DateTime.Now,
|
||||
Level = level,
|
||||
Message = message
|
||||
};
|
||||
|
||||
WriteToFile(entry);
|
||||
|
||||
if (_sink == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_sink(new LogEntry
|
||||
_sink(entry);
|
||||
}
|
||||
|
||||
private void WriteToFile(LogEntry entry)
|
||||
{
|
||||
Timestamp = DateTime.Now,
|
||||
Level = level,
|
||||
Message = message
|
||||
});
|
||||
try
|
||||
{
|
||||
var line = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"[{0:yyyy-MM-dd HH:mm:ss}][{1}] {2}{3}",
|
||||
entry.Timestamp,
|
||||
entry.Level.ToString().ToUpperInvariant(),
|
||||
entry.Message,
|
||||
Environment.NewLine);
|
||||
|
||||
lock (FileLock)
|
||||
{
|
||||
File.AppendAllText(LogFilePath, line);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Logging must never interrupt BizTalk operations.
|
||||
}
|
||||
}
|
||||
|
||||
private void CleanupOldLogs()
|
||||
{
|
||||
if (Interlocked.Exchange(ref _cleanupDone, 1) == 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var cutoff = DateTime.Now.Date.AddDays(-(RetentionDays - 1));
|
||||
foreach (var file in Directory.GetFiles(_logDirectory, LogFilePrefix + "*" + LogFileExtension))
|
||||
{
|
||||
var lastWrite = File.GetLastWriteTime(file);
|
||||
if (lastWrite.Date < cutoff)
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Log retention cleanup is best-effort.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace BizTalkPlatformManagementTool.Ui
|
||||
private readonly DataGridView _logGrid = new DataGridView();
|
||||
private readonly StatusStrip _statusStrip = new StatusStrip();
|
||||
private readonly ToolStripStatusLabel _statusLabel = new ToolStripStatusLabel();
|
||||
private readonly OperationLogger _logger;
|
||||
private readonly BizTalkOperationService _service;
|
||||
|
||||
private Button _diagnoseButton;
|
||||
@@ -37,8 +38,10 @@ namespace BizTalkPlatformManagementTool.Ui
|
||||
MinimumSize = new Size(980, 640);
|
||||
StartPosition = FormStartPosition.CenterScreen;
|
||||
|
||||
_service = new BizTalkOperationService(new OperationLogger(AppendLog));
|
||||
_logger = new OperationLogger(AppendLog);
|
||||
_service = new BizTalkOperationService(_logger);
|
||||
BuildUi();
|
||||
_logger.Info("Log file: " + _logger.LogFilePath);
|
||||
}
|
||||
|
||||
private void BuildUi()
|
||||
@@ -264,12 +267,12 @@ namespace BizTalkPlatformManagementTool.Ui
|
||||
try
|
||||
{
|
||||
work();
|
||||
AppendLog(new LogEntry { Timestamp = DateTime.Now, Level = LogLevel.Success, Message = "Operation completed." });
|
||||
_logger.Success("Operation completed.");
|
||||
SetBusy(false, "Ready.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendLog(new LogEntry { Timestamp = DateTime.Now, Level = LogLevel.Error, Message = ex.Message });
|
||||
_logger.Error(FormatException(ex));
|
||||
SetBusy(false, "Failed: " + ex.Message);
|
||||
}
|
||||
});
|
||||
@@ -458,5 +461,27 @@ namespace BizTalkPlatformManagementTool.Ui
|
||||
grid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
|
||||
grid.BackgroundColor = SystemColors.Window;
|
||||
}
|
||||
|
||||
private static string FormatException(Exception ex)
|
||||
{
|
||||
if (ex == null)
|
||||
{
|
||||
return "Operation failed.";
|
||||
}
|
||||
|
||||
var message = ex.Message;
|
||||
var inner = ex.InnerException;
|
||||
while (inner != null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(inner.Message) && message.IndexOf(inner.Message, StringComparison.OrdinalIgnoreCase) < 0)
|
||||
{
|
||||
message += " Inner error: " + inner.Message;
|
||||
}
|
||||
|
||||
inner = inner.InnerException;
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user