手头任务简介: 不耐烦可跳过
我工作的公司不是软件公司,而是专注于机械和热力学工程问题。为了帮助解决他们的系统设计挑战,他们开发了一个软件来计算更换单个组件对系统的影响。该软件相当陈旧,是用 FORTRAN 编写的,并且已经发展了 30 年,这意味着我们无法快速重写或更新它。
正如您可能想象的那样,该软件的安装方式也发生了变化,但比系统的其他部分慢得多,这意味着打包是通过一个批处理脚本完成的,该脚本从不同的地方收集文件,并将它们放在一个文件夹中,然后编译成iso,刻录成CD,并随邮件一起发送。
你们年轻的程序员(我 30 岁)可能希望程序加载 dll,但链接后会相当独立。即使代码由多个类组成,来自不同的命名空间等。
然而,在 FORTRAN 70 中.. 不是那么多。这意味着它本身的软件包含对预建模块的数量惊人的调用(阅读:单独的程序)..
我们需要能够通过互联网进行分发,就像任何其他现代公司在一段时间内都能够做到的那样。为此,我们可以让 *.iso 可下载,对吗?
好吧,很遗憾没有,iso 包含几个特定于用户的文件。正如您可能想象的那样,有数千个用户,这将是数千个几乎相同的 iso。
此外,我们不想将基于 FORTRAN 的旧安装软件转换为真正的安装包,我们所有其他(和更现代的)程序都是打包为 MSI 的 C# 程序。但是使用这个旧软件的单个 msi 的编译时间在我们的服务器上,接近 10 秒,因此当用户请求时,我们根本无法构建 msi。(如果多个用户同时请求,服务器将无法在请求超时之前完成..)我们也不能预先构建用户特定的 msi 并缓存它们,因为我们会耗尽服务器上的内存..(每个已发布版本总计约 15 GB)
任务描述 tl:dr;
这是我想做的事情:(灵感来自Christopher Painter的评论)
- 使用虚拟文件而不是用户特定文件创建基本 MSI
- 为每个用户创建 cab 文件,其中包含用户特定的文件
- 在请求时,使用“_Stream”表将用户特定的 cab 文件注入到基本 msi 的临时副本中。
- 使用与额外文件相对应的新“DiskID”和“LastSequence”以及注入的 cabfile 的名称将引用插入到 Media 表中。
- 使用新 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;
}
更新:愚蠢的我,忘记了在事务模式下的“提交”——但现在它和直接模式下的一样,所以对问题没有真正的改变。