MSBuild Working with ItemGroup and EXEC Command - msbuild

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.

Related

Can MSBuild ItemGroup's be chunked?

I've got an ItemGroup that includes source files from my project:
<ItemGroup>
<SourceFiles Include=".\**\*.h;.\**\*.cpp"/>
</ItemGroup>
There are a few hundred source files. I want to pass them to a command line tool in an Exec task.
If I call the command line tool individually for each file:
<Exec Command="tool.exe %(SourceFiles.FullPath)" WorkingDirectory="."/>
Then, it runs very slowly.
If I call the command line tool and pass all of the files in one go:
<Exec Command="tool.exe #(SourceFiles -> '"%(FullPath)"', ' ')" WorkingDirectory="."/>
Then, I get an error if there are too many files (I'm guessing the command line length exceeds some maximum).
Is there a way I can chunk the items so that the tool can be called a number of times, each time passing up to a maximum number of source file names to the tool?
I'm not aware of any mechanism to do that with well known item metadata. What you could do is load all those paths into their own item group and write a custom task that calls the exec task. Writing a custom task is pretty simple, it can be done inline:
http://msdn.microsoft.com/en-us/library/vstudio/dd722601(v=vs.100).aspx

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.

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 Executing only Changed SQL Scripts

I need to construct an MSBUILD script executes .SQL Scripts which have changed since last build.
I initially thought that I could copy all scripts from one folder to another using the <Copy> task and using the CopiedFiles <Output> for the copy task. However the copy task returns All files that it Attempted to copy, not actual copied files.
I am able to get MSBUILD to execute SQL Scripts via MSBUILD.ExtensionPack but Im scratching my head on this one
You can do this with a concept known as incremental building. The idea is that you would create a target and then specify the inputs and outputs, which would be files. MSBuild will compare the timestamps of the input files to the output files. If all outputs were created after all outputs then the target is skipped. If all inputs are newer then all the target will be executed for all files. If only a portion are out of date, then only those will be passed to the target. For more info on this see the section Using Incremental Builds in my article Best Practices For Creating Reliable Builds, Part 2.
Also for more resources on MSBuild I have compiled a list at http://sedotech.com/Resources#MSBuild
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="RunScripts">
<Import Project="$(MSBuildExtensionsPath)\ExtensionPack\MSBuild.ExtensionPack.tasks"/>
<PropertyGroup>
<ConnStr>Server=Example;Database=Example;Trusted_Connection=True</ConnStr>
<BuildFolder>Build\</BuildFolder>
</PropertyGroup>
<ItemGroup>
<Scripts Include="*.sql"/>
</ItemGroup>
<Target Name="RunScripts"
Inputs="#(Scripts)"
Outputs="#(Scripts->'$(BuildFolder)%(Filename)%(Extension)')">
<SqlExecute TaskAction="ExecuteScalar"
Files="#(Scripts)"
ConnectionString="$(ConnStr)"/>
<Copy SourceFiles="#(Scripts)"
DestinationFiles="#(Scripts->'$(BuildFolder)%(Filename)%(Extension)')"/>
</Target>
</Project>
Could it be that you copying into an empty destination?
SkipUnchangedFiles
If true, skips the copying of files that are unchanged
between the source and destination. The Copy task considers
files to be unchanged if they have the same size and the
same last modified time.
In your case i suspect that all files are considered changed since they don't exist at the destination.