This question already has answers here:
How to add a timestamp to TFSBuild.proj?
(2 answers)
Closed 8 years ago.
I am using MSBuild/yuicompressor to combine and minify JavaScript.
As part of this process, I want to modify my script references so they have a timestamp in the querystring. That way, a user always gets the non-cached version of the file when a new release is published. For example:
<script type="text/javascript" src="/scripts/combined-minified.js?20100727" />
I am using FileUpdate from MSBuildCommunityTasks to update the <script> reference, but it does not have a timestamp:
<FileUpdate
Files="#(includeFile)"
Regex="#scriptfiletoken#"
ReplacementText="<script type='text/javascript' src='/scripts/combined-minified.js' />"
/>
What is the best way to output this timestamp using MSBuild?
This method worked for me:
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
<Target Name="MyTarget">
<!-- Build timestamp. -->
<Time>
<Output TaskParameter="Month" PropertyName="Month" />
<Output TaskParameter="Day" PropertyName="Day" />
<Output TaskParameter="Year" PropertyName="Year" />
</Time>
<!-- ....... -->
<!-- Add timestamp to includeFile -->
<FileUpdate
Files="#(includeFile)"
Regex="#scriptfiletoken#"
ReplacementText="<script type='text/javascript' src='/scripts/combined-minified.js?$(Year)$(Month)$(Day)' />"
/>
</Target>
Related
I use the following to get a list of project files that need to be compiled. Each project is stored in a subdirectory of the projects directory.
<ItemGroup>
<dprs Include="c:\projects\**\*.dpr" />
</ItemGroup>
Is there a task that I can use to extract to extract the directory that each project file is in? I know I can write my own task to do this but I was hoping that one already exists and that I simply have not found it yet.
If I understand the question correctly, you shouldn't need a task - you can do this with well-known meta data. Does this do the trick?
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
<ItemGroup>
<dprs Include="c:\projects\**\*.dpr" />
</ItemGroup>
<Target Name="Default">
<CreateItem Include="%(dprs.RelativeDir)">
<Output ItemName="_ProjectFileLocations" TaskParameter="Include" />
</CreateItem>
<Message Text="#(_ProjectFileLocations->'%(FullPath)', '%0D%0A')" />
</Target>
</Project>
From the tests I ran, it shouldn't list a directory twice in the new item group.
I found a similar question like this here.
But this doesn't solve my problem. I have a ItemGroup like this
<ItemGroup>
<DocumentationSource Include="TestLibrary\TestLibrary.csproj;TestLibrary2\TestLibrary2.csproj;TestLibrary2\TestLibrary3.csproj" />
</ItemGroup>
I need to change this into propertygroup in this format
<PropertyGroup>
<DocumentationSources>
<DocumentationSource sourceFile="TestLibrary\TestLibrary.csproj" />
<DocumentationSource sourceFile="TestLibrary2\TestLibrary2.csproj" />
<DocumentationSource sourceFile="TestLibrary2\TestLibrary3.csproj" />
</DocumentationSources>
</PropertyGroup>
I am using sandcastle document builder for generating documents. That needs the document source as I shown in the format of PropertyGroup. But in my build script already I have a ItemGroup which has all the project as mentioned in the above format.
How to use that ItemGroup here as document source of SandCastle or how to convert the ItemGroup to PropertyGroup in the above format?
Actually I can change the ItemGroup to the PropertyGroup format but that has been formed dynamically with some logic like this
<_ProjectFilesPlatform Include="%(ProjectDefinitionsPlatform.Identity)">
<_ProjectPath>$([System.String]::Copy(%(ProjectDefinitionsPlatform.Identity)).Replace(".","\"))</_ProjectPath>
</_ProjectFilesPlatform>
[This is a rough outline I gave here. This manipulation is not the actual one used]
I am new to this MSBUILD scripts. Can anyone throw some light on this ?
Thanks.
You can use the #() syntax to transform the items to a string, separated by newlines. Here is an example project file (tested with MSBuild 15 on .net core):
<Project>
<ItemGroup>
<DocumentationSource Include="TestLibrary\TestLibrary.csproj;TestLibrary2\TestLibrary2.csproj;TestLibrary2\TestLibrary3.csproj" />
</ItemGroup>
<PropertyGroup>
<DocumentationSources>
#(DocumentationSource->'<DocumentationSource sourceFile="%(Identity)" />', '
')
</DocumentationSources>
</PropertyGroup>
<Target Name="Build">
<Message Importance="high" Text="Value of DocumentationSources: $(DocumentationSources)" />
</Target>
</Project>
Which produces the following output:
$ dotnet msbuild
Microsoft (R) Build Engine version 15.3.378.6360 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.
Value of DocumentationSources:
<DocumentationSource sourceFile="TestLibrary/TestLibrary.csproj" />
<DocumentationSource sourceFile="TestLibrary2/TestLibrary2.csproj" />
<DocumentationSource sourceFile="TestLibrary2/TestLibrary3.csproj" />
This even allows you to use a wildcard for your item element:
<DocumentationSource Include="**\*.csproj" />
Need some help with this MSBuild code.
I want to generate 4 app.config files with different settings and create 2 setup files for QA and production.
Each setup file will have 2 physical installations (Production lines).
So QA setup should include 2 app.configs with qa settings for production line 1 and 2, the same for production setup.
Here is a extract of the msbuild I have so far.
<ItemGroup>
<BuildEnvironment Include="QA">
<Server>qa-server</Server>
<BuildEnvironment/>
<BuildEnvironment Include="Prod">
<Server>prod-server</Server>
<BuildEnvironment/>
<Line Include="1">
<Setting>A</Setting>
</Line>
<Line Include="2">
<Setting>B</Setting>
</Line>
<ItemGroup>
<Target Name="PublishSetup" Inputs="#(BuildEnvironment)" Outputs="%(BuildEnvironment.Identity)">
<!-- Doesn't work at all -->
<ItemGroup>
<AppConfig Include="#(BuildEnvironment);#(Line)">
<Path>$(MyOutDir)\App.Config-%(Identity)</Path>
</AppConfig>
</ItemGroup>
<!-- Copy app.config to the four new files -->
<Copy SourceFiles="$(AppConfigFile)" DestinationFiles="%(AppConfig.Path)" />
<!-- Update each new app.config with XmlUpdate (community task), something like the following -->
<XmlUpdate XmlFileName="%(AppConfig.Path)" XPath=".." Value="%(AppConfig.Server)" />
<XmlUpdate XmlFileName="%(AppConfig.Path)" XPath=".." Value="%(AppConfig.Setting)" />
<!-- Build 2 setup.exe, one for qa and one prod using a Exec-task passing in qa and prod as command line argument -->
<Exec Command="setupcompiler.exe /d%(BuildEnvironment.Identity)" />
</Target>
The 4 resulting app.configs should be like this
app.config-QA-1
<connectionstring datasource="qa-server" ../>
<applicationSetting name="aName" value="A" />
app.config-QA-2
<connectionstring datasource="qa-server" ../>
<applicationSetting name="aName" value="B" />
app.config-Prod-1
<connectionstring datasource="prod-server" ../>
<applicationSetting name="aName" value="A" />
app.config-Prod-2
<connectionstring datasource="prod-server" ../>
<applicationSetting name="aName" value="B" />
The idea is to first build a 'cross-product', an ItemGroup containing the 4 combinations. Can be done by combining # and % for the two groups, as shown here. Then in a second step populate the ItemGroup with extra metadata based on existing metadata (adding metadata is just declaring the group again and adding metadata). It's a bit tricky here, because from Line you both want Identity and Setting - I don't know a nice msbuild way of doing this so I resorted to building a string with Identity|Setting, then splitting on the | later on.
<Target Name="PublishSetup">
<ItemGroup>
<AppConfig Include="#(BuildEnvironment)">
<Mod>%(Line.Identity)|%(Line.Setting)</Mod>
</AppConfig>
<AppConfig>
<Line>$([System.String]::Copy('%(Mod)').Split('|')[0])</Line>
<Setting>$([System.String]::Copy('%(Mod)').Split('|')[1])</Setting>
</AppConfig>
<AppConfig>
<Path>app.config-%(Identity)-%(Line)</Path>
</AppConfig>
</ItemGroup>
<Message Text="Path=%(AppConfig.Path) Server=%(AppConfig.Server) Setting=%(AppConfig.Setting)" />
</Target>
Output:
Path=app.config-QA-1 Server=qa-server Setting=A
Path=app.config-Prod-1 Server=prod-server Setting=A
Path=app.config-QA-2 Server=qa-server Setting=B
Path=app.config-Prod-2 Server=prod-server Setting=B
I have a .config in a target project and I need to add a line to it programmatically via an MSBuild task.
Pseduo operations like:
find target .config file
determine the value of attributes for new node (e.g. 'id' and 'version' for 'package' node)
insert new node in correct parent node
save changes
The .config file at $TargetProjectDir\Config\packages.config:
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="ABC" version="1.1.0.4" />
<package id="XYZ" version="2.0.0.0" />
</packages>
Needs to look like this afterwards:
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="ABC" version="1.1.0.4" />
<package id="XYZ" version="2.0.0.0" />
<package id="CarDataWidget" version="3.0.0.0" />
</packages>
So far i've considered using 'inline tasks', the 'EXEC' task and 'XmlPoke' task but haven't managed to get any of them working.
Here is my attempt with XmlPoke and XmlPeek:
I used the following article as an inspiration on how to add nodes to the packages.config file:
http://weblogs.asp.net/bsimser/appending-nodes-in-xml-files-with-xmlpeek-and-xmlpoke-using-nant
<Target Name="AfterBuild" DependsOnTargets="AddPackage">
</Target>
<Target Name="AddPackage">
<!-- Load existing nodes into a Property -->
<XmlPeek XmlInputPath="config/packages.config" Query="/packages/package" >
<Output TaskParameter="Result" PropertyName="Peeked" />
</XmlPeek>
<Message Text="From Peek: $(Peeked)"></Message>
<!-- Load new node into Property -->
<PropertyGroup>
<WidgetName>CarDataWidget</WidgetName>
<WidgetVersion>2.0.0.0</WidgetVersion>
<NewNode><package id="$(WidgetName)" version="$(WidgetVersion)" /></NewNode>
<!-- Concatenate existing and new node into a Property -->
<ConcatenatedNodes>$(Peeked)$(NewNode)</ConcatenatedNodes>
</PropertyGroup>
<Message Text="New pacakges: $(ConcatenatedNodes)"></Message>
<!-- Replace existing nodes with concatenated nodes -->
<XmlPoke Value="$(ConcatenatedNodes)" XmlInputPath="config/packages.config" Query="/packages">
</XmlPoke>
</Target>
The output from the above build is:
1>AddPackage:
1> From Peek: <package id="ABC" version="1.1.0.4" />;<package id="XYZ" version="2.0.0.0" />
1> New pacakges: <package id="ABC" version="1.1.0.4" />;<package id="XYZ" version="2.0.0.0" /><package id="CarDataWidget" version="2.0.0.0" />
1> C:\_dev\CarDataWidget.csproj(184,14):
error MSB4094: "<package id="ABC" version="1.1.0.4" />;<package id="XYZ" version="2.0.0.0" /><package id="CarDataWidget" version="2.0.0.0" />"
is an invalid value for the "Value" parameter of the "XmlPoke" task.
Multiple items cannot be passed into a parameter of type "Microsoft.Build.Framework.ITaskItem".
1>
1>Build FAILED.
THE QUESTION:
How can get it to add to a .config file with existing package nodes???
I had the same problem. I found the solution here.
The problem is than XmlPoke considers semicolon as a value separator.
Should replace this:
<NewNode><package id="$(WidgetName)" version="$(WidgetVersion)" /></NewNode>
With:
<NewNode><%3Bpackage id=%3B"%3B$(WidgetName)"%3B version=%3"%3$(WidgetVersion)"%3 />%3</NewNode>
Must replace each semicolon by the secuence %3B
Here is a way to do it using MSBuild Extension Pack.
Set the packages and versions in the NewPackage item group and it adds them to the XML file.
<Project
ToolsVersion="4.0"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\ExtensionPack\4.0\MSBuild.ExtensionPack.tasks" />
<Target Name="Test" DependsOnTargets="AddPackage">
</Target>
<ItemGroup>
<NewPackage Include="CarDataWidget">
<Version>3.0.0.0</Version>
</NewPackage>
<NewPackage Include="FooBarWidget">
<Version>1.2.3.4</Version>
</NewPackage>
</ItemGroup>
<Target Name="AddPackage">
<PropertyGroup>
<InputFile>in.xml</InputFile>
<OutputFile>out.xml</OutputFile>
</PropertyGroup>
<Copy SourceFiles="$(InputFile)" DestinationFiles="$(OutputFile)" />
<MSBuild.ExtensionPack.Xml.XmlFile
TaskAction="AddElement"
File="$(OutputFile)"
XPath="//packages"
Element="package"
Key="id"
Value="%(NewPackage.Identity)" />
<MSBuild.ExtensionPack.Xml.XmlFile
TaskAction="AddAttribute"
File="$(OutputFile)"
XPath="//packages/package[#id='%(NewPackage.Identity)']"
Key="version"
Value="%(NewPackage.Version)" />
</Target>
</Project>
Not hoping to wake up an old thread.I had the exact scenario were I had to add new keys to the appsettings section of web.config. I started off with OPs code and was stuck with the same problem with ; in the peeked value preventing the new concatenated value to be written. I fixed it by using Replace function to remove the ;
<ConcatenatedNodes>$(Peeked)$(NewNode)</ConcatenatedNodes>
<!--in the concatenatednode, remove semicolon-->
<ChangedPeek>$(ConcatenatedNodes.Replace(";",""))</ChangedPeek>
<!-- Replace existing nodes with concatenated nodes-->
<XmlPoke XmlInputPath="%(WebConfigFilesSolutionDir.FullPath)" Query="//appSettings" Value="$(ChangedPeek)" />
For the complete answer on how to add a new key to appsetting section of webconfig using MSBuild refer https://stackoverflow.com/a/56760009/6664129
Take a look at my blog post http://sedodream.com/2011/12/29/UpdatingXMLFilesWithMSBuild.aspx which compares the following methods.
Use SlowCheetah to transform the files for you
Use the TransformXml task directly
Use the built in (MSBuild 4.0) XmlPoke task
Use a third party task library
I have a MSBuild project and I want the current date to be added to a zip file that I am creating.
I am using the MSBuildCommunityTasks.
<!-- Import the CommunityTasks Helpper -->
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" />
On the website http://msbuildtasks.tigris.org/ I can see a task called time. I have not been able to find doc on how to use Time.
In msbuild 4 you can now
$([Namespace.Type]::Method(..parameters…))
$([Namespace.Type]::Property)
$([Namespace.Type]::set_Property(value))
so I am using
$([System.DateTime]::Now.ToString(`yyyy.MMdd`))
those ticks around the format are backticks not '
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Deploy" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
<!-- Include MSBuild tasks here -->
<ItemGroup>
<DefaultExclude Include="****" />
</ItemGroup>
<Target Name="Deploy" >
<Time Format="yyyy-MM-dd">
<Output TaskParameter="FormattedTime" PropertyName="buildDate" />
</Time>
<Message Text="Deploying ...."></Message>
<Copy SourceFiles="#(DeploymentFiles)" DestinationFolder="C:\CCNET\$(buildDate)\bin\" />
</Target>
</Project>
Maslow's answer is correct (I can't comment on it or I would); I would only add to it that you have to be careful when implicitly calling System.DateTime.Parse.
A parsed string value like $([System.DateTime]::Parse("1970-01-01T00:00:00.0000000Z") doesn't seem to end up with a Kind of DateTimeKind.Utc.
But you can use nested property functions to make it work; like this (to get the Unix timestamp):
$([System.DateTime]::UtcNow.Subtract($([System.DateTime]::Parse("1970-01-01T00:00:00.0000000Z").ToUniversalTime())).TotalSeconds.ToString("F0"))