diff --git a/source/ditjson/Options.cs b/source/ditjson/Options.cs index f0f195e..cb3eb6a 100644 --- a/source/ditjson/Options.cs +++ b/source/ditjson/Options.cs @@ -11,7 +11,10 @@ internal class Options [Option('n', "ntds", Required = true, Default = "", HelpText = "Path to ntds.dit file")] public string Ntds { get; set; } - [Option('t', "tables", Required = false, Default = "", HelpText = "ntds.dit tables to include. Default: datatable, link_table")] + [Option('t', "tables", Required = false, Default = new[] { "datatable", "link_table" }, HelpText = "ntds.dit tables to include.")] public IEnumerable Tables { get; set; } + + [Option('s', "schema", Required = false, Default = false, HelpText = "Export schema from ntds.dit file. When provided, -t parameter is ignored.")] + public bool Schema { get; set; } } } diff --git a/source/ditjson/Program.cs b/source/ditjson/Program.cs index b391b69..d75f015 100644 --- a/source/ditjson/Program.cs +++ b/source/ditjson/Program.cs @@ -14,15 +14,14 @@ namespace ditjson { internal static class Program { + private static readonly string[] defaultTables = ["datatable", "link_table"]; + private static readonly JsonSerializerOptions options = new() { DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull, NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.Strict, WriteIndented = true, }; - - private static readonly string[] defaultTables = ["datatable", "link_table"]; - /// /// Application entry point /// @@ -64,60 +63,41 @@ internal static void RunOptions(Options opts) Api.JetAttachDatabase(session, opts.Ntds, AttachDatabaseGrbit.ReadOnly); Api.JetOpenDatabase(session, opts.Ntds, null, out var dbid, OpenDatabaseGrbit.ReadOnly); - var tablesToQuery = GetTablesToQuery(opts.Tables, session, dbid); - - var ntdsDictionary = new Dictionary(); - foreach (var table in tablesToQuery) + if (opts.Schema) { - ntdsDictionary.Add(table, TableToList(session, dbid, table)); - } - - string json; - try - { - json = JsonSerializer.Serialize(ntdsDictionary, options); - } - catch (NotSupportedException ex) - { - throw new NtdsException("Failed to serialize to JSON.", ex); - } + var csv = GenerateSchemaCsv(session, dbid); - try - { - File.WriteAllText("ntds.json", json); + File.WriteAllText("schema.csv", csv); } - catch (Exception ex) + else { - throw new NtdsException("Failed to write to JSON to file.", ex); - } - } + var tablesToQuery = GetTablesToQuery(opts.Tables, session, dbid); - private static List GetTablesToQuery(IEnumerable? tablesInOptions, Session session, JET_DBID dbid) - { - var tablesInDb = Api.GetTableNames(session, dbid); + var ntdsDictionary = new Dictionary(); + foreach (var table in tablesToQuery) + { + ntdsDictionary.Add(table, TableToList(session, dbid, table)); + } - List tablesToQuery; + string json; + try + { + json = JsonSerializer.Serialize(ntdsDictionary, options); + } + catch (NotSupportedException ex) + { + throw new NtdsException("Failed to serialize to JSON.", ex); + } - if (tablesInOptions == null) - { - // Add only the default tables - tablesToQuery = new List(defaultTables); - } - else - { - // If user asks all - if (tablesInOptions.Count() == 1 && tablesInOptions.First().Equals("*", StringComparison.Ordinal)) + try { - tablesToQuery = new List(tablesInDb); + File.WriteAllText("ntds.json", json); } - else + catch (Exception ex) { - // if user asks oly specific tables - tablesToQuery = new List(tablesInOptions.Where(t => tablesInDb.Contains(t))); + throw new NtdsException("Failed to write to JSON to file.", ex); } } - - return tablesToQuery; } /// @@ -143,6 +123,19 @@ private static string FormatBytes(byte[] data) return sb.ToString(); } + private static string GenerateSchemaCsv(Session session, JET_DBID dbid) + { + var sb = new StringBuilder(); + sb.AppendLine("Table,Column Name,Column Type,Is Multivalue"); + + // Get all tables and export + foreach (var table in GetTablesToQuery(["*"], session, dbid)) + { + PopulateCsv(session, dbid, table, sb); + } + return sb.ToString(); + } + /// /// Extracts the column data as the correct data type and formats appropriately /// @@ -226,6 +219,65 @@ private static string GetFormattedValue(Session session, return temp.Replace("\0", string.Empty); } + private static List GetTablesToQuery(IEnumerable? tablesInOptions, Session session, JET_DBID dbid) + { + var tablesInDb = Api.GetTableNames(session, dbid); + + List tablesToQuery; + + if (tablesInOptions == null) + { + // Add only the default tables + tablesToQuery = new List(defaultTables); + } + else + { + // If user asks all + if (tablesInOptions.Count() == 1 && tablesInOptions.First().Equals("*", StringComparison.Ordinal)) + { + tablesToQuery = new List(tablesInDb); + } + else + { + // if user asks oly specific tables + tablesToQuery = new List(tablesInOptions.Where(t => tablesInDb.Contains(t))); + } + } + + return tablesToQuery; + } + /// + /// Populates the instance with column data. + /// + /// ESENT Session + /// Handle to the database + /// A containing table data + /// + /// + /// + private static void PopulateCsv(Session session, JET_DBID dbid, string tableName, StringBuilder sb) + { + var columns = new List(Api.GetTableColumns(session, dbid, tableName)); + + using var table = new Table(session, dbid, tableName, OpenTableGrbit.ReadOnly); + Api.JetSetTableSequential(session, table, SetTableSequentialGrbit.None); + Api.MoveBeforeFirst(session, table); + + while (Api.TryMoveNext(session, table)) + { + foreach (var column in columns) + { + sb + .Append(table.Name).Append(',') + .Append(column.Name).Append(',') + .Append(column.Coltyp).Append(',') + .AppendLine(column.Grbit.HasFlag(ColumndefGrbit.ColumnMultiValued).ToString()); + } + } + + Api.JetResetTableSequential(session, table, ResetTableSequentialGrbit.None); + } + /// /// Export table as a ///