diff --git a/Dokumentation.md b/Dokumentation.md index 8fc0274..1df54e3 100644 --- a/Dokumentation.md +++ b/Dokumentation.md @@ -82,6 +82,8 @@ Das BizTalk Platform Management Tool unterstützt kontrollierte Wartungsfenster 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 `-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. +Snapshot- und Plan-JSON-Dateien werden als UTF-8 ohne BOM geschrieben. Beim Laden werden vorhandene Dateien mit UTF-8-BOM oder durch Encoding-Konvertierung sichtbar gewordenem BOM-Marker toleriert. + ## Status Mapping - `MSBTS_SendPort.Status`: `1=Bound`, `2=Stopped`, `3=Started` diff --git a/README.md b/README.md index 256e8b0..8920472 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,8 @@ Log files are retained for the current day plus the previous four days. Older `B 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 ` query and a client-side key filter so names containing special characters do not break the WMI query parser. +Snapshot and plan JSON files are written as UTF-8 without BOM. Loading is tolerant of existing files that contain a UTF-8 BOM or a visible BOM marker from previous encoding conversions. + ## Build 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. diff --git a/src/BizTalkPlatformManagementTool/Services/JsonFileStore.cs b/src/BizTalkPlatformManagementTool/Services/JsonFileStore.cs index 0c1e93a..4818aec 100644 --- a/src/BizTalkPlatformManagementTool/Services/JsonFileStore.cs +++ b/src/BizTalkPlatformManagementTool/Services/JsonFileStore.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Runtime.Serialization; using System.Runtime.Serialization.Json; using System.Text; @@ -6,6 +7,8 @@ namespace BizTalkPlatformManagementTool.Services { public static class JsonFileStore { + private static readonly Encoding Utf8NoBom = new UTF8Encoding(false); + public static void Save(string path, T value) { Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path))); @@ -17,8 +20,8 @@ namespace BizTalkPlatformManagementTool.Services using (var stream = new MemoryStream()) { serializer.WriteObject(stream, value); - var json = Encoding.UTF8.GetString(stream.ToArray()); - File.WriteAllText(path, json, Encoding.UTF8); + var json = Utf8NoBom.GetString(stream.ToArray()); + File.WriteAllText(path, json, Utf8NoBom); } } @@ -29,10 +32,55 @@ namespace BizTalkPlatformManagementTool.Services UseSimpleDictionaryFormat = true }); - using (var stream = File.OpenRead(path)) + var json = NormalizeJson(File.ReadAllText(path, Encoding.UTF8)); + using (var stream = new MemoryStream(Utf8NoBom.GetBytes(json))) { - return (T)serializer.ReadObject(stream); + try + { + return (T)serializer.ReadObject(stream); + } + catch (SerializationException ex) + { + throw new InvalidDataException("Failed to load JSON file '" + path + "'. " + DescribeJsonStart(json), ex); + } } } + + private static string NormalizeJson(string json) + { + if (string.IsNullOrEmpty(json)) + { + return json; + } + + if (json[0] == '\uFEFF') + { + json = json.Substring(1); + } + + if (json.StartsWith("\u00EF\u00BB\u00BF")) + { + json = json.Substring(3); + } + + return json; + } + + private static string DescribeJsonStart(string json) + { + if (string.IsNullOrWhiteSpace(json)) + { + return "The file is empty or contains only whitespace."; + } + + var trimmed = json.TrimStart(); + var first = trimmed.Length == 0 ? '\0' : trimmed[0]; + if (first != '{' && first != '[') + { + return "The first JSON character is '" + first + "' instead of '{' or '['."; + } + + return "The file starts with valid JSON syntax but could not be deserialized into the expected model."; + } } }