MSBuild batch task without including metadata value used for batching - msbuild

Is it possible in MSBuild to batch tasks without having the metadata value you are using to bucketize the items appearing in the output?
Let's say I've got the following .proj:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ExampColl Include="Item1">
<Bucket>1</Bucket>
</ExampColl>
<ExampColl Include="Item2">
<Bucket>2</Bucket>
</ExampColl>
<ExampColl Include="Item3">
<Bucket>1</Bucket>
</ExampColl>
<ExampColl Include="Item4">
<Bucket>2</Bucket>
</ExampColl>
</ItemGroup>
<Target Name="MyBatch">
<Message Text="#(ExampColl) in bucket %(Bucket)">
</Message>
</Target>
</Project>
If I run the MyBatch target, I get this output:
Item1;Item3 in bucket 1
Item2;Item4 in bucket 2
What I'm asking is how to batch like this without having the "bucketizer" actually be present in the output, to get output like this:
Item1;Item3 is a batch
Item2;Item4 is a batch
Is this possible? Where would I put the %() or whatever else it is that's needed to accomplish this?

You cannot do this. What are you trying to do?

Related

How to add an item only when an incrementally executed target is run?

Say we have the following MSBuild project that defines a target which can be partially run:
<Project DefaultTargets="Foo">
<ItemGroup>
<MyInputs Include="**/*.json"/>
</ItemGroup>
<Target Name="Foo"
Condition="'#(MyInputs)' != ''"
Inputs="#(MyInputs)"
Outputs="#(MyInputs->'%(FileName).cs')">
<MyCustomTask FileToProcess="%(MyInputs.Identity)"/>
<ItemGroup>
<Compile Include="%(MyInputs.FileName).cs"/>
</ItemGroup>
</Target>
</Project>
The problem is that all items are included into ProcessedFiles; even these whose respective MyCustomTasks are not run, due to incremental building. Apparently, MSBuild always processes ItemGroups inside targets.
Is there a way to add an item inside a target, only when the respective target batch is run? I tried using CreateItem, because it is a task and might not get executed just like MyCustomTask, but it didn't work.
My specific problem was that when the source files had already existed, they were included twice in the Compile item, which raised a warning. It was then when I learned about the KeepDuplicates attribute that saved me.
But the question still stands.

Using the Zip task in MSBuild

I have been attempting to use the zip task of msbuild in a project I am working on at the moment.
My project file looks something like this:
<PropertyGroup> <MSBuildCommunityTasksPath>$(SolutionDir)\.build</MSBuildCommunityTasksPath> </PropertyGroup>
<Import Project="$(MSBuildCommunityTasksPath)\MSBuild.Community.Tasks.Targets" />
<ItemGroup>
<FileToZip include="C:\FilePath"></FilesToZip>
<FileToZip include="C:\FilePath"></FilesToZip>
</ItemGroup>
<Target Name="BeforeBuild">
<PropertyGroup>
<ReleasePath>\releasepath</ReleasePath>
<Zip Files="#(FilesToZip)" WorkingDirectory="$(ReleasePath)" ZipFileName="HTMLeditor.html" ZipLevel="9" />
</Target>
However, the zip file updates but does not contain the files specified in the item group FilesToZip. I cannot figure out why they aren't being recognised! I have double checked file paths and they are correct. Any ideas?
I think you want to do something like this:
<ItemGroup>
<FileToZip include="C:\FilePath;C:\FilePath"/>
</ItemGroup>
As I mentioned in my comment, simply creating a variable (FileToZip) and repeating it twice with different values does not give you an array that contains both of the values. You end up with only the last value (and not an array at all). Your include attribute is a selector which is used to build the array and it can contain multiple values, wildcards and other patterns which are used to build out that array for you.
Here's a link to MSDN that gives you more information on how to use the Include attribute: http://msdn.microsoft.com/en-us/library/ms171454.aspx
I ditched the ItemGroup in the end, and went with another way of doing it.
<Target Name="Zip">
<CreateItem Include="FilesToInclude" >
<Output ItemName="ZipFiles" TaskParameter="Include"/>
<Zip ZipFileName="ZipFile.zip" WorkingDirectory="FolderToWriteZipTo" Files="#(ZipFiles)" />
</Target>
This method seemed to be easier and wasn't adding files to the root of the file.
Thanks for the help though guys.

MSBuild batch updating ItemGroup MetaData with output from a custom task

I am trying to use MSBuild 4.0 to Uninstall\install a set of windows services I have defined in an ItemGroup using the MSBuild extensions. The problem I am running into is if the service does not exist the uninstall TaskAction will error out. I want to be able to use the CheckExists TaskAction to set a flag in my metadata that I can evaluate on the condition statement.
However, I cannot figure out how to batch over the list of services, calling the CheckExist task and updating the flag in my metadata. See sample below:
<ItemGroup>
<ServiceName Include="Service1">
<ExeName>Service1.exe</ExeName>
<ServicePath>$(LocalBin)\Service1.exe</ServicePath>
<User>LocalSystem</User>
<Exists></Exists>
</ServiceName>
<ServiceName Include="Service2">
<ExeName>Service2.exe</ExeName>
<ServicePath>$(LocalBin)\Service2.exe</ServicePath>
<User>LocalSystem</User>
<Exists></Exists>
</ServiceName>
</ItemGroup>
<Target Name="UninstallServices">
<!--how can I batch over this command to set %(ServiceName.Exist)-->
<MSBuild.ExtensionPack.Computer.WindowsService TaskAction="CheckExists" ServiceName="%(ServiceName.Identity)">
<Output TaskParameter="Exists" PropertyName="DoesExist"/>
</MSBuild.ExtensionPack.Computer.WindowsService>
<MSBuild.ExtensionPack.Computer.WindowsService TaskAction="Uninstall" ServiceName="%(ServiceName.Identity)" User="%(ServiceName.User)" ServicePath="%(ServiceName.ServicePath)" Condition="%(ServiceName.Exists) = 'True'" />
</Target>
I have done some searching and have not found an example where metadata is being updated based on an Output result of a Task. Is this something that is possible? Should I be taking a different approach?
My current solution for this problem has been to set ContinueOnError to true on the call for the uninstall, but I do not like this approach because I could be hiding other errors.
Any help would be appreciated.
Thanks,
Tyson Moncrief

MSBuild WriteLinesToFile without new line at the end

I want to write a number in a text file using WriteLinesToFile but the task is putting a line feed at the end which causes me trouble when i want to read or combine in other places
Example:
<WriteLinesToFile File="$(TextFile)" Lines="#(BuildNumber)" Overwrite="true"/>
UPDATE as the user comment below:
The problem that I had was that I was using a very simple command in Property to read the content of a file $([System.IO.File]::ReadAllText("$(TextFile)")) and I really want to use this one but it also included the line feed from WriteLinesToFiles. I ended up using similar solution like yours using ReadLinesFromFile.
There is a slight dissconnect between the title and the description. I would have liked to post this "answer" as an edit, but do not have enough reputation points :)
Do you have a problem with the newline at the end of a file, or do you have a problem ignoring that newline? Could you please clarify?
One way how I suppose you could ignore that newline follows.
This small snippet of code writes a build number to a file, then reads it out and then increments the number read by 1.
<Target Name="Messing_around">
<PropertyGroup>
<StartBuildNumber>1</StartBuildNumber>
</PropertyGroup>
<ItemGroup>
<AlsoStartBuildNumber Include="1"/>
</ItemGroup>
<!-- Use a property
<WriteLinesToFile File="$(ProjectDir)test.txt" Lines="$(StartBuildNumber)" Overwrite="true"/>
-->
<WriteLinesToFile File="$(ProjectDir)test.txt" Lines="#(AlsoStartBuildNumber)" Overwrite="true"/>
<ReadLinesFromFile File="$(ProjectDir)test.txt">
<Output
TaskParameter="Lines"
ItemName="BuildNumberInFile"/>
</ReadLinesFromFile>
<PropertyGroup>
<OldBuildNumber>#(BuildNumberInFile)</OldBuildNumber>
<NewBuildNumber>$([MSBuild]::Add($(OldBuildNumber), 1))</NewBuildNumber>
</PropertyGroup>
<Message Importance="high" Text="Stored build number: #(BuildNumberInFile)"/>
<Message Importance="high" Text="New build number: $(NewBuildNumber)"/>
</Target>
And this is what I see
Output:
1>Build started xx/xx/xxxx xx:xx:xx.
1>Messing_around:
1> Stored build number: 1
1> New build number: 2
1>
1>Build succeeded.
If you attempting to read, in an MSBuild Task, a single line containing only a number from a file with a trailing line feed, then you should not have a problem.
As a side note: With the little information at hand I'd assume that BuildNumber is an Item in an ItemGroup. If you have only one build number to deal with, perhaps Property may have been an option. But then, again, I haven't been tinkering with MSBuild for too long. So, I am open to feedback on the Item vs Property issue.

How to batch in MSBuild?

I can't figure out how to pass values into an MSBuild task like I would a method. Take the following project file...
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0" DefaultTargets="Main">
<PropertyGroup>
<Var1>Foo</Var1>
<Var2>Bar</Var2>
</PropertyGroup>
<Target Name="Main">
<Message Text="$(Var1)" Importance="high" />
<Message Text="$(Var2)" Importance="high" />
</Target>
</Project>
I want to refactor the Message task into a target and then pass over Var1 and Var2 to it to get the same output. This is a very simplified example but the concept is the same.
I think you want to do something like this:
<ItemGroup>
<Messages Include="Message1">
<Text>Hello from Message1</Text>
</Messages>
<Messages Include="Message2">
<Text>Hello from Message2</Text>
</Messages>
</ItemGroup>
<Target Name="TestMessage">
<Message Text="%(Messages.Text)"/>
</Target>
This produces the following output:
TestMessage:
Hello from Message1
Hello from Message2
This is meant to complement, not replace, #BryanJ’s answer.
There are two types of batching. One is Task batching which happens when you use %(ItemName.MetaData) syntax. You just specify this value into a task parameter as if %(ItemName.MetaData) would only ever expand to one particular value. MSBuild then automatically executes the task multiple times, avoiding the need for the task to explicitly support iterating over a list of items.
Another batching type is Target batching. Target batching happens when you use the Inputs and Outputs attributes of <Target/>. To batch over an arbitrary set of Items in such a way that the target gets executed exactly once per Item, you can specify Inputs="#(ItemName)" Outputs=%(Identity).bogus. What’s important is that %(Identity) is present. Batching will look at all the possible expansions of Inputs and Outputs and decide its batching based on these expansions. Thus, you must make sure that each item gets a unique expansion if you want the Target to run separately for each item. I give #BryanJ’s code with modifications to use Target batching of this style:
<?xml version="1.0"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0" DefaultTargets="all">
<ItemGroup>
<Messages Include="Message1">
<Text>Hello from Message1</Text>
<Group>1</Group>
</Messages>
<Messages Include="Message2">
<Text>Hello from Message2</Text>
<Group>1</Group>
</Messages>
<Messages Include="Message3">
<Text>Hello from Message3</Text>
<Group>2</Group>
</Messages>
</ItemGroup>
<Target Name="all" DependsOnTargets="TestMessage;TestMessageGrouping" />
<!--
Use the Inputs/Outputs attributes to specify Target
batching. The metadata value I am batching over is
Identity. Since Identity is unique per item, this means the
Target will get run in full once for every value in
Messages. We provide something bogus for Outputs. It is
important that our bogus values do not coincide with real
filenames. If MSBuild finds a file with the name of a value
in Outputs and another file, with an older timestamp,
matching the corresponding value in Inputs, it will skip
running this Target. (This is useful in many situations, but
not when we want to just print out messages!)
-->
<Target Name="TestMessage" Inputs="#(Messages)" Outputs="%(Identity).bogus">
<Message Text="I will print the Text metadata property of %(Messages.Identity)" />
<Message Text="%(Messages.Text)" />
</Target>
<!--
If you want to combine Task and Target batching, you can specify
a different metadata value than Identity to group the items
by. I use the Group metadata I specified in the ItemGroup.
-->
<Target Name="TestMessageGrouping" Inputs="#(Messages)" Outputs="%(Group).bogus">
<Message Text="I will print the Text metadata property of all messages from Group %(Messages.Group)" />
<!--
Now, within the Target batch, we use Task batching to print
all of the messages in our %(Messages.Group) at once.
-->
<Message Text="%(Messages.Text)" />
</Target>
</Project>
with output:
TestMessage:
I will print the Text metadata property of Message1
Hello from Message1
TestMessage:
I will print the Text metadata property of Message2
Hello from Message2
TestMessage:
I will print the Text metadata property of Message3
Hello from Message3
TestMessageGrouping:
I will print the Text metadata property of all messages from Group 1
Hello from Message1
Hello from Message2
TestMessageGrouping:
I will print the Text metadata property of all messages from Group 2
Hello from Message3