How to convert WindowsInstaller.Record to System.D

2019-08-12 12:37发布

I want to read MSI file(Windows Installer Package). I have written a function as below which takes two input parameters : msifileName and Table Name and returns a data table which is one of the MSI table.

public DataTable ReadMsiPropertyTable(string msiFile, string tableName)
    {            
        Type installerType = Type.GetTypeFromProgID("WindowsInstaller.Installer");

        WindowsInstaller.Installer installer = (WindowsInstaller.Installer)Activator.CreateInstance(installerType);

        Database database = installer.OpenDatabase(msiFile, 0);

        string sqlQuery = String.Format("SELECT * FROM {0}",tableName);

        View view = database.OpenView(sqlQuery);

        view.Execute(null);

        Record record = view.Fetch();

        DataTable msiPropertyTable = new DataTable();

        msiPropertyTable.Columns.Add("Column1", typeof(string));
        msiPropertyTable.Columns.Add("Column2", typeof(string));
        msiPropertyTable.Columns.Add("Column3", typeof(string));
        msiPropertyTable.Columns.Add("Column4", typeof(string));

        while (record != null)
        {
            int fieldCount;
            fieldCount = record.FieldCount;

            msiPropertyTable.Rows.Add(record.get_StringData(0), record.get_StringData(1), record.get_StringData(2), record.get_StringData(3));                
            record = view.Fetch();
        }
        return msiPropertyTable;
    }

Using above code snippet, the number of rows and columns of Record are not known. SO I am statically returning only four columns. I want to return all the rows and columns of MSI Table which are in record.

So Please let me know how can I convert output of view or record to Dataset and then bind to Datatable. Or is there any other way to return all the rows and columns. Thanks in advance.

3条回答
Deceive 欺骗
2楼-- · 2019-08-12 13:12

I would suggest you using the Deployment Tools Foundation (the API library to work with MSI packages, comes together with WiX Toolset) for this task. The API is handy and straight-forward.

For instance, there's a class Database. You can create it by providing the path to the MSI package to the constructor:

var db = new Database("path\to\MSI");

And it gives the any information about the MSI database. For instance, db.Tables["TableName"] returns an instance of the TableInfo class, which in its turn contains the information about columns, rows, primary keys, etc.

Download and install WiX Toolset, and get more info from DTF.chm help file.

查看更多
男人必须洒脱
3楼-- · 2019-08-12 13:13

Here's similar logic using the interop types rather then the DTF types. As you can see it's a lot more work. It's also a lot more fragile since COM is involved instead of P/Invoke. Also, if you can eliminate the requirement to create an ADO.NET DataTable, DTF supports LINQ queries and databinding. In other words, I wrote this for fun only. I would NEVER use this method in real code.

 public static DataTable ReadMsiPropertyTable(string msiFile, string tableName)
        {
            DataTable dataTable = new DataTable(tableName);
            Type installerType = Type.GetTypeFromProgID("WindowsInstaller.Installer");
            Installer installer = (WindowsInstaller.Installer)Activator.CreateInstance(installerType);
            Database database = installer.OpenDatabase(msiFile, MsiOpenDatabaseMode.msiOpenDatabaseModeReadOnly);

            string sqlQuery = String.Format("SELECT * FROM {0}", tableName);
            View view = database.OpenView(sqlQuery);
            view.Execute(null);

            Record names = view.ColumnInfo[MsiColumnInfo.msiColumnInfoNames];
            Record types = view.ColumnInfo[MsiColumnInfo.msiColumnInfoTypes];
            Record row = view.Fetch();

            for (int index = 1; index < names.FieldCount+1; index++)
            {
                string columnName = names.get_StringData(index);
                string columnSpec = types.get_StringData(index);

                switch (columnSpec.Substring(0, 1).ToLower())
                {
                    case "s":
                        dataTable.Columns.Add(columnName, typeof(String));
                        break;

                    case "l":
                        dataTable.Columns.Add(columnName, typeof(String));
                        break;

                    case "i":
                        dataTable.Columns.Add(columnName, typeof(Int32));
                        break;

                    case "v":
                        dataTable.Columns.Add(columnName, typeof (Stream));
                        break;
                }
            }

            while (row != null)
            {
                DataRow dataRow = dataTable.NewRow();
                for (int index = 0; index < dataTable.Columns.Count; index++)
                {

                    if(dataTable.Columns[index].DataType == typeof(String))
                    {
                        dataRow[index] = row.StringData[index + 1];
                    }
                    else if(dataTable.Columns[index].DataType == typeof(Int32))
                    {
                        dataRow[index] = row.IntegerData[index + 1];
                    }
                    else if(dataTable.Columns[index].DataType == typeof(Stream))
                    {
                       // Insanity has it's limits. Not implemented.
                    }
                }
                dataTable.Rows.Add(dataRow);
                row = view.Fetch();
            }
            return dataTable;
        }
查看更多
▲ chillily
4楼-- · 2019-08-12 13:32

Here's how you do it using DTF to create the types for your Database, View and Record objects. It would be similar using COM Interop but I have no desire to do things the hard way.

public static DataTable TableToDataTable( string msiPath, string tableName )
{
    DataTable dataTable = null;
    using(var database = new Database(msiPath, DatabaseOpenMode.ReadOnly))
    {
        using(var view = database.OpenView("SELECT * FROM `{0}`", tableName))
        {
            view.Execute();

            dataTable = new DataTable(tableName);
            foreach (var column in view.Columns)
            {
                dataTable.Columns.Add(column.Name, column.Type);
            }

            foreach (var record in view) using (record)
            {
                var row = dataTable.NewRow();
                foreach (var column in view.Columns)
                {
                    row[column.Name] = record[column.Name];
                }
                dataTable.Rows.Add(row);
            }
        }
    }
    return dataTable;
}
查看更多
登录 后发表回答