WIX : Adding and Reading cabinet file to msi Insta

2019-09-10 23:04发布

问题:

We created our MSI Installer using WIX Toolset. and we need to customize the installer dynamically according to user specific files e.g Themes and Dialogues. We use this link to add Add Cabinet File to Installer but i can't read it. We want to know the best place to read the Cabinet file along with its files(customization files). Shall we do this in a custom action or what is the best place to do that? Also, we need a sample code that we can follow to achieve this task? note:- Our Cabinet file will contain a lot of files(txt files, Images and so on)

回答1:

I'm assuming that, since you followed that link (to my own question none the less) You now have the files embedded in the msi as a new cabinet file with a MediaID

Warning: the code in this reply, is all untested for now

Notice that an msi is basicly just a database, that can be queried using SQL like statements. The cabfiles are found embedded in the database, in the _Streams table, and can be extracted to their original cab file format.

You can verify this using ORCA, and 7zip.

The solution to the SO question you referred to, was designed to "replace" files. So a dummy file was used in the msi build, and the placement was configured in wix. then after msi building, the file table was modified, to change the reference from the original cab files generated by wix, to a new cab file injected. In this way the dummy files where orphaned, but still embedded in the msi.

This approach is fine when one knows what files will be customized for each user, and when all users will have the same folder/file structure, independently of customizations.

I assume you have a different number of files for each user, or different folder structure for each user, since you doesn't just copy that solution. To achieve this several msi table edits are required.

Directory Table: you will need to create directories in this table, if they where not already defined by your wix configuration.

Something like this should allow you to insert new directories:

    string query = "INSERT INTO `Directory` (`Directory`, `Directory_Parent`, `DefaultDir`) ";
    query += "VALUES ('" + The_Directory_ID + "', '" + The_Parents_ID + "', '" + FolderName + ")";
    pkg.Execute(query);

From here on, everything must be repeated for all files in the new cab file

** Component Table **

you will need to create a component that controls your file, so that it can be installed/uninstalled by msiexec.

    string query = "INSERT INTO `Component` (`Component`, `ComponentId`, `Directory_`, `Attributes`, `Condition`, `KeyPath`) ";
    query += "VALUES ('" + The_new_files_name_or_Similar + "', '{" + FileGUID + "}', '" + The_Directory_ID + "0, \"\", "+ A_FILE_ID +" )";
    pkg.Execute(query);

where:

  • FileGUID can be generated using Guid.NewGuid()..
  • A_FILE_ID can be the files name, if wix generated the MSI, all others are typically referenced by "FileID##" so it would probably work for you, otherwise you need to determine what FileID you can use that is not already in the file table..

CreateFolder Table: Usually only required if you need to create an empty folder, so we will ignore this for now, as you can dump a readme file or something in the folders..

File Table This table tells msiexec where to locate the file in your msi, and what version the file is in, so it knows if it needs to copy it, update it, ignore it, etc..

The sequence number is used to tell the msi where to locate the file, a Media Table relates sequences to cab files, or external medias..

the code is also just an insert into, statement:

  • File: A_FILE_ID
  • Component: The_new_files_name_or_Similar
  • Filename: The files name, (when installed)
  • FileSize: It is the files size, in bytes...
  • Version: Add a version number if the file has one, otherwise leave blank. How to retrieve the file version depends on the file type..
  • Language: This is the files language version, typically 1033, but you can leave it blank if you do not know..
  • Attributes: This depends on if the media file (cabinet) is embedded, external, or the file is external, etc.. use the same number as everything else in the msi, and you'll often be fine. I have always used 512 when embedding the cabinet file after build.

and then the magic part, which we will have to use later:

  • Sequence: you need to get the highest sequence value already found in the File table, and increment it

FeatureComponent Table This table is used to add this to the feature tree, making it possible for the user, to add or remove this feature. All components should belong to a feature.

  • Feature_ The feature that installs this component. you may make a new one, or not. I suggest that you use an existing one!
  • Component_ The_new_files_name_or_Similar

Media Table the code you referred to already adds a cabinet file to the msi, and creates an entry in the media table:

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 + "')";
pkg.Execute(query);

So this should already be set up for you.. if someone else stumbles upon this answer, I copied the relevant snippet here, to show how the seuquence numbers are made for the new cab file.

so the file table have a sequence number larger than what was already in the msi, but below what can be found in the new media you have already added.

Note: It is very important that the files in the cab file are in the same order as the sequence numbers, otherwise msiexec can throw an error of not being able to find the file.