Easy pure-WiX patching with Melt

“Pure WiX patching” is a godsend, if you’ve ever wrestled with the painfully useless errors common with MsiMsp.exe/PatchWiz.dll. However, pure WiX patching follows a different model than MsiMsp. One wrinkle a lot of people run into is that the loose files for the target and upgrade packages are expected to be available at the paths as written into the .wixpdb files you’re using to build the patches from. That works without additional effort only if the paths in the .wixpdbs differ between the builds and are available from the machine building the patch, such as if the build output is available in a versioned directory on a file server. Typically, the paths are instead local to a build machine and aren’t valid months later, even on the same build machine.

MsiMsp/PatchWiz requires the use of administrative installations, which are essentially an .msi package “unzipped” into a layout. On the plus side, it means you simply need the .msi package (and any external .cab files), which hopefully you kept a copy of, after shipping it to customers. One negative is that even though creating an admin installation just writes files to a directory, it requires elevated permissions, which is unpleasant in a build tool.

In WiX v3.6, I added functionality that lets you get the best of both worlds: Pure WiX patching without having to keep whole build trees around forever. All you need is the .msi package you shipped, the .wixpdb that goes with it, and Melt.exe.

Isn’t Melt for merge modules?

The Melt tool originally had one purpose: To decompile a merge module (.msm file) into the equivalent WiX authoring as a ComponentGroup. Melt lets you accept merge modules if they’re the only way you can get some bit of setup but build them as normal WiX fragments instead of forcing WiX to use the black box of mergemod.dll from Windows.

During the WiX v3.6 development cycle, I was working on patching and ran into the problem I talked about a couple paragraphs ago: Even though I had access to the full build tree, the .wixpdbs contained paths that were local to the original machine that built the product .msi packages, not the tree copied to the drop server.

It turns out that these two problems are very similar, at least in implementing a solution. In addition to Melt.exe supporting an input of a merge module and an output of a WiX source file, I added a new mode: inputs of the .msi and its matching .wixpdb files and as output, the directory where the .msi contents are extracted and a new .wixpdb that’s a copy of the input .wixpdb updated to point to the extracted .msi contents.

Here’s a typical command line:

Melt.exe ProductV1.msi -out %INTERMEDIATE_DIRECTORY%\ProductV1corrected.wixpdb -pdb ProductV1.wixpdb -x %INTERMEDIATE_DIRECTORY%\ProductV1bits

Repeat for ProductV2.msi and pass ProductV1corrected.wixpdb and ProductV2corrected.wixpdb to Torch.exe on its way to Pyro.exe.

Melt.exe doesn’t require elevated permissions. The only extra thing you need is the .wixpdb created by Light.exe when the original .msi package was built.

10 thoughts on “Easy pure-WiX patching with Melt”

  1. That’s great Bob, just what I’ve been waiting for. “Typically, the paths are instead local to a build machine and aren’t valid months later, even on the same build machine.” Bingo! Who doesn’t use a version control system these days?

  2. @Matt: Everybody uses version control these days, of course. But are you saying you store your build output in version control?

  3. Yep, beta might be ourExe.exe#1-5, RTM #6 patch1 might be #7 etc. We don’t need to store whole source tree somewhere else (although we do keep out media releases like that) as we can always get the old versions by syncing to back if we have to.

    Currently I use admin extractions and msimsp.exe but will try generating a ‘corrected’ wixpdb (with a copy of the source tree ) for each release just to try purely WiX patching. Doing a little comparison on one app’s patches, the admin extractions took 20GB for 2 patches (in for 4 lang) and the sources tree is 6GB (which i’d need 3 times) so there is not much in it space wise .

  4. This might be better somewhere else:
    Either patch method wastes space on files that aren’t actually changed. Most of our content is unchanging but I need to copy all files that the wixpdb refs.Seem a shame that there is not either:
    -a way to point the WiXpdb at an admin extraction for it’s sources. Once I am done I can simply delete the extraction. Getting it re-extracted is trivial. Syncing to labels is very time consuming in perforce.
    – a way to use a revision database version number to perform the diffs. Eg. in the wixpdb say I want exe#1 compared to exe#2, have torch? check out the old version or get a temp copy from perforce(or whatever) for comparison to the workspace/latest version. I guess this would require code for multiple revisioning systems.

  5. @Matt, with Melt, you don’t need the admin images. You could extract/sync only the particular files you’ve changed, if you’re using PatchFamily filtering; Pyro will try to compare only the files in components you referenced in the PatchFamily.

  6. Thanks for your work on this feature, Bob. I’m running across something I figure has something to do with “expected paths”. I have %WiX%\bin in my path and was trying to use the method you detail above. I had to copy a few DLLs to the bin directory(Microsoft.Deployment.WindowsInstaller, Microsoft.Deployment.Compression, Microsoft.Deployment.Compression.Cab) but finally ended up with a melt error:

    melt.exe : error MELT0001 : The specified path, file name, or both are too long.
    The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters.

    There are some pretty deep paths and fairly long names, but I’m pretty sure there is nothing that exceeds these limits.

  7. The melt command is pretty much a copy of the example.
    melt .msi -out .\.wixpdb -pdb .wixpdb -x .\-testBits

    I extracted the pdbs to the bin folder and I get the stack trace below. If I need to move some things around for you to get more detail, let me know.

    melt.exe : error MELT0001 : The specified path, file name, or both are too long.
    The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters.

    Exception Type: System.IO.PathTooLongException

    Stack Trace:
    at System.IO.PathHelper.GetFullPathName()
    at System.IO.Path.NormalizePath(String path, Boolean fullCheck, Int32 maxPathLength)
    at System.IO.Path.GetFullPathInternal(String path)
    at System.IO.FileInfo.Init(String fileName, Boolean checkHost)
    at System.IO.FileInfo..ctor(String fileName)
    at Microsoft.Deployment.Compression.ArchiveFileStreamContext.OpenFileWriteStream(String path, Int64 fileSize, DateTime lastWriteTime)
    at Microsoft.Deployment.Compression.Cab.CabUnpacker.CabExtractCopyFile(NOTIFICATION notification)
    at Microsoft.Deployment.Compression.Cab.CabUnpacker.CabExtractNotify(NOTIFICATIONTYPE notificationType, NOTIFICATION notification)
    at Microsoft.Deployment.Compression.Cab.NativeMethods.FDI.Copy(Handle hfdi, String pszCabinet, String pszCabPath, Int32 flags, PFNNOTIFY pfnfdin, IntPtr pfnfdid, IntPtr pvUser)
    at Microsoft.Deployment.Compression.Cab.CabUnpacker.Unpack(IUnpackStreamContext streamContext, Predicate`1 fileFilter)
    at Microsoft.Deployment.Compression.Cab.CabEngine.Unpack(IUnpackStreamContext streamContext, Predicate`1 fileFilter)
    at Microsoft.Deployment.Compression.ArchiveInfo.UnpackFileSet(IDictionary`2 fileNames, String destDirectory, EventHandler`1 progressHandler)
    at Microsoft.Deployment.WindowsInstaller.Package.InstallPackage.ExtractFilesOnOneMediaDisk(String mediaCab, InstallPathMap compressedFileMap, InstallPathMapuncompressedFileMap)
    at Microsoft.Deployment.WindowsInstaller.Package.InstallPackage.ProcessFilesByMediaDisk(ICollection`1 fileKeys, ProcessFilesOnOneMediaDiskHandler diskHandler
    )
    at Microsoft.Deployment.WindowsInstaller.Package.InstallPackage.ExtractFiles(ICollection`1 fileKeys)
    at Microsoft.Deployment.WindowsInstaller.Package.InstallPackage.ExtractFiles()
    at Microsoft.Tools.WindowsInstallerXml.Tools.Melt.MeltProduct()
    at Microsoft.Tools.WindowsInstallerXml.Tools.Melt.Run(String[] args)

    Thanks!

  8. I just got some hints that this may be a corrupted install. I’m reinstalling and hopefully that will fix the issues I’m seeing.

Comments are closed.