Skip to content

Commit

Permalink
Added schema export flag
Browse files Browse the repository at this point in the history
  • Loading branch information
zbalkan committed Feb 20, 2024
1 parent cda090e commit 7af81ef
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 46 deletions.
5 changes: 4 additions & 1 deletion source/ditjson/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> 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; }
}
}
142 changes: 97 additions & 45 deletions source/ditjson/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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"];

/// <summary>
/// Application entry point
/// </summary>
Expand Down Expand Up @@ -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<string, object>();
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<string> GetTablesToQuery(IEnumerable<string>? tablesInOptions, Session session, JET_DBID dbid)
{
var tablesInDb = Api.GetTableNames(session, dbid);
var ntdsDictionary = new Dictionary<string, object>();
foreach (var table in tablesToQuery)
{
ntdsDictionary.Add(table, TableToList(session, dbid, table));
}

List<string> 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<string>(defaultTables);
}
else
{
// If user asks all
if (tablesInOptions.Count() == 1 && tablesInOptions.First().Equals("*", StringComparison.Ordinal))
try
{
tablesToQuery = new List<string>(tablesInDb);
File.WriteAllText("ntds.json", json);
}
else
catch (Exception ex)
{
// if user asks oly specific tables
tablesToQuery = new List<string>(tablesInOptions.Where(t => tablesInDb.Contains(t)));
throw new NtdsException("Failed to write to JSON to file.", ex);
}
}

return tablesToQuery;
}

/// <summary>
Expand All @@ -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();
}

/// <summary>
/// Extracts the column data as the correct data type and formats appropriately
/// </summary>
Expand Down Expand Up @@ -226,6 +219,65 @@ private static string GetFormattedValue(Session session,
return temp.Replace("\0", string.Empty);
}

private static List<string> GetTablesToQuery(IEnumerable<string>? tablesInOptions, Session session, JET_DBID dbid)
{
var tablesInDb = Api.GetTableNames(session, dbid);

List<string> tablesToQuery;

if (tablesInOptions == null)
{
// Add only the default tables
tablesToQuery = new List<string>(defaultTables);
}
else
{
// If user asks all
if (tablesInOptions.Count() == 1 && tablesInOptions.First().Equals("*", StringComparison.Ordinal))
{
tablesToQuery = new List<string>(tablesInDb);
}
else
{
// if user asks oly specific tables
tablesToQuery = new List<string>(tablesInOptions.Where(t => tablesInDb.Contains(t)));
}
}

return tablesToQuery;
}
/// <summary>
/// Populates the <see cref="StringBuilder"/> instance with column data.
/// </summary>
/// <param name="session">ESENT Session</param>
/// <param name="dbid">Handle to the database</param>
/// <returns>A <see cref="List{Dictionary{string, object}}"/> containing table data</returns>
/// <exception cref="NtdsException"></exception>
/// <exception cref="FormatException"></exception>
/// <exception cref="OverflowException"></exception>
private static void PopulateCsv(Session session, JET_DBID dbid, string tableName, StringBuilder sb)
{
var columns = new List<ColumnInfo>(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);
}

/// <summary>
/// Export table as a <see cref="List{Dictionary{string, object}}"/>
/// </summary>
Expand Down

0 comments on commit 7af81ef

Please sign in to comment.