How to modify folder structure while extracting an MSI using WiX DTF

661 views Asked by At

I need to extract the files from an MSI into a folder, but change the directory structure of the files, all from within a C# application (ie: not by doing an admin install, or any other install of the MSI). Conceptually, I want to "unzip" the MSI's embedded cab into the folder of my choosing. To do the extraction, I'm using the WiX 3.6 DTF libraries, but am unable to determine how to change the folder structure.

So, for instance, if I ran the MSI installer, the target folder for "Component1" would be c:\Program Files(x86)\Company Name\Demo Product Installer\Component1\, but at runtime in my extractor application, I'd like to put change that to c:\SomeOtherPlace\Demo Product Installer\Component1\, preferably by changing the APPLICATIONFOLDER directory path (see below).

For the MSI, I've defined the directory structure like this:

<Fragment>
  <Directory Id="TARGETDIR" Name="SourceDir">
    <Directory Id="ProgramFilesFolder">
      <Directory Id="APPLICATIONFOLDER" Name="Company Name">
        <Directory Id="ProductFolder" Name="Demo Product Installer">
          <Directory Id="Cmp1Folder" Name="Component1" />
          <Directory Id="Cmp2Folder" Name="Component2" />
        </Directory>
      </Directory>
    </Directory>
  </Directory>
</Fragment>

Then, in the code that needs to extract the files, I've done this:

var msiFilePath = "myInstallerFile.msi";
var targetFolder = @"c:\SomeOtherPlace\";
using (var msiPackage = new InstallPackage(msiFilePath, DatabaseOpenMode.ReadOnly))
{
    msiPackage.WorkingDirectory = targetFolder;

    var dirMapping = msiPackage.Directories;    
    if (dirMapping.ContainsKey("APPLICATIONFOLDER"))                    
    {
        //This doesn't work, but represents what I hope to do:
        var oldInstallPath = dirMapping["APPLICATIONFOLDER"];                        
        oldInstallPath.TargetPath = targetFolder;                   
    }

    msiPackage.UpdateDirectories();                    
    msiPackage.ExtractFiles();                    
    msiPackage.Close();
}

Is there a way to modify the folder structure like this at runtime using the DTF objects? I know I can just move the files around after the fact, but if I can do it this way, it'd be much cleaner.

2

There are 2 answers

0
John M. Wright On BEST ANSWER

Based on the hint ("You might be albe to do it with in memory updates of the tables...") from Chirtopher's answer, I was able to find a working solution:

var msiFilePath = "myInstallerFile.msi";
var targetFolder = @"c:\SomeOtherPlace\";
using (var msiPackage = new InstallPackage(msiFilePath, DatabaseOpenMode.Transact))
{
    msiPackage.WorkingDirectory = targetFolder;

    var dirMapping = msiPackage.Directories;    
    if (dirMapping.ContainsKey("APPLICATIONFOLDER"))                    
    {
        //**** Modified code starts here ***//           
        // Changed the "APPLICATIONFOLDER" entry from "Company Name" to ".",
        // which is a special value to denote the extracted folder.
        var record = new Record(".", "APPLICATIONFOLDER");
        msiPackage.Execute("UPDATE `Directory` SET `DefaultDir` = ? WHERE `Directory` = ?", record);
        //**** Modified code ends here ***//
    }

    msiPackage.UpdateDirectories();                    
    msiPackage.ExtractFiles();  

    //Close **without** calling Commit() to ensure changes are not persisted                  
    msiPackage.Close();
}

Note that I also changed the DatabaseOpenMode to Transact.

Of course, if you wanted to do more advanced changes to the folder structure, you'd have to modify/insert/remove additional records from the Directory table. (see http://msdn.microsoft.com/en-us/library/windows/desktop/aa368295(v=vs.85).aspx for reference)

2
Christopher Painter On

It certainly would be eaiser to move the files around after extraction. You might be able to copy the database to a temp file, open it for edit and manipulate the tables before opening it as a package and extracting it. You might also be able to do it with in memory updates of the tables (temporary tables) without committing to disk.