MSBuild
Carriage return line feed
You can escape the carriage return line feed using %0A and %0D respectively.
Display item lists
This will print out the items on their own line:
<Message Text="Items: @(Items, '%0a%0d')" />
Emitting .metaproj from a Visual Studio .sln
When building a Visual Studio solution file (.sln), MSBuild creates a temporary meta MSBuild project file from the proprietary solution format.
You can get dotnet, Visual Studio or MSBuild to emit the .metaproj for you to see the results.
Diagnosing what triggered a build
MSBuild won't rebuild a project if not necessary; usually if the inputs haven't changed, the outputs don't need to be re-generated.
In older versions of MSBuild, you could pass /diag to enable diagnostic logging, and see in the first line what triggered the build.
Optimizations
Never use Copy Always for files in your projects; this will always trigger a build of your project(s). This can cause unexpected side effects in your CI/CD pipeline,
or stop you from debugging in Visual Studio easily without a slow build every time even when your code hasn't changed.
Recent versions of MSBuild may have optimized this issue away but I need to check this in the future.
Parallel builds
By default, MSBuild uses one process to build your project.
You can pass the /m or /maxCpuCount argument to use all CPU cores to build projects in parallel.
You can limit the number of processes by specifying /m:[count] e.g. /m:2 to use 2 processes.
Build Optimization
Never use Copy Always; use Copy If Newer. Copy Always will always force a “build” of that project and its dependent projects. This can do things like preventing quick running or debugging when you haven't changed code. It can also affect CI/CD DevOps; if the build pipeline does several operations, they can trigger multiple builds when you're likely only wanting to run the build once.
Notable Tasks & Targets
Microsoft.Common.CurrentVersion.targets: Targets: CoreResGen, BeforeResGen, AfterResGen Properties: $(CoreResGenDependsOn)
<Target
Name="CoreResGen"
DependsOnTargets="$(CoreResGenDependsOn)">