Guarantee durable visible runtime logging
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.3.0";
|
||||
private const string ProductVersion = "2.3.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.3.0"
|
||||
Text = "BizTalk Platform Management Tool 2.3.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.3.0.0")]
|
||||
[assembly: AssemblyFileVersion("2.3.0.0")]
|
||||
[assembly: AssemblyVersion("2.3.1.0")]
|
||||
[assembly: AssemblyFileVersion("2.3.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.3.0.0" name="BizTalkPlatformManagementTool.Setup" />
|
||||
<assemblyIdentity version="2.3.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.3.0.0")]
|
||||
[assembly: AssemblyFileVersion("2.3.0.0")]
|
||||
[assembly: AssemblyVersion("2.3.1.0")]
|
||||
[assembly: AssemblyFileVersion("2.3.1.0")]
|
||||
[assembly: InternalsVisibleTo("BizTalkPlatformManagementTool.Tests")]
|
||||
|
||||
@@ -37,6 +37,16 @@ namespace BizTalkPlatformManagementTool
|
||||
SnapshotStore.SaveSnapshotSet(Path.Combine(directory, "before.json"), loaded);
|
||||
SnapshotStore.SaveDiffSet(Path.Combine(directory, "diff.json"), diff);
|
||||
|
||||
var runtimeLogDirectory = Path.Combine(directory, "runtime-logs");
|
||||
var runtimeLogger = new OperationLogger(null, runtimeLogDirectory);
|
||||
runtimeLogger.Info("runtime log self-test marker");
|
||||
if (!runtimeLogger.IsFileLoggingAvailable
|
||||
|| !File.Exists(runtimeLogger.LogFilePath)
|
||||
|| !runtimeLogger.ReadRecentEntries(10).Any(x => x.Message == "runtime log self-test marker"))
|
||||
{
|
||||
throw new InvalidOperationException("Runtime log write/read self-test failed.");
|
||||
}
|
||||
|
||||
var operationService = new BizTalkOperationService(null);
|
||||
var emergencyPlan = operationService.CreateEmergencyRestorePlan(loaded, loaded.Server);
|
||||
if (emergencyPlan.Steps.Count == 0 || emergencyPlan.Steps[0].Kind != "WindowsService" || emergencyPlan.Steps[0].Name != "ENTSSO")
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
/// <summary>
|
||||
/// Current tool version written into generated snapshots.
|
||||
/// </summary>
|
||||
public const string Version = "2.3.0-net461";
|
||||
public const string Version = "2.3.1-net461";
|
||||
|
||||
/// <summary>
|
||||
/// Fallback application name used when WMI does not expose an application property.
|
||||
|
||||
@@ -89,16 +89,28 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
private readonly Action<LogEntry> _sink;
|
||||
|
||||
/// <summary>
|
||||
/// Directory where daily log files are written.
|
||||
/// Ordered local directories available for primary and fallback logging.
|
||||
/// </summary>
|
||||
private readonly string _logDirectory;
|
||||
private readonly List<string> _candidateDirectories;
|
||||
|
||||
/// <summary>Index of the currently active writable directory, or minus one.</summary>
|
||||
private int _activeDirectoryIndex;
|
||||
|
||||
/// <summary>Directory where daily log files are currently written.</summary>
|
||||
private string _logDirectory;
|
||||
|
||||
/// <summary>Latest actionable storage warning for the operator.</summary>
|
||||
private string _storageWarning;
|
||||
|
||||
/// <summary>Last storage notice already forwarded to the UI sink.</summary>
|
||||
private string _reportedStorageNotice;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new logger that writes beside the executable.
|
||||
/// Initializes a logger that verifies ProgramData and ordered local fallback directories.
|
||||
/// </summary>
|
||||
/// <param name="sink">Optional callback that receives entries for display.</param>
|
||||
public OperationLogger(Action<LogEntry> sink)
|
||||
: this(sink, ResolveLogDirectory())
|
||||
: this(sink, BuildLogDirectoryCandidates())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -106,15 +118,42 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
/// <param name="sink">Optional callback that receives new entries.</param>
|
||||
/// <param name="logDirectory">Directory used for plain and compressed logs.</param>
|
||||
internal OperationLogger(Action<LogEntry> sink, string logDirectory)
|
||||
: this(sink, new[] { logDirectory })
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Initializes a logger with ordered primary and fallback directories.</summary>
|
||||
/// <param name="sink">Optional callback that receives new entries.</param>
|
||||
/// <param name="candidateDirectories">Ordered local directories to verify.</param>
|
||||
internal OperationLogger(Action<LogEntry> sink, IEnumerable<string> candidateDirectories)
|
||||
{
|
||||
_sink = sink;
|
||||
_logDirectory = Path.GetFullPath(logDirectory);
|
||||
Directory.CreateDirectory(_logDirectory);
|
||||
MaintainLogs(DateTime.Now.Date);
|
||||
_candidateDirectories = NormalizeDirectories(candidateDirectories).ToList();
|
||||
_activeDirectoryIndex = -1;
|
||||
SelectInitialLogDirectory();
|
||||
if (_logDirectory != null)
|
||||
{
|
||||
MaintainLogs(DateTime.Now.Date);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets the directory containing active and compressed runtime logs.</summary>
|
||||
public string LogDirectory { get { return _logDirectory; } }
|
||||
public string LogDirectory
|
||||
{
|
||||
get { lock (FileLock) { return _logDirectory ?? string.Empty; } }
|
||||
}
|
||||
|
||||
/// <summary>Gets whether a daily runtime log can currently be written.</summary>
|
||||
public bool IsFileLoggingAvailable
|
||||
{
|
||||
get { lock (FileLock) { return _activeDirectoryIndex >= 0 && !string.IsNullOrWhiteSpace(_logDirectory); } }
|
||||
}
|
||||
|
||||
/// <summary>Gets the latest fallback or total-storage-failure diagnostic.</summary>
|
||||
public string StorageWarning
|
||||
{
|
||||
get { lock (FileLock) { return _storageWarning; } }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path of the daily log file for the current date.
|
||||
@@ -123,7 +162,12 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
{
|
||||
get
|
||||
{
|
||||
return Path.Combine(_logDirectory, LogFilePrefix + DateTime.Now.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture) + LogFileExtension);
|
||||
lock (FileLock)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(_logDirectory)
|
||||
? string.Empty
|
||||
: BuildLogFilePath(_logDirectory, DateTime.Now);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,9 +185,14 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
}
|
||||
|
||||
var entries = new List<LogEntry>();
|
||||
var directory = LogDirectory;
|
||||
if (string.IsNullOrWhiteSpace(directory) || !Directory.Exists(directory))
|
||||
{
|
||||
return entries;
|
||||
}
|
||||
try
|
||||
{
|
||||
var files = Directory.GetFiles(_logDirectory, LogFilePrefix + "*" + LogFileExtension + "*")
|
||||
var files = Directory.GetFiles(directory, LogFilePrefix + "*" + LogFileExtension + "*")
|
||||
.Where(IsSupportedLogFile)
|
||||
.OrderBy(x => x, StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
@@ -213,13 +262,30 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
Message = message
|
||||
};
|
||||
|
||||
WriteToFile(entry);
|
||||
var storageNotice = WriteToFile(entry);
|
||||
DeliverToSink(entry);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(storageNotice))
|
||||
{
|
||||
var warning = new LogEntry
|
||||
{
|
||||
Timestamp = DateTime.Now,
|
||||
Level = LogLevel.Warning,
|
||||
Message = storageNotice
|
||||
};
|
||||
WriteToFile(warning);
|
||||
DeliverToSink(warning);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Forwards one record to the optional GUI without affecting runtime work.</summary>
|
||||
/// <param name="entry">The record to display.</param>
|
||||
private void DeliverToSink(LogEntry entry)
|
||||
{
|
||||
if (_sink == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_sink(entry);
|
||||
@@ -242,39 +308,79 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
/// Appends one entry to the current daily log file.
|
||||
/// </summary>
|
||||
/// <param name="entry">The entry to write.</param>
|
||||
private void WriteToFile(LogEntry entry)
|
||||
/// <returns>A new operator-visible storage notice, or null.</returns>
|
||||
private string WriteToFile(LogEntry entry)
|
||||
{
|
||||
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 ?? string.Empty).Replace("\r", "\\r").Replace("\n", "\\n"),
|
||||
Environment.NewLine);
|
||||
var line = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"[{0:yyyy-MM-dd HH:mm:ss}][{1}] {2}{3}",
|
||||
entry.Timestamp,
|
||||
entry.Level.ToString().ToUpperInvariant(),
|
||||
(entry.Message ?? string.Empty).Replace("\r", "\\r").Replace("\n", "\\n"),
|
||||
Environment.NewLine);
|
||||
|
||||
lock (FileLock)
|
||||
{
|
||||
File.AppendAllText(LogFilePath, line);
|
||||
}
|
||||
}
|
||||
catch
|
||||
lock (FileLock)
|
||||
{
|
||||
// Ein Logfehler darf niemals eine fachliche BizTalk-Operation abbrechen.
|
||||
var failures = new List<string>();
|
||||
var previousDirectory = _logDirectory;
|
||||
var startIndex = _activeDirectoryIndex >= 0 ? _activeDirectoryIndex : 0;
|
||||
for (var index = startIndex; index < _candidateDirectories.Count; index++)
|
||||
{
|
||||
var candidate = _candidateDirectories[index];
|
||||
string error;
|
||||
if (!TryAppend(candidate, line, out error))
|
||||
{
|
||||
failures.Add(candidate + " => " + error);
|
||||
continue;
|
||||
}
|
||||
|
||||
_activeDirectoryIndex = index;
|
||||
_logDirectory = candidate;
|
||||
if (!string.Equals(previousDirectory, candidate, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_storageWarning = "Runtime log path switched to writable fallback '" + candidate
|
||||
+ "'. Failed path(s): " + string.Join(" | ", failures.ToArray());
|
||||
return TakeUnreportedStorageNotice(_storageWarning);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
_activeDirectoryIndex = -1;
|
||||
_logDirectory = null;
|
||||
_storageWarning = "RUNTIME FILE LOGGING UNAVAILABLE. No candidate directory accepted an append. Attempted: "
|
||||
+ string.Join(" | ", failures.ToArray()) + ". Operations continue and remain visible in the grid, but no durable runtime log is being written.";
|
||||
return TakeUnreportedStorageNotice(_storageWarning);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Returns a storage notice only once per distinct failure state.</summary>
|
||||
/// <param name="notice">The current diagnostic.</param>
|
||||
/// <returns>The notice when it has not been reported before; otherwise null.</returns>
|
||||
private string TakeUnreportedStorageNotice(string notice)
|
||||
{
|
||||
if (string.Equals(_reportedStorageNotice, notice, StringComparison.Ordinal))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
_reportedStorageNotice = notice;
|
||||
return notice;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compresses completed daily logs and removes all records outside retention.
|
||||
/// </summary>
|
||||
/// <param name="today">The current local date.</param>
|
||||
internal void MaintainLogs(DateTime today)
|
||||
{
|
||||
var directory = LogDirectory;
|
||||
if (string.IsNullOrWhiteSpace(directory) || !Directory.Exists(directory))
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
var cutoff = today.Date.AddDays(-(RetentionDays - 1));
|
||||
foreach (var file in Directory.GetFiles(_logDirectory, LogFilePrefix + "*" + LogFileExtension + "*"))
|
||||
foreach (var file in Directory.GetFiles(directory, LogFilePrefix + "*" + LogFileExtension + "*"))
|
||||
{
|
||||
DateTime fileDate;
|
||||
if (!TryGetLogDate(file, out fileDate))
|
||||
@@ -409,23 +515,144 @@ namespace BizTalkPlatformManagementTool.Services
|
||||
return DateTime.TryParseExact(value, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out date);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ermittelt das bevorzugte maschinenweite Logverzeichnis mit Rückfall auf das EXE-Verzeichnis.
|
||||
/// </summary>
|
||||
/// <returns>Ein verwendbares Verzeichnis für die täglichen Laufzeitlogs.</returns>
|
||||
private static string ResolveLogDirectory()
|
||||
/// <summary>Selects the first directory that passes a real create/write/delete probe.</summary>
|
||||
private void SelectInitialLogDirectory()
|
||||
{
|
||||
var commonData = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
|
||||
var preferred = Path.Combine(commonData, "BizTalkPlatformManagementTool", "Logs");
|
||||
var failures = new List<string>();
|
||||
for (var index = 0; index < _candidateDirectories.Count; index++)
|
||||
{
|
||||
string error;
|
||||
if (!TryVerifyWritable(_candidateDirectories[index], out error))
|
||||
{
|
||||
failures.Add(_candidateDirectories[index] + " => " + error);
|
||||
continue;
|
||||
}
|
||||
_activeDirectoryIndex = index;
|
||||
_logDirectory = _candidateDirectories[index];
|
||||
if (index > 0)
|
||||
{
|
||||
_storageWarning = "Primary runtime log path is not writable. Using verified fallback '"
|
||||
+ _logDirectory + "'. Failed path(s): " + string.Join(" | ", failures.ToArray());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
_storageWarning = "RUNTIME FILE LOGGING UNAVAILABLE. No candidate directory passed the startup write probe. Attempted: "
|
||||
+ (failures.Count == 0 ? "(none)" : string.Join(" | ", failures.ToArray()))
|
||||
+ ". Operations remain visible in the grid, but no durable runtime log is being written.";
|
||||
}
|
||||
|
||||
/// <summary>Builds primary and local fallback directories in deterministic order.</summary>
|
||||
/// <returns>ProgramData, LocalAppData, executable-local and Temp candidates.</returns>
|
||||
private static IEnumerable<string> BuildLogDirectoryCandidates()
|
||||
{
|
||||
yield return Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
|
||||
"BizTalkPlatformManagementTool",
|
||||
"Logs");
|
||||
yield return Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"BizTalkPlatformManagementTool",
|
||||
"Logs");
|
||||
yield return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
|
||||
yield return Path.Combine(Path.GetTempPath(), "BizTalkPlatformManagementTool", "Logs");
|
||||
}
|
||||
|
||||
/// <summary>Normalizes and de-duplicates candidate paths without requiring existence.</summary>
|
||||
/// <param name="directories">Raw ordered paths.</param>
|
||||
/// <returns>Safe absolute unique paths.</returns>
|
||||
private static IEnumerable<string> NormalizeDirectories(IEnumerable<string> directories)
|
||||
{
|
||||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var directory in directories ?? Enumerable.Empty<string>())
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(directory))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
string fullPath;
|
||||
try
|
||||
{
|
||||
fullPath = Path.GetFullPath(directory.Trim()).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
||||
}
|
||||
catch
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (seen.Add(fullPath))
|
||||
{
|
||||
yield return fullPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Performs a real write probe and removes the temporary probe file.</summary>
|
||||
/// <param name="directory">Candidate directory.</param>
|
||||
/// <param name="error">Detailed failure when the probe fails.</param>
|
||||
/// <returns>True only after bytes were created and flushed successfully.</returns>
|
||||
private static bool TryVerifyWritable(string directory, out string error)
|
||||
{
|
||||
var probe = Path.Combine(directory, ".runtime-log-write-probe-" + Guid.NewGuid().ToString("N") + ".tmp");
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(preferred);
|
||||
return preferred;
|
||||
Directory.CreateDirectory(directory);
|
||||
using (var stream = new FileStream(probe, FileMode.CreateNew, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
stream.WriteByte(0x42);
|
||||
stream.Flush();
|
||||
}
|
||||
File.Delete(probe);
|
||||
error = null;
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
return AppDomain.CurrentDomain.BaseDirectory;
|
||||
error = ex.GetType().Name + ": " + ex.Message;
|
||||
try
|
||||
{
|
||||
if (File.Exists(probe))
|
||||
{
|
||||
File.Delete(probe);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Probe cleanup is best-effort and the original write error remains authoritative.
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Appends one physical line to a candidate daily log.</summary>
|
||||
/// <param name="directory">Candidate directory.</param>
|
||||
/// <param name="line">Serialized log line.</param>
|
||||
/// <param name="error">Detailed append failure.</param>
|
||||
/// <returns>True after the append completed.</returns>
|
||||
private static bool TryAppend(string directory, string line, out string error)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
File.AppendAllText(BuildLogFilePath(directory, DateTime.Now), line, new UTF8Encoding(false));
|
||||
error = null;
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = ex.GetType().Name + ": " + ex.Message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Builds the stable daily file path for a directory and local date.</summary>
|
||||
/// <param name="directory">Active directory.</param>
|
||||
/// <param name="timestamp">Timestamp whose local date selects the file.</param>
|
||||
/// <returns>Full daily log path.</returns>
|
||||
private static string BuildLogFilePath(string directory, DateTime timestamp)
|
||||
{
|
||||
return Path.Combine(
|
||||
directory,
|
||||
LogFilePrefix + timestamp.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture) + LogFileExtension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +155,18 @@ namespace BizTalkPlatformManagementTool.Ui
|
||||
BuildUi();
|
||||
FormClosing += MainFormClosing;
|
||||
LoadLogHistory();
|
||||
_logger.Info("Log file: " + _logger.LogFilePath);
|
||||
if (!string.IsNullOrWhiteSpace(_logger.StorageWarning))
|
||||
{
|
||||
_logger.Warning(_logger.StorageWarning);
|
||||
}
|
||||
if (_logger.IsFileLoggingAvailable)
|
||||
{
|
||||
_logger.Success("Runtime log storage verified by startup append. Active file: " + _logger.LogFilePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error("Runtime file logging is unavailable. Use the Operation Log grid and resolve the storage warning before a real maintenance operation.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -591,6 +602,10 @@ namespace BizTalkPlatformManagementTool.Ui
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_logger.IsFileLoggingAvailable || string.IsNullOrWhiteSpace(_logger.LogDirectory))
|
||||
{
|
||||
throw new InvalidOperationException(_logger.StorageWarning ?? "No writable runtime log directory is active.");
|
||||
}
|
||||
Directory.CreateDirectory(_logger.LogDirectory);
|
||||
Process.Start("explorer.exe", _logger.LogDirectory);
|
||||
}
|
||||
|
||||
@@ -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.3.0.0" name="BizTalkPlatformManagementTool" />
|
||||
<assemblyIdentity version="2.3.1.0" name="BizTalkPlatformManagementTool" />
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
|
||||
Reference in New Issue
Block a user