msbuild with multiple DefineConstant values, including embedded semicolon - msbuild

I have an msbuild script which uses the Exec tag to kick off another MSBuild command including a DefineConstants property with multiple values. Since I have multiple values for that property, I'm using the technique described in this question to call MSBuild.exe directly (not use the MSBuild tag) and enclose the values in quotes.
However, I now need to embed a special symbol into one of those values, which I'm attempting to do using the code ¯ (registered trademark ®)
The problem is that the special character code must end in a semicolon, but when I embedded the semicolon, msbuild reads that as a value seperator. I can seem to find a valid way to escape the semicolon and have it still show up in the end parameter value.
My question is this:
How can I embed the special char into my build parameter?
To improve readability, I've moved the value to a variable in my example, but that's not a requirement.
File: TopLevelBuildScript.proj
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="BuildWithSpecialChar">
<ItemGroup>
<!-- Note: %3B is the escape code for semicolon -->
<MyProductName Include="Trademarked&#174%3B Product" />
<OtherProperty Include="foo" />
</ItemGroup>
<Exec Command="msbuild.exe subBuildScript.proj /p:DefineConstants="MyProductName=#(MyProductName);OtherVar=#(foo);AnotherVar=asdf;" " />
</Target>
</Project>
When executed, the above produces the command line:
msbuild.exe subBuildScript.proj /p:DefineConstants="MyProductName=Trademarked® Product;OtherVar=foo;AnotherVar=asdf;"
Which ends up defining the constants with the product name split at the semicolon:
MyProductName=Trademarked&#174
Product
OtherVar=foo
AnotherVar=asdf
Note: I've also tried using %25 for the percent sign:
<MyProductName Include="Trademarked&#174%253B Product" />
But that gives me:
MyProductName=Trademarked&#174B Product
OtherVar=foo
AnotherVar=asdf
Update
It turns out the issue wasn't with MSBuild, rather with the way WiX parses parameters (my subBuildScript utilizes WiX). I didn't consider that, so didn't mention it in my original posting.
Ultimately, I went with a solution based on this answer to another question:
<PropertyGroup>
<WixValues>
<MyProductName>Trademarked® Product<MyProductName>
<OtherProperty>foo"</OtherProperty>
</WixValues>
</PropertyGroup>
<MSBuild
Projects="subProjectScript.proj"
Properties="Configuration=Release;WixValues=$([MSBuild]::Escape($(WixValues)))"
Targets="Clean;Rebuild"
ContinueOnError="false"
StopOnFirstFailure="true">
</MSBuild>
and unescaping the values in subProjectScript.proj as described in the other answer.

First, MSBuild files are XML, which you can encode as UTF-8, so you should be able to copy/paste the trademark character directly in the MSBuild script, without escaping.
If you can't take that approach, don't escape the ampersand. There are two levels of escaping things in an MSBuild script. The first is XML-encoding, so that XML-sensitive characters (< and &) can be used and you'll still have safe XML. When MSBuild reads in the file, XML-escape sequences get unescaped. After that process, MSBuild then does its own un-escaping (i.e. %3B for semi-colons).
Because you're using & at the beginning of your XMl escape sequence, the XML processor is interpreting that as a literal ampersand, not the special XML ampersand, which denotes an escape sequence. Change your MyProductName item group's value to be:
<MyProductName Include="Trademarked® Product" />
Since MyProductName appears to be a scalar value, and not a list, I would use a property instead:
<PropertyGroup>
<MyProductName>Trademarked® Product</MyProductName>
</PropertyGroup>
In your Exec task you can then use it like this:
<Exec Command="msbuild.exe subBuildScript.proj /p:DefineConstants="MyProductName=$(MyProductName);OtherVar=#(foo);AnotherVar=asdf;" " />

Related

Replace a sign in a property

Lets say I got this AB_1.2.3.112211 in a property
What I want is to exchange the first "." to a "_"
So it becomes AB_1_2.3.112211
The two first characters could be longer fx ABCD_1.2.3.112211 but it should still be the output ABCD_1_2.3.112211
Is there a way to do this in msbuild task?
If the input strings are always as you show them, i.e. the first "." to replace comes after an underscore followed by a number you can use a property function and use C#'s static Regex.Replace method:
<Target Name="Repl">
<PropertyGroup>
<Prop>ABCD_1.2.3.112211</Prop>
</PropertyGroup>
<Message Text="$([System.Text.RegularExpressions.Regex]::Replace( $(Prop), '_(\d*)\.', '_$1_' ) )" />
</Target>
If the use case is more complicated there are other options:
use another regular expression in the code above
MSBuild Community tasks has a Regex task which supports the Count parameter of Regex.Replace, so you can use a simple regex to replace "." with "_" and set count to one to only do the first instance
use an inline task in which you write your preferred implementation to replace the first instance from the answers here

How can you remove empty or blank lines from a text file using MSBuild?

I would like to remove all blank lines (lines that just have some number of spaces and a newline character) from a list of files using MSBuild.
What is the best way to accomplish this?
I recognize that I could write a MSBuild plug in C# or VB.NET that will do this using simple Regex replacement, but would prefer a solution that doesn't need me to do this.
If there's a open source MSBuild plugin that does this - I'd welcome that solution as well.
#Ludwo is right, you have to take into account white-space characters. Moreover to replace any text with empty string you need to use ReplacementTextEmpty property instead of passing empty string to ReplacementText property. So, the following target should solve the problem:
<Target Name="Minify">
<ItemGroup>
<File Include="**\*.cs" />
</ItemGroup>
<FileUpdate
Files="#(File)"
Regex="(\n\s*\n)+"
Multiline="False"
ReplacementTextEmpty="True"/>
</Target>
You just need to call the target via MSBuild:
msbuild MyProject.csproj /t:Minify

How to make msbuild ItemGroup items be separated with a space rather than semi-colon?

Observe the following piece of an msbuild script:
<ItemGroup>
<R Include="-Microsoft.Design#CA1000" />
<R Include="-Microsoft.Design#CA1002" />
</ItemGroup>
I want to convert it to
/ruleid:-Microsoft.Design#CA1000 /ruleid:-Microsoft.Design#CA1002
Now, the best I came up with is #(R -> '/ruleid:%(Identity)'), but this only yields
/ruleid:-Microsoft.Design#CA1000;/ruleid:-Microsoft.Design#CA1002
Note the semi-colon separating the two rules, instead of a space. This is bad, it is not recognized by the fxcop - I need a space there.
Now, this is a simple example, so I could just declare something like this:
<PropertyGroup>
<R>/ruleid:-Microsoft.Design#CA1000 /ruleid:-Microsoft.Design#CA1002</R
</PropertyGroup>
But, I do not like this, because in reality I have many rules I wish to disable and listing all of them like this is something I wish to avoid.
To delimit each item by using a character other than a semicolon, use the syntax #(myType, 'separator')
<ItemGroup>
<R Include="-Microsoft.Design#CA1000" />
<R Include="-Microsoft.Design#CA1002" />
</ItemGroup>
<Target Name="FxcopRulesFlattening">
<!-- Using the syntax #(ItemName, 'Separator')-->
<Message Text="#(R -> '/ruleid:%(Identity)', ' ')"/>
</Target>

How to escape quote marks in Exec Command in MSBuild

I'm trying to build an MSBuild script that maps a network drive to a drive letter in the script, but unfortunately the path to the target folder includes an embedded space. The embedded space causes the mapping to fail, and I don't know if it is possible to escape quotes around the path. I've tried double quote marks, but MSBuild doesn't like it (either that or Windows XP doesn't like it). Anyone know how to code this beast so the map works?
<Exec Command="net use x: \\ofmapoly703\c$\program files\ar\iap /user:$(UserID) $(Password)"
WorkingDirectory="c:\"
ContinueOnError="false"
/>
The embedded space of course occurs in "program files".
Use " to encode the double quotes that you want net to see inside the Command attribute value :
<Exec Command="net use x: "\\ofmapoly703\c$\program files\ar\iap" /user:$(UserID) $(Password)"
WorkingDirectory="c:\"
ContinueOnError="false"
/>
You can use single quotes for command ,e.g.
<Exec Command='explorer.exe "$(DestinationDir)"' IgnoreExitCode="true" />
(From MSBuild exec task without blocking)
Escape the quotation marks - instead of "foo bar baz", use %22foo bar baz%22.
The hex value of " is 22.
References
Escape special characters in MSBuild
MSBuild special characters
As detailed by #Michael-Freidgeim the solution to this is to single quote, however you still have issue with trailing slashes (in Paths) being treated as escape characters in certain circumstances, a good method to avoid that would be to follow any folder paths with a dot (.)

Why does a property value work when being used as an attribute value and not an Item value in msbuild?

I am trying to build the documentation for my application using Sandcastle Help File Builder. One requirement is that I must specify the documentation source for e.g.:
<DocumentationSources>
<DocumentationSource sourceFile="#(DocumentationSourceFiles)" xmlns="" />
</DocumentationSources>
I have defined #(DocumentationSourceFiles) in a separate file as follows:
<ItemGroup>
<DocumentationSourceFiles Include="..\src\**\*.exe"></DocumentationSourceFiles>
</ItemGroup>
I then imported this file in the .shfbproj file and used it as stated above. The problem is that #(DocumentationSourceFiles) is not being recognized as a list of items but merely as a string. Am I doing anything wrong? If I were to change #(DocumentationSourceFiles) into a property with a single value like:
<PropertyGroup>
<DocumentationSourceFiles>S:\SVN\myApp\src\myAppName\Debug\bin\myApp</DocumentationSourceFiles>
</PropertyGroup>
And then use:
<DocumentationSources>
<DocumentationSource sourceFile="$(DocumentationSourceFiles)" xmlns="" />
</DocumentationSources>
Everything works fine. Any ideas?
Using the notation #(myType) allows a collection of items of type myType to be expanded into a semicolon (;) delimited list of strings, and passed to a parameter. If the parameter is of type string, then the value of the parameter is the list of elements separated by semicolons. If the parameter is an array of strings (string[]), each element is inserted into the array based on the location of the semicolons. If the task parameter is of type ITaskItem[], the value is the contents of the item collection with any metadata attached. To delimit each item with a character other than a semicolon, use the syntax #(myType, 'separator').
If you want to have each item separately, use the metadata notation : %(ItemCollectionName.ItemMetaDataName)
<ItemGroup>
<DocumentationSourceFiles Include="..\src\**\*.exe"></DocumentationSourceFiles>
</ItemGroup>
<Target Name="TestItem">
<Message Text="Using # Notation"/>
<Message Text="#(DocumentationSourceFiles)"/>
<Message Text="Using Metadata Notation"/>
<Message Text="%(DocumentationSourceFiles.RecursiveDir)%(Filename)%(Extension)"/>
</Target>
> Output:
Using # Notation
..\src\doc1.exe;..\src\doc2.exe;..\src\subdir\doc3.exe
Using Metadata Notation
..\src\doc1.exe
..\src\doc2.exe
..\src\subdir\doc3.exe