How to retrieve AssemblyVersionAttribute in MSBuild script - visual-studio

When trying to retrieve the [assembly: AssemblyVersion("1.2.3.4")] present in AssemblyInfo.cs file from a custom MSBuild script, I always get $(Version) value 1.0.0.0 instead of the value written in the file.
Can somebody tell me how to fix this issue? Unfortunately, other entries I have found so far, didn't point me to the right direction.

Actually, you just want to get the assembly dll version by MSBuild rather the nuget package version.
$(Version) is the value of the nuget package version after you pack the lib project. See this official document.
If you want to get the internal assembly dll version, it can be a bit complex but it can be done with some MSBuild tasks.
Use this:
<Target Name="RetrieveIdentities" BeforeTargets="Build">
<GetAssemblyIdentity
AssemblyFiles="$(TargetPath)">
<Output
TaskParameter="Assemblies"
ItemName="MyAssemblyIdentities"/>
</GetAssemblyIdentity>
<Message Text="Version: %(MyAssemblyIdentities.Version)"/>
</Target>
And %(MyAssemblyIdentities.Version) is the value of the AssemblyVersion.

Related

Why does overriding the Build target in msbuild work for C++ projects but fail for C# projects?

I am overriding the Build target like this in my file OverrideBuild.targets:
<Target Name="OriginalBuild" DependsOnTargets="$(BuildDependsOn)">
<Message Text="Finished running target OriginalBuild" Importance="High" />
</Target>
<Target Name="Build" >
<CheckArtifacts ProjectGuid = "$(ProjectGuid)" SolutionPath = "$(SolutionPath)" >
<Output PropertyName = "ArtifactsHaveChanged" TaskParameter = "Result" />
</CheckArtifacts>
<Message Text="ArtifactsHaveChanged = $(ArtifactsHaveChanged)" Importance="high" />
<!-- if the artifacts.props file has not just been updated then we can run the original build target -->
<Message Condition="'$(ArtifactsHaveChanged)' == 'false'" Text="Running target OriginalBuild" Importance="High" />
<CallTarget Condition="'$(ArtifactsHaveChanged)' == 'false'" Targets="OriginalBuild" />
<!-- Otherwise we need to run a new msbuild to avoid using an out-of-date cached version of the artifacts.props file.
To force the msbuild process not to use the cached values from this process we must pass at least one property.
-->
<Message Condition="'$(ArtifactsHaveChanged)' == 'true'" Text="Running target OriginalBuild in nested msbuild" Importance="High" />
<MSBuild Condition="'$(ArtifactsHaveChanged)' == 'true'" Targets="OriginalBuild"
Projects="$(MSBuildProjectFullPath)" Properties="InNestedMsbuild=true" />
<!-- Visual Studio doesn't pick up on the modified artifacts.props file unless we force it to reload the solution -->
<Touch Condition="'$(ArtifactsHaveChanged)' == 'true' and '$(BuildingInsideVisualStudio)' == 'true'" Files = "$(SolutionPath)" />
<Message Text="Finished running build target override" Importance="High" />
</Target>
and each of my .vcxproj or .csproj files includes this file at the end:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\OverrideBuild.targets" />
</Project>
This works as I want it to for the C++ projects but fails with the C# projects. When building a C# project via msbuild it fails because the command line to the C# compiler is missing reference arguments for local assemblies. For example, a C# file that has a line like this at the top of the file:
using My.Utils.Common;
fails with the following error message:
error CS0234: The type or namespace name 'Common' does not exist in the namespace 'My.Utils' (are you missing an assembly reference?)
And looking at the compiler command used it is missing this line:
/reference:C:\Code\scratch\Build\My.Utils.Common\Bin\Release\My.Utils.Common.dll
That missing line is present when I comment out my override of the Build target. And weirdly enough it will build fine from within Visual Studio even with my Build override in place. It only fails when building using msbuild from the command line and only for C# projects.
I thought that the way I had overriden the Build target would be completely transparent but apparently it isn't. Can anybody shed some light on what is going wrong ?
It seems that when project A depends on project B with a project reference, the outputs of the Build target of B are used to deduce what should be passed as a reference to the compiler when building A. This is presumably somewhere in the ResolveAssemblyReferences logic.
Therefore to get your replacement Build target working, you need to make its outputs match those of the standard Build.
Here is how you can achieve this:
<Target
Name="Build"
Condition=" '$(_InvalidConfigurationWarning)' != 'true' "
DependsOnTargets="GetTargetPathWithTargetPlatformMoniker"
Returns="#(TargetPathWithTargetPlatformMoniker)" >
</Target>
Here Returns="#(TargetPathWithTargetPlatformMoniker)" is what the Returns of the standard Build in the SDK is. But the item array #(TargetPathWithTargetPlatformMoniker) is initially empty, so you need to run the Target GetTargetPathWithTargetPlatformMoniker to populate it before hand.
These are implementation details of the build system, so they may vary by SDK version, but you can always inspect the logic in C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\Microsoft.Common.CurrentVersion.target or equivalent.
Note that this cannot be used directly with C++ projects, their default Build target is a bit different. You may need to vary by the project type to support both. The Condition on a Target does not stop it from overwriting the existing one, it only stops it from executing, so if you need a target overwrite to differ, you need to put the alternatives in files and import them conditionally. I don't know of a more convenient way, but that at least works.
Why does overriding the Build target in msbuild work for C++ projects but fail for C# projects?
After test your sample, I found the error not comes from the overriden the Build target, it should be related to the project type which you referenced.
Because I have tried comment the import line in the HelloWorld project file:
<Import Project="..\..\OverrideBuild.targets" />
Then MSBuild command line still throw that error.
Besides, I found your referenced project HelloWorldHelper is a Console application project, which output type is Class library.
To resolve this issue, I have created a new Class library instead of Console application, then build it from MSBuild command line, it works fine.
So, please try to convert your referenced project to Class library.
Hope this helps.

MSbuild Update all assembly file in a solution with svn build revision

Is there a way using msbuild community task, to update all the assemblyInfo.cs in a solution with the SVN build number. There is a a lot of solution out there using FileUpdate but the source is one file and not all the files.
<FileUpdate Files="version.txt"
Regex="(\d+)\.(\d+)\.(\d+)\.(\d+)"
ReplacementText="$1.$2.$3.123" />
I want dynamically to go through all the projects and change it version without knowing the projects file name in the solution
It could look for all assemblyinfo.cs files by using wildcards. For example:
<ItemGroup>
<AssemblyInfoFiles Include="..\**\AssemblyInfo.cs"/>
</ItemGroup>
<Target Name="AfterBuild">
<FileUpdate Files="#(AssemblyInfoFiles)"
Regex="(\d+)\.(\d+)\.(\d+)\.(\d+)"
ReplacementText="$1.$2.$3.123" />
</Target>
Then as stijn said that, you can add a project which all others depend on if you want update all assemblyinfo.cs files before build.
If you want to update all asseblyinfo.cs files after build, you need to add other projects' reference to this project.

How to use Msbuild.Community.Tasks.Version in a csproj file

I want to use the VersionTask from the MSBuild Community Tasks to set the Revision calculation type. However, I am having difficulty understanding how to actually wire up the task within my csproj file.
The project has an AssemblyInfo.cs which has the following attribute defined:
[assembly: AssemblyVersion("3.2.5.*")]
What I want to do is over-ride the generation of the Revision number handling with my own custom handling.
I have put the customised Version task into the csproj file as follows:
<UsingTask TaskName="MyCo.Build.Tasks.Version" AssemblyFile="$(SolutionDir)\..\Build\.build\MyCo.Build.Tasks.dll" />
The actual task is then called as follows:
<Target Name="BeforeBuild">
<Message Text="Setting Revision Number to $(BuildNumber)" />
<MyCo.Build.Tasks.Version RevisionType="BuildServerNumber" Revision="$(BuildNumber)" /></Target>
I can see the target BeforeBuild being called because of the Message Task but the exe file still has the standard generated numbering as follows: File Version : 3.2.5.27547
I was expecting something like 3.2.5.111 (the build number passed into MSBuild as a parameter).
As the Version task is overriding the default handling of the '*' value for Revision I don't believe it is necessary to actually modify the AssemblyInfo.cs file.
Do I need to pass the output value from the Version task into an MSBuild parameter? Do I actually need to use the AssemblyVersion task to update the values in the file?
Obviously I am trying to avoid having to change the AssemblyInfo.cs, I just want to override the Version number handling.
Can someone advise please?
EDIT: I just found the following example of usage in the chm file from the installer which partly answers my question.
<Version BuildType="Automatic" RevisionType="Automatic" Major="1" Minor="3" >
<Output TaskParameter="Major" PropertyName="Major" />
<Output TaskParameter="Minor" PropertyName="Minor" />
<Output TaskParameter="Build" PropertyName="Build" />
<Output TaskParameter="Revision" PropertyName="Revision" />
</Version>
<Message Text="Version: $(Major).$(Minor).$(Build).$(Revision)"/>
However, when I run the build I can output the generated Assembly Version in a Message task but the exe file still has the default Revision as before
I got a resolution to this here: How to override the revision number
In the end I had to actually update the AssemblyInfo file for each project during the build. So on the build server (TeamCity) I placed the code I needed in the Microsoft.Common.targets file so that it wasn't required in each project ad then passed the release number and TeamCity build number to each build task.
The Revision is the number of days since 1/1/2000 when you set it to Automatic - you can supply your own "StartDate" if you want.

Error MSB4062 when trying to use ServiceController

I use Visual Studio 2010 with TFS 2010 on a x64 machine.
I am trying to use the MSBuild Community Tasks target in my build. This target exists in source control. So in my csproj file i am import that particular target but i now get the following error:
error MSB4062: The "MSBuild.Community.Tasks.Attrib" task could not be loaded from the assembly C:\Program Files (x86)\MSBuild\MSBuildCommunityTasks\MSB
uild.Community.Tasks.dll. Could not load file or assembly 'file:///C:\Program F
iles (x86)\MSBuild\MSBuildCommunityTasks\MSBuild.Community.Tasks.dll' or one of
its dependencies. The system cannot find the file specified. Confirm that the
declaration is correct, that the assembly and all its dependencies
are available, and that the task contains a public class that implements Micros
oft.Build.Framework.ITask. [C:\SampleTest\SampleTest.csproj]
Here is my code:
<Import Project="..\..\Builds\Common\MSBuildTasks\MSBuild.Community.Tasks\MSBuild.Community.Tasks.Targets" />
<Target Name="BeforeBuild">
<PropertyGroup>
<MyService>ServiceName</MyService>
</PropertyGroup>
<ServiceController ServiceName="$(MyService)" Action="Stop" />-->
</Target>
Any thoughts on the above?
Why is MSBuild trying to look for the dll elsewhere when i have specified it in the project file?
Thanks in advance,
I think the problem comes from within the MSBuild.Community.Tasks.Targets file - it is this file that actually references the MSBuild.Community.Tasks.dll assembly.
If you open the file you can see a bunch of UsingTask elements, such as:
<UsingTask AssemblyFile="$(MSBuildCommunityTasksLib)" TaskName="MSBuild.Community.Tasks.Attrib" />
The $(MSBuildCommunityTasksLib) property is defined at the top of the file as:
<PropertyGroup>
<MSBuildCommunityTasksPath Condition="'$(MSBuildCommunityTasksPath)' == ''">$(MSBuildExtensionsPath)\MSBuildCommunityTasks</MSBuildCommunityTasksPath>
<MSBuildCommunityTasksLib>$(MSBuildCommunityTasksPath)\MSBuild.Community.Tasks.dll</MSBuildCommunityTasksLib>
</PropertyGroup>
So it looks like you need to set the $(MSBuildCommunityTasksPath) property before calling <Import>.

How to Integrate ILMerge into Visual Studio Build Process to Merge Assemblies?

I want to merge one .NET DLL assembly and one C# Class Library project referenced by a VB.NET Console Application project into one command-line console executable.
I can do this with ILMerge from the command-line, but I want to integrate this merging of reference assemblies and projects into the Visual Studio project. From my reading, I understand that I can do this through a MSBuild Task or a Target and just add it to a C#/VB.NET Project file, but I can find no specific example since MSBuild is large topic. Moreover, I find some references that add the ILMerge command to the Post-build event.
How do I integrate ILMerge into a Visual Studio (C#/VB.NET) project, which are just MSBuild projects, to merge all referenced assemblies (copy-local=true) into one assembly?
How does this tie into a possible ILMerge.Targets file?
Is it better to use the Post-build event?
The "MSBuild ILMerge task" (or MSBuild.ILMerge.Task) NuGet package makes this process quite simple. It defaults to merging any "copy local" references into your main assembly.
Note: Although the packages have similar names, this one is different from ILMerge.MSBuild.Tasks that Davide Icardi mentioned in his answer. The one I'm suggesting here was first published in August 2014.
Here an alternative solution:
1) Install ILMerge.MSBuild.Tasks package from nuget
PM> Install-Package ILMerge.MSBuild.Tasks
2) Edit the *.csproj file of the project that you want to merge by adding the code below:
<!-- Code to merge the assemblies into one:setup.exe -->
<UsingTask TaskName="ILMerge.MSBuild.Tasks.ILMerge" AssemblyFile="$(SolutionDir)\packages\ILMerge.MSBuild.Tasks.1.0.0.3\tools\ILMerge.MSBuild.Tasks.dll" />
<Target Name="AfterBuild">
<ItemGroup>
<MergeAsm Include="$(OutputPath)$(TargetFileName)" />
<MergeAsm Include="$(OutputPath)LIB1_To_MERGE.dll" />
<MergeAsm Include="$(OutputPath)LIB2_To_MERGE.dll" />
</ItemGroup>
<PropertyGroup>
<MergedAssembly>$(ProjectDir)$(OutDir)MERGED_ASSEMBLY_NAME.exe</MergedAssembly>
</PropertyGroup>
<Message Text="ILMerge #(MergeAsm) -> $(MergedAssembly)" Importance="high" />
<ILMerge InputAssemblies="#(MergeAsm)" OutputFile="$(MergedAssembly)" TargetKind="SameAsPrimaryAssembly" />
</Target>
3) Build your project as usual.
Some more information that might be useful to some people implementing Scott Hanselman's solution.
When I first set this up it would complain about not being able to resolve references to System.Core, etc.
It is something to do with .NET 4 support. Including a /lib argument pointing to the .NET 4 Framework directory fixes it (in fact just include the $(MSBuildBinPath)).
/lib:$(MSBuildBinPath)
I then found that IlMerge would hang while merging. It was using a bit of CPU and a lot of RAM but wasn't outputting anything. I found the fix on stackoverflow of course.
/targetplatform:v4
I also found that some of the MSBuild properties used in Scott's blog article relied on executing MsBuild from the project's directory, so I tweaked them a bit.
I then moved the targets & ilmerge.exe to the tools folder of our source tree which required another small tweak to the paths...
I finally ended up with the following Exec element to replace the one in Scott's original article:
<Exec Command=""$(MSBuildThisFileDirectory)Ilmerge.exe" /lib:$(MSBuildBinPath) /targetplatform:v4 /out:#(MainAssembly) "$(MSBuildProjectDirectory)\#(IntermediateAssembly)" #(IlmergeAssemblies->'"%(FullPath)"', ' ')" />
UPDATE
I also found Logic Labs answer about keeping the CopyLocal behaviour and just excluding ilMerged assemblies from CopyLocal essential if you are using Nuget packages. Otherwise you need to specify a /lib argument for each package directory of referenced assemblies that aren't being merged.
The article Mixing Languages in a Single Assembly in Visual Studio seamlessly with ILMerge and MSBuild at http://www.hanselman.com/blog/MixingLanguagesInASingleAssemblyInVisualStudioSeamlesslyWithILMergeAndMSBuild.aspx demonstrates how to use ILMerge and MSBuild within a Visual Studio Project.
One issue I found with the article at: http://www.hanselman.com/blog/MixingLanguagesInASingleAssemblyInVisualStudioSeamlesslyWithILMergeAndMSBuild.aspx.
If you have any references that you do not wish to ILMerge then the code in the article fails because it overrides the default CopyLocal behaviour to do nothing.
To fix this - Instead of:
<Target Name="_CopyFilesMarkedCopyLocal"/>
Add this entry to the targets file instead (.NET 3.5 only) (to filter out the non-ilmerge copylocal files, and treat them as normal)
<Target Name="AfterResolveReferences">
<Message Text="Filtering out ilmerge assemblies from ReferenceCopyLocalPaths" Importance="High" />
<ItemGroup>
<ReferenceCopyLocalPaths Remove="#(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.IlMerge)'=='true'" />
</ItemGroup>
</Target>
This is a great article that will show you how to merge your referenced assemblies into the output assembly. It shows exactly how to merge assemblies using msbuild.
My 2 cents - I picked up #Jason's response and made it work for my solution where I wanted to generate the *.exe in the bin/Debug folder with all *.dlls inside the same folder.
<Exec Command=""$(SolutionDir)packages\ILMerge.2.13.0307\Ilmerge.exe" /wildcards /out:"$(SolutionDir)..\$(TargetFileName)" "$(TargetPath)" $(OutDir)*.dll" />
Note: This solution is obviously hardcoded into the ILMerge nuget package version. Please let me know if you have some suggestions to improve.
Edit the *.csproj file of the project that you want to merge by adding the code below:
<Target Name="AfterBuild" Condition=" '$(ConfigurationName)' == 'Release' " BeforeTargets="PostBuildEvent">
<CreateItem Include="#(ReferenceCopyLocalPaths)" Condition="'%(Extension)'=='.dll'">
<Output ItemName="AssembliesToMerge" TaskParameter="Include" />
</CreateItem>
<Exec Command=""$(SolutionDir)packages\ILMerge.3.0.29\tools\net452\ILMerge.exe" /internalize:"$(MSBuildProjectPath)ilmerge.exclude" /ndebug /out:#(MainAssembly) "#(IntermediateAssembly)" #(AssembliesToMerge->'"%(FullPath)"', ' ')" />
<Delete Files="#(ReferenceCopyLocalPaths->'$(OutDir)%(DestinationSubDirectory)%(Filename)%(Extension)')" />
</Target>
Notes:
Replace $(SolutionDir)packages\ILMerge.3.0.29\tools\net452\ILMerge.exe with whatever path you have the ILMerge.exe in.
You can remove the Condition in the target to also merge on Debug but then the Debugger might not work
If you are not excluding anything you can remove: /internalize:"$(MSBuildProjectPath)ilmerge.exclude"
Check out this article by Jomo. He has a quick process to hack ILMerge into the msbuild system
http://blogs.msdn.com/jomo_fisher/archive/2006/03/05/544144.aspx

Resources