MSBuild batch updating ItemGroup MetaData with output from a custom task - msbuild

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

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.

Proper use of msbuild inputs and outputs with Targets

We have an in house compiler and linker tool that we're trying to make MSBUILD compatible (i.e. proper behavior in build/incremental builds/rebuild/clean scenarios)
In the first step we actually call CL task to preprocess our files. The issue is I can't seem to figure out how to set up the tasks properly to do that so it detects if the output is deleted or if one of the inputs is modified.
Second step is call our Compiler with it's proper parameters.
Third step is to call our Linker with it's proper parameters.
I think once step one works making step two and three will be simple; I'm stuck on step one. Example code below. The main MFT contains "#includes" which reference all the other MFT files named in _MFTFiles - so we only need to process the main file; but we need to monitor them all so if we change them incremental builds work properly. If anyone has any idea I'd love to hear it. I have the MSBUILD book and of course scoured here but I don't see an example of what I'm trying to accomplish.
Thanks in advance.
<ItemGroup Label="_MainMFT">
<MainMFT Include="MFTSystem.MFT"/>
</ItemGroup>
<ItemGroup Label="_MFTFiles">
<MFTFiles Include="MFTbject.MFT;DebuggerSupport.MFT;enumerations.MFT;collections.MFT;DataStream.MFT;Version.MFT"/>
</ItemGroup>
<Target Name="_PreprocessFiles"
BeforeTargets="Build"
DependsOnTargets=""
Inputs="#(MFTFiles)"
Outputs="#(MFTFiles->'%(Filename).MFTpp')">
<Message Text="PlatformToolsetVersion is $(PlatformToolsetVersion)" Importance="high"/>
<CL Sources="#(MainMFT)" PreprocessorDefinitions="_DEBUG;EL_DIAG_ENABLED" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" PreprocessToFile="true" PreprocessOutputPath="$(ProjectDir)%(Filename).MFTpp" />
<CL Sources="#(MainMFT)" PreprocessorDefinitions="" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" PreprocessToFile="true" PreprocessOutputPath="$(ProjectDir)%(Filename).MFTpp"/>
</Target>
<Target Name="_ObjectCompiler" AfterTargets="_PreprocessFiles;Build">
<Message Text="Calling ObjectCompiler...." Importance="high"/>
</Target>
<Target Name="_ObjectLinker" AfterTargets="_ObjectCompiler;Link">
<Message Text="Calling ObjectLinker...." Importance="high"/>
</Target>

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.

Trouble with outputting MSBuild variables

I'm trying to output the variable from one target, into the parent target which started it. For example,
Target 1 simply calls the task in file 2 and is supposed to be able to use the variable set within that. However, I just can't seem to get it to work (wrong syntax perhaps?). Target 1 looks like this:
<Target Name="RetrieveParameter">
<MSBuild Projects="$(MSBuildProjectFile)" Targets="ObtainOutput" />
<Message Text="Output = $(OutputVar)" />
</Target>
Target 2 is where it reads in the value of the text file and sets it to the property and sets the variable 'OutputVar' to match. This is supposed to be returned to the parent.
<Target Name="ObtainOutput" Outputs="$(OutputVar)">
<ReadLinesFromFile File="output.txt">
<Output TaskParameter="Lines"
PropertyName="OutputVar" />
</ReadLinesFromFile>
</Target>
I'm quite new to MSBuild tasks, so it could well be something obvious. All I want to do is set a variable in one task, and then have that available in the parent task which called it.
Julien has given you the right answer, but not explained why it is correct.
As you're new to MSBuild tasks, I'll explain why Julien's answer is correct.
All tasks in MSBuild have parameters - you will know them as the attributes that you put on the task. Any of these parameters can be read back out by placing an Output element within it. The Output element has three attributes that can be used:
TaskParameter - this is the name of the attribute/parameter on the task that you want to get
ItemName - this is the itemgroup to put that parameter value into
PropertyName - this is the name of the property to put that parameter value into
In your original scripts, you were invoking one from the other. The second script will execute in a different context, so any property or itemgroup it sets only exists in that context. Therefore when the second script completes, unless you have specified some Output elements to capture values they will be discarded.
Note that you can put more than one Output element under a task to capture multiple parameters or just set the same value to multiple properties/itemgroups.
You have to use TargetOutputs of the MSBuild task:
<Target Name="RetrieveParameter">
<MSBuild Projects="$(MSBuildProjectFile)" Targets="ObtainOutput">
<Output TaskParameter="TargetOutputs" ItemName="OutputVar"/>
</MSBuild>
<Message Text="Output = #(OutputVar)" />
</Target>
(More information on MSBuild task.)

MSBuild Working with ItemGroup and EXEC Command

I created the ItemGroup shown in the code snippet. I need to iterate through this ItemGroup and run the EXEC command - also shown in the code snippet. I cannot seem to get it to work. The code returns the error shown below (note - the Message is written 2 times, which is correct), but the EXEC Command is not running correctly. The value is not being set; therefore the EXEC is not executing at all. I need the EXEC to execute twice or by however sections I define in the ItemGroup.
ERROR:
Encrypting WebServer appSettings section
Encrypting WebServer connectionStrings section
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_regiis.exe -pef "" "\gaw\UI" -prov "RSACustomProvider"
Encrypting configuration section...
The configuration section '' was not found.
CODE SNIPPET:
<ItemGroup>
<SectionsToEncrypt Include="Item">
<Section>appSettings</Section>
</SectionsToEncrypt>
<SectionsToEncrypt Include="Item">
<Section>connectionStrings</Section>
</SectionsToEncrypt>
</ItemGroup>
<Target Name="EncryptWebServerWebConfigSections">
<Message Text="Encrypting WebServer %(SectionsToEncrypt.Section) section" />
<Exec Command="$(AspNetRegIis) -pef "%(SectionsToEncrypt.Section)" "$(DropLocation)\$(BuildNumber)\%(ConfigurationToBuild.FlavorToBuild)\$(AnythingPastFlavorToBuild)" -prov "$(WebSiteRSACustomProviderName)""/>
</Target>
The problem is that you are batching on 2 items at a time. What I mean is the you have the statements
%(SectionsToEncrypt.Section)
%(ConfigurationToBuild.FlavorToBuild)
In the same task invocation. When you batch on more than 1 item at a time in the same task invocation, they will be batch independently. That's why you're error is stating The configuration section '' ...
If you your FlavorToBuild just has one value what you should do is to stuff that into a property before you call to Exec and then use the property. So your one liner would then convert to:
<PropertyGroup>
<_FlavToBuild>%(ConfigurationToBuild.FlavorToBuild)<_FlavToBuild>
</PropertyGroup>
<Exec Command="$(AspNetRegIis) -pef "%(SectionsToEncrypt.Section)" "$(DropLocation)\$(BuildNumber)\$(_FlavToBuild)\$(AnythingPastFlavorToBuild)" -prov "$(WebSiteRSACustomProviderName)""/>
If you have multiple values for FlavorToBuild then it's more complicated. You would have 2 options:
Hard code Exec more than once
Use target batching with task batching to perform the foreach/foreach
Batching is one of the most confusing elements of MSBuild. I've put together some online resources at http://sedotech.com/Resources#batching. If you want to know more than that then you can pick up a copy of my book.