Введение в текущую задачу: можно пропустить, если вы нетерпеливы
Компания, в которой я работаю, не занимается разработкой программного обеспечения, а занимается проблемами механической и термодинамической инженерии. Чтобы помочь решить проблемы проектирования системы, они разработали программное обеспечение для расчета воздействия на систему замены отдельных компонентов. Программное обеспечение довольно старое, написано на FORTRAN и развивалось в течение 30 лет, а это означает, что мы не можем быстро его переписать или обновить.
Как вы можете себе представить, способ установки этого программного обеспечения также изменился, но значительно медленнее, чем остальная часть системы, что означает, что упаковка выполняется пакетным сценарием, который собирает файлы из разных мест и помещает их в папку, которая затем скомпилирован в iso, записан на компакт-диск и отправлен по почте.
Вы, молодые программисты (мне 30), можете ожидать, что программа загрузит библиотеки DLL, но в остальном после компоновки будет достаточно самодостаточной. Даже если код состоит из нескольких классов, из разных пространств имен и т. Д.
Однако в FORTRAN 70 .. Не так уж и много. Это означает, что само программное обеспечение состоит из тревожного количества обращений к готовым модулям (читайте: отдельные программы).
Мы должны иметь возможность распространять через Интернет, как и любая другая современная компания какое-то время. Для этого мы могли бы просто сделать * .iso загружаемым, верно?
К сожалению, нет, iso содержит несколько файлов, специфичных для пользователя. Как вы можете себе представить, учитывая тысячи пользователей, это будут тысячи практически идентичных ISO.
Также мы не хотим преобразовывать старое установочное программное обеспечение на основе FORTRAN в настоящий установочный пакет, и все наши другие (и более современные) программы являются программами на C #, упакованными как MSI ... Но время компиляции для одного msi с этим старым программным обеспечением на нашем сервере это близко к 10 секундам, поэтому мы просто не можем создавать msi по запросу пользователя. (если несколько пользователей запрашивают одновременно, сервер не сможет завершить работу до истечения тайм-аута запросов ..) Мы также не можем предварительно создать пользовательские msi и кэшировать их, так как у нас закончится память на сервере .. ( всего ~ 15 гигабайт на выпущенную версию)
Описание задачи tl: dr;
Вот что я хотел бы сделать: (вдохновленный комментариями Кристофера Пейнтера)
- Создайте базовый MSI с фиктивными файлами вместо пользовательских файлов.
- Создайте CAB-файл для каждого пользователя с пользовательскими файлами.
- Во время запроса вставьте пользовательский cab-файл во временную копию базового msi, используя таблицу "_Stream".
- Вставьте ссылку в таблицу Media с новым DiskID и LastSequence, соответствующими дополнительным файлам, и именем внедренного cab-файла.
- Обновите таблицу файлов, указав имя пользовательского файла в новом CAB-файле, новый порядковый номер (в диапазоне нового диапазона последовательности CAB-файлов) и размер файла.
Вопрос
Мой код не справляется с только что описанной задачей. Я могу читать из msi нормально, но файл кабинета никогда не вставляется.
Также:
Если я открою msi в режиме DIRECT, он повредит таблицу мультимедиа, , а если я открою его в режиме 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;
}
update: глупо, я забыл о "фиксации" в режиме транзакции - но теперь он делает то же самое, что и в прямом режиме, поэтому никаких реальных изменений в вопросе нет.