Extending the build process with MSBuild
The purpose of the article is to tell a little about MSBuild, to show what are the targets and tasks in MSBuild, to teach how to work with the .csproj file, to give useful links. If you have a more appropriate title for the article, I will be glad to discuss in the comments.
MSBuild is designed in such a way that the assembly of the project is divided into several stages.
Target is a certain stage (event) that occurs during the assembly of the project. You can use standard targets, or define your own.
Task is some task that can be executed at a certain stage. You can use the standard task or create your own.
Quote from the documentation of the target ( https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-targets ):
For MSBuild, Microsoft has defined a number of standard targets (in the files Microsoft.Common.targets, Microsoft.CSharp.targets, etc.). A great number of various targets have been defined, but in this article we will not dwell on this in detail. Some standard targets (ordered):
Targets BeforeBuild and AfterBuild are specifically designed for overriding and can be used. I do not recommend using the remaining targets from the list so that nothing breaks .
For a more detailed view of the list of targets, you can use the / pp: option . Thanks to this parameter, a file will be generated in which all imports will be included (including .targets files). You can find a lot of targets and variables in it (thanks to aikixd for the hint).
For examples you need:
In all examples of this article, you will need to edit the MSBuildExample.csproj file. Each example involves removing the code from the previous example and adding a new one. The code must be added to the end of the .csproj file to the last line containing the closing Project tag.

Attention! The case is important in the .csproj file .
To run the sample, you must run build in the Visual Studio development environment. For some examples, you will need to choose a solution configuration.

The result will be displayed in the Output window in Visual Studio (below). If not, then open it through the menu items View => Output.

For examples we will use Task Message , which will display information in the Output window in Visual Studio. As mentioned earlier, there are standard BeforeBuild and AfterBuild targets, we will use them. Read about the preparation in the section Preparing the environment for examples .
As you can see, the task Message was executed , which displayed the text we specified at the time of BeforeBuild and AfterBuild in the Output window in Visual Studio.
When defining a target with the same name, it is overwritten!
Only the second message was displayed, because they used the target with the same name and it was overwritten by the second value.
If BeforeBuild and AfterBuild are not enough, or if the task is to be performed at a different stage of the build life cycle, then you can define your own target. For these purposes there are BeforeTargets and AfterTargets parameters.
It was defined two own target - MyCustomBeforeTarget and MyCustomAfterTarget.
The MyCustomBeforeTarget target is executed before the BeforeBuild target, because we specified:
MyCustomAfterTarget target is executed after BeforeBuild target, because we specified:
This article does not cover how to write your own tasks, but before you write a task, review the list of tasks provided by Microsoft .
Consider a few examples of using task and macros.
A number of standard macros can be used in the .csproj file, a list of them can be found here https://msdn.microsoft.com/en-us/library/c02as0cs.aspx and here https://msdn.microsoft.com/en-us/ library / bb629394.aspx . Consider some useful macros:
To define your own parameters use PropertyGroup . An example of defining your own variable can be found in the task section in MSBuild .
Menu
- Basic concepts - what is target and task in MSBuild
- MSBuild assembly life cycle - what sequence of calls to target
- Preparing the environment for examples
- MSBuild Targets - An Example of Using Some MSBuild Targets
- Create your own MSBuild Target
- MSBuild Tasks - An Example of Using Some MSBuild Tasks
- Variables and Macros in .csproj
- Links
Basic Concepts ( Menu )
MSBuild is designed in such a way that the assembly of the project is divided into several stages.
Target is a certain stage (event) that occurs during the assembly of the project. You can use standard targets, or define your own.
Task is some task that can be executed at a certain stage. You can use the standard task or create your own.
Quote from the documentation of the target ( https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-targets ):
The Targets group of tasks together.
For example, the output directory for the output
directory.
MSBuild assembly life cycle ( Menu )
For MSBuild, Microsoft has defined a number of standard targets (in the files Microsoft.Common.targets, Microsoft.CSharp.targets, etc.). A great number of various targets have been defined, but in this article we will not dwell on this in detail. Some standard targets (ordered):
Target list (spoiler)
- BeforeRebuild
- Clean
- BeforeBuild
- BuildOnlySettings
- PrepareForBuild
- PreBuildEvent
- ResolveReferences
- PrepareResources
- ResolveKeySource
- Compile
- UnmanagedUnregistration
- GenerateSerializationAssemblies
- CreateSatelliteAssemblies
- GenerateManifests
- GetTargetPath
- PrepareForRun
- UnmanagedRegistration
- IncrementalClean
- Postbuildevent
- Afterbuild
- AfterRebuild
Targets BeforeBuild and AfterBuild are specifically designed for overriding and can be used. I do not recommend using the remaining targets from the list so that nothing breaks .
For a more detailed view of the list of targets, you can use the / pp: option . Thanks to this parameter, a file will be generated in which all imports will be included (including .targets files). You can find a lot of targets and variables in it (thanks to aikixd for the hint).
Preparing the environment for examples ( Menu )
For examples you need:
- Installed Visual Studio Development Environment
- Create a project of type Console Application named MSBuildExample
- Open the project folder and find the MSBuildExample.csproj file there
- Open MSBuildExample.csproj file in Notepad or another editor.
In all examples of this article, you will need to edit the MSBuildExample.csproj file. Each example involves removing the code from the previous example and adding a new one. The code must be added to the end of the .csproj file to the last line containing the closing Project tag.

Attention! The case is important in the .csproj file .
To run the sample, you must run build in the Visual Studio development environment. For some examples, you will need to choose a solution configuration.

The result will be displayed in the Output window in Visual Studio (below). If not, then open it through the menu items View => Output.

Targets in MSBuild ( Menu )
For examples we will use Task Message , which will display information in the Output window in Visual Studio. As mentioned earlier, there are standard BeforeBuild and AfterBuild targets, we will use them. Read about the preparation in the section Preparing the environment for examples .
Example of using target (spoiler)
Код примера:
Результат выполнения (лишнее исключено):
<TargetName="AfterBuild"><MessageText="AfterBuild event"Importance="high"></Message></Target><TargetName="BeforeBuild"><MessageText="BeforeBuild event"Importance="high"></Message></Target>
Результат выполнения (лишнее исключено):
…
BeforeBuild event
…
AfterBuild event
…
As you can see, the task Message was executed , which displayed the text we specified at the time of BeforeBuild and AfterBuild in the Output window in Visual Studio.
When defining a target with the same name, it is overwritten!
Example of rewriting target (spoiler)
Код примера:
Результат выполнения (лишнее исключено):
<TargetName="BeforeBuild"><MessageText="First message"Importance="high"></Message></Target><TargetName="BeforeBuild"><MessageText="Second message"Importance="high"></Message></Target>
Результат выполнения (лишнее исключено):
…
Second message
…
Only the second message was displayed, because they used the target with the same name and it was overwritten by the second value.
Creating your own MSBuild target ( Menu )
If BeforeBuild and AfterBuild are not enough, or if the task is to be performed at a different stage of the build life cycle, then you can define your own target. For these purposes there are BeforeTargets and AfterTargets parameters.
An example of determining your own target (spoiler)
Код примера:
Результат выполнения (лишнее исключено):
<TargetName="BeforeBuild"><MessageText="BeforeBuild event"Importance="high"></Message></Target><TargetName="MyCustomBeforeTarget"BeforeTargets="BeforeBuild"><MessageText="MyCustomBeforeTarget event"Importance="high"></Message></Target><TargetName="MyCustomAfterTarget"AfterTargets="BeforeBuild"><MessageText="MyCustomAfterTarget event"Importance="high"></Message></Target>
Результат выполнения (лишнее исключено):
…
MyCustomBeforeTarget event
BeforeBuild event
MyCustomAfterTarget event
…
It was defined two own target - MyCustomBeforeTarget and MyCustomAfterTarget.
The MyCustomBeforeTarget target is executed before the BeforeBuild target, because we specified:
BeforeTargets="BeforeBuild"
MyCustomAfterTarget target is executed after BeforeBuild target, because we specified:
AfterTargets="BeforeBuild"
MSBuild Tasks ( Menu )
This article does not cover how to write your own tasks, but before you write a task, review the list of tasks provided by Microsoft .
Consider a few examples of using task and macros.
Condition parameter (spoiler)
Параметр Condition присутствует у всех тасков. Цитата из документации docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-task-reference:
Если будет выбрана solution конфигурация Debug, то результат будет выглядеть так (лишнее исключено):
Информацию о синтаксисе условий можно прочитать там https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-conditions
A Boolean expression that the MSBuild engine uses to determine whether this task will be executed.Код примера:
<TargetName="BeforeBuild"><MessageText="Current configuration is Debug"Condition="'$(Configuration)' == 'Debug'"Importance="high"></Message><MessageText="Current configuration is Release"Condition="'$(Configuration)' == 'Release'"Importance="high"></Message></Target>
Если будет выбрана solution конфигурация Debug, то результат будет выглядеть так (лишнее исключено):
…Если будет выбрана solution конфигурация Release, то результат будет выглядеть так (лишнее исключено):
Current configuration is Debug
...
…Информацию о макросе $(Configuration) и других макросах можете найти в разделе переменные и макросы в .csproj.
Current configuration is Release
…
Информацию о синтаксисе условий можно прочитать там https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-conditions
Variable definition in csproj (spoiler)
В примере выше у нас есть текст, который можно унифицировать. Чтобы исключить дублирование вынесем в отдельную переменную текст сообщения.
Код примера:
Для определения собственной переменной используется элемент PropertyGroup.
Код примера:
<PropertyGroup><MessageText>Current configuration is $(Configuration)</MessageText></PropertyGroup><TargetName="BeforeBuild"><MessageText="$(MessageText)"Condition="'$(Configuration)' == 'Debug'"Importance="high"></Message><MessageText="$(MessageText)"Condition="'$(Configuration)' == 'Release'"Importance="high"></Message></Target>
Для определения собственной переменной используется элемент PropertyGroup.
Check for file existence, error output (spoiler)
В данном примере сделаем таск, который проверяет создан ли файл App.Debug.config. Если он не создан, то выдаем ошибку. В случае ошибки билд будет остановлен и ошибка будет отображена как ошибки компиляции в окне Error List.
Используем для этого таск Error и уже знакомый нам параметр Condition.
Код примера:
Результат:

В условии Exists используется относительный путь от папки, в которой находится файл .csproj. Для обращения к папке выше текущей использовать '../'. Если нужно обратиться к вложенной папке, то использовать формат '[DirectoryName]/App.Debug.config'.
Используем для этого таск Error и уже знакомый нам параметр Condition.
Код примера:
<TargetName="BeforeBuild"><ErrorCondition="!Exists('App.Debug.config')"Text="File App.Debug.config not found"></Error></Target>
Результат:

В условии Exists используется относительный путь от папки, в которой находится файл .csproj. Для обращения к папке выше текущей использовать '../'. Если нужно обратиться к вложенной папке, то использовать формат '[DirectoryName]/App.Debug.config'.
Copying files (spoiler)
В данном примере будем использовать таск Copy. С помощью таска скопируем файл App.config в папку bin/[Configuration]/Config в два файла App.config и App.test.config.
Код примера:
Свойство SourceFiles — массив файлов, которые необходимо скачать. Указывать без кавычек, через точку с запятой.
Свойство DestinationFiles — массив файлов куда будут копироваться файлы. Указывать без кавычек, через точку с запятой.
Подробнее о макросе $(OutputPath) читать в разделе переменные и макросы в .csproj.
Код примера:
<TargetName="BeforeBuild"><CopySourceFiles="App.config;App.config"DestinationFiles="$(OutputPath)/Test/App.config;$(OutputPath)/Test/App.test.config"></Copy></Target>
Свойство SourceFiles — массив файлов, которые необходимо скачать. Указывать без кавычек, через точку с запятой.
Свойство DestinationFiles — массив файлов куда будут копироваться файлы. Указывать без кавычек, через точку с запятой.
Подробнее о макросе $(OutputPath) читать в разделе переменные и макросы в .csproj.
Variables and macros in .csproj ( Menu )
A number of standard macros can be used in the .csproj file, a list of them can be found here https://msdn.microsoft.com/en-us/library/c02as0cs.aspx and here https://msdn.microsoft.com/en-us/ library / bb629394.aspx . Consider some useful macros:
- $ (MSBuildToolsPath) - points to the path to the MSBuild folder. For example, C: \ Program Files (x86) \ MSBuild \ 14.0 \ Bin. This macro is used with a slash when combining the path. For example, $ (MSBuildToolsPath) \ Microsoft.Web.Publishing.Tasks.dll. Otherwise, it may incorrectly form the path and give an error that the file was not found.
- $ (OutputPath) - relative path to the output folder. For example, bin \ Stage. Use this macro with a slash, for example, $ (OutputPath) $ (TargetFileName) .config.
- $ (TargetFileName) - the name of the output file along with the extension. For example, MSBuildExample.exe. The extension and format of the output file name may differ from different types of projects. Using this macro, you can safely determine what will be the name of the config file. It can be useful for the transformations of configs.
- $ (Configuration) - the name of the current configuration. For example, Release, Debug
- $ (IntermediateOutputPath) - path to the obj folder. For example, obj \ Stage.
To define your own parameters use PropertyGroup . An example of defining your own variable can be found in the task section in MSBuild .
Links ( Menu )
- documentation on targeting
- documentation of tasks
- list of tasks provided by Microsoft
- conditions
- macro information
- variable information
- build process information
- order execution
- information about target files
- some more information about the target and build process. There are also links to a series of articles about transformations.