diff --git a/NAVSCM Library/NavInterface/DevEnvInterface.cs b/NAVSCM Library/NavInterface/DevEnvInterface.cs
index 1075ee6..07ed91c 100644
--- a/NAVSCM Library/NavInterface/DevEnvInterface.cs
+++ b/NAVSCM Library/NavInterface/DevEnvInterface.cs
@@ -4,6 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
+using System.Diagnostics;
using System.Diagnostics.Contracts;
namespace NavScm.NavInterface
@@ -22,6 +23,49 @@ namespace NavScm.NavInterface
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(DevEnvInterface));
+ ///
+ /// Helper structure to capture the full execution result of finsql.exe.
+ ///
+ public struct CommandResult
+ {
+ ///
+ /// The exit code of finsql.exe. So far this is always 0, tests pending.
+ ///
+ public int ExitCode;
+
+ ///
+ /// True, if the command executed successfully.
+ ///
+ public bool Success;
+
+ ///
+ /// The output produced by the command, as written to navcommandresult.txt by finsql.exe.
+ ///
+ public string CommandOutput;
+
+ ///
+ /// The errormessage if any, as written to the log file passed to finsql.exe. Empty, if
+ /// the call was successful.
+ ///
+ public string ErrorMessage;
+
+ ///
+ /// Constructs the whole object.
+ ///
+ /// The exit code of finsql.exe.
+ /// Flag indicating success.
+ /// The output produced by the command, as written to navcommandresult.txt by finsql.exe.
+ /// The errormessage if any, as written to the log file passed to finsql.exe. Empty, if
+ /// the call was successful.
+ public CommandResult(int exitCode, bool success, string commandOutput, string errorMessage)
+ {
+ this.ExitCode = exitCode;
+ this.Success = success;
+ this.CommandOutput = commandOutput;
+ this.ErrorMessage = errorMessage;
+ }
+ }
+
///
/// Path to the dev env executable.
///
@@ -65,6 +109,131 @@ namespace NavScm.NavInterface
}
}
+ ///
+ /// Creates a new temporary directory for finsql.exe to store its stuff into it. Creates an
+ /// unique temporary directory using Path.GetTempPath and Path.GetRandomFileName, which should
+ /// work easily out of the box. Retries 3 times in case of unexpected errors and aborts afterwards.
+ ///
+ /// The full path to the created directory, which is empty.
+ protected string GetNewTempDirectory()
+ {
+ Contract.Ensures(Directory.Exists(Contract.Result()));
+ Contract.Ensures(! Directory.EnumerateFileSystemEntries(Contract.Result()).Any());
+
+ // Try a few times. Uniqueness should not be a problem, as GetRandomFileName is cryptographically strong,
+ // but beware of other errors e.g. during director creation.
+ // Note, that in theory an race condition is possible here in case GetRandomFileName doesn't behave.
+ for (int i = 0; i < 3; i++)
+ {
+ string tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
+ log.DebugFormat("GetNewTempDirectoy: Checking against tempPath {0}", tempPath);
+ if (Directory.Exists(tempPath))
+ {
+ log.ErrorFormat("GetNewTempDirectory: The directory {0} did already exist, this is highly unusual, trying again nevertheless...", tempPath);
+ continue;
+ }
+
+ DirectoryInfo dirInfo;
+ try
+ {
+ dirInfo = Directory.CreateDirectory(tempPath);
+ }
+ catch (Exception ex)
+ {
+ log.ErrorFormat("GetNewTempDirectory: The directory {0} did not exist but could be created: {1}", tempPath, ex.Message);
+ log.Debug("GetNewTempDirectory: Exception Details:", ex);
+ log.Error("GetNewTempDirectory: Trying again nevertheless...");
+ continue;
+ }
+
+ return tempPath;
+ }
+
+ log.Fatal("GetNewTempDirectory: Could not generate a new temporary directory after three attempts, this is fatal.");
+ throw new InvalidOperationException("Could not create a new temp directory, this is fatal.");
+ }
+
+ ///
+ /// Executes the given command with finsql.exe. Database access parameters and log file
+ /// parameters are added automatically and must not be included in the given command.
+ /// finsql.exe is run synchronously, so this call blocks until whatever you requested
+ /// from it is done.
+ ///
+ ///
+ /// Uses GetNewTempDirectory to create a directory to work in. Be aware, that all
+ /// files stored in it will be deleted unconditionally after execution completes.
+ /// The directory itself is deleted as well.
+ /// Appends LogFile, ServerName and Database arguments to the given command.
+ ///
+ /// The command to execute, excluding any database login information
+ /// and log file specification. Do not add a trailing comma as well.
+ /// Full command execution result, see the CommandResult structure for details.
+ protected CommandResult ExecuteCommand(string command)
+ {
+ Contract.Requires(command != "");
+
+ string tempPath = GetNewTempDirectory();
+ Contract.Ensures(!Directory.Exists(tempPath));
+
+ string errorLog = $"{tempPath}\\error.log";
+ string commandOutput = $"{tempPath}\\navcommandresult.txt";
+ string fullArguments = $"{command},LogFile=\"{errorLog}\",ServerName=\"{DatabaseServer}\",Database=\"{DatabaseName}\"";
+
+ log.DebugFormat("ExecuteCommand: Working in {0}", tempPath);
+ log.InfoFormat("ExecuteCommand: Executing: {0} {1}", DevEnvPath, fullArguments);
+
+ // Execute finsql.exe and wait for its exit...
+ Process process = new Process();
+ Contract.Ensures(process.HasExited);
+
+ process.StartInfo.FileName = DevEnvPath;
+ process.StartInfo.Arguments = fullArguments;
+ process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
+ process.StartInfo.WorkingDirectory = tempPath;
+ process.StartInfo.CreateNoWindow = true;
+
+ Stopwatch sw = new Stopwatch();
+ if (log.IsDebugEnabled)
+ {
+ sw.Start();
+ }
+
+ process.Start();
+ process.WaitForExit();
+
+ if (log.IsDebugEnabled)
+ {
+ sw.Stop();
+ log.Debug($"ExecuteCommand: Execution took: {sw.Elapsed}");
+ }
+
+ // Parse and store finsql.exe result information
+ CommandResult result;
+ if (File.Exists(errorLog))
+ {
+ result = new CommandResult(process.ExitCode, false, File.ReadAllText(commandOutput), File.ReadAllText(errorLog));
+ log.ErrorFormat("ExecuteCommand: Command failed with: {0}", result.ErrorMessage);
+ log.DebugFormat("ExecuteCommand: Command result: {0}", result.CommandOutput);
+ log.DebugFormat("ExecuteCommand: finsql.exe finished with exit code {0}", process.ExitCode);
+ }
+ else
+ {
+ result = new CommandResult(process.ExitCode, true, File.ReadAllText(commandOutput), "");
+ log.Info("ExecuteCommand: Command executed successfully");
+ log.DebugFormat("ExecuteCommand: Command result: {0}", result.CommandOutput);
+ }
+
+ // Do some cleanup and be done with it.
+ foreach (var entry in Directory.EnumerateFileSystemEntries(tempPath))
+ {
+ log.DebugFormat("ExecuteCommand: Deleting File {0}", entry);
+ File.Delete(entry);
+ }
+ Directory.Delete(tempPath);
+
+ return result;
+ }
+
///
/// Exports a given NAV object to disk.
///