diff --git a/NAV Source Control Test Host/NAV Source Control Test Host.csproj b/NAV Source Control Test Host/NAV Source Control Test Host.csproj
index 226b651..8ce0894 100644
--- a/NAV Source Control Test Host/NAV Source Control Test Host.csproj
+++ b/NAV Source Control Test Host/NAV Source Control Test Host.csproj
@@ -41,7 +41,7 @@
4
True
False
- False
+ True
False
False
True
diff --git a/NAV Source Control Test Host/Program.cs b/NAV Source Control Test Host/Program.cs
index 5a895db..8fc71a6 100644
--- a/NAV Source Control Test Host/Program.cs
+++ b/NAV Source Control Test Host/Program.cs
@@ -29,20 +29,20 @@ namespace NavScm.TestHost
log.InfoFormat("{0} total entries in NavSqlObjects", NavSqlObjects.Count());
var query = from sql in NavSqlObjects
- where sql.Modified == 1
+ where sql.Modified == 1 && sql.Type > 0
select sql;
int count = query.Count();
- log.InfoFormat("{0} modified objects detected, reading them...", count);
+ log.InfoFormat("{0} modified objects detected, reading them into the cache...", count);
var foundObjects = new Dictionary(count);
- int i = 1;
+ // int i = 1;
foreach (NavObject o in query)
{
- log.DebugFormat("Row {6}/{7}: Type {0}, ID {1}, Name {2}, Modified {3} {4}, Version {5}",
- o.Type, o.ID, o.Name, o.Date.ToShortDateString(), o.Time.ToShortTimeString(), o.Version_List, i++, count);
+ //log.DebugFormat("Row {6}/{7}: Type {0}, ID {1}, Name {2}, Modified {3} {4}, Version {5}",
+ // o.Type, o.ID, o.Name, o.Date.ToShortDateString(), o.Time.ToShortTimeString(), o.Version_List, i++, count);
foundObjects.Add(o.CacheKey, o);
}
@@ -59,12 +59,21 @@ namespace NavScm.TestHost
XmlDictionaryReader xmlReader = XmlDictionaryReader.CreateTextReader(reader, new XmlDictionaryReaderQuotas());
var loadedObjects = (Dictionary)serializer.ReadObject(xmlReader, true);
- log.Debug("Dumping sample object 1.82004");
+ // 5.99997 => CU TN_Test
- NavObject o2 = loadedObjects["1.82004"];
+ log.Debug("Dumping sample object descriptor for 5.99997 ");
+
+ NavObject o2 = loadedObjects["5.99997"];
log.DebugFormat("Type {0}, ID {1}, Name {2}, Modified {3} {4}, Version {5}",
o2.Type, o2.ID, o2.Name, o2.Date.ToShortDateString(), o2.Time.ToShortTimeString(), o2.Version_List);
+ DevEnvInterface devenv = new DevEnvInterface("C:\\Program Files (x86)\\Microsoft Dynamics NAV\\tbrt-nav-erp-02\\RoleTailored Client\\finsql.exe",
+ "tbrt-sql-erp-01", "TERRABIT 2015 DEV");
+
+ devenv.Export(loadedObjects["5.80"], $"{Directory.GetCurrentDirectory()}\\CU80.txt");
+ devenv.Export(loadedObjects["5.99997"], $"{Directory.GetCurrentDirectory()}\\CU99997.txt");
+ devenv.Export(loadedObjects["1.13"], $"{Directory.GetCurrentDirectory()}\\TAB13.txt");
+
log.Info("Shutting down...");
Console.ReadLine();
diff --git a/NAVSCM Library/NavInterface/DevEnvInterface.cs b/NAVSCM Library/NavInterface/DevEnvInterface.cs
index 07ed91c..64555a5 100644
--- a/NAVSCM Library/NavInterface/DevEnvInterface.cs
+++ b/NAVSCM Library/NavInterface/DevEnvInterface.cs
@@ -19,7 +19,7 @@ namespace NavScm.NavInterface
/// Currently, the interface expects to be able to access the database using
/// NTLM Single Sign on. SQL user/pass authentication is not supported.
///
- class DevEnvInterface
+ public class DevEnvInterface
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(DevEnvInterface));
@@ -31,23 +31,23 @@ namespace NavScm.NavInterface
///
/// The exit code of finsql.exe. So far this is always 0, tests pending.
///
- public int ExitCode;
+ public readonly int ExitCode;
///
/// True, if the command executed successfully.
///
- public bool Success;
+ public readonly bool Success;
///
/// The output produced by the command, as written to navcommandresult.txt by finsql.exe.
///
- public string CommandOutput;
+ public readonly 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;
+ public readonly string ErrorMessage;
///
/// Constructs the whole object.
@@ -117,9 +117,6 @@ namespace NavScm.NavInterface
/// 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.
@@ -146,6 +143,8 @@ namespace NavScm.NavInterface
continue;
}
+ Contract.Assert(!Directory.EnumerateFileSystemEntries(tempPath).Any());
+
return tempPath;
}
@@ -173,7 +172,6 @@ namespace NavScm.NavInterface
Contract.Requires(command != "");
string tempPath = GetNewTempDirectory();
- Contract.Ensures(!Directory.Exists(tempPath));
string errorLog = $"{tempPath}\\error.log";
string commandOutput = $"{tempPath}\\navcommandresult.txt";
@@ -184,7 +182,6 @@ namespace NavScm.NavInterface
// Execute finsql.exe and wait for its exit...
Process process = new Process();
- Contract.Ensures(process.HasExited);
process.StartInfo.FileName = DevEnvPath;
process.StartInfo.Arguments = fullArguments;
@@ -207,18 +204,22 @@ namespace NavScm.NavInterface
log.Debug($"ExecuteCommand: Execution took: {sw.Elapsed}");
}
- // Parse and store finsql.exe result information
+ // Parse and store finsql.exe result information, be aware, that we are not getting
+ // Unicode back from finsql.exe, this is CP850 instead. Interesting, all this in the
+ // current millenia, as CP850 was introduced 1987 with DOS 3.3... Not to mention that
+ // the current windows ANSI Encoding is ignored as well. (Checked up to NAV 2015).
+ var finsqlEncoding = Encoding.GetEncoding(850);
CommandResult result;
if (File.Exists(errorLog))
{
- result = new CommandResult(process.ExitCode, false, File.ReadAllText(commandOutput), File.ReadAllText(errorLog));
+ result = new CommandResult(process.ExitCode, false, File.ReadAllText(commandOutput, finsqlEncoding), File.ReadAllText(errorLog, finsqlEncoding));
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), "");
+ result = new CommandResult(process.ExitCode, true, File.ReadAllText(commandOutput, finsqlEncoding), "");
log.Info("ExecuteCommand: Command executed successfully");
log.DebugFormat("ExecuteCommand: Command result: {0}", result.CommandOutput);
}
@@ -231,19 +232,40 @@ namespace NavScm.NavInterface
}
Directory.Delete(tempPath);
+ Contract.Assert(!Directory.Exists(tempPath));
+ Contract.Assume(process.HasExited);
+
return result;
}
///
/// Exports a given NAV object to disk.
///
+ ///
+ /// The file name must end with .txt, as finsql.exe deduces the export format from the destiation files
+ /// extension (crap). We have no other option here as to play by these rules.
+ /// Be aware, that NAV uses some strange mix of CP850 and CP1252 to encode the text files,
+ /// this is mean stuff here. The call does not try to convert this into something more sensible
+ /// at this point, especially since the IDE won't be able to handle this properly if you have to
+ /// work with the files manually. Checked with NAV 2015, YMMV.
+ /// Check http://forum.mibuso.com/discussion/37078/encoding-of-exported-navision-objects-txt-files
+ /// for further details about this.
/// The NAV object as taken from the SQL database or from the cache (doesn't matter).
- /// The name of the destination file. The system ensures, that the file
- /// ends with .txt, as finsql.exe deduces the export format from the destiation files extension (crap).
+ /// The name of the destination file. The file name must end with .txt.
public void Export(NavObject obj, string destinationFileName)
{
Contract.Requires(obj != null);
Contract.Requires(destinationFileName != "");
+ Contract.Requires(Path.GetExtension(destinationFileName) == ".txt");
+
+ // TODO: Skip Unlicensed objects?
+ string command = $"Command=ExportObjects,File=\"{destinationFileName}\",Filter=\"{obj.GetFilter()}\"";
+ log.DebugFormat("Export: Build command string: {0}", command);
+ var result = ExecuteCommand(command);
+ if (! result.Success)
+ {
+ throw new ArgumentException($"Cannot export object {obj.NavType} ID {obj.ID}: {result.ErrorMessage}");
+ }
}
}
diff --git a/NAVSCM Library/NavInterface/NavObject.cs b/NAVSCM Library/NavInterface/NavObject.cs
index 9c8c341..3018b47 100644
--- a/NAVSCM Library/NavInterface/NavObject.cs
+++ b/NAVSCM Library/NavInterface/NavObject.cs
@@ -52,7 +52,7 @@ namespace NavScm.NavInterface
partial void OnLoaded()
{
Contract.Requires(Company_Name.Length == 0);
- Contract.Requires(Type >= 0 && Type <= 9 && Type != 2 && Type != 4 );
+ Contract.Requires(Type >= 1 && Type <= 9 && Type != 2 && Type != 4 );
/*
if (Company_Name.Length > 0)
throw new InvalidOperationException($"The object {CacheKey} holds a variant with the company name {Company_Name}, which is unsupported");
@@ -93,6 +93,27 @@ namespace NavScm.NavInterface
get { return Date.Add(Time.TimeOfDay); }
}
+ ///
+ /// Returns a filter string suitable to filter the NAV Object table during finsql operation.
+ ///
+ /// Filter-String usable f.x. in ExportObjects.
+ [Pure]
+ public string GetFilter()
+ {
+ switch(NavType)
+ {
+ case NavObjectType.Codeunit: return $"Type=Codeunit;ID={ID}";
+ case NavObjectType.MenuSuite: return $"Type=MenuSuite;ID={ID}";
+ case NavObjectType.Page: return $"Type=Page;ID={ID}";
+ case NavObjectType.Query: return $"Type=Query;ID={ID}";
+ case NavObjectType.Report: return $"Type=Report;ID={ID}";
+ case NavObjectType.Table: return $"Type=Table;ID={ID}";
+ case NavObjectType.XmlPort: return $"Type=XmlPort;ID={ID}";
+ }
+
+ throw new InvalidOperationException($"The Type {Type} is unknown, cannot convert to filter.");
+ }
+
///
/// Constructs a hash key based on Type and ID.
///