codec
codec

Reputation: 355

How to exclude files in Wix toolset

While harvesting files for heat.exe, I would like to exclude the files with the extension .exe from the input folder since it fetches all the files in the folder at first place.

Below is my code.

    %WIX_PATH%\Heat.exe" dir "%input_folder%" -cg SourceProjectComponents 
    -dr INSTALLLOCATION -scom -sreg -srd -var var.BasePath -gg -sfrag 
    -var var.BasePath -out "%output_folder%\Output.wxs

PS: the input_folder consists of severall .dll and .exe files. hence individual harvesting of the file wasn't possible.

Thanks in advance.

Upvotes: 22

Views: 16571

Answers (5)

Mark Terry
Mark Terry

Reputation: 51

This may be blazingly obvious, but the easiest way to do this is to use the pre-build event in wix to create a directory with only the files that you do want, then run heat.exe on that.

Upvotes: 5

Maxence
Maxence

Reputation: 13329

I like WiX, but heat.exe is an utility harder to use than to make. You can write your own replacement. You just have to enumerate files in a directory, then output some XML. Here is some code to start which skip .pdb files:

foreach (string file in Directory.EnumerateFiles(directoryName))
{
    string extension = Path.GetExtension(file) ?? "";
    if (extension.Equals(".pdb", OrdinalIgnoreCase)) continue;
    string relativePath = GetRelativePath(wixProjectDirectoryName, file);
    string guid = Guid.NewGuid().ToString("D").ToUpperInvariant();
    stringBuilder.AppendLine($"<Component Id=\"Comp{fileName}\" Guid=\"{guid}\">");
    stringBuilder.AppendLine($"  <File Source=\"{relativePath}\" />");
    stringBuilder.AppendLine("</Component>");
}

public static string GetRelativePath(string baseDirectoryName, string fileFullPath)
{
    string[] absDirs = baseDirectoryName.Split('\\');
    string[] relDirs = fileFullPath.Split('\\');

    int len = absDirs.Length < relDirs.Length
        ? absDirs.Length
        : relDirs.Length;

    int lastCommonRoot = -1;
    int index;

    for (index = 0; index < len; index++)
    {
        if (absDirs[index] == relDirs[index]) lastCommonRoot = index;
        else break;
    }

    if (lastCommonRoot == -1)
        throw new ArgumentException("No common base");

    var relativePath = new StringBuilder();

    for (index = lastCommonRoot + 1; index < absDirs.Length; index++)
    {
        if (absDirs[index].Length > 0) relativePath.Append("..\\");
    }

    for (index = lastCommonRoot + 1; index < relDirs.Length - 1; index++)
        relativePath.Append(relDirs[index] + "\\");
    
    relativePath.Append(relDirs[relDirs.Length - 1]);

    return relativePath.ToString();
}

Upvotes: 5

Iulian M.
Iulian M.

Reputation: 161

If you’re using WiX toolset 4.0:

The xsl filters won't work until you set the correct namespace (xmlns:wix="http://wixtoolset.org/schemas/v4/wxs") I've been in the situation of upgrading from 3.11 to 4.0 and it took me hours to find out why the filter simply didn't work. Either in VS solution nor in command line (heat.exe) version.

Hope this helps someone

Upvotes: 14

Brian Sutherland
Brian Sutherland

Reputation: 4798

You will need to use an XSLT transform.

Something like this should work for you; Just include -t <Path to the xslt file> in your command line for heat.

This XSLT outputs a new XML file that contains all XML nodes of the input, except if any nodes are <Component> elements with .exe <File> elements.

RemoveExeComponentsTransform.xslt

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:wix="http://schemas.microsoft.com/wix/2006/wi"
    xmlns="http://schemas.microsoft.com/wix/2006/wi"

    version="1.0" 
    exclude-result-prefixes="xsl wix"
>

    <xsl:output method="xml" indent="yes" omit-xml-declaration="yes" />

    <xsl:strip-space elements="*" />

    <!--
    Find all <Component> elements with <File> elements with Source="" attributes ending in ".exe" and tag it with the "ExeToRemove" key.

    <Component Id="cmpSYYKP6B1M7WSD5KLEQ7PZW4YLOPYG61L" Directory="INSTALLDIR" Guid="*">
        <File Id="filKUS7ZRMJ0AOKDU6ATYY6IRUSR2ECPDFO" KeyPath="yes" Source="!(wix.StagingAreaPath)\ProofOfPEqualsNP.exe" />
    </Component>

    Because WiX's Heat.exe only supports XSLT 1.0 and not XSLT 2.0 we cannot use `ends-with( haystack, needle )` (e.g. `ends-with( wix:File/@Source, '.exe' )`...
    ...but we can use this longer `substring` expression instead (see https://github.com/wixtoolset/issues/issues/5609 )
    -->
    <xsl:key
        name="ExeToRemove"
        match="wix:Component[ substring( wix:File/@Source, string-length( wix:File/@Source ) - 3 ) = '.exe' ]"
        use="@Id"
    /> <!-- Get the last 4 characters of a string using `substring( s, len(s) - 3 )`, it uses -3 and not -4 because XSLT uses 1-based indexes, not 0-based indexes. -->

    <!-- We can also remove .pdb files too, for example: -->
    <xsl:key
        name="PdbToRemove"
        match="wix:Component[ substring( wix:File/@Source, string-length( wix:File/@Source ) - 3 ) = '.pdb' ]"
        use="@Id"
    />

    <!-- By default, copy all elements and nodes into the output... -->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()" />
        </xsl:copy>
    </xsl:template>

    <!-- ...but if the element has the "ExeToRemove" key then don't render anything (i.e. removing it from the output) -->
    <xsl:template match="*[ self::wix:Component or self::wix:ComponentRef ][ key( 'ExeToRemove', @Id ) ]" />

    <xsl:template match="*[ self::wix:Component or self::wix:ComponentRef ][ key( 'PdbToRemove', @Id ) ]" />

</xsl:stylesheet>

Upvotes: 41

Murat Aykanat
Murat Aykanat

Reputation: 1708

I had the same issue where I had lots of files that I needed to include into the WXS file in a project and I wrote an open source command line application to generate XMLs of directory structure, files and components while ignoring folders, extensions, files etc via a .wixignore file (formatted similar to .gitignore).

You can take a look at the it here.

Upvotes: 7

Related Questions