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

Введение в текущую задачу: можно пропустить, если вы нетерпеливы

Компания, в которой я работаю, не занимается разработкой программного обеспечения, а занимается проблемами механической и термодинамической инженерии. Чтобы помочь решить проблемы проектирования системы, они разработали программное обеспечение для расчета воздействия на систему замены отдельных компонентов. Программное обеспечение довольно старое, написано на 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: глупо, я забыл о "фиксации" в режиме транзакции - но теперь он делает то же самое, что и в прямом режиме, поэтому никаких реальных изменений в вопросе нет.


person Henrik    schedule 10.04.2015    source источник


Ответы (1)


Я отвечу на это сам, так как я только что узнал кое-что о режиме ПРЯМОГО, чего не знал раньше, и не хочу оставлять его здесь, чтобы в конечном итоге можно было повторно использовать 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
            }
        }
}

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

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

person Henrik    schedule 10.04.2015
comment
Я настоятельно рекомендую использовать операторы () в вашем коде для вызова IDispose, когда объекты выходят за пределы места. DTF закроет и очистит все ваши ресурсы, чтобы вы избежали этих проблем. - person Christopher Painter; 18.04.2015
comment
Отметил, и спасибо за внимание. Как программист встраиваемых систем по профессии, новые причудливые приемы в C # неуместны, но не являются нежелательными. - person Henrik; 20.04.2015
comment
Кстати, я имел в виду масштаб, а не пространство. Я понимаю ... Я главный эксперт по сборке / выпуску, который за последние двадцать лет пробовал себя в дюжине языков, но так и не стал экспертом ни по одному из них. Я узнаю достаточно, чтобы понять технологии и то, что разработчикам нужно для сборки / выпуска. - person Christopher Painter; 20.04.2015
comment
Поскольку я делал в другом месте, просто перейдите по ссылке на другой вопрос - person Henrik; 04.04.2016
comment
Привет, Хенрик! Спасибо, но после вставки CAB-файла в MSI я хочу знать, как читать этот файл во время установки? обратите внимание, я использую набор инструментов WIX. Спасибо за вашу помощь - person yo2011; 04.04.2016
comment
В целом это интересная проблема, но на самом деле она не рассматривается в данном вопросе / ответе. Вы можете задать новый вопрос по SO или использовать подход, описанный в этом вопросе: см. раздел «Описание задачи» в вопросе. - Я предполагаю, что вам придется написать код, который либо: использует СВОЙСТВА для выполнения некоторых действий, таких как извлечение файла cab в определенном месте (он не может быть отменен установщиком). или заставьте этот предустановленный код редактировать таблицы msi и добавлять ссылки на файлы напрямую, используя mediaID, порядковые номера и т. д. - person Henrik; 05.04.2016
comment
Привет, Хенрик, не могли бы вы уточнить? - person yo2011; 05.04.2016
comment
не в комментариях, так как это займет много места. Пожалуйста, создайте вопрос, и кто-нибудь (возможно, я) ответит на него. вы можете направить меня туда;) - person Henrik; 05.04.2016
comment
Привет, Хенрик, я уже создал новый вопрос stackoverflow.com/questions/36427006/ - person yo2011; 05.04.2016
comment
и ждем вашей помощи как можно скорее - person yo2011; 05.04.2016