Force GenerateApplicationManifest to run in MSBUILD - msbuild

My requirement is to change the ProductName in the application manifest file. I did the solution mentioned here, but for me, the product name still doesn't change. In the log, I can see that the GenerateApplicationManifest task is not getting executed. The log says:
Skipping target "GenerateApplicationManifest" because all output files are up-to-date with respect to the input files.
But I want this task to run, so that the product name is changed. This is the code.
<DeployEXE>The application .exe file</DeployEXE>
<DeployManifest>The .exe.manifest file</DeployManifest>
<AppManifest>The .application file</AppManifest>
Line 1: <Exec Command="$(Msbuild) $(ClientAppProject) /t:Clean;Publish /p:BootstrapperEnabled=true;UpdateRequired=true;MinimumRequiredVersion=$(Version);UpdateEnabled=true;UpdateMode=Foreground;UpdateInterval=7;UpdateIntervalUnits=Days;UpdatePeriodically=false;PublishDir=$(PublishDir);ApplicationVersion=$(Version);PublisherName="$(PublisherName)";ProductName="$(ClickOnceAppTitle)";PublishUrl=$(ClickOnceUrl);GenerateManifests=true;Install=true;Configuration=Release;Platform=AnyCPU"/>
Line 2: <Exec Command="$(SN) -R $(DeployEXE) $(SNKFile)"/>
Line 3: <Exec Command="$(MAGE) -UPDATE $(DeployManifest) -CertFile $(CertFile) -Password $(Password) -pub "$(PublisherName)" -UseManifestForTrust t" />
Line 4: <Exec Command="$(MAGE) -UPDATE $(AppManifest) -AppManifest $(DeployManifest) -CertFile $(CertFile) -Password $(Password) -pub "$(PublisherName)" -UseManifestForTrust t"/>
Line 5: <GenerateApplicationManifest
AssemblyName="$(EXEName).exe"
Product="$(ClickOnceAppTitle)"
EntryPoint="$(EXEName).exe"
OutputManifest="$(EXEName).exe.manifest">
<Output
ItemName="ApplicationManifest"
TaskParameter="OutputManifest"/>
</GenerateApplicationManifest>
After Line 1, I can see the correct publisher & product names set in the .application & .exe.manifet files (eg: PublisherName is set to MyCompany, ProductName is set to My Company Software). After Line 4 is executed, the publisher name stays correct, but the product name is then replaced with the assembly name (eg: ProductName changes to My.Company.Software). For correcting this, I've added Line 5, which ideally should have corrected the ProductName, but it doesn't happen.
What could be the problem here? Why is this task ignored by MSBUILD?

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 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.

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.

How do I check if any file in an item list exist using msbuild?

I would like to run a task if any file in an item list is missing. How do I do that?
My current script has a list of "source" files #(MyComFiles) that I translate another list of "destination" files #(MyInteropLibs), using the following task:
<CombinePath BasePath="$(MyPath)\interop"
Paths="#(MyComFiles->'%(filename).%(extension)')">
<Output TaskParameter="CombinedPaths"
ItemName="MyInteropLibs" />
</CombinePath>
I want to check if any of the files in #(MyInteropLibs) is missing and run a task that will create them.
If you only need to create the missing files, and not get a list of the files that were missing you can you the touch task, which will create if the files don't exist.
<Touch Files="#(MyInteropLibs)" AlwaysCreate="True" />
If you only want to create the missing files, and avoid changing timestamps of the existing files, then batching can help
<Touch Files="%(MyInteropLibs.FullPath)" AlwaysCreate="True"
Condition=" ! Exists(%(MyInteropLibs.FullPath)) "/>
If you want a list of the files created then
<Touch Files="%(MyInteropLibs.FullPath)" AlwaysCreate="True"
Condition=" ! Exists(%(MyInteropLibs.FullPath)) ">
<Output TaskParameter="TouchedFiles" ItemName="CreatedFiles"/>
</Touch>
<Message Text="Created files = #(CreatedFiles)"/>
I am not very experienced with MSBuild so there may be better solutions than this but you could write a FilesExist task that takes the file list and passes each file to File.Exists returning true if they do exist and false otherwise and thenn react based on the result
Sorry I can't provide code to help out, my knowlege of MSBuild sytax is not strong
You can find out pretty easily using Exec.
To test if ALL of a set of files exists: The DOS FOR /D command accepts a semicolon-separated list of files - i.e. a flattened item array.
<!-- All exist -->
<Exec
Command="for /D %%i in (#(MyFiles)) do if not exist %%i exit 1"
IgnoreExitCode="true">
<Output TaskParameter="ExitCode" PropertyName="ExistExitCode"/>
</Exec>
To test if ANY of a set of files exists: The DOS DIR command accepts a semicolon-separated list of files. It sets the %ERRORLEVEL% to 0 if it finds any files in the list, nonzero if it finds none. (This is the simpler case, but it does not address the original question...)
<!-- Any exists -->
<Exec Command="dir /B #(MyFiles)" IgnoreExitCode="true">
<Output TaskParameter="ExitCode" PropertyName="DirExitCode"/>
</Exec>
Then most likely you will want to define a boolean property based on the output.
EDIT: BTW this is a code smell. Usually when you find yourself wanting to do this, it's an indication that you should set the Outputs property of the target so it will loop over the items.

How to email multiple address with MSbuild

I have a target in my build script that will send an email with an attachment detailing svn changes for a module.
This works if I hard code a single email address however I now want to email multiple developers and the script is failing. Below is the code
<Target Name="MailInformationUpdate" DependsOnTargets="ZipArtifact" Condition="!Exists('BUILD_IS_PERSONAL')">
<ReadLinesFromFile File="$(BuildDir)\$(recipientListFileName)">
<Output PropertyName="Recipients" TaskParameter="Lines"/>
</ReadLinesFromFile>
<Mail SmtpServer="$(smptServer)"
To="#(Recipients)"
From="$(senderEmail)"
Body="Attached is a list of the changes made since the last release. "
Subject="This module has been updated. You may wish to update." Attachments="$(BuildDir)\Builds\$(svnChangeFileName)"
/>
</Target>
If I change the To line to read $(Recipients) the first person on the list will get the email, subsequent addresses do not recieve the email.
I then changed the To line to what you see below #(Recipients), as I thougt it might then loop round each recipient. No such luck!!! I get the error message
Emailing "{0}".
<path> error : A recipient must be specified.
The file that I read in is simply a text file in the format (emailAddress1),(emailAddress2), etc
The task ReadLinesFromFile reads a list of items from a text file. But the file must have one item on each line.
With your text file in the format (emailAdress1),emailAddress2)... you will only have one item containing (emailAdress1),emailAddress2)....
Your email.txt should be like this:
emailAdress1
emailAdress2
...
You get items from ReadLinesFromFile task and not properties, so modify your task like that:
<Target Name="MailInformationUpdate" DependsOnTargets="ZipArtifact" Condition="!Exists('BUILD_IS_PERSONAL')">
<ReadLinesFromFile File="$(BuildDir)\$(recipientListFileName)">
<Output ItemName="Recipients" TaskParameter="Lines"/>
</ReadLinesFromFile>
<Mail SmtpServer="$(smptServer)"
To="#(Recipients)"
From="$(senderEmail)"
Body="Attached is a list of the changes made since the last release. "
Subject="This module has been updated. You may wish to update."
Attachments="$(BuildDir)\Builds\$(svnChangeFileName)"
/>
</Target>
(There is a bug in the log of the mail target, even with multiple recipients only the first one will be shown in the log.)