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

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

Related

How should I include Package References in a VSIX project

My solution creates a Visual Studio Package from multiple projects, using multiple NuGet packages.
All of the Nuget packages are specified in the project files using PackageReference (rather than the older packages.config file). I am using Visual Studio 2019.
I have had a problem, that the DLLs referenced by NuGet Packages are not included in the VSIX installation.
There is a solution to this problem, described in this article by Daniel Cazzulino, by adding the following code to the project file:
<PropertyGroup>
<GetVsixSourceItemsDependsOn>$(GetVsixSourceItemsDependsOn);IncludeNuGetResolvedAssets</GetVsixSourceItemsDependsOn>
</PropertyGroup>
<Target Name="IncludeNuGetResolvedAssets" DependsOnTargets="ResolveNuGetPackageAssets">
<ItemGroup>
<VSIXCopyLocalReferenceSourceItem Include="#(ReferenceCopyLocalPaths)" />
</ItemGroup>
</Target>
This does work, but it blows up the size of the installation from about 20MB to about 40MB.
The installation now includes a lot of PDB files, which I don't really need.
More significantly, it brings in about 46MB of Visual Studio DLLs which are not necessary, because they are part of Visual Studio.
Is there a better way to ensure that the referenced NuGet packages are included in the VSIX, without inflating the installation with these other files?
You can use a simple script like this:
<Target Name="IncludeNuGetPackageReferences" AfterTargets="GetVsixSourceItems">
<ItemGroup>
<VSIXSourceItem Include="#(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.NuGetPackageId)' == 'Newtonsoft.Json'" />
<VSIXSourceItem Include="#(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.NuGetPackageId)' == 'xxx'" />
... </ItemGroup>
</Target>
You can specify what assemblies should be included into .vsix . And it won't copy the unnecessary VS assemblies after tests in my machine. Hint from smourier, thanks to him.
Hope it 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.

SlowCheetah executes after post-build events

I use SlowCheetah to transform my app.configs. I have a multi-project solution where one of the projects executes a post-build event where the output of the bin is copied elsewhere. I've found that SlowCheetah does it's transforms after the post-build event, so the app.config I'm copying is the pre-transformed version.
Does anyone have a suggestion of how I can execute my copy after the SlowCheetah transforms? Is this going to require that I write a custom build task?
If you are using msbuild 4.0 for building your projects - you can hook to slowcheetah targets with new AfterTargets BeforeTargets attributes.
I dont know what exactly target name you want to hook after but this code could gave you base concept how to do this
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Some_Target_Name" AfterTargets="TransformAllFiles" >
<Message Text="= Script here will run after SlowCheetah TransformAllFiles ="/>
</Target>
<Project>
Edited: I installed SlowCheetah and found that AfterTargets attribute should be "TransformAllFiles".
Just set up your target dependency AfterTargets="TransformAllFiles"
Alexey's answer leads to the correct solution but here it is in full:
Right-click your project and select Unload Project
Now right-click the project and select Edit [your project name].csproj
Scroll to the bottom and uncomment the target named AfterBuild and add this attribute AfterTargets="TransformAllFiles"
Move your post build actions into this target using the Exec command:
An example:
<Target Name="AfterBuild" AfterTargets="TransformAllFiles">
<Exec Command="ECHO Hello PostBuild World!" />
</Target>
I have bumped into this problem too... decided to update to latest version of SlowCheetah (current 2.5.8), and this problem appears to have been fixed! No more problems using post-build events to deploy a project with transformed XML!
After the NuGet package upgrade process, I had a strange issue, though... transforms were no longer happening. Editing the project like Naeem Sarfraz suggested, I have found that the SlowCheetah's PropertyGroup section was placed at the end of the .csproj.
It was just a matter of moving it to the top, near the other PropertyGroup sections, and now it works like a charm!
If you need to copy/move other .config files (other than web.config) around after the build before publishing here is how it can be done with Visual Studio 2013 (I didn't test it on earlier versions). This section can be added at the end of the .csproj file right before the closing tag </Project> and it'll be fired just before MSDeploy starts the Publishing process.
<Target Name="MoveConfigFile" BeforeTargets="MSDeployPublish">
<Move
SourceFiles="$(IntermediateOutputPath)Package\PackageTmp\ThirdPartyApp.config"
DestinationFolder="$(IntermediateOutputPath)Package\PackageTmp\bin"
OverwriteReadOnlyFiles="true"
/>
</Target>
The company I work for purchased a third party product that needs to have a .config files in the bin folder along with its assembly in order to work.
At the same time we need to process the product's .config file and be able to move it to the bin folder after transformations.
The $(IntermediateOutputPath)Package\PackageTmp folder contains the whole application that will be copied over the target server.

How can I exclude a project from a build in MSBuild?

I need to build a solution, but exclude one project. How should I do it?
I searched a lot about this issue, but nothing could help.
An ItemGroup section rises the following exception:
Invalid element . Unknown task or datatype.
PropertyGroup also rises the exception.
Below is my code sample:
<project name="TI 8.1.6 build script">
<ItemGroup>
<Solution Include="${ROOT}\Core\TI Core.sln" Exclude="${ROOT}\Utilities\DTS Indexing Service\Tdi.Origami.IndexUpdaterServiceSetup\Tdi.Origami.IndexUpdaterServiceSetup.wixproj"/>
</ItemGroup>
...
</project>
How can I do this?
You can exclude projects at the solution level for a specific build configuration by using the Configuration Manager Dialog in Visual Studio:
Then you can simply invoke msbuild on the solution file specifying the build configuration to use:
msbuild /property:Configuration=Release MySolution.sln
The solution suggested by Enrico is the most versatile solution that would work always. An alternative solution might be to use a <MSBuild> task directly. This will work for you if you have all your project files under a particular directory, or be able to easily enumerate all projects you want to build (i.e. number of projects in your solution is not very big).
For example, this MSBuild file will build every project under your current directory except for a specific project:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<MyProjectReferences Include="**\*.*proj" />
<MyProjectReferences Exclude="Utilities\DTS Indexing Service\Tdi.Origami.IndexUpdaterServiceSetup\Tdi.Origami.IndexUpdaterServiceSetup.wixproj" />
</ItemGroup>
<Target Name="BuildAllExceptWixProject">
<MSBuild Projects="#(MyProjectReferences)" Targets="Build" />
</Target>
</Project>
Then you can build that using command line msbuild <myproject> /t:BuildAllExceptWixProject
In your solution file (.sln), remove the Build.0 entries. For example:
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MyProject", "MyProject.vcxproj", "{2281D9E7-5261-433D-BB04-176A61500CA3}"
EndProject
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2281D9E7-5261-433D-BB04-176A61500CA3}.Debug|x86.Build.0 = Debug|x64
If you delete this "Build.0" entry, it will load in the solution fine, but will not be built, either through the GUI or via external MSBuild.
Since VS 2019 and MSBuild 16.7, the right way is to use Solution filters. Ref
create a master.proj file:
in another ItemGroup add DefaultExclude properties for programs - put it in front of the solution
-- BA was Canadian
Configuration=Release
Release
drop the master.proj into the directory with the programs and msbuild the master.proj
compiles everything except... that HelloWorld

VS2010 Web Publish command line version of File System deploy

Folks,
In a nutshell, I want to replicate this dialog:
It's a Visual Studio 2010 ASP.Net MVC project. If I execute this command, I get all the files I want, including the transformed web.configs in the "C:\ToDeploy" directory.
I want to replicate this on the command line so I can use it for a QA environment build.
I've seen various articles on how to do this on the command line for Remote Deploys, but I just want to do it for File System deploys.
I know I could replicate this functionality using nAnt tasks or rake scripts, but I want to do it using this mechanism so I'm not repeating myself.
I've investigated this some more, and I've found these links, but none of them solve it cleanly:
VS 2008 version, but no Web.Config transforms
Creates package, but doesn't deploy it..do I need to use MSDeploy on this package?
Deploys package after creating it above...does the UI really do this 2 step tango?
Thanks in advance!
Ok, finally figured this out.
The command line you need is:
msbuild path/to/your/webdirectory/YourWeb.csproj /p:Configuration=Debug;DeployOnBuild=True;PackageAsSingleFile=False
You can change where the project outputs to by adding a property of outdir=c:\wherever\ in the /p: section.
This will create the output at:
path/to/your/webdirectory/obj/Debug/Package/PackageTmp/
You can then copy those files from the above directory using whatever method you'd like.
I've got this all working as a ruby rake task using Albacore. I am trying to get it all done so I can actually put it as a contribution to the project. But if anyone wants the code before that, let me know.
Another wrinkle I found was that it was putting in Tokenized Parameters into the Web.config. If you don't need that feature, make sure you add:
/p:AutoParameterizationWebConfigConnectionStrings=false
I thought I'd post a another solution that I found, I've updated this solution to include a log file.
This is similar to Publish a Web Application from the Command Line, but just cleaned up and added log file. also check out original source http://www.digitallycreated.net/Blog/59/locally-publishing-a-vs2010-asp.net-web-application-using-msbuild
Create an MSBuild_publish_site.bat (name it whatever) in the root of your web application project
set msBuildDir=%WINDIR%\Microsoft.NET\Framework\v4.0.30319
set destPath=C:\Publish\MyWebBasedApp\
:: clear existing publish folder
RD /S /Q "%destPath%"
call %msBuildDir%\msbuild.exe MyWebBasedApp.csproj "/p:Configuration=Debug;PublishDestination=%destPath%;AutoParameterizationWebConfigConnectionStrings=False" /t:PublishToFileSystem /l:FileLogger,Microsoft.Build.Engine;logfile=Manual_MSBuild_Publish_LOG.log
set msBuildDir=
set destPath=
Update your Web Application project file MyWebBasedApp.csproj by adding the following xml under the <Import Project= tag
<Target Name="PublishToFileSystem" DependsOnTargets="PipelinePreDeployCopyAllFilesToOneFolder">
<Error Condition="'$(PublishDestination)'==''" Text="The PublishDestination property must be set to the intended publishing destination." />
<MakeDir Condition="!Exists($(PublishDestination))" Directories="$(PublishDestination)" />
<ItemGroup>
<PublishFiles Include="$(_PackageTempDir)\**\*.*" />
</ItemGroup>
<Copy SourceFiles="#(PublishFiles)" DestinationFiles="#(PublishFiles->'$(PublishDestination)\%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="True" />
</Target>
this works better for me than other solutions.
Check out the following for more info:
1) http://www.digitallycreated.net/Blog/59/locally-publishing-a-vs2010-asp.net-web-application-using-msbuild
2) Publish a Web Application from the Command Line
3) Build Visual Studio project through the command line
My solution for CCNET with the Web.config transformation:
<tasks>
<msbuild>
<executable>C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe</executable>
<workingDirectory>E:\VersionesCC\Trunk_4\SBatz\Gertakariak_Orokorrak\GertakariakMS\Web</workingDirectory>
<projectFile>GertakariakMSWeb2.vbproj</projectFile>
<targets>Build</targets>
<timeout>600</timeout>
<logger>C:\Program Files\CruiseControl.NET\server\ThoughtWorks.CruiseControl.MSBuild.dll</logger>
<buildArgs>
/noconsolelogger /p:Configuration=Release /v:diag
/p:DeployOnBuild=true
/p:AutoParameterizationWebConfigConnectionStrings=false
/p:DeployTarget=Package
/p:_PackageTempDir=E:\Aplicaciones\GertakariakMS2\Web
</buildArgs>
</msbuild>
</tasks>
On VS2012 and above, you can refer to existing publish profiles on your project with msbuild 12.0, this would be equivalent to right-click and publish... selecting a publish profile ("MyProfile" on this example):
msbuild C:\myproject\myproject.csproj "/P:DeployOnBuild=True;PublishProfile=MyProfile"
I've got a solution for Visual Studio 2012: https://stackoverflow.com/a/15387814/2164198
However, it works with no Visual Studio installed at all! (see UPDATE).
I didn't checked yet whether one can get all needed stuff from Visual Studio Express 2012 for Web installation.
A complete msbuild file with inspiration from CubanX
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Publish">
<RemoveDir Directories="..\build\Release\Web\"
ContinueOnError="true" />
<MSBuild Projects="TheWebSite.csproj"
Targets="ResolveReferences;_CopyWebApplication"
Properties="Configuration=Release;WebProjectOutputDir=..\build\Release\Web;OutDir=..\build\Release\Web\bin\"
/>
</Target>
<Target
Name="Build"
DependsOnTargets="Publish;">
</Target>
</Project>
This places the published website in the Web..\build\Release folder

Resources