使用DTF(wix)以编程方式将cabinet文件添加到msi

时间:2015-04-10 07:07:13

标签: c# wix cab dtf

手头的任务简介:

我工作的公司不是软件公司,而是专注于机械和热力学工程问题。 为了帮助解决他们的系统设计挑战,他们开发了一个软件,用于计算更换单个组件的系统影响。 该软件很老,用FORTRAN编写,经过30年的发展,这意味着我们无法快速重写或更新它。

正如您可能想象的那样,这个软件的安装方式也在不断发展,但速度明显慢于系统的其他部分,这意味着打包是通过批处理脚本完成的,该脚本从不同的地方收集文件,并将它们放在一个文件夹中,然后将其编译成iso,刻录到CD,然后通过邮件发送。

你们年轻的程序员(我30岁),可能期望一个程序加载dll,但在链接之后是相当自包含的。即使代码由几个类组成,也可以来自不同的命名空间等。

然而在FORTRAN 70中..并非如此。这意味着它自己的软件包括对预建模块的大量调用(阅读:单独的程序)..

我们需要能够通过互联网进行分发,就像任何其他现代公司已经能够一段时间一样。要做到这一点,我们可以直接下载* .iso吗?

嗯,不幸的是,iso包含几个用户特定的文件。 正如您可能想象的那样,成千上万的用户,那将是成千上万的isos,几乎相同。

此外,我们不想将旧的基于FORTRAN的安装软件转换为真正的安装包,而我们所有其他(和更现代的)程序都是打包为MSI的C#程序。 但是在我们的服务器上使用这个旧软件的单个msi的编译时间接近10秒,因此当用户请求时,我们根本不能构建msi。 (如果多个用户同时请求,服务器将无法在请求超时之前完成..) 我们也不能预先构建用户特定的msi并缓存它们,因为我们在服务器上的内存不足......(每个发布版本的总量大约为15千兆字节)

任务说明 tl:dr;

以下是我会做的事情:(受到Christopher Painter的评论启发)

  • 创建基本MSI,使用虚拟文件而不是用户特定文件
  • 使用特定于用户的文件为每个用户创建cab文件
  • 在请求时,使用“_Stream”将用户特定的cab文件注入基本msi的临时副本。
  • 在Media表中插入一个引用,其中包含一个新的'DiskID'和一个与额外文件对应的'LastSequence',以及注入的cabfile的名称。
  • 使用新cab文件中用户特定文件的名称,新的序列号(在新的cab文件序列范围内)和文件大小更新Filetable。

问题

我的代码无法完成上述任务。我可以从msi中读取就好了,但是从不插入cabinet文件。

此外:

如果我用DIRECT模式打开msi,它会破坏媒体表,如果我在TRANSACTION模式下打开它,它根本无法改变任何内容..

在直接模式下,Media表中的现有行将替换为:

DiskId: 1
LastSequence: -2145157118
Cabinet: "Name of action to invoke, either in the engine or the handler DLL."

我做错了什么?

下面我提供了注入新cab文件所涉及的片段。

摘录1

public string createCabinetFileForMSI(string workdir, List<string> filesToArchive)
    {
        //create temporary cabinet file at this path:
        string GUID = Guid.NewGuid().ToString();
        string cabFile = GUID + ".cab";
        string cabFilePath = Path.Combine(workdir, cabFile);

        //create a instance of Microsoft.Deployment.Compression.Cab.CabInfo
        //which provides file-based operations on the cabinet file
        CabInfo cab = new CabInfo(cabFilePath);

        //create a list with files and add them to a cab file
        //now an argument, but previously this was used as test:
        //List<string> filesToArchive = new List<string>() { @"C:\file1", @"C:\file2" };
        cab.PackFiles(workdir, filesToArchive, filesToArchive);

        //we will ned the path for this file, when adding it to an msi..
        return cabFile;
    }

摘录2

    public int insertCabFileAsNewMediaInMSI(string cabFilePath, string pathToMSIFile, int numberOfFilesInCabinet = -1)
    {
        //open the MSI package for editing
        pkg = new InstallPackage(pathToMSIFile, DatabaseOpenMode.Direct); //have also tried direct, while database was corrupted when writing.
        return insertCabFileAsNewMediaInMSI(cabFilePath, numberOfFilesInCabinet);
    }

摘录3

 public int insertCabFileAsNewMediaInMSI(string cabFilePath, int numberOfFilesInCabinet = -1)
    {
        if (pkg == null)
        {
            throw new Exception("Cannot insert cabinet file into non-existing MSI package. Please Supply a path to the MSI package");
        }

        int numberOfFilesToAdd = numberOfFilesInCabinet;
        if (numberOfFilesInCabinet < 0)
        {
            CabInfo cab = new CabInfo(cabFilePath);
            numberOfFilesToAdd = cab.GetFiles().Count;
        }

        //create a cab file record as a stream (embeddable into an MSI)
        Record cabRec = new Record(1);
        cabRec.SetStream(1, cabFilePath);

        /*The Media table describes the set of disks that make up the source media for the installation.
          we want to add one, after all the others
          DiskId - Determines the sort order for the table. This number must be equal to or greater than 1,
          for out new cab file, it must be > than the existing ones...
        */
        //the baby SQL service in the MSI does not support "ORDER BY `` DESC" but does support order by..
        IList<int> mediaIDs = pkg.ExecuteIntegerQuery("SELECT `DiskId` FROM `Media` ORDER BY `DiskId`");
        int lastIndex = mediaIDs.Count - 1;
        int DiskId = mediaIDs.ElementAt(lastIndex) + 1;

        //wix name conventions of embedded cab files is "#cab" + DiskId + ".cab"
        string mediaCabinet = "cab" + DiskId.ToString() + ".cab";

        //The _Streams table lists embedded OLE data streams.
        //This is a temporary table, created only when referenced by a SQL statement.
        string query = "INSERT INTO `_Streams` (`Name`, `Data`) VALUES ('" + mediaCabinet + "', ?)";
        pkg.Execute(query, cabRec);
        Console.WriteLine(query);

        /*LastSequence - File sequence number for the last file for this new media.
          The numbers in the LastSequence column specify which of the files in the File table
          are found on a particular source disk.

          Each source disk contains all files with sequence numbers (as shown in the Sequence column of the File table)
          less than or equal to the value in the LastSequence column, and greater than the LastSequence value of the previous disk
          (or greater than 0, for the first entry in the Media table).
          This number must be non-negative; the maximum limit is 32767 files.
          /MSDN
         */
        IList<int> sequences = pkg.ExecuteIntegerQuery("SELECT `LastSequence` FROM `Media` ORDER BY `LastSequence`");
        lastIndex = sequences.Count - 1;
        int LastSequence = sequences.ElementAt(lastIndex) + numberOfFilesToAdd;

        query = "INSERT INTO `Media` (`DiskId`, `LastSequence`, `Cabinet`) VALUES (" + DiskId.ToString() + "," + LastSequence.ToString() + ",'#" + mediaCabinet + "')";
        Console.WriteLine(query);
        pkg.Execute(query);

        return DiskId;

    }

更新:愚蠢的我,在交易模式中忘了“提交” - 但现在它和直接模式一样,所以问题没有真正的改变。

1 个答案:

答案 0 :(得分:0)

我会自己回答这个问题,因为我刚刚学到了一些我以前不知道的DIRECT模式,并且不想把它留在这里以允许最终的重新谷歌...

显然,如果我们在程序最终崩溃之前关闭数据库句柄,我们只会成功更新MSI。

为了回答这个问题,这个析构函数应该这样做。

~className()
{
        if (pkg != null)
        {
            try
            {
                pkg.Close();
            }
            catch (Exception ex)
            {
                //rollback not included as we edit directly?

                //do nothing.. 
                //atm. we just don't want to break anything if database was already closed, without dereferencing
            }
        }
}

添加正确的关闭语句后,MSI的大小增加 (并且媒体行已添加到媒体表:))

我会发布整个课程来解决这个任务,当它完成并经过测试时, 但我会在关于SO的相关问题中这样做。 the related question on SO

相关问题