Passing multiple values to Wix DefineConstants property with MSBuild

22,072

Solution 1

The problem:

The MSBuild task (not MSBuild.exe, the MSBuild task named MSBuild) cannot handle multiple constants used by WIX projects. Normally you would specify the properties in your build script like:

<MSBuild Projects="YourSolution.sln" Properties="Configuration=MyConfig;Platform=x86;DefineConstants=&quot;SOMETHING=1;SOMETHINGELSE=2&quot;" />

What you see however, when looking at the build logs is the MSBuild separates the constants and does not keep the values grouped together like you would expect - similar to:

Task "MSBuild" Global Properties:
Configuration=MyConfig
Platform=x86
DefineConstants="SOMETHING=1
SOMETHINGELSE=2"

So when candle tries to use those constants it typically responds with "error CNDL0150: Undefined preprocessor variable '$(var.SOMETHINGELSE)'. What this means is the MSBuild task is not properly handling properties which contain multiple '=' in the value even when grouped within quotation marks. Without the property value being grouped in quotation marks, they should obviously be treated as separate properties, rather than a single value.

The workaround:

In order to fix this problem, you need to call MSBuild.exe directly and pass those values to it manually.

msbuild.exe /p:Configuration=MyConfig /p:Platform=x86 /p:DefineConstants="SOMETHING=1;SOMETHINGELSE=2" YourSolution.sln

This will get your constants the work the way you want them to, without having to redesign your WiX installation project.

NOTE: If you're only using a single constant you can still use the MSBuild task like so:

<MSBuild Projects="YourSolution.sln" Properties="Configuration=MyConfig;Platform=x86;DefineConstants=&quot;SOMETHING=1&quot;" />

Solution 2

The problem lies in passing the name-value pairs to the MSBuild task, and then having MSBuild parse them properly so they can be passed to the Candle task. It seems that MSBuild can handle a list of simple names, or a single name-value pair, but not a list of pairs.

My solution is to escape the list when it's passed into the MSBuild task, and unescape it again when it goes to the Candle task.

In your MSBuild file, define the pairs in a property like this:

<PropertyGroup>
    <WixValues>
        One=1;
        Two=2;
        Three=3;
    </WixValues>
</PropertyGroup>

When you call the MSBuild task, escape the property (requires MSBuild 4):

<MSBuild 
    Projects="setup.wixproj"
    Properties="WixValues=$([MSBuild]::Escape($(WixValues)))" />

The unescaping has to be done in the wixproj file, but there's no need to edit the file by hand. Just open the project's properties, go to the Build tab, and where it says "Define preprocessor variables", put:

$([MSBuild]::Unescape($(WixValues)))

This works even if there are other variables already in that box; just add this to the list along with a semicolon.

You'll see in the MSBuild log that the candle.exe tool receives the arguments correctly:

candle.exe -dOne=1 -dTwo=2 -dThree=3 -dConfiguration=Release...

Solution 3

I still had a lot of trouble getting this to work, but peices of each answer above, contributed to my solution, so I wanted to share.

Environment: TFS Build Server 2010 - Powershell calling MSBuild.exe Wix 3.6 is installed on the build server

Goal is to pass the build version number and 5 directories into MSBuild, so that Candle.exe will receive them correctly

The Build calls a command file at the end of the build, and that command file, includes a step to call a powershell script to build the Wix installer. It passes in the version number for the build and the source and output directories (the output is a UNC fileshare reference \tfsbuildserver..)

To get it to work, in the Powershell ps1 file,

  • use a single /p:DefineConstants= that starts with a double "
  • encode all the seperating ; as %3b
  • the = are ok unencoded
  • there should be no additional quotes around any file names with spaces

    $msbuild = "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe"
    
    $options = " /p:Configuration=Release /p:Platform=x64 "
    $options = $options + " /p:DefineConstants="""
    $options = $options + "SolutionDir=" + $SourceDir
    $options = $options + "%3bTFSBuildSourceLanding=" + $OutputLocation + "SharepointWebRoot\Landing"
    $options = $options + "%3bTFSBuildSourceLandingAdmin=" + $OutputLocation + "SharepointWebRoot\LandingAdmin"
    $options = $options + "%3bTFSBuildSourceRegistration=" + $OutputLocation + "Extranet_Registration"
    $options = $options + "%3bTFSBuildSourceGAC=" + $OutputLocation + "GAC"
    $options = $options + "%3bTFSBuildSourceSQL=" + $OutputLocation + "SQL"
    $options = $options + "%3bProductVersion=" + $BuildVersion + """" 
    
    
    $build = $msbuild + " ""EUM WIX\EUM Wix.wixproj"" " + $options + " /t:Build"
    $clean = $msbuild + " ""EUM WIX\EUM Wix.wixproj"" " + $options + " /t:Clean"
    
    
    Write-Host "Building Wix Installer..."
    Invoke-Expression $clean
    Invoke-Expression $build
    
  • Inside the wixproj file, we have to unescape (down at the bottom, where it says to uncomment to modify)

      <Target Name="BeforeBuild">
          <CreateProperty Value="$([MSBuild]::Unescape($(DefineConstants)))">
             <Output TaskParameter="Value" PropertyName="DefineConstants" />
          </CreateProperty>
      </Target>
    
  • Also note that in Visual Studio, I will use the debug settings, and I can have defaults set for these values, in the Build Tab, "Define preprocessor Variables"

  • in the Release settings this item should be blank as it will get passed in on the command line above.

Solution 4

The following works for me when using an MSBuild task to build a Visual Studio Solution:

<MSBuild Projects="Solution.sln"
         Targets="Rebuild"
         Properties="Configuration=Debug;DefineConstants=DEBUG%3bTRACE" />

The trick is using %3b to escape the ; separator inside the DefineConstants value. I'm not sure if this will work for the = too. They may need to be escaped as %3d or it may not work at all...

There's also a TargetAndPropertyListSeparators attribute on the MSBuild element. I can't find any documentation for it but it might be possible to use it to set a separator other than ;.

Solution 5

You could pass it as parameter, not constant. The code will look like this:

<MSBuild ...
    Properties="ProductVersion=%(WixSetups.ISVersion)" />

Now in WiX project add a constant:

<DefineConstants>BuildVersion=$(ProductVersion)</DefineConstants>

And use it in *.wxs file where needed:

$(var.BuildVersion)

For instance:

<Product Id="*" Name="My Company" Language="1033" Version="$(var.BuildVersion)"... />

This will work with multiple parameters.

Share:
22,072
Sardaukar
Author by

Sardaukar

Thinks that software development is part science, part art (is just not sure which part is bigger).

Updated on November 10, 2021

Comments

  • Sardaukar
    Sardaukar over 2 years

    I'm currently integrating my Wix projects in MSBuild. It is necessary for me to pass multiple values to the Wix project. One value will work (ProductVersion in the sample below).

    <Target Name="BuildWixSetups">
        <MSBuild Condition="'%(WixSetups.Identity)'!=''"
                    Projects="%(WixSetups.Identity)"
                    Targets="Rebuild" Properties="Configuration=Release;OutputPath=$(OutDir);DefineConstants=ProductVersion=%(WixSetups.ISVersion)" ContinueOnError="true"/>
    </Target>
    

    However, how do I pass multiple values to the DefineConstants key? I've tried all the 'logical' separators (space, comma, semi-colon, pipe-symbol), but this doesn't work.

    Has someone else come across this problem?

    Solutions that don't work:

    1. Trying to add a DefineConstants element does not work because DefineConstants needs to be expressed within the Properties attribute.
  • Sardaukar
    Sardaukar over 15 years
    This does not work, because DefineConstants needs to be expressed WITHIN the Properties attribute. It does not work as an attribute on its own.
  • Sardaukar
    Sardaukar over 15 years
    Thanks, but I already read both those articles. Even if the first one works, I still consider it a hack because projects need to be built two times.
  • Aamir
    Aamir over 15 years
    I can't test it myself but after defining this element can't you use it like Properties="DefineConstants=$(DefineConstants)" ?
  • Sardaukar
    Sardaukar about 14 years
    Done. Thanks for the explanation.
  • Rory MacLeod
    Rory MacLeod over 13 years
    That works when you're passing simple names like "debug" and "trace", but not when you want to pass name-value pairs like "config=debug;log=trace".
  • Rory MacLeod
    Rory MacLeod over 13 years
    There is a way of doing this using the MSBuild task, rather than calling the exe: stackoverflow.com/questions/506687/…
  • Daniel Powell
    Daniel Powell almost 13 years
    seems like if you aren't using msbuild 4 this is the way to go
  • Yunnosch
    Yunnosch over 2 years
    I recommend against rhetoric questions in answers. They risk being misunderstood as not an answer at all. You are trying to answer the question at the top of this page, aren't you? Otherwise please delete this post.