I'm working on an ASP.NET 4 WebAPI project and am including a wpp.targets file. I need to use MSBuild.ExtensionPack.Xml.XmlFile to replace a value in one of my configuration XML files.
The problem is that I don't want to install MSBuild.ExtensionPack on all the machines so I packaged it up with the project. On my local build, the path to the MSBuild.ExtensionPack.dll resolves correctly. On my build machine though, I keep getting this error: The "MSBuild.ExtensionPack.Xml.XmlFile" task could not be loaded from the assembly C:\Program Files (x86)\MSBuild\ExtensionPack\4.0\MSBuild.ExtensionPack.dll.
It seems to be resolving to the default install location of the package.
Here's what's in my wpp.targets file:
<?xml version="1.0" encoding="utf-8"?>
<!-- Sets the assembly which will run the transformation on Web.config (Should be installed on Dev machines) -->
<UsingTask TaskName="TransformXml"
AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v11.0\Web\Microsoft.Web.Publishing.Tasks.dll"/>
<!-- Get the path to the MSBuild.Extension.Pack -->
<PropertyGroup>
<TPath>$(MSBuildProjectDirectory)\..\packages\MSBuild.Extension.Pack.1.3.0\tools\net40\MSBuild.ExtensionPack.tasks</TPath>
<TPath Condition="Exists('$(MSBuildProjectDirectory)\..\packages\MSBuild.Extension.Pack.1.3.0\tools\net40\MSBuild.ExtensionPack.tasks')">$(MSBuildProjectDirectory)\..\packages\MSBuild.Extension.Pack.1.3.0\tools\net40\MSBuild.ExtensionPack.tasks</TPath>
</PropertyGroup>
<!--Import the MSBuild.Extension.Pack package -->
<Import Project="$(TPath)"/>
<!-- Make sure web.config and transformation files exist -->
<Target Name="ConfigurationTransform" BeforeTargets="BeforeBuild" Condition="Exists('Web.config')" />
<Target Name="ConfigurationTransform" BeforeTargets="BeforeBuild" Condition="Exists('Web.$(Configuration).config')" />
<!-- Make sure web.config will be there even for package/publish -->
<Target Name="CopyWebConfig" BeforeTargets="Build;Rebuild">
<Copy SourceFiles="Web.Base.config"
DestinationFiles="Web.config"
OverwriteReadOnlyFiles="true"
SkipUnchangedFiles="false" />
</Target>
<!-- Run Web.Config transformation on a build as well (not just a publish) -->
<Target Name="CustomTransformWebConfigOnBuild" AfterTargets="CopyWebConfig" >
<Message Text="Transforming: Web.$(Configuration).config" Importance="high" />
<TransformXml Source="Web.Base.config"
Transform="Web.$(Configuration).config"
Destination="Web.config" />
</Target>
<!-- Update Web.Config's config attribute -->
<Target Name="UpdateConfigAttribute" AfterTargets="CustomTransformWebConfigOnBuild" Condition="$(Configuration) != 'Release'">
<Message Text="Transforming: Web.config" Importance="high" />
<MSBuild.ExtensionPack.Xml.XmlFile TaskAction="UpdateAttribute"
File="Web.config"
XPath="/configuration/appSettings/add[#key='config_url']"
Key="value"
Value="www.randomurl.com"/>
</Target>
When you have more than 1 option for the source location for a helper-dll...I like to do it like with the style below.
Something like this (Obviously, you'll have to put real locations in for "PossibleLocationOne" and "PossibleLocationTwo").
<PropertyGroup>
<MyFoundMSBuildExtensionPackLocation Condition="Exists('..\PossibleLocationOne\MSBuild.ExtensionPack.dll')">..\PossibleLocationOne\MSBuild.ExtensionPack.dll</MyFoundMSBuildExtensionPackLocation>
<MyFoundMSBuildExtensionPackLocation Condition="Exists('..\PossibleLocationTwo\MSBuild.ExtensionPack.dll')">..\PossibleLocationTwo\MSBuild.ExtensionPack.dll</MyFoundMSBuildExtensionPackLocation>
<!--Now check to see if either of the two above resolved , if not , add something to the path so you can at least -->
<MyFoundMSBuildExtensionPackLocation Condition="'$(MyFoundMSBuildExtensionPackLocation)'==''">DID_NOT_FIND_A_PATH_FOR_MSBUILDEXENSIONPACK\MSBuild.ExtensionPack.dll</MyFoundMSBuildExtensionPackLocation>
</PropertyGroup>
<UsingTask AssemblyFile="$(MyFoundMSBuildExtensionPackLocation)" TaskName="TransformXml"/>
Add all options for possible locations..and one extra for "I didn't find a match"....
Then use the "UsingTask".
"UsingTask" is ~~after~~ the MyFoundMSBuildExtensionPackLocation(PropertyGroup).......so the $(MyFoundMSBuildExtensionPackLocation) resolves before the UsingTask is called.
Related
I'm trying to setup a csproj with assembly info automatically generated from compilation datetime and mercurial revision.
<!-- assembly info -->
<PropertyGroup>
<!-- versioning number: Maj.Min.TimeStamp.Rev -->
<Major>2</Major>
<Minor>90</Minor>
<!-- define Build based on the compilation date: yyddd that sould be integer in [0..65535] -->
<Build>$([System.DateTime]::Now.ToString(`yy`))$([System.DateTime]::Now.DayOfYear.ToString(`000`))</Build>
<Revision>0</Revision>
<Version>$(Major).$(Minor).$(Build)</Version>
<AssemblyVersion>$(Major).$(Minor).$(Build).$(Revision)</AssemblyVersion>
<FileVersion>$(Major).$(Minor).$(Build).$(Revision)</FileVersion>
</PropertyGroup>
<Import Project="..\packages\MSBuild.Mercurial.1.2.1\build\MSBuild.Mercurial.targets" Condition="Exists('..\packages\MSBuild.Mercurial.1.2.1\build\MSBuild.Mercurial.targets')" />
<Target Name="GetHgRev" BeforeTargets="GenerateAssemblyInfo" DependsOnTargets="PrepareForBuild">
<HgVersion LocalPath="$(SolutionDir)" Timeout="5000">
<Output TaskParameter="Revision" PropertyName="Revision" />
</HgVersion>
<Message Text="Last revision from HG: $(Revision)" Importance="High" />
<Message Text="$(Major).$(Minor).$(Build).$(Revision)" Importance="High" />
</Target>
On console output, I get something consistent: 2.90.20160.239
But only $(Build) is properly used in tmp AssemblyInfo.cs, $(Revision) is basically 0.
In previous csproj format (2015), I got the job done with explicit cmd in BeforeBuild target:
<PropertyGroup>
<AssemblyCompany>AAA</AssemblyCompany>
<MyAppName>BBB</MyAppName>
<Major>2</Major>
<Minor>90</Minor>
<Build>0</Build>
<Revision>0</Revision>
</PropertyGroup>
<Target Name="BeforeBuild">
<!-- get the Mercurial revision no -->
<HgVersion LocalPath="$(SolutionDir)" Timeout="5000">
<Output TaskParameter="Revision" PropertyName="Revision" />
</HgVersion>
<Message Text="Last revision from HG: $(Revision), today date $([System.DateTime]::Now.ToString(`yyyy.MM.dd`)) -> $([System.DateTime]::Now.ToString(`yy`))$([System.DateTime]::Now.DayOfYear.ToString(`000`))" Importance="High" />
<!-- define Build based on the compilation date: yyddd that sould be integer in [0..65535] -->
<CreateProperty Value="$([System.DateTime]::Now.ToString(`yy`))$([System.DateTime]::Now.DayOfYear.ToString(`000`))">
<Output PropertyName="Build" TaskParameter="Value" />
</CreateProperty>
<!-- generate version file, i.e. AssemblyInfo.cs used later for compilation -->
<Message Text="Creating Version File: $(Major).$(Minor).$(Build).$(Revision)" Importance="High" />
<AssemblyInfo CodeLanguage="CS" OutputFile=".\Properties\AssemblyInfo.cs" AssemblyTitle="AAA" AssemblyDescription="BBB" AssemblyCompany="$(AssemblyCompany)" AssemblyProduct="CCC" AssemblyCopyright="Copyright © DDD $([System.DateTime]::Now.ToString(`yyyy`))" AssemblyVersion="$(Major).$(Minor).$(Build).$(Revision)" AssemblyFileVersion="$(Major).$(Minor).$(Build).$(Revision)" />
</Target>
Would you see any fix to this issue?
New test:
<Import Project="..\packages\MSBuild.Mercurial.1.2.1\build\MSBuild.Mercurial.targets" Condition="Exists('..\packages\MSBuild.Mercurial.1.2.1\build\MSBuild.Mercurial.targets')" />
<Target Name="GetHgRev" BeforeTargets="GenerateAssemblyInfo" DependsOnTargets="PrepareForBuild">
<HgVersion LocalPath="$(SolutionDir)" Timeout="5000">
<Output TaskParameter="Revision" PropertyName="Revision" />
</HgVersion>
<Message Text="Proposed number: $(Major).$(Minor).$(Build).$(Revision)" Importance="High" />
<Message Text="AssemblyVersion before: $(AssemblyVersion)" Importance="High" />
<PropertyGroup>
<AssemblyVersion>$(Major).$(Minor).$(Build).$(Revision)</AssemblyVersion>
</PropertyGroup>
<Message Text="AssemblyVersion after: $(AssemblyVersion)" Importance="High" />
</Target>
Kind regards,
Charles
The static portion of a project file (everything outside a <Target>) is evaluated completely before beginning to run the actual build logic (which is made up by targets).
Properties are like a string-to-string dictionary, so Version and others will be set to the string values, with $() replacements being evaluated to in the string but not logically preserved.
This means that once the target runs, you will also need to update all the properties you now need to change again (Version, AssemblyVersion, FileVersion etc.). You can do so by adding a <PropertyGroup> inside the target after the HgVersion task invocation.
I currently have a solution with a web.api project that I want to deploy to different virtual directories in my local IIS. Currently I am doing the following in the .csproj of the api:
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(Configuration)' == 'CustomerOne.Debug'">
<CustomerName>CustomerOne</CustomerName>
....
</PropertyGroup>
...
These variables are used extenisvely further on for web.config transforms, copying to different locations, etc., by referencing them like $(CustomerName).
The only place where it does not work is in the definition of the virtual directory, i.e., I'd like to connect the build configuration to the IISUrl below, which you can hardcode:
<ProjectExtensions>
<VisualStudio>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
<WebProjectProperties>
...
<IISUrl>http://localhost/api/something</IISUrl>
...
</WebProjectProperties>
</FlavorProperties>
</VisualStudio>
</ProjectExtensions>
Replacing this by <IISUrl>http://localhost/api/$(CustomerName)</IISUrl> does not work. Ideas?
Replacing this by http://localhost/api/$(CustomerName) does not work. Ideas?
That because Anything inside of a ProjectExtensions element will be ignored by MSBuild.
You can get the detailed info from this document ProjectExtensions Element (MSBuild):
Allows MSBuild project files to contain non-MSBuild information.
Anything inside of a ProjectExtensions element will be ignored by
MSBuild.
That is the reason why the Msbuild variables not work in Project Extensions.
Hope this helps.
You could update the underlying project file. A Target like this in your project file would do it.
<Target Name="AfterBuild">
<PropertyGroup>
<NewUrl>http://localhost/api/$(CustomerName)</NewUrl>
</PropertyGroup>
<Message Text="Updating IISUrl: $(NewUrl) in $(MSBuildProjectFile)" />
<XmlPeek Namespaces="<Namespace Prefix='msb' Uri='http://schemas.microsoft.com/developer/msbuild/2003'/>" XmlInputPath="$(MSBuildProjectFile)" Query="/msb:Project/msb:ProjectExtensions/msb:VisualStudio/msb:FlavorProperties/msb:WebProjectProperties/msb:IISUrl/text()">
<Output TaskParameter="Result" ItemName="Peeked" />
</XmlPeek>
<Message Text="Current Url: #(Peeked)" />
<!-- Only update the IISUrl if its changed -->
<XmlPoke Condition=" '#(Peeked)'!='$(NewUrl)' " XmlInputPath="$(MSBuildProjectFile)" Namespaces="<Namespace Prefix='msb' Uri='http://schemas.microsoft.com/developer/msbuild/2003'/>" Query="/msb:Project/msb:ProjectExtensions/msb:VisualStudio/msb:FlavorProperties/msb:WebProjectProperties/msb:IISUrl" Value="$(NewUrl)" />
</Target>
However it does have side affects. Changing the underlying project file means Visual Studio decides it should reload the project.
To use it you cannot go directly into Debug. Instead build, reload the project and then go into debug. If you go directly into Debug (with a compile) it will use the old url.
During a building of my binaries I am able to set lots of VERSIONINFO properties like FILEVERSION, PRODUCTVERSION and StringFileInfo, for example:
[assembly: AssemblyCompany("XXX")]
[assembly: AssemblyProduct("My Product")]
[assembly: AssemblyCopyright("© XXX, 2017")]
[assembly: AssemblyVersion("9.5")]
[assembly: AssemblyFileVersion("9.5.1001")]
My question is how to set FILEFLAGS field?
In vcxproj I am including a rc-file with correct VERSIONINFO structure. Is similar possible for the csproj projects?
Note, I have a lots of csproj-projects and I don't want to add custom build step to every project file (as I can forget to do it in new projects).
You can't set the FILEFLAGS with CSC (it's implicitly equal to 0). You can set the following though:
AssemblyInformationalVersion /productversion
AssemblyVersion /version
AssemblyFileVersion /version
AssemblyCopyright /copyright
AssemblyCompany /company
AssemblyProduct /product
you can add a custom build action to your project file
in your csproj file add this line
<Import Project="Properties\build.targets" />
build.targets file
<?xml version="1.0" encoding="utf-8" ?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(ProjectDir)..\packages\MSBuildTasks.1.5.0.235\tools\MSBuild.Community.Tasks.Targets" />
<Import Project="$(ProjectDir)..\packages\MSBuild.Extension.Pack.1.9.1\build\net40\MSBuild.Extension.Pack.targets"/>
<!-- Overriding the Microsoft.CSharp.targets target dependency chain -->
<!-- Call our custom AssemblyVersion target before build, even from VS -->
<PropertyGroup>
<BuildDependsOn>
AssemblyVersion;
$(BuildDependsOn)
</BuildDependsOn>
<CompanyName>My Company</CompanyName>
<Copyright>Copyright © My Company 2017</Copyright>
<Title>My Project </Title>
</PropertyGroup>
<ItemGroup>
<AssemblyVersionFiles Include="$(MSBuildProjectDirectory)\Properties\AssemblyInfo.cs"/>
</ItemGroup>
<Target Name="AssemblyVersion"
Inputs="#(AssemblyVersionFiles)"
Outputs="UpdatedAssemblyVersionFiles">
<Attrib Files="%(AssemblyVersionFiles.FullPath)"
Normal="true"/>
<Message Importance="High" Text="-------------------------------------------------------------------------"/>
<Message Importance="High" Text="[#(AssemblyVersionFiles)]"/>
<Message Importance="High" Text="-------------------------------------------------------------------------"/>
<MSBuild.ExtensionPack.Xml.XmlFile TaskAction="ReadAttribute" File="$(SolutionDir)AnyFolder\data.xml"
XPath="//Config/VersionNum/#value">
<Output PropertyName="ExecutableVersion" TaskParameter="Value"/>
</MSBuild.ExtensionPack.Xml.XmlFile>
<AssemblyInfo
CodeLanguage="CS"
OutputFile="%(AssemblyVersionFiles.FullPath)"
AssemblyCompany="$(CompanyName)"
AssemblyCopyright="$(Copyright)"
AssemblyVersion="$(ExecutableVersion)"
AssemblyFileVersion="$(ExecutableVersion)"
AssemblyTitle="$(Title)"
AssemblyProduct="$(Title)"
ComVisible="false"
AssemblyDescription=""
AssemblyConfiguration=""
AssemblyTrademark=""
AssemblyCulture="">
<Output TaskParameter="OutputFile"
ItemName="UpdatedAssemblyVersionFiles"/>
</AssemblyInfo>
</Target>
</Project>
it also includes how to log some info to the output window
and even read an xml file to get the version of your application
I have a VS 2010 solution that uses config transforms like so:
<Target Name="AfterBuild">
<TransformXml Condition="'$(IsDesktopBuild)' == 'false'" Source="Web.config" Transform="$(ProjectConfigTransformFileName)" Destination="$(PublishDir)\_PublishedWebsites\$(ProjectName)\Web.config" />
<Delete Files="$(PublishDir)\_PublishedWebsites\$(ProjectName)\Web.Debug.config" Condition="'$(IsDesktopBuild)' == 'false'" />
<Delete Files="$(PublishDir)\_PublishedWebsites\$(ProjectName)\Web.Release.config" Condition="'$(IsDesktopBuild)' == 'false'" />
<Delete Files="$(PublishDir)\_PublishedWebsites\$(ProjectName)\Web.Something.config" Condition="'$(IsDesktopBuild)' == 'false'" />
<Delete Files="$(PublishDir)\_PublishedWebsites\$(ProjectName)\Web.Else.config" Condition="'$(IsDesktopBuild)' == 'false'" />
<AspNetCompiler Condition="('$(IsDesktopBuild)' != 'false') AND ('$(MvcBuildViews)'=='true')" VirtualPath="temp" PhysicalPath="$(ProjectDir)\..\$(ProjectName)" />
<AspNetCompiler Condition="('$(IsDesktopBuild)' == 'false') AND ('$(MvcBuildViews)'=='true')" VirtualPath="temp" PhysicalPath="$(PublishDir)\_PublishedWebsites\$(ProjectName)" />
</Target>
This is in each website project (.csproj) in .sln.
When I run the v 4.0 MSBuild with a /p:PublishDir specified everything is fine. But when I run this as a Build Definition using Default template (GitTemplate.xaml) the folder for some reason resolves to $(ProjectDir), not the publish location. so the transform target is trying to copy files to the folder that doesn't exist ($(ProjectDir)\_PublishedWebsites\$(ProjectName)\Web.config).
If I manually specify the /p:PublishDir to a STATIC location on disk, the build succeeds. How can I tell the MSBuild where the PublishDir actually is?
I am happy to edit the workflow to assign a variable or something, I just don't see how its possible.
I also tried specifying relative paths (e.g. /p:PublishDir="..\..\bin\\") and the team build agent variables (same as we have configured for build paths, e.g. /p:PublishDir="D:\$(BuildAgentId)\$(BuildDefinitionId)\bin\\") to no avail - it seems that the variables are not unwrapped and relative path doesn't work.
Add something like this before the AfterBuild target:
<PropertyGroup>
<PublishDir>PATH_TO_YOUR_PUBLISH_DIR</PublishDir>
</PropertyGroup>
You should be able to use relative paths without problem:
<PropertyGroup>
<PublishDir>..\..\bin\</PublishDir>
</PropertyGroup>
Remember that the path will be relative to the current project file.
If you want to override it from the command line you can add a condition like this:
<PropertyGroup>
<PublishDir Condition="'$(PublishDir)'==''">PATH_TO_YOUR_PUBLISH_DIR</PublishDir> <!-- This will be the default value -->
</PropertyGroup>
Then call MsBuild with /p:PublishDir="Your_New_Path" to override the default value.
If you have a VS solution you could use something like this:
<PropertyGroup>
<PublishDir Condition="'$(PublishDir)'==''">$(SolutionDir)PublishDir</PublishDir> <!-- This will be the default value -->
</PropertyGroup>
This will use the directory where the solution is placed as root and will create a "PublishDir" folder there.
This will work only if you have a solution, off course you could define a default value in case there's no solution:
<PropertyGroup>
<SolutionDir Condition="'$(SolutionDir)'=='' Or '$(SolutionDir)'=='*Undefined*'">Your_Default_SolutionDir</SolutionDir>
<PublishDir Condition="'$(PublishDir)'==''">$(SolutionDir)PublishDir</PublishDir> <!-- This will be the default value -->
</PropertyGroup>
I have a situation where i want the versioning to be dynamic at build time.
Version Pattern: <year>.<month>.<day>.<hhmm>
But i have read where the String value used in the Attribute is reparsed at compile time.
Any advise on how to get this dynamic versioning completed?
Ideal situation:
<Assembly: AssemblyVersion("4.0.0.0")>
<Assembly: AssemblyFileVersion(Year(Now) & "." & Month(Now()) & "." & Day(Now()) & "." & String.format("hhmm", now()))>
I know it wont work but should get the point acrossed.
You can use the MsbuildCommunityTasks to generate the build number and to customize the assembly file version on pre-build time.
Download the zip at MsbuildCommunityTasks
Unzip to the folder [SolutionFolder]\MsBuildExtensions\MSBuildCommunityTasks
Add the sample below on your project (csproj), just after the Microsoft.CSharp.Targets import.
<PropertyGroup>
<MSBuildCommunityTasksPath>$(MSBuildThisFileDirectory)..\MsBuildExtensions\MSBuildCommunityTasks</MSBuildCommunityTasksPath>
<My-PropertiesDir>Properties</My-PropertiesDir>
</PropertyGroup>
<Import Project="$(MSBuildCommunityTasksPath)\MSBuild.Community.Tasks.Targets"/>
<Target Name="BeforeBuild">
<Time Format="yyyy.MM.dd.HHmm">
<Output TaskParameter="FormattedTime" PropertyName="My-VersionNumber" />
</Time>
<Message Text="Building $(My-VersionNumber) ...">
</Message>
<ItemGroup>
<My-AssemblyInfo Include="$(My-PropertiesDir)\AssemblyVersionInfo.cs" />
<Compile Include="#(My-AssemblyInfo)" />
</ItemGroup>
<MakeDir Directories="$(My-PropertiesDir)" />
<AssemblyInfo OutputFile="#(My-AssemblyInfo)"
CodeLanguage="CS"
AssemblyFileVersion="$(My-VersionNumber)"
AssemblyInformationalVersion="$(My-VersionNumber)"
Condition="$(My-VersionNumber) != '' "
/>
</Target>
<Target Name="AfterBuild">
<Delete Files="#(My-AssemblyInfo)" />
</Target>
Wipe the AssemblyFileVersion attribute from your AssemblyInfo.cs. It will be generated at build time.
You'll see the version number being printed on the console when you build. The generated file is deleted on the AfterBuild target, to keep your source control clean.
BeforeBuild:
Building 2013.01.14.1016 ...
Created AssemblyInfo file "Properties\AssemblyVersionInfo.cs".
(...)
AfterBuild:
Deleting file "Properties\AssemblyVersionInfo.cs".
If you want do this to many projects with less msbuild code, it will be necessary to create a customized build script to wrap up your solution.