How can I change multiple files with XMLUpdate? - msbuild

I'm using XMLUpdate to update multiple config files in subdirectories.
I thought I would be able to do something like this:
<XmlUpdate Namespace="http://schemas.microsoft.com/.NetConfiguration/v2.0"
XmlFileName="\\$(BuildEnvironment)\websites\*.config"
Xpath="//configuration/appSettings/add[#key='Site']/#value"
Value="sitename"
/>
Where I have the following structure:
Websites
|
|-site1\web.config
|
|-site2\web.config
|
|-site3\web.config
So the idea is that rather than writing the xmlupdate task many times, I would be able to use the above and update many config files at once.
Is this possible?

Yes, I'm pretty sure it is possible, but I think you need to use <ItemGroup> for that to get a collection of files. Something like:
<ItemGroup>
<documentation_files Include="\\$(BuildEnvironment)\websites\**web.config" />
</ItemGroup>
<XmlUpdate
XmlFileName="#(documentation_files)"
Xpath="//configuration/appSettings/add[#key='Site']/#value"
Value="sitename" />
I left the Value static, but if you want to change it to the current folder, you can use something like Value="%(documentation_files.RecursiveDir)".
Note: This code is just an example. You may have to change it a bit to get what you want, but I hope it helps you.

Related

How to get Directory name in msbuild configuration file?

Here is the simple code which I am using. Which gets all the folders in the directory and then give me the Folder name.
<TestProjectFolderPath Include="$([System.IO.Directory]::GetDirectories(`$(SolutionDir)`,`*.Tests`))" />
<TestProjectFolderNames Include="#(TestProjectFolderPath->'$([System.IO.Path]::GetDirectoryName(`$([System.IO.Path]::GetFileName(`%(Identity)`))`)',' ')" />
But in TestProjectFolderNames [System.IO.Path] functions are not getting evaluated and returned as just string eg:
$([System.IO.Path]::GetDirectoryName($([System.IO.Path]::GetFileName(C:\Some.Unit.Tests)))
I need help to understand the correct syntax to get this working.
Using property functions on Item Metadata while transforming an Item is not supported I think (maybe it is in the latest MSBuild version but I cannot test that right now). As a workaround add new Metadata yourself and because it acts like a Property things work out ok for recent MSBuild versions:
<ItemGroup>
<TestProjectFolderPath Include="$([System.IO.Directory]::GetDirectories(`$(SolutionDir)`,`*.Tests`))" />
<TestProjectFolderPath>
<FolderName>$([System.IO.Path]::GetFileName(`%(Identity)`))</FolderName>
</TestProjectFolderPath>
</ItemGroup>
<Message Text="#(TestProjectFolderPath->'%(FolderName)', ' ')" />
edit see comments, according to Sherry for older MSBuild versions the equivalent Item code is:
<TestProjectFolderPath Include="$([System.IO.Directory]::GetDirectories($(SolutionDir),*.Tests))">
<FolderName>$([System.IO.Path]::GetFileName(%(Identity)))</FolderName>
</TestProjectFolderPath>
I left out GetDirectoryName because it makes little sense calling that on the result of GetFileName.

MSBuild Filtering ItemGroup of files with condition

This feels like it's so simple, but I cannot get it to work.
All I'm trying to achieve is a filtered list of the embedded resources. I've tried various approaches but I can't seem to get it right.
Here's what I thought was the right solution:
<ItemGroup>
<AllEmbeddedResources Include="#(EmbeddedResource)" Condition="$(FullPath.Contains('Change')"/>
</ItemGroup>
Edit...
To clarify, the results are without the condition, the list is all embedded resources, with the condition, the group is empty.
I've tried this inside and outside of target's, and I've tried getting the full list in one group, and then filtering in a separate group. I know I'm just misunderstanding some fundamental part of msbuild syntax, I just can't seem to work it out. Looking forward to being shown my stupid mistake!
Inside a target, this can be done using the batching syntax for items and using the System.String.Copy method to be able to call instance functions on the string:
<Target Name="ListAllEmbeddedResources">
<ItemGroup>
<AllEmbeddedResources Include="#(EmbeddedResource)" Condition="$([System.String]::Copy(%(FullPath)).Contains('Change'))" />
</ItemGroup>
<Message Importance="high" Text="AllEmbeddedResources: %(AllEmbeddedResources.Identity)" />
</Target>
Note that this syntax only works inside a target and not during static evaluation (item group directly under the <Project> node).
The Condition Attribute must return a boolean, and it operates on each element of the itemgroup.
You can access each element using %(Identity).
Say you have some unfiltered itemgroup called UnfilteredItems, and you want to filter those into a group called MyFilteredItems, using some regex pattern.
<ItemGroup>
<MyFilteredItems Include="#(UnfilteredItems)" Condition="$([System.Text.RegularExpressions.Regex]::Match(%(Identity),'.*\\bin\\.*').Success)"/>
</ItemGroup>

MSBUILD - Remove a character from a variable without knowing its position

I read a build number from my TFS Team build which looks like "AB-1.2.3.4-CDE-REV.1". I want to edit this number and remove the last decimal point and make it look like "AB-1.2.3.4-CDE-REV1".
Usually when you want to manipulate strings in msbuild you're looking to use Property Functions. In the documentation of those you'll read you can use String functions so next up is figuring out which methods of System.String you need. In this case: LastIndexOf and Remove should do the trick:
<!-- BuildNumber property is fetched elsewhere -->
<PropertyGroup>
<BuildNumber>AB-1.2.3.4-CDE-REV.1</BuildNumber>
</BuildNumber>
<Target Name="ManipulateBuildNumber">
<PropertyGroup>
<BuildNumber>$(BuildNumber.Remove($(BuildNumber.LastIndexOf('.')),1))</BuildNumber>
</PropertyGroup>
<Message Text="New build number is $(BuildNumber)" />
</Target>
Thanks for the solution stijn. It works. I had figured out another lame and crude way of doing it.
<BuildNumber>AB-1.2.3.4-CDE-REV.1</BuildNumber>
<Part1>$(BuildNumber.Split('.')[0])</Part1>
<Part2>$(BuildNumber.Split('.')[1])</Part2>
<Part3>$(BuildNumber.Split('.')[2])</Part3>
<Part4>$(BuildNumber.Split('.')[3])</Part4>
<Part5>$(BuildNumber.Split('.')[4])</Part5>
<BuildNumber>$(Part1).$(Part2).$(Part3).$(Part4)$(Part5)</BuildNumber>

Condition statement to be used with "AfterCompileSolution" in tfsbuild

i need your help. I am running into a situation. I am trying to copy certain binaries into a particular folder. I am adding those task into "AfterCompileSolution" . I know it is incorrect, bcos it's gonna execute this step after every solution is compiled.
Here is my situation, i tried adding a condition like a SolutionFileName, but i get empty result. The target doesn't get executed because the SolutionFileName parameter is empty.
So do you know of any parameter that i can use between solutiontobuild i.e i want to copy certain binaries only after solution "A" is completed and i want these parameters to be part of "AfterCompileSolution" or maybe "BeforeCompileSolution"
Please suggest
Thanks
Satesh
It's been a while since I've done this but I believe you reference the file name with a syntax such as:
<Target Name="AfterCompileSolution" DependsOnTargets="RandomPreReqTarget">
<SomeTask Condition="'%(SolutionToBuildItem.Identity)' == 'ConditionValue'" />
</Target>
Another cool thing you can do is product extra properties in your SolutionToBuild item and reference them as metadata also like:
<SolutionToBuild Include="$(SolutionRoot)\$(SourceBranch)\RandomDirectory\Project.csproj">
<Targets>Build</Targets>
<Properties>OutDir=$(RandomDirectory);Configuration=$(Configuration);Platform=AnyCPU</Properties>
<GAC>True</GAC>
</SolutionToBuild>
You would then be able to access the metadata like this:
<Target Name="AfterCompileSolution" DependsOnTargets="RandomPreReqTarget">
<SomeTask Condition="'%(SolutionToBuildItem.GAC)' == 'True'" />
</Target>

Dynamic Metadata Assignment in an ItemGroup

I have an ItemGroup defined as:
<ItemGroup>
<ProtoFiles Include="Protos\*.proto"/>
</ItemGroup>
It yields a list of all .proto files in a directory of my project. I want each item in the group to include a piece of metadata that specifies the name of the file that will be generated based on the .proto file. I know I can do this:
<ItemGroup>
<ProtoFiles Include="Protos\*.proto">
<OutputFile>%(ProtoFiles.Filename).cs</OutputFile>
</ProtoFiles>
</ItemGroup>
But my problem is that it is not a simple mapping from .proto filename to output filename. There is some tricky logic involved that I need to encapsulate somewhere and call that when assigning metadata. I need something like:
<ItemGroup>
<ProtoFiles Include="Protos\*.proto">
<OutputFile><GetOutputFilename ProtoFilename="%(ProtoFiles.Filename)"/></OutputFile>
</ProtoFiles>
</ItemGroup>
The idea being that my custom GetOutputFilename task would be called in order to get the metadata value.
Is this possible? Am I barking up the wrong tree?
I think it's not, try instead passing the ItemGroup to a task to generate this metadata. Property Functions can operate on metadata values, but unfortunately cannot be used to define metadata.
It's hard to know if the logic is too tricky for MSBuild without knowing exactly what it is. Do you have a custom task that operates on #(ProtoFiles) to generate the output files? If so, why not alter your task (or refactor to a new one) that just calculates the output files without creating them, something like this,
<ProtoTask
Files="#(ProtoFiles)"
... other params
DryRun="true">
<Output
TaskParameter="OutputFiles"
ItemName="ProtoFiles" />
</ProtoFiles>
The task can clone the item array, calculate the metadata value, and assign it to the output item array, which in the example here overwrites the original item array passed into the task.