【问题标题】:Adding cabinet file to msi programatically with DTF (wix)使用 DTF (wix) 以编程方式将cabinet文件添加到msi
【发布时间】:2015-04-10 18:47:53
【问题描述】:

手头任务简介:不耐烦可以跳过

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

正如您想象的那样,该软件的安装方式也发生了变化,但比系统的其他部分慢得多,这意味着打包是通过一个批处理脚本完成的,该脚本从不同的地方收集文件,并将它们放在一个文件夹中,然后将其编译为 iso,刻录到 cd,并随邮件一起发送。

你们年轻的程序员(我 30 岁)可能希望程序加载 dll,但链接后会相当独立。即使代码由多个类组成,来自不同的命名空间等。

但是在 FORTRAN 70 中.. 没那么多。这意味着它本身的软件包含对预建模块的数量惊人的调用(阅读:单独的程序)..

我们需要能够通过互联网进行分发,就像任何其他现代公司在一段时间内都能够做到的那样。为此,我们可以让 *.iso 可下载,对吗?

很遗憾,没有,iso 包含几个特定于用户的文件。 正如您可能想象的那样,有数千个用户,这将是数千个几乎相同的 iso。

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

任务描述 tl:dr;

这是我想做的事情:(灵感来自 Christopher Painter 的 cmets)

  • 使用虚拟文件而不是用户特定文件创建基本 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 文件所涉及的 sn-ps。

sn-p 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;
    }

sn-p 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);
    }

sn-p 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;

    }

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

【问题讨论】:

    标签: c# wix cab dtf


    【解决方案1】:

    我会自己回答这个问题,因为我刚刚学到了一些我以前不知道的关于 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

    【讨论】:

    • 我强烈建议在您的代码中使用 using() 语句,以便在对象空间不足时调用 IDispose。 DTF 将为您关闭并清理您的所有资源,避免这些问题。
    • 已注意到,并感谢您的提醒。作为一名嵌入式程序员,C# 中的花哨的新技巧是陌生的,但并非不受欢迎
    • 顺便说一句,我的意思是范围而不是空间。我明白...我是一名构建/发布专家,在过去的二十年里涉足了十几种语言,但从未成为其中任何一种语言的专家。我只学到足够的知识来理解技术以及开发人员构建/发布的需求是什么。
    • 因为我在其他地方做过,所以直接点击其他问题的链接
    • 您好 Henrik,谢谢,但我想知道在 MSI 中插入一个压缩包文件后,如何在安装过程中读取此文件?注意,我使用 WIX 工具集。感谢您的帮助
    猜你喜欢
    • 1970-01-01
    • 2016-10-31
    • 2021-11-19
    • 1970-01-01
    • 2018-12-04
    • 2014-06-11
    • 2013-07-09
    • 2011-10-25
    • 2015-12-29
    相关资源
    最近更新 更多