Добавление CAB-файла в MSI программно с помощью DTF (wix)

Введение в задачу: можно пропустить при нетерпении

Компания, в которой я работаю, не является разработчиком программного обеспечения, но сосредоточена на проблемах машиностроения и термодинамики. Чтобы помочь решить свои проблемы проектирования системы, они разработали программное обеспечение для расчета воздействия системы на замену отдельных компонентов. Программное обеспечение довольно старое, написано на Фортране и развивалось в течение 30 лет, что означает, что мы не можем быстро переписать или обновить его.

Как вы можете себе представить, способ установки этого программного обеспечения также эволюционировал, но значительно медленнее, чем остальная система, что означает, что упаковка выполняется пакетным сценарием, который собирает файлы из разных мест и помещает их в папку, которая затем скомпилирован в ISO, записан на CD и отправлен с почтой.

Вы, молодые программисты (мне 30 лет), можете ожидать, что программа загрузит dll, но в остальном после компоновки будут достаточно автономными. Даже если код состоит из нескольких классов, из разных пространств имен и т.д..

В Фортране 70, однако.. Не так много. Это означает, что само программное обеспечение состоит из тревожного количества обращений к предварительно собранным модулям (читай: отдельные программы).

Мы должны иметь возможность распространять их через Интернет, как это может сделать любая другая современная компания. Для этого мы могли бы просто сделать *.iso скачиваемым, верно?

Ну, к сожалению, нет, iso содержит несколько файлов, которые зависят от пользователя. Как вы можете себе представить, с тысячами пользователей, это будут тысячи isos, которые почти идентичны.

Кроме того, мы не хотим преобразовывать старое установочное программное обеспечение на базе FORTRAN в настоящий установочный пакет, а все другие (и более современные) программы являются программами на C#, упакованными как MSI. Но время компиляции для одного msi с этим старым программным обеспечением на нашем сервере, это близко к 10 секундам, поэтому мы просто не можем построить MSI по запросу пользователя. (если запросы нескольких пользователей одновременно, сервер не сможет завершить работу до истечения времени ожидания запросов...) Также мы не можем предварительно создать специальные MSI-файлы для пользователей и кэшировать их, так как у нас не хватило бы памяти на сервере.. (всего около 15 гигабайт байт на выпущенную версию)

Описание задачи tl: dr;

Вот что я хотел бы сделать: (вдохновлено комментариями Christopher Painter)

  • Создайте базовый MSI с фиктивными файлами вместо пользовательских файлов
  • Создайте файл cab для каждого пользователя с файлами, специфичными для пользователя
  • Во время запроса вставьте пользовательский cab-файл во временную копию базовой msi, используя таблицу"_Stream".
  • Вставьте ссылку в таблицу "Media" с новым "DiskID" и "LastSequence", соответствующими дополнительным файлам, и именем внедренного файла.
  • Обновите Filetable, указав имя конкретного пользовательского файла в новом 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;

    }

Обновление: глупо, я забыл о "фиксации" в режиме транзакций - но теперь он делает то же самое, что и в прямом режиме, поэтому никаких реальных изменений в вопросе нет.

1 ответ

Решение

Я отвечу на этот вопрос сам, так как я только что узнал кое-что о режиме DIRECT, чего раньше не знал, и не хочу держать его здесь, чтобы учесть возможный повторный поиск в Google..

По-видимому, мы успешно обновляем 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
            }
        }
}

после добавления правильного оператора close размер MSI увеличился (и в таблицу media была добавлена ​​строка мультимедиа:))

Я опубликую весь класс для решения этой задачи, когда это будет сделано и протестировано, но я сделаю это в соответствующем вопросе о SO. связанный вопрос по SO

Другие вопросы по тегам