简体   繁体   English

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

[英]Adding cabinet file to msi programatically with DTF (wix)

Introduction to the Task at hand: can be skipped if impatient 即将进行的任务介绍: 如果不耐烦可以跳过

The company I work for is not a software company, but focus on mechanical and thermodynamic engineering problems. 我工作的公司不是软件公司,而是专注于机械和热力学工程问题。 To help solve their system design challenges, they have developed a software for calculating the system impact of replacing individual components. 为了帮助解决他们的系统设计难题,他们开发了一种软件来计算更换单个组件对系统的影响。 The software is quite old, written in FORTRAN and has evolved over a period of 30 years, which means that we cannot quickly re-write it or update it. 该软件已经很老了,是用FORTRAN编写的,并且已经发展了30年,这意味着我们无法快速重写或更新它。

As you may imagine the way this software is installed has also evolved, but significantly slower than the rest of the system, meaning that packaging is done by a batch script that gathers files from different places, and puts them in a folder, which is then compiled into an iso, burned to a cd, and shipped with mail. 您可能会想到,该软件的安装方式也有所发展,但比系统的其余部分慢得多,这意味着打包工作是通过批处理脚本完成的,该批处理脚本从不同位置收集文件,然后将其放在文件夹中,然后该文件夹编译成ISO,刻录到CD,并随邮件一起寄出。

You young programmers (I am 30), may expect a program to load dll's, but otherwise be fairly self-contained after linking. 你们年轻的程序员(我30岁)可能期望程序加载dll,但在链接后还是相当独立的。 Even if the code is made up of several classes, from different namespaces etc.. 即使代码是由几个类组成的,它们来自不同的名称空间等。

In FORTRAN 70 however.. Not so much. 但是在FORTRAN 70中。 Which means that the software it self consists of an alarming number of calls to prebuilt modules (read: seperate programs ).. 这意味着它本身的软件包括对预制模块的惊人数量的调用(阅读: 单独的程序 )。

We need to be able to distribute via the internet, as any other modern company have been able to for a while. 我们需要能够通过互联网进行分发,就像任何其他现代公司已经能够这样做一样。 To do this we could just make the *.iso downloadable right? 为此,我们可以使* .iso可下载对吗?

Well, unfortunately no, the iso contains several files which are user specific. 好吧,不幸的是,iso包含几个特定于用户的文件。 As you may imagine with thousands of users, that would be thousands of isos, that are nearly identical. 就像您想象的那样,对于成千上万的用户,这将是成千上万的isos,几乎是相同的。

Also we wan't to convert the old FORTRAN based installation software, into a real installation package, and all our other (and more modern) programs are C# programs packaged as MSI's.. But the compile time for a single msi with this old software on our server, is close to 10 seconds, so it is simply not an option for us to build the msi, when requested by the user. 同样,我们也不会将基于FORTRAN的旧安装软件转换为真实的安装程序包,而我们所有其他(以及更现代的)程序都是C#程序,打包为MSI。.但是,使用此旧软件的单个msi的编译时间在我们的服务器上,该时间接近10秒,因此,当用户要求时,我们根本无法选择构建msi。 (if multiple users requests at the same time, the server won't be able to complete before requests timeout..) Nor can we prebuild the user specific msi's and cache them, as we would run out of memory on the server.. (total at ~15 giga Byte per released version) (如果多个用户同时请求,则服务器将无法在请求超时之前完成。。)我们也无法预先构建用户特定的msi并将其缓存,因为我们将耗尽服务器上的内存。(每个发布版本总计约15千兆字节)

Task Description tl:dr; 任务描述 tl:dr;

Here is what I though I would do: (inspired by comments from Christopher Painter ) 这是我虽然会做的事情:(受Christopher Painter的评论启发)

  • Create a base MSI, with dummy files instead of the the user specific files 创建基本的MSI,使用虚拟文件而不是用户特定的文件
  • Create cab file for each user, with the user specific files 为每个用户创建cab文件,以及用户特定的文件
  • At request time inject the userspecific cab file into a temporary copy of the base msi using the "_Stream" table . 在请求时,使用“ _Stream” 将用户特定的cab文件注入到基本msi的临时副本中。
  • Insert a reference into the Media table with a new 'DiskID' and a 'LastSequence' corresponding to the extra files, and the name of the injected cabfile. 将带有新“ DiskID”和“ LastSequence”(与额外文件相对应)以及注入的cabfile的名称的引用插入媒体表。
  • Update the Filetable with the name of the user specific file in the new cab file, a new Sequence number (in the range of the new cab files sequence range), and the file size. 使用新cab文件中用户特定文件的名称,新序号(在新cab文件序列范围内)和文件大小更新Filetable。

Question

My code fails to do the task just described. 我的代码无法完成刚刚描述的任务。 I can read from the msi just fine, but the cabinet file is never inserted. 我可以从msi读取,但绝对不会插入cabinet文件。

Also: 也:

If I open the msi with DIRECT mode, it corrupts the media table, and if I open it in TRANSACTION mode, it fails to change anything at all.. 如果以DIRECT模式打开msi,它会损坏媒体表, 而如果以TRANSACTION模式打开它,则根本无法更改任何内容。

In direct mode the existing line in the Media table is replaced with: 在直接模式下,Media表中的现有行替换为:

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

What Am I doing wrong ? 我究竟做错了什么 ?

Below I have provided the snippets involved with injecting the new cab file. 下面,我提供了有关注入新cab文件的代码段。

snippet 1 片段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;
    }

snippet 2 片段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);
    }

snippet 3 片段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;

    }

update: stupid me, forgot about "committing" in transaction mode - but now it does the same as in direct mode, so no real changes to the question. 更新:愚蠢的我,忘记了在事务模式下的“提交”-但是现在它与直接模式下的一样,因此对该问题没有真正的改变。

I will answer this my self, since I just learned something about DIRECT mode that I didn't know before, and wan't to keep it here to allow for the eventual re-google.. 我会自我回答,因为我刚刚学到了一些以前不知道的有关DIRECT模式的知识,并且不想将其保留在这里,以便最终重新使用Google。

Apparently we only succesfully updates the MSI, if we closed the database handle before the program eventually chrashed. 显然,如果我们在程序最终崩溃之前关闭了数据库句柄,就只能成功更新MSI。

for the purpose of answering the question, this destructor should do it. 为了回答问题,此析构函数应该这样做。

~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
            }
        }
}

after adding the correct close statement, the MSI grew in size (and a media row was added to the media table :) ) 添加正确的close语句后,MSI的大小增加了(并且在介质表中添加了介质行:))

I will post the entire class for solving this task, when its done and tested, but I'll do it in the related question on SO. 当完成并测试了该任务后,我将发布整个课程来解决该任务,但是我将在SO的相关问题中进行讨论。 the related question on SO 有关SO的相关问题

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM