Can you perform a case-insensitive string comparison in MSBuild? - msbuild

I have the following code in my MSBuild project file:
<Error Text="Some Text" Condition="'$(StringName)' != 'Test'"/>
The string comparison here is case-sensitive, so when $(StringName) is something like 'test', the condition is not met.
How can I change the condition so that 'test' also meets the comparison? Is there any case-insensitive comparison function available in MSBuild?

Dan Moseley has a detailed view on MSBuild Property Functions here:
https://devblogs.microsoft.com/visualstudio/msbuild-property-functions/
For your example you could use something like:
<Error Text="Some Text" Condition="'$(StringName.ToUpper())' != 'TEST'"/>

MSBuild string comparisons are case-insensitive:
<PropertyGroup>
<MyProperty>FOOBAR</MyProperty>
</PropertyGroup>
<Message Text="ALL CAPS" Importance="high" Condition="'$(MyProperty)' == 'FOOBAR'" />
<Message Text="all lower" Importance="high" Condition="'$(MyProperty)' == 'foobar'" />
will output
ALL CAPS
all lower
I'm pretty sure something else is going weird in your script.

Use MSBuild property function with a special parameter to perform a case-insensitive string comparison:
<Error Text="Some Text" Condition="!$(StringName.Equals('Test', StringComparison.OrdinalIgnoreCase))"/>

No, there's not. You'd have to write your own.

Related

How can I evaluate MS Build Variables?

For example, if I wanted to output the value of
$(SolutionRoot)
How can I do it?
In a Target, use Task 'Message'
<Target Name="PrintCurrentSettings">
<Message Text=" $SolutionRoot : $(SolutionRoot)"/>
</Target>

How to set 'condition' using a condition stored in a property?

I have a condition such as 'a==1' stored in property $(c) and I wanna used it as the condition for task Message like below code:
<PropertyGroup>
<aa>1>2</aa>
</PropertyGroup>
<Target Name="t">
<Message Text="122333" Condition="$(aa)" />
</Target>
Error was raised!
So, how can I do it? Please help!
You can easily use property values for evaluating conditions. Here is an example:
<PropertyGroup>
<aa>1</aa>
</PropertyGroup>
<Target Name="Build">
<Message Text="Some text" Condition=" $(aa) < 2 " />
</Target>
Note that:
Property values are strings, you must evaluate the condition in the Condition attribute. See MSDN Docs on evaluating conditions.
You must escape XML characters (replace < with < )

Is there a way to print a new-line when using <Message...>?

I wanna write <Message Text="Line1\nLine2\nLine3" /> but \n seems not to be working. What should I replace \n with?
(I read in the books they said that to print # and % we use %40 and %25, so I guess the should be a number for the new-line).
Try this:
<Message Text="Line1%0aLine2%0aLine3%0a" />
CR = 0x0D or 13
LF = 0x0A or 10
Here's a better way. Use the "Escape()" property function [1] in a trivial msbuild file like so:
<Message Text="$([MSBuild]::Escape('A\r\nB\r\nC\r\n'))" />
The 'Escape()' property function should do all the magic in terms of escaping exotic characters.
[1] https://learn.microsoft.com/en-us/visualstudio/msbuild/how-to-escape-special-characters-in-msbuild?view=vs-2017 & https://learn.microsoft.com/en-us/visualstudio/msbuild/property-functions?view=vs-2017
You could put the lines into an item group
<ItemGroup>
<Lines Include="Line 1 " />
<Lines Include="Line 2 " />
<Lines Include="Line 3 " />
</ItemGroup>
<Message Text="%(Lines.Identity)"/>
Put the multiline message in a property (in the example below called MyMultilineMessage) and then use that property in the message text. (Works at least with MSBuild for VisualStudio 2019)
<PropertyGroup>
<MyMultilineMessage>
Lorum ipsum...
</MyMultilineMessage>
</PropertyGroup>
<Message Importance="High" Text="$(MyMultilineMessage)"/>
This also works for Error elements as well.
Edit: Modified XML example above after comment about missing PropertyGroup.

MSBuild Annoyances (or blatant ignorance on my part)

In reworking our deployment process I moved over to using an MSBuild project in place of our existing batch files. All of the major elements are in place, and I was looking to cut out a step or two but ran into a snag.
I'm creating a property called OutputPath using the CombinePath task, and, while I can access it with no issues after it has been created I'm at a loss as for how to use it to my advantage. Consider:
<CombinePath BasePath ="$(DeployFolderRoot)" Paths ="$(DeployReleaseFolder)$(ReleaseFolderFormatted)" >
<Output TaskParameter ="CombinedPaths" ItemName ="OutputFolder"/>
</CombinePath>
<MakeDir Directories="#(OutputFolder)" />
<MakeDir Directories="#(OutputFolder)\Foo" />
<MakeDir Directories="#(OutputFolder)\Bar" />
Commands 2 and 3 fail because I'm referencing an array and attempting to concatenate with a string. Creating a property and assigning it #(OutputFolder) simply results in another item group, not a property I can reference with the $ accessor. I do have an ugly workaround but I'd love to clear this up somewhat.
Thanks,
-Jose
I'm not sure of the answer exactly but here is an idea:
<CombinePath BasePath ="$(DeployFolderRoot)" Paths ="$(DeployReleaseFolder)$(ReleaseFolderFormatted)" >
<Output TaskParameter ="CombinedPaths" ItemName ="OutputFolder"/>
</CombinePath>
<OutputFolder Include="$(DeployFolderRoot)$(DeployReleaseFolder)$(ReleaseFolderFormatted)\Foo" />
<OutputFolder Include="$(DeployFolderRoot)$(DeployReleaseFolder)$(ReleaseFolderFormatted)\Bar" />
<MakeDir Directories="#(OutputFolder)" />
Essentially, if you create OutputFolder items with the path they will just be appended to the list. This would have to be in an element btw, and you have to use Include="".
dOh! Definitely ignorance, used the wrong attribute on the Output element.
<CombinePath BasePath ="$(DeployFolderRoot)" Paths ="$(DeployReleaseFolder)$(ReleaseFolderFormatted)" >
<Output TaskParameter ="CombinedPaths" PropertyName="OutputFolder"/>
</CombinePath>
<MakeDir Directories="$(OutputFolder)" />
<MakeDir Directories="$(OutputFolder)\Foo" />
<MakeDir Directories="$(OutputFolder)\Bar" />

MSBuild: asterisks and strange ItemGroup Exclude behaviour

I have a script that attempts to construct an ItemGroup out of all files in a certain directory while excluding files with certain names (regardless of extension).
The list of files to be excluded initially contains file extensions, and I am using Community Tasks' RegexReplace to replace the extensions with an asterisk. I then use this list in the item's Exclude attribute. For some reason the files do not get excluded properly, even though the list appears to be correct.
To try and find the cause I created a test script (below) which has two tasks: first one initialises two properties with the list of file patterns in two different ways. The second task prints both properties and the files resulting from using both these properties in the Exclude attribute.
The properties' values appear to be identical, however the resulting groups are different. How is this possible?
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
DefaultTargets="Init;Test" ToolsVersion="3.5">
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
<Target Name="Init">
<ItemGroup>
<OriginalFilenames Include="TestDir\SampleProj.exe"/>
<OriginalFilenames Include="TestDir\SampleLib1.dll"/>
</ItemGroup>
<RegexReplace Input="#(OriginalFilenames)" Expression="\.\w+$" Replacement=".*">
<Output TaskParameter="Output" ItemName="PatternedFilenames"/>
</RegexReplace>
<PropertyGroup>
<ExcludeFilesA>TestDir\SampleProj.*;TestDir\SampleLib1.*</ExcludeFilesA>
<ExcludeFilesB>#(PatternedFilenames)</ExcludeFilesB>
</PropertyGroup>
</Target>
<Target Name="Test">
<Message Text='ExcludeFilesA: $(ExcludeFilesA)' />
<Message Text='ExcludeFilesB: $(ExcludeFilesB)' />
<ItemGroup>
<AllFiles Include="TestDir\**"/>
<RemainingFilesA Include="TestDir\**" Exclude="$(ExcludeFilesA)"/>
<RemainingFilesB Include="TestDir\**" Exclude="$(ExcludeFilesB)"/>
</ItemGroup>
<Message Text="
**AllFiles**
#(AllFiles, '
')" />
<Message Text="
**PatternedFilenames**
#(PatternedFilenames, '
')" />
<Message Text="
**RemainingFilesA**
#(RemainingFilesA, '
')" />
<Message Text="
**RemainingFilesB**
#(RemainingFilesB, '
')" />
</Target>
</Project>
Output (reformatted somewhat for clarity):
ExcludeFilesA: TestDir\SampleProj.*;TestDir\SampleLib1.*
ExcludeFilesB: TestDir\SampleProj.*;TestDir\SampleLib1.*
AllFiles:
TestDir\SampleLib1.dll
TestDir\SampleLib1.pdb
TestDir\SampleLib2.dll
TestDir\SampleLib2.pdb
TestDir\SampleProj.exe
TestDir\SampleProj.pdb
PatternedFilenames:
TestDir\SampleProj.*
TestDir\SampleLib1.*
RemainingFilesA:
TestDir\SampleLib2.dll
TestDir\SampleLib2.pdb
RemainingFilesB:
TestDir\SampleLib1.dll
TestDir\SampleLib1.pdb
TestDir\SampleLib2.dll
TestDir\SampleLib2.pdb
TestDir\SampleProj.exe
TestDir\SampleProj.pdb
Observe that both ExcludeFilesA and ExcludeFilesB look identical, but the resulting groups RemainingFilesA and RemainingFilesB differ.
Ultimately I want to obtain the list RemainingFilesA using the pattern generated the same way ExcludeFilesB is generated. Can you suggest a way, or do I have to completely rethink my approach?
The true cause of this was revealed accidentally when a custom task threw an exception.
The actual value of ExcludeFilesA is TestDir\SampleProj.*;TestDir\SampleLib1.* like one might expect. However the actual value of ExcludeFilesB is TestDir\SampleProj.%2a;TestDir\SampleLib1.%2a.
Presumably Message unescapes the string before using it, but Include and Exclude do not. That would explain why the strings look the same but behave differently.
Incidentally, the execution order doesn't seem to have anything to do with this, and I'm pretty sure (following extensive experimentation) that everything gets executed and evaluated exactly in the order in which it appears in this script.
ItemGroups need to be evaluated before targets execution, and the PatternedFilenames ItemGroup is being created on the fly within its target container.
You could workaround this using the CreateItem task, which will ensure the PatternedFilenames scope throughout the execution:
<RegexReplace Input="#(OriginalFilenames)" Expression="\.\w+$" Replacement=".*">
<Output TaskParameter="Output" ItemName="PatternedFilenames_tmp"/>
</RegexReplace>
<CreateItem Include="#(PatternedFilenames_tmp)">
<Output TaskParameter="Include" ItemName="PatternedFilenames"/>
</CreateItem>