Fixed host instances WMI query error when dealing with shutdown / restart
This commit is contained in:
@@ -13,6 +13,9 @@ obj/
|
|||||||
*.log
|
*.log
|
||||||
*.json
|
*.json
|
||||||
*.html
|
*.html
|
||||||
|
*.zip
|
||||||
|
*.txt
|
||||||
|
*.png
|
||||||
work/
|
work/
|
||||||
release/*
|
release/*
|
||||||
|
|
||||||
|
|||||||
+11
-3
@@ -1,7 +1,7 @@
|
|||||||
# BizTalk Platform Management Tool Dokumentation
|
# BizTalk Platform Management Tool Dokumentation
|
||||||
|
|
||||||
**Stand:** 2026-04-27
|
**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`
|
**Archivierte PowerShell-Version:** `archive/powershell/BizTalkPlatformManagementTool.ps1`
|
||||||
|
|
||||||
## Zweck
|
## Zweck
|
||||||
@@ -40,10 +40,13 @@ Das BizTalk Platform Management Tool unterstützt kontrollierte Wartungsfenster
|
|||||||
- Dry-run ist standardmäßig aktiviert.
|
- Dry-run ist standardmäßig aktiviert.
|
||||||
- Echte Shutdown-/Restore-Aktionen verlangen bei deaktiviertem Dry-run eine zusätzliche Bestätigung.
|
- Echte Shutdown-/Restore-Aktionen verlangen bei deaktiviertem Dry-run eine zusätzliche Bestätigung.
|
||||||
- Jede Operation schreibt Einträge in das sichtbare Operation Log.
|
- 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.
|
- Operationspläne werden vor Laufzeitänderungen gespeichert.
|
||||||
- WMI-Methodenrückgaben werden geprüft.
|
- WMI-Methodenrückgaben werden geprüft.
|
||||||
- Wartezeiten nutzen konfigurierbare Timeout- und Polling-Werte.
|
- Wartezeiten nutzen konfigurierbare Timeout- und Polling-Werte.
|
||||||
- Host Instances auf anderen Servern werden übersprungen und als Warnung protokolliert.
|
- 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
|
## Shutdown-Reihenfolge
|
||||||
|
|
||||||
@@ -73,6 +76,11 @@ Das BizTalk Platform Management Tool unterstützt kontrollierte Wartungsfenster
|
|||||||
- Nachher-Snapshots: `shutdown-after.json`, `restore-after.json`
|
- Nachher-Snapshots: `shutdown-after.json`, `restore-after.json`
|
||||||
- Diff: `diff.json`, `diff.csv`, `diff.html`
|
- Diff: `diff.json`, `diff.csv`, `diff.html`
|
||||||
- Snapshot-Reports: `*.csv`, `*.hosts.csv`, `*.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
|
## Status Mapping
|
||||||
|
|
||||||
@@ -83,7 +91,7 @@ Das BizTalk Platform Management Tool unterstützt kontrollierte Wartungsfenster
|
|||||||
|
|
||||||
## Anforderungen
|
## 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
|
- BizTalk Server 2020 oder installierte BizTalk Administration Tools
|
||||||
- Berechtigungen auf den WMI-Namespace `root\MicrosoftBizTalkServer`
|
- 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
|
- Windows Server 2019/2022 oder ein Windows-Administrationshost
|
||||||
- Microsoft BizTalk Server 2020 oder BizTalk Administration Tools
|
- Microsoft BizTalk Server 2020 oder BizTalk Administration Tools
|
||||||
- .NET Framework 4.7.1 Runtime
|
- .NET Framework 4.6.1 Runtime
|
||||||
- Für Builds: Visual Studio mit .NET Framework 4.7.1 Developer Pack
|
- Für Builds: Visual Studio mit .NET Framework 4.6.1 Developer Pack
|
||||||
- Zugriff auf den WMI-Namespace `root\MicrosoftBizTalkServer`
|
- Zugriff auf den WMI-Namespace `root\MicrosoftBizTalkServer`
|
||||||
- Ausreichende Rechte zum Lesen und Ändern von BizTalk-Artefakten
|
- Ausreichende Rechte zum Lesen und Ändern von BizTalk-Artefakten
|
||||||
|
- Schreibrechte im Verzeichnis der EXE für die tägliche Logdatei
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@
|
|||||||
2. Sicherstellen, dass der ausführende Benutzer WMI-Zugriff auf `root\MicrosoftBizTalkServer` hat.
|
2. Sicherstellen, dass der ausführende Benutzer WMI-Zugriff auf `root\MicrosoftBizTalkServer` hat.
|
||||||
3. `BizTalkPlatformManagementTool.exe` starten.
|
3. `BizTalkPlatformManagementTool.exe` starten.
|
||||||
4. Als Ausgabeverzeichnis einen Ordner wählen, in dem Plan-, Snapshot- und Report-Dateien abgelegt werden dürfen.
|
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
|
## Erster Funktionstest
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ WinForms tool for controlled Microsoft BizTalk Server 2020 platform operations d
|
|||||||
## Current State
|
## Current State
|
||||||
|
|
||||||
- Application: C# WinForms
|
- Application: C# WinForms
|
||||||
- Target framework: .NET Framework 4.7.1
|
- Target framework: .NET Framework 4.6.1
|
||||||
- Solution: `BizTalkPlatformManagementTool.sln`
|
- Solution: `BizTalkPlatformManagementTool.sln`
|
||||||
- Project: `src/BizTalkPlatformManagementTool/BizTalkPlatformManagementTool.csproj`
|
- Project: `src/BizTalkPlatformManagementTool/BizTalkPlatformManagementTool.csproj`
|
||||||
- Primary namespace and assembly: `BizTalkPlatformManagementTool`
|
- 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
|
- Host instance handling for the selected BizTalk server
|
||||||
- Dry-run mode enabled by default
|
- Dry-run mode enabled by default
|
||||||
- WMI access through `root\MicrosoftBizTalkServer`
|
- 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
|
- No compile-time dependency on BizTalk ExplorerOM assemblies
|
||||||
|
|
||||||
## Safe Usage
|
## Safe Usage
|
||||||
@@ -56,12 +57,19 @@ Orchestrations that were `Bound` are deliberately left unchanged during restore
|
|||||||
- `shutdown-after.json`, `restore-after.json`
|
- `shutdown-after.json`, `restore-after.json`
|
||||||
- `diff.json`, `diff.csv`, `diff.html`
|
- `diff.json`, `diff.csv`, `diff.html`
|
||||||
- Snapshot sidecars: `*.csv`, `*.hosts.csv`, `*.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
|
## 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
|
## Documentation
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<configuration>
|
<configuration>
|
||||||
<startup>
|
<startup>
|
||||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.1" />
|
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
|
||||||
</startup>
|
</startup>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<RootNamespace>BizTalkPlatformManagementTool</RootNamespace>
|
<RootNamespace>BizTalkPlatformManagementTool</RootNamespace>
|
||||||
<AssemblyName>BizTalkPlatformManagementTool</AssemblyName>
|
<AssemblyName>BizTalkPlatformManagementTool</AssemblyName>
|
||||||
<TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>
|
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||||
<FileAlignment>512</FileAlignment>
|
<FileAlignment>512</FileAlignment>
|
||||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
<Deterministic>true</Deterministic>
|
<Deterministic>true</Deterministic>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
{
|
{
|
||||||
public sealed class BizTalkOperationService
|
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 const string UnknownApplication = "(Unknown Application)";
|
||||||
|
|
||||||
private readonly OperationLogger _logger;
|
private readonly OperationLogger _logger;
|
||||||
@@ -116,7 +116,7 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
{
|
{
|
||||||
foreach (var item in app.ReceiveLocations.Where(x => x.Enabled))
|
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))
|
foreach (var item in app.Orchestrations.Where(x => x.OrchestrationStatus == ArtifactStates.OrchestrationStarted))
|
||||||
@@ -232,6 +232,9 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.Info("Executing step: " + DescribeStep(step));
|
||||||
using (var instance = client.FindByProperty(step.WmiClass, step.KeyProperty, step.KeyValue))
|
using (var instance = client.FindByProperty(step.WmiClass, step.KeyProperty, step.KeyValue))
|
||||||
{
|
{
|
||||||
if (instance == null)
|
if (instance == null)
|
||||||
@@ -242,6 +245,11 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
ExecuteStep(client, instance, step, options);
|
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;
|
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)
|
private static ApplicationSnapshot GetApplication(Dictionary<string, ApplicationSnapshot> apps, string appName)
|
||||||
{
|
{
|
||||||
ApplicationSnapshot app;
|
ApplicationSnapshot app;
|
||||||
|
|||||||
@@ -34,11 +34,24 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<ManagementObject> Query(string className)
|
public List<ManagementObject> Query(string className)
|
||||||
|
{
|
||||||
|
return Query(className, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ManagementObject> Query(string className, bool logQuery)
|
||||||
{
|
{
|
||||||
EnsureConnected();
|
EnsureConnected();
|
||||||
|
|
||||||
var result = new List<ManagementObject>();
|
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 searcher = new ManagementObjectSearcher(_scope, query))
|
||||||
using (var collection = searcher.Get())
|
using (var collection = searcher.Get())
|
||||||
{
|
{
|
||||||
@@ -47,31 +60,62 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
result.Add(item);
|
result.Add(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
catch (ManagementException ex)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("WMI query failed. Query: " + queryText + ". WMI error: " + ex.Message, ex);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ManagementObject FindByProperty(string className, string propertyName, string value)
|
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();
|
EnsureConnected();
|
||||||
|
|
||||||
var query = string.Format(
|
var queryText = "SELECT * FROM " + className;
|
||||||
|
var filter = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
"SELECT * FROM {0} WHERE {1} = \"{2}\"",
|
"{0} = \"{1}\"",
|
||||||
className,
|
|
||||||
propertyName,
|
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)))
|
try
|
||||||
using (var collection = searcher.Get())
|
|
||||||
{
|
{
|
||||||
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)
|
public uint InvokeMethod(ManagementObject instance, string methodName, params object[] arguments)
|
||||||
@@ -84,11 +128,20 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
var methodToCall = ResolveMethodName(instance, methodName);
|
var methodToCall = ResolveMethodName(instance, methodName);
|
||||||
_logger.Info("Calling " + instance.Path.ClassName + "." + methodToCall + " on " + SafeGetString(instance, "Name", SafeGetString(instance, "InstanceName", "<unknown>")));
|
_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);
|
var returnCode = ExtractReturnCode(result);
|
||||||
if (returnCode != 0)
|
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;
|
return returnCode;
|
||||||
@@ -101,7 +154,7 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
|
|
||||||
while (DateTime.UtcNow <= deadline)
|
while (DateTime.UtcNow <= deadline)
|
||||||
{
|
{
|
||||||
using (var current = FindByProperty(className, keyProperty, keyValue))
|
using (var current = FindByProperty(className, keyProperty, keyValue, false))
|
||||||
{
|
{
|
||||||
if (current != null && isReached(current))
|
if (current != null && isReached(current))
|
||||||
{
|
{
|
||||||
@@ -113,7 +166,7 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
Thread.Sleep(TimeSpan.FromSeconds(delay));
|
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)
|
public static string SafeGetString(ManagementBaseObject item, string propertyName, string fallback)
|
||||||
@@ -191,15 +244,60 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
return false;
|
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)
|
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))
|
if (string.Equals(method.Name, requestedName, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return method.Name;
|
return method.Name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
throw new MissingMethodException(instance.Path.ClassName, requestedName);
|
throw new MissingMethodException(instance.Path.ClassName, requestedName);
|
||||||
}
|
}
|
||||||
@@ -220,11 +318,6 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
return Convert.ToUInt32(result, CultureInfo.InvariantCulture);
|
return Convert.ToUInt32(result, CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string EscapeWql(string value)
|
|
||||||
{
|
|
||||||
return (value ?? string.Empty).Replace("\\", "\\\\").Replace("\"", "\\\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace BizTalkPlatformManagementTool.Services
|
namespace BizTalkPlatformManagementTool.Services
|
||||||
{
|
{
|
||||||
@@ -19,11 +22,28 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
|
|
||||||
public sealed class OperationLogger
|
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 Action<LogEntry> _sink;
|
||||||
|
private readonly string _logDirectory;
|
||||||
|
|
||||||
public OperationLogger(Action<LogEntry> sink)
|
public OperationLogger(Action<LogEntry> sink)
|
||||||
{
|
{
|
||||||
_sink = 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)
|
public void Info(string message)
|
||||||
@@ -48,17 +68,69 @@ namespace BizTalkPlatformManagementTool.Services
|
|||||||
|
|
||||||
private void Write(LogLevel level, string message)
|
private void Write(LogLevel level, string message)
|
||||||
{
|
{
|
||||||
|
var entry = new LogEntry
|
||||||
|
{
|
||||||
|
Timestamp = DateTime.Now,
|
||||||
|
Level = level,
|
||||||
|
Message = message
|
||||||
|
};
|
||||||
|
|
||||||
|
WriteToFile(entry);
|
||||||
|
|
||||||
if (_sink == null)
|
if (_sink == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_sink(new LogEntry
|
_sink(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteToFile(LogEntry entry)
|
||||||
{
|
{
|
||||||
Timestamp = DateTime.Now,
|
try
|
||||||
Level = level,
|
{
|
||||||
Message = message
|
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 DataGridView _logGrid = new DataGridView();
|
||||||
private readonly StatusStrip _statusStrip = new StatusStrip();
|
private readonly StatusStrip _statusStrip = new StatusStrip();
|
||||||
private readonly ToolStripStatusLabel _statusLabel = new ToolStripStatusLabel();
|
private readonly ToolStripStatusLabel _statusLabel = new ToolStripStatusLabel();
|
||||||
|
private readonly OperationLogger _logger;
|
||||||
private readonly BizTalkOperationService _service;
|
private readonly BizTalkOperationService _service;
|
||||||
|
|
||||||
private Button _diagnoseButton;
|
private Button _diagnoseButton;
|
||||||
@@ -37,8 +38,10 @@ namespace BizTalkPlatformManagementTool.Ui
|
|||||||
MinimumSize = new Size(980, 640);
|
MinimumSize = new Size(980, 640);
|
||||||
StartPosition = FormStartPosition.CenterScreen;
|
StartPosition = FormStartPosition.CenterScreen;
|
||||||
|
|
||||||
_service = new BizTalkOperationService(new OperationLogger(AppendLog));
|
_logger = new OperationLogger(AppendLog);
|
||||||
|
_service = new BizTalkOperationService(_logger);
|
||||||
BuildUi();
|
BuildUi();
|
||||||
|
_logger.Info("Log file: " + _logger.LogFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BuildUi()
|
private void BuildUi()
|
||||||
@@ -264,12 +267,12 @@ namespace BizTalkPlatformManagementTool.Ui
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
work();
|
work();
|
||||||
AppendLog(new LogEntry { Timestamp = DateTime.Now, Level = LogLevel.Success, Message = "Operation completed." });
|
_logger.Success("Operation completed.");
|
||||||
SetBusy(false, "Ready.");
|
SetBusy(false, "Ready.");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
AppendLog(new LogEntry { Timestamp = DateTime.Now, Level = LogLevel.Error, Message = ex.Message });
|
_logger.Error(FormatException(ex));
|
||||||
SetBusy(false, "Failed: " + ex.Message);
|
SetBusy(false, "Failed: " + ex.Message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -458,5 +461,27 @@ namespace BizTalkPlatformManagementTool.Ui
|
|||||||
grid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
|
grid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
|
||||||
grid.BackgroundColor = SystemColors.Window;
|
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