Expand MSBuild property containing the name of other property - msbuild

Let's say I have property $(Foo), that is defined as a result of some function, that returns the string value $(Bar). Is it possible to expand it somehow, so that $(Foo) will be expanded to the value of $(Bar)?
Given example project:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Bar>Bar Value</Bar>
<Foo>$([System.String]::Concat("$(","Bar",")"))</Foo>
<Baz>$(Foo)</Baz>
<Qux>$(Bar)</Qux>
</PropertyGroup>
<Target Name="Test">
<Message Text="Foo == $(Foo)" />
<Message Text="Baz == $(Baz)" />
<Message Text="Qux == $(Qux)" />
</Target>
</Project>
Here's what I have:
S:\>msbuild Test.proj /t:Test /nologo
Build started 18.09.2013 17:52:14.
Project "S:\Test.proj" on node 1 (Test target(s)).
Test:
Foo == $(Bar)
Baz == $(Bar)
Qux == Bar Value
Done Building Project "S:\Test.proj" (Test target(s)).
Build succeeded.
0 Warning(s)
0 Error(s)
So, $(Qux), which is defined to $(Bar) directly, is expanded correctly, but $(Foo) and $(Baz) are not. Is it possible to expand them too?
S:\>msbuild /version
Microsoft (R) Build Engine version 4.0.30319.17929
[Microsoft .NET Framework, version 4.0.30319.18052]
Copyright (C) Microsoft Corporation. All rights reserved.
4.0.30319.17929

You want to simulate something like $($(Foo)) that is invalid syntax for MsBuild. But you can simulate this behavior by using items and by dynamically creating items in targets. You can’t do it in the "global scope" because of Property and Item evaluation order.
So you have to do it in some targets.
Here is sample that sets property by means of InitialTargets.
<?xml version="1.0" encoding="utf-8"?>
<Project InitialTargets="MyPropertiesSetup" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Bar>Bar Value</Bar>
<!-- A value of Foo property specifies the name of the property it takes value from. -->
<Foo>Bar</Foo>
<Baz></Baz>
<Qux>$(Bar)</Qux>
</PropertyGroup>
<Target Name="Test">
<Message Text="Foo == $(Foo)" />
<Message Text="Baz == $(Baz)" />
<Message Text="Qux == $(Qux)" />
</Target>
<Target Name="MyPropertiesSetup">
<ItemGroup>
<_Foo Include="$(Foo)" />
<_Baz Include="%(_Foo.Identity)" />
</ItemGroup>
<PropertyGroup>
<Foo>$(%(_Foo.Identity))</Foo>
<Baz>$(%(_Baz.Identity))</Baz>
</PropertyGroup>
</Target>
</Project>
If you can use item for values instead properties, there is more elegant way do it (MSBuild Trickery #68 - #($(CanYouDoThis)):
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Foo>Bar</Foo>
<Foo2>Bar2</Foo2>
</PropertyGroup>
<ItemGroup>
<Bar Include="Bar Value" />
<Bar2 Include="Bar2 Value" />
</ItemGroup>
<Target Name="Test">
<Message Text="Foo == '#($(Foo))'" />
<Message Text="Foo2 == '#($(Foo2))'" />
</Target>
</Project>

Related

Setting OutputName from wxi MSBUILD -Customizing Target Order

I'm trying to set the <OutputName> of my .msi when using Wix in VisualStudio.
I have seen the other questions related to this, but I am approaching it a bit differently. Hoping you can all clear up some things for me.
This is my .wixproj
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" InitialTargets="EnsureWixToolsetInstalled" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProjectGuid>fa14</ProjectGuid>
<SchemaVersion>2.0</SchemaVersion>
<OutputName>Installer_x64_v$(Version)</OutputName>
<OutputType>Package</OutputType>
<!--<ProjectGuid>{fa1414f5-97eb-4d39-8ff6-190cd92ca99f}</ProjectGuid>-->
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<OutputPath>bin\$(Configuration)\</OutputPath>
<IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath>
<DefineConstants>Debug</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<OutputPath>bin\$(Configuration)\</OutputPath>
<IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath>
<WixVariables>
</WixVariables>
</PropertyGroup>
<ItemGroup>
<Compile Include="Product.wxs" />
<Compile Include="Files.wxs" />
<Compile Include="UI.wxs" />
</ItemGroup>
<ItemGroup>
<WixExtension Include="WixUIExtension">
<HintPath>$(WixExtDir)\WixUIExtension.dll</HintPath>
<Name>WixUIExtension</Name>
</WixExtension>
</ItemGroup>
<Import Project="$(WixTargetsPath)" Condition=" '$(WixTargetsPath)' != '' " />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets" Condition=" '$(WixTargetsPath)' == '' AND Exists('$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets') " />
<Target Name="EnsureWixToolsetInstalled" Condition=" '$(WixTargetsImported)' != 'true' ">
<Error Text="The WiX Toolset v3.11 (or newer) build tools must be installed to build this project. To download the WiX Toolset, see http://wixtoolset.org/releases/" />
</Target>
<!--
To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Wix.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
<PropertyGroup>
</PropertyGroup>
<Target Name="BeforeBuild">
<ReadLinesFromFile File="Configuration.wxi" >
<Output TaskParameter="Lines" PropertyName="FileContents"/>
</ReadLinesFromFile>
<PropertyGroup>
<!-- Find occurence of string ProductVersion, return everything after 'Productversion', trimming spaces and " -->
<TrimmedWXI>$(FileContents.Substring($(FileContents.IndexOf(ProductVersion))).Replace(" ","").Replace('"',""))</TrimmedWXI>
<!-- Find the equals sign, add 1-->
<VersionStart>$([MSBuild]::Add($(TrimmedWXI.IndexOf(=)),1))</VersionStart>
<!-- Find the ?-->
<VersionEnd>$(TrimmedWXI.IndexOf(?))</VersionEnd>
<!-- Find the length of the version-->
<VersionLength>$([MSBuild]::Subtract($(VersionEnd),$(VersionStart)))</VersionLength>
<!-- Find the version-->
<Version>$(TrimmedWXI.Substring($(VersionStart),$(VersionLength)))</Version>
</PropertyGroup>
<Message Importance="High" Text=" Attempting build for $(Version)" />
</Target>
<Target Name="AfterBuild">
<Message Importance="High" Text="BuildDone" />
</Target>
</Project>
And the configuration.wxi
<?xml version="1.0" encoding="utf-8"?>
<Include>
<!-- Setup Configuration -->
<?define ProductName = "Acme" ?>
<?define ProductManufacturer = "AcmeInc." ?>
<?define ProductVersion = "9.99" ?>
<?define ProductUpgradeCode = "FAKEGUID" ?>
</Include>
Now, I am setting the <Version> property appropriately, it parses the file string properly and spits it out in the message, but when it comes to the building of the .msi, it is still blank. Looks like Version gets set after the OutputName is set, and I cant overwrite or make Version set before the OutputName.
I've done some reading and messed around with the Target and build order, but I can't quite get anything to work.
It seems like the OutputName is required to be set before everything else?
Why doesn't this get overridden in the BeforeBuild target?
Is it possible to have this ReadLinesFromFile task happen before the OutputName is set?
Thanks!

msbuild - export all properties defined in block to MSBuild calls

<Target Name="micropython_prebuild">
<PropertyGroup>
<uP_PrebuildPyExe>"$(ProjectDir)\Source\micropython\py\make_prebuild_wrapper.exe"</uP_PrebuildPyExe>
<GnuCat>$(uP_PrebuildPyExe) gnu_cat</GnuCat>
<GnuSed>$(uP_PrebuildPyExe) gnu_sed</GnuSed>
<uP_GenHdrFolder>$(ObjectFolder)\genhdr</uP_GenHdrFolder>
<uP_QSTR_GEN_EXTRA_CFLAGS>-DNO_QSTR -DN_X64 -DN_X86 -DN_THUMB -DN_ARM -DN_XTENSA</uP_QSTR_GEN_EXTRA_CFLAGS>
<uP_SRC>$(ProjectDir)\Source\micropython</uP_SRC>
<uP_MPY_CROSS>"$(ProjectDir)\Source\micropython\mpy-cross\mpy-cross.exe"</uP_MPY_CROSS>
</PropertyGroup>
<MakeDir Directories="$(uP_GenHdrFolder)\"/>
<MSBuild
Projects="$(MSBuildProjectFile)"
Condition="'' == ''"
Targets="prebuild_mpversion"
Properties="uP_PrebuildPyExe=$(uP_PrebuildPyExe);
uP_GenHdrFolder=$(uP_GenHdrFolder)"
/>
</Target>
How do I "export" all the properties I've defined at the top to the calls to MSBuild within this target?
Otherwise, I have to set the Properties of each MSBuild.
You can also create a single property that contains all the definitions:
<Target Name="micropython_prebuild">
<PropertyGroup>
<PrebuildProperties>
uP_PrebuildPyExe="$(ProjectDir)\Source\micropython\py\make_prebuild_wrapper.exe";
GnuCat=$(uP_PrebuildPyExe) gnu_cat;
GnuSed=$(uP_PrebuildPyExe) gnu_sed;
uP_GenHdrFolder=$(ObjectFolder)\genhdr;
uP_QSTR_GEN_EXTRA_CFLAGS=-DNO_QSTR -DN_X64 -DN_X86 -DN_THUMB -DN_ARM -DN_XTENSA;
uP_SRC=$(ProjectDir)\Source\micropython;
uP_MPY_CROSS="$(ProjectDir)\Source\micropython\mpy-cross\mpy-cross.exe";
</PrebuildProperties>
</PropertyGroup>
<MakeDir Directories="$(uP_GenHdrFolder)\"/>
<MSBuild
Projects="$(MSBuildProjectFile)"
Condition="'' == ''"
Targets="prebuild_mpversion"
Properties="$(PrebuildProperties)"
/>
</Target>

MSBuild overwrite properties value with Import

I have a msbuild script which contains only PropertyGroup: DefaultVariables.msbuild
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- default values if nothing is set in Main.proj -->
<PropertyGroup>
<ProjectName Condition="'$(PublishService)'==''">DefaultService</ProjectName>
</PropertyGroup>
</Project>
The PublishService can be changed depend on environment.
I also have a Variables.msbuild with is same with above script, except the service name:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- default values if nothing is set in Main.proj -->
<PropertyGroup>
<ProjectName Condition="'$(PublishService)'==''">ErpService</ProjectName>
</PropertyGroup>
</Project
My main build script: BuildMsi.msbuild import the DefaultVariables.msbuild and has a target CreateEnvironmentSpecificInstaller which calls to Msi.msbuild
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="CreateInstaller">
<PropertyGroup>
<BaseDir Condition="$(BaseDir)==''">$(MSBuildProjectDirectory)</BaseDir>
</PropertyGroup>
<Import Project="DefaultVariables.msbuild" />
<!-- Something else -->
<Target Name="CreateEnvironmentSpecificInstaller" DependsOnTargets="$(SpecificBuildSteps)">
<MSBuild Projects="$(RedistDir)\Framework\Msi.msbuild" Targets="CreateBatchScripts" StopOnFirstFailure="true" Properties="Configuration=$(Configuration)" RebaseOutputs="true" />
</Target>
<Target Name="CreateInstaller" DependsOnTargets="PrintVersion;$(GenericBuildSteps)">
<MSBuild Condition=" '$(EnvironmentName)' == '**AllEnvironments**' " Projects="$(BaseDir)\$(BtsDeploymentFrameworkDir)\BuildMsi.msbuild" Targets="CreateEnvironmentSpecificInstaller" StopOnFirstFailure="true"
Properties="Configuration=$(Configuration);" RebaseOutputs="true" />
<CallTarget Targets="RemoveGeneratedEnvironmentSettings" />
</Target>
</Project>
In Msi.msbuild script I add an Import to Variables.msbuild script, but the PublishService after this is still the DefaultService:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="CreateInstaller">
<Import Project="Variables.msbuild" />
<Target Name="CreateBatchScripts">
<Message Text="PublishService = $(PublishService)" />
</Target>
</Project>
How can I overwrite this property value in runtime?
First of all, you are never giving a value to PublishService. I assume in DefaultVariables.msbuild what you wanted to do is
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- default values if nothing is set in Main.proj -->
<PropertyGroup>
<PublishService> Condition="'$(PublishService)'==''">DefaultService</PublishService>
</PropertyGroup>
</Project>
Then, I suggest you also rename the property in Variables.msbuild and remove the condition Condition="'$(PublishService)'==''. As you have given the property a default value in DefaultVariables.msbuild, the condition will not be fulfilled and thus the value will not be changed.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishService>ErpService</PublishService>
</PropertyGroup>
</Project>

Why does the MsbuildExtension Detokenise class reload the project?

This is problematic because any Properties being passed in are lost.
Further Explanation: I pass in a property to the project file. This property is a path to a .props file. It contains tokens and replacement values for the detokenise class. The task apparently reloads the project and the path is not maintained. This doesn't seem to be the case for other task, for example the guid tasks.
In the example I am using a example proj entitled guids.proj
Invoked Using :
<MSBuild.ExtensionPack.FileSystem.Detokenise TaskAction="Detokenise" TargetFiles="#(FileCollectionToBeDetokenized )"/>
Some command line out put follows :
Task "MSBuild.ExtensionPack.FileSystem.Detokenise" (TaskId:11)
Detokenise Task Execution Started [13:04:35] (TaskId:11)
Loading Project: C:\Users\bstrausser\Desktop\guids.proj (TaskId:11)
Detokenising Collection: 1 files (TaskId:11)
C:\Users\*****\Desktop\guids.proj(37,9): error : Property not found: Asset
Directory
Full project file :
Project ToolsVersion="4.0" DefaultTargets="Default" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(ParentMSBuildProjectDirectory)\Bin\MSBuild\ExtensionPack\MSBuild.ExtensionPack.tasks" Condition="Exists('$(ParentMSBuildProjectDirectory)\Bin\MSBuild\ExtensionPack\MSBuild.ExtensionPack.tasks')"/>
<Import Project="C:\Program Files (x86)\MSBuild\ExtensionPack\4.0\MSBuild.ExtensionPack.tasks" Condition="!Exists('$(ParentMSBuildProjectDirectory)\Bin\MSBuild\ExtensionPack\MSBuild.ExtensionPack.tasks') AND Exists('C:\Program Files (x86)\MSBuild\ExtensionPack\4.0\MSBuild.ExtensionPack.tasks')"/>
<PropertyGroup>
<TPath>$(MSBuildProjectDirectory)\..\MSBuild.ExtensionPack.tasks</TPath>
<TPath Condition="Exists('$(MSBuildProjectDirectory)\..\..\Common\MSBuild.ExtensionPack.tasks')">$(MSBuildProjectDirectory)\..\..\Common\MSBuild.ExtensionPack.tasks</TPath>
<PROPS>$(DACP)</PROPS>
</PropertyGroup>
<Import Project="$(PROPS)" Condition="'$(DACP)' != ''" />
<Target Name="Default">
<Message text = "$(DACP)" />
<!-- Create a new Guid and get the formatted and unformatted values -->
<MSBuild.ExtensionPack.Framework.Guid TaskAction="Create">
<Output TaskParameter="FormattedGuidString" PropertyName="FormattedGuidString1" />
<Output TaskParameter="GuidString" PropertyName="GuidStringItem" />
</MSBuild.ExtensionPack.Framework.Guid>
<Message Text="GuidStringItem: $(GuidStringItem)"/>
<Message Text="FormattedGuidString: $(FormattedGuidString1)"/>
<!-- Create a new cryptographically strong Guid and get the formatted and unformatted values -->
<MSBuild.ExtensionPack.Framework.Guid TaskAction="CreateCrypto">
<Output TaskParameter="FormattedGuidString" PropertyName="FormattedGuidString1" />
<Output TaskParameter="GuidString" PropertyName="GuidStringItem" />
</MSBuild.ExtensionPack.Framework.Guid>
<Message Text="GuidStringItem Crypto: $(GuidStringItem)"/>
<Message Text="FormattedGuidString Crypto: $(FormattedGuidString1)"/>
<ItemGroup>
<FileCollectionToBeDetokenized Include="C:\Code\MSBuildGit\Configuration\TaskExecutorConfigTransforms\App.GREEN.SCRATCH.config"/>
</ItemGroup>
<Message text = "BaseUrl : $(BaseUrl)" />
<Message text = "DetokenizedTransformFile : #(FileCollectionToBeDetokenized)" />
<MSBuild.ExtensionPack.FileSystem.Detokenise TaskAction="Detokenise" TargetFiles="#(FileCollectionToBeDetokenized )"/>
</Target>

How to add a custom Property as a child of a PropertyGroup in MSBuild?

I have the following build script, which I run with MSBuild:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Compile" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion ="3.5">
<PropertyGroup>
<BuildDir Condition=" '$(BuildDir)'==' ' ">$(BaseDir)/build</BuildDir>
<ProdDir >$(BuildDir)/prod</ProdDir>
<TestDir>$(BuildDir)/test</TestDir>
<MMC2SourceDir>SteuerungsZugriffTest/mmc2</MMC2SourceDir>
<UserSourceDir>SteuerungsZugriffTest/user</UserSourceDir>
<TestXMLDir>$(BuildDir)/test-results</TestXMLDir>
<SolutionFile Condition=" '$(SolutionFile)'==' ' ">HMI2.0.sln</SolutionFile>"
<NUnitTest>nunit-console.exe</NUnitTest>
</PropertyGroup>
<Target Name="Prepare">
<Message Text="Prepare everything" />
<MakeDir Directories="$(BuildDir)" />
<MakeDir Directories="$(ProdDir)" />
</Target>
...
When I now start script on the command line:
D:\MyDir>msbuild /property:BaseDir=D:\MyDir MyScript.build
I got the following error on the commandline output:
D:\MyDir>MyScript.build(11,78): error MSB4067: Das <#text>-Element unterhalb des <PropertyGroup>-Elements ist unbekannt.
Which basically means: The element<#text> is an unknown child.
Does anybody have an idea?
Edit: Sorry, I completed now the script
You have an extra " at the end of this line
<SolutionFile Condition=" '$(SolutionFile)'==' ' ">HMI2.0.sln</SolutionFile>"
It is outside a tag, so it is considered as a text element...