Extracting files from merge module

19,892

Solution 1

You can use the decompiler tool included with WiX (called Dark) to decompile the merge module and extract the files:

dark.exe myMergeModule.msm -x "path_to_extracted_files"

The files will get extraced to the path specified in the -x parameter.

Note: The files will get extracted using the names specified in the File table of the installation database, which may not actually be the file names used when the files actually get installed. If you need extract the files using the actual file names, see my other answer to this question: Extracting files from merge module

Solution 2

I just had to do this by creating a blank msi and then use Orca to attempt to merge the module into my msi and then extract the files.

  1. Create a blank .msi. I used WiX 3.6 to create the .msi and below is the minimal source. I named it "blank.msi".

    <?xml version="1.0" encoding="UTF-8"?>
    <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
        <Product Id="*" Name="blank" Language="1033" Version="1.0.0.0" Manufacturer="blank" UpgradeCode="298878d0-5e7b-4b2e-84f9-45bb66541b10">
            <Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
    
            <MediaTemplate />
    
            <Directory Id="TARGETDIR" Name="SourceDir">
                <Directory Id="ProgramFilesFolder"/>
            </Directory>
    
            <ComponentGroup Id="ProductComponents" Directory="ProgramFilesFolder" />
    
            <Feature Id="ProductFeature" Title="blank" Level="1">
                <ComponentGroupRef Id="ProductComponents" />
            </Feature>
         </Product>
    </Wix>
    
  2. Use Orca to extract the files from the merge module.

    orca -m "myModule.msm" -f ProductFeature -x .\xdir blank.msi
    

The files will be extracted to the directory specified by the -x parameter (in this case .\xdir).

Note that the value for the -f parameter "ProductFeature" matches the name of the feature specified in msi file above.

Solution 3

I had a similar problem, but I went at it from a different direction.

I installed InstallSheild Express that came with an earlier version of Visual Studio, created a new project, but I only added the MSM file that I required.

After compiling and running my new install I was able to retrieve the files that the MSM file contained.

Solution 4

The DeploymentToolsFoundation class library in WiX, has an InstallPackage class with an ExtractFiles() method that should do just what you want, but fails for Merge Modules. This appears to be a bug.

The following PowerShell script, which uses DTF to access the CAB in the mergemodule, should do what you want. Apologies if the scripting is a bit wonky, I'm new to PowerShell.

[Reflection.Assembly]::LoadFrom("[InsertPath]\Microsoft.Deployment.WindowsInstaller.dll")

function ExtractMSM([string]$file, [string]$targetDir)
{
    write-host "Extracting files from merge module: "$file

    if(![IO.Directory]::Exists($targetDir)) { new-item -type directory -path $targetDir }

    $cabFile = join-path $targetDir "temp.cab"
    if([IO.File]::Exists($cabFile)) { remove-item $cabFile }

    $db = new-object Microsoft.Deployment.WindowsInstaller.DataBase($file, [Microsoft.Deployment.WindowsInstaller.DataBaseOpenMode]::ReadOnly)
    $view = $db.OpenView("SELECT `Name`,`Data` FROM _Streams WHERE `Name`= 'MergeModule.CABinet'")
    $view.Execute()
    $record = $view.Fetch()
    $record.GetStream(2, $cabFile)
    $view.Dispose()

    expand -F:* $cabFile $targetDir

    remove-item $cabFile

    $extractedFiles = get-childitem $targetDir
    $hashFiles = @{}
    foreach($extracted in $extractedFiles)
    {
        try
        {
            $longName = $db.ExecuteScalar("SELECT `FileName` FROM `File` WHERE `File`='{0}'", $extracted.Name) 
        }
        catch 
        {
            write-host "$($extracted.Name) is not in the MSM file"
        }

        if($longName)
        {
            $longName = $LongName.SubString($LongName.IndexOf("|") + 1)
            Write-host $longName

            #There are duplicates in the 
            if($hashFiles.Contains($longName))
            {
                write-host "Removing duplicate of $longName"
                remove-item $extracted.FullName
            }
            else
            {
                write-host "Rename $($extracted.Name) to $longName"
                $hashFiles[$longName] = $extracted
                $targetFilePath = join-path $targetDir $longName
                if([IO.File]::Exists($targetFilePath)) {remove-item $targetFilePath}
                rename-item $extracted.FullName -NewName $longName    
            }
        }
    }
    $db.Dispose()
}
Share:
19,892
Brent Arias
Author by

Brent Arias

Brent is a full-stack, hands-on software and cloud architect. He can be reached at [email protected]. LinkedIn profile My company AxisCode

Updated on June 05, 2022

Comments

  • Brent Arias
    Brent Arias almost 2 years

    All I want is a command-line tool that can extract files from a merge module (.msm) onto disk. Said differently, I want the same "administrative install" functionality that is possible for an MSI:

    msiexec /a myProduct.msi TARGETDIR="C:\myInstallation" /qn

    The above only works on an msi (near as I can tell). So to get the same effect for a merge module, I'm trying msidb.exe and orca.exe The documentation for orca states:

    Many merge module options can be specified from the command line...

    Extracting Files from a Merge Module

    Orca supports three different methods for extracting files contained in a merge module. Orca can extract the individual CAB file, extract the files into a module tree and extract the files into a source image once it has been merged into a target database...

    Extracting Files

    To extract the individual files from a merge module, use the

    ... -x ... option on the command line, where is the desired path to the new directory tree.

    The specified path is used as the root path for the extracted files. All files are extracted from the CAB file embedded in the module and placed in the specified path. The directory layout for the extracted files is based on the directory tree of the merge module.

    It sounds like what I need. But when I try it, orca simply opens up an editor (with info on the msm I specified) and then does nothing. I've tried a variety of command lines, usually starting with this:

    orca -x theDirectory theModule.msm

    I use "theDirectory" as whatever empty folder I want. Like I said - it didn't do anything.

    Then I tried msidb, where a couple of attempts I've made look like this:

    msidb -d theModule.msm -w {storage}

    msidb -d theModule.msm -x MergeModule.CABinet

    In the first case, I don't know what to put for {storage}. In the second case, it turns out that the literal string "MergeModule.CABinet" is necessary (it is a reserved name). However, the extracted cabinet does not preserve the file hierarchy or "normal" file names; so I can't use it for my purposes.

    Can someone explain what I'm doing wrong with the command line options? Is there any other tool that can do this?

  • Brent Arias
    Brent Arias about 14 years
    On your suggestion, I used it: "msi2xml -b streams -c files installation.msi" ...and it had basically the same result as doing: "msidb -d theModule.msm -x MergeModule.CABinet" and "expand MergeModule.CABinet" So I'm still stuck. If only the "administrative install" of msiexec.exe would work on an msm file. That is basically the "file extraction" behavior I'm looking for.
  • AH.
    AH. almost 9 years
    That syntax does not seem to be correct for dark version 3.5? I got this example to work: dark.exe -x c:\msm\extracted c:\msm\myMergeModule.msm The parameter after "-x" is the path to extract to (dark version 3.5).
  • Gmt
    Gmt over 8 years
    Unfortunately in the xml is not included how do you include Merge and MergeRef nodes
  • BryanJ
    BryanJ over 8 years
    @Gmt the xml above is only used to create a blank msi. Including the merge model happens in the call to orca. -m merges the merge module, -f associates it with a feature, and -x then extracts the files.