使用DTF(wix)以编程方式将Cabinet文件添加到MSI [英] Adding cabinet file to msi programatically with DTF (wix)

查看:136
本文介绍了使用DTF(wix)以编程方式将Cabinet文件添加到MSI的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



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



<您可能会想到,该软件的安装方式也有所发展,但比系统的其余部分慢得多,这意味着打包工作是通过批处理脚本完成的,该批处理脚本从不同位置收集文件并将其放置在文件夹中,



年轻的程序员(我30岁)可能期望程序加载dll,但是否则,链接后必须完全独立。即使代码由几个类组成,也来自不同的命名空间,等等。



但是在FORTRAN 70中。这意味着它自己的软件包括对预制模块的惊人数量的调用(请参阅:单独的程序)。



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



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



我们也不会转换旧的FORTRAN基于Windows的安装软件,并放入一个实际的安装程序包中,而我们所有其他(以及更现代的)程序都是C#程序,打包为MSI。.
但是,在我们的服务器上使用该旧软件的单个msi的编译时间已接近到10秒,因此当用户要求时,我们根本无法选择构建msi。 (如果有多个用户同时请求,则服务器将无法在请求超时之前完成。)
我们也无法预先构建用户特定的msi并将其缓存,因为我们将耗尽内存。服务器..(每个发布版本总计约15 GB)



任务说明 tl:dr;



这是我要做的事情:(受 Christopher Painter




  • 创建基本的MSI,其中包含虚拟文件而不是用户特定文件

  • 为每个用户创建带有用户特定文件的cab文件

  • 在请求时,使用 _Stream将用户特定的cab文件注入基本msi的临时副本中 table

  • 将引用插入带有新 DiskID和 LastSequence(与额外文件相对应)的Media表中,并插入注入的cabfile的名称

  • 使用新cab文件中用户特定文件的名称,新序号(在新cab文件序列范围内)和文件大小来更新Filetable。



问题



我的代码无法完成上述任务。我可以从msi读取,但是从没插入内阁文件。



也:



如果我以直接模式打开msi,则会损坏媒体表,,如果以事务模式打开它,则根本无法更改任何内容。



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

  DiskId :1 
LastSequence:-2145157118
内阁:要在引擎或处理程序DLL中调用的动作的名称。

我在做什么错了?



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



代码段1

 公用字符串createCabinetFileForMSI(字符串workdir,List< string> filesToArchive)
{
//在此路径下创建临时文件柜:
字符串GUID = Guid.NewGuid()。ToString();
字符串cabFile = GUID + .cab;
字符串cabFilePath = Path.Combine(workdir,cabFile);

//创建Microsoft.Deployment.Compression.Cab.CabInfo的实例
//对橱柜文件提供基于文件的操作
CabInfo cab = new CabInfo(cabFilePath );

//创建一个包含文件的列表,并将它们添加到cab文件中
//现在是一个参数,但以前它用作测试:
// List< string> filesToArchive = new List< string>(){@ C:\file1,@ C:\file2};
cab.PackFiles(workdir,filesToArchive,filesToArchive);

//将文件添加到msi时,我们将为其指定路径。
return cabFile;
}

片段2

  public int insertCabFileAsNewMediaInMSI(string cabFilePath,string pathToMSIFile,int numberOfFilesInCabinet = -1)
{
//打开MSI包以编辑
pkg = new InstallPackage(pathToMSIFile,DatabaseOpenMode.Direct); //还尝试了直接操作,而写入时数据库已损坏。
return insertCabFileAsNewMediaInMSI(cabFilePath,numberOfFilesInCabinet);
}

摘要3

  public int insertCabFileAsNewMediaInMSI(string cabFilePath,int numberOfFilesInCabinet = -1)
{
if(pkg == null)
{
抛出新的Exception(无法将Cabinet文件插入不存在的MSI包中。请提供MSI包的路径);
}

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

//将cab文件记录创建为流(可嵌入到MSI中)
Record cabRec = new Record(1);
cabRec.SetStream(1,cabFilePath);

/ *媒体表描述了组成安装源媒体的磁盘集。
我们要添加一个,在所有其他之后。
DiskId-确定表的排序顺序。对于新的cab文件,此数字必须等于或大于1,
,且必须>比现有的...
* /
// MSI中的婴儿SQL服务不支持 ORDER BY DESC,但支持按...排序。
IList< int> ; mediaIDs = pkg.ExecuteIntegerQuery(从媒体中选择磁盘ID,然后按磁盘ID排序);
int lastIndex = mediaIDs.Count-1;
int DiskId = mediaIDs.ElementAt(lastIndex)+ 1;

//嵌入式cab文件的名称约定为 #cab + DiskId + .cab
string mediaCabinet = cab + DiskId.ToString()+ .cab ;

// _ St​​reams表列出了嵌入式OLE数据流。
//这是一个临时表,仅在由SQL语句引用时创建。
string query = INSERT INTO`_Streams`(`Name`,`Data`)VALUES(' + mediaCabinet +',?);
pkg.Execute(查询,cabRec);
Console.WriteLine(query);

/ * LastSequence-此新媒体的最后一个文件的文件序列号。
LastSequence列中的数字指定在特定源磁盘上找到文件表
中的哪些文件。

每个源磁盘都包含所有具有序列号的文件(如文件表的序列列中所示)
小于或等于LastSequence列中的值,并且大于LastSequence前一个磁盘
的值(对于Media表中的第一个条目,该值大于0)。
此数字必须为非负数;最大限制为32767个文件。
/ MSDN
* /
IList< int> sequence = pkg.ExecuteIntegerQuery(从`Media` ORDER BY`LastSequence`中选择`LastSequence`);
lastIndex = sequence.Count-1;
int LastSequence = sequence.ElementAt(lastIndex)+ numberOfFilesToAdd;

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

返回DiskId;

}

更新:愚蠢的我,忘记了在交易中提交模式-但现在它与直接模式下的功能相同,因此对该问题没有真正的改变。

解决方案

我将回答这个问题我自己,因为我刚刚学到了以前不知道的有关DIRECT模式的知识,也不想将其保留在这里以允许最终重新使用Google。



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



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

 〜className()
{
if(pkg!= null)
{
try
{
pkg.Close();
}
捕获(例外)
{
//不包含回滚,因为我们直接进行编辑?

//什么都不做..
// atm。我们只是不想在数据库已经关闭的情况下破坏任何东西,而不会取消引用
}
}
}

添加正确的结束语句后,MSI的大小增加了
(并且在介质表中添加了介质行:))



我将发布整个班级来解决此任务,完成并测试后,
,但是我将在SO的相关问题中完成。
有关SO的相关问题


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.

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.

You young programmers (I am 30), may expect a program to load dll's, but otherwise be fairly self-contained after linking. Even if the code is made up of several classes, from different namespaces etc..

In FORTRAN 70 however.. Not so much. 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?

Well, unfortunately no, the iso contains several files which are user specific. As you may imagine with thousands of users, that would be thousands of isos, that are nearly identical.

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. (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)

Task Description tl:dr;

Here is what I though I would do: (inspired by comments from Christopher Painter)

  • Create a base MSI, with dummy files instead of the the user specific files
  • Create cab file for each user, with the user specific files
  • At request time inject the userspecific cab file into a temporary copy of the base msi using the "_Stream" table.
  • 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.
  • 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.

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.

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..

In direct mode the existing line in the Media table is replaced with:

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.

snippet 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

    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

 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..

Apparently we only succesfully updates the MSI, if we closed the database handle before the program eventually chrashed.

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 :) )

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. the related question on SO

这篇关于使用DTF(wix)以编程方式将Cabinet文件添加到MSI的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆