Properties
|
This section documents MSBuild 18.9, the .NET SDK 10.0.402 (with its This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The C# Reference bibliography lists the reference material consulted while preparing these pages. |
A property is a named string. Properties configure everything in a build — Configuration, TargetFramework,
OutputPath, Nullable, Version — and pass values between files and targets. They are simple, but five sources can
set them (the project and its imports, the environment, the command line, the registry, tasks during execution) with
different precedence, and the property that "doesn’t work" is almost always one that another source silently overrides.
This page covers them with MSBuild 18.9 (.NET SDK 10.0.402); the SDK’s own properties (TargetFramework,
GenerateDocumentationFile, …) are listed on
SDK MSBuild Properties Reference.
Defining and referencing
A property is an element inside a PropertyGroup; $(Name) reads it. Names are case-insensitive, an undefined
property reads as the empty string (no error), and a property can reference its own previous value to append to it.
One project shows most of the rules — with an import, an environment variable, TreatAsLocalProperty, XML in a value
and a property created by a task:
<Project TreatAsLocalProperty="Region">
<Import Project="build/ledger.props" />
<PropertyGroup>
<Currency>EUR</Currency>
<!-- A property can reference itself to append. -->
<Accounts>1000-Cash</Accounts>
<Accounts>$(Accounts);3000-Equity</Accounts>
<!-- Environment variables are properties; the project can override them. -->
<ReportTitle Condition="'$(LEDGERLINE_TITLE)' != ''">$(LEDGERLINE_TITLE)</ReportTitle>
<ReportTitle Condition="'$(ReportTitle)' == ''">Trial balance</ReportTitle>
<!-- TreatAsLocalProperty: a -p:Region=... cannot override this assignment. -->
<Region>EU</Region>
<!-- XML can be stored in a property. -->
<Footer><b>Ledgerline</b> report</Footer>
</PropertyGroup>
<Target Name="Show">
<!-- A property created during execution, from a task output. -->
<MakeDir Directories="$(MSBuildProjectDirectory)/out">
<Output TaskParameter="DirectoriesCreated" PropertyName="CreatedDir" />
</MakeDir>
<Message Importance="high" Text="Currency=$(Currency) Accounts=$(Accounts) Region=$(Region)" />
<Message Importance="high" Text="ReportTitle=$(ReportTitle) Footer=$(Footer)" />
<Message Importance="high" Text="MSBuildProjectName=$(MSBuildProjectName) MSBuildProjectFile=$(MSBuildProjectFile)" />
<Message Importance="high" Text="LedgerPropsFolder=$(LedgerPropsFolder)" />
<Message Importance="high" Text="LedgerProjectFolder=$(LedgerProjectFolder)" />
<Message Importance="high" Text="CreatedDir=$(CreatedDir)" />
</Target>
</Project>
build/ledger.props records two reserved properties as seen from inside the imported file:
<Project>
<PropertyGroup>
<!-- Reserved properties describe the file being evaluated. -->
<LedgerPropsFolder>$(MSBuildThisFileDirectory)</LedgerPropsFolder>
<LedgerProjectFolder>$(MSBuildProjectDirectory)</LedgerProjectFolder>
</PropertyGroup>
</Project>
dotnet msbuild props.proj -t:Show -nologo -v:m
LEDGERLINE_TITLE="Q1 balance" dotnet msbuild props.proj -t:Show -nologo -v:m -p:Currency=USD -p:Region=US
Currency=EUR Accounts=1000-Cash;3000-Equity Region=EU
ReportTitle=Trial balance Footer=<b>Ledgerline</b> report
MSBuildProjectName=props MSBuildProjectFile=props.proj
LedgerPropsFolder=~/props/build/
LedgerProjectFolder=~/props
CreatedDir=~/props/out
---
Currency=USD Accounts=1000-Cash;3000-Equity Region=EU
ReportTitle=Q1 balance Footer=<b>Ledgerline</b> report
(The --- separates the two runs; the second shows only its first two lines.) -p:Currency=USD overrode the project’s
EUR; -p:Region=US did not override EU, because Region is in TreatAsLocalProperty; and the environment
variable won only because the project chose to read it. MSBuildThisFileDirectory is the folder of the file that
contains the reference (build/), whereas MSBuildProjectDirectory is always the project’s folder — which is why
imported files use the former to find their neighbours.
Reserved and well-known properties
MSBuild defines a set of read-only properties for every project. They describe the project file, the file currently being evaluated, MSBuild itself and the build:
| Property | Value |
|---|---|
|
the project being built (no trailing slash on the directory) |
|
the file that contains the reference — a |
|
where MSBuild and its extensions are (inside the SDK for |
|
the engine version ( |
|
the invocation: where MSBuild was started, how many nodes, the last task’s result |
|
codes promoted or demoted for this project |
|
change waves and extension locations |
They cannot be assigned:
<Project>
<PropertyGroup>
<MSBuildProjectName>Ledger</MSBuildProjectName>
</PropertyGroup>
<Target Name="T" />
</Project>
~/props/reserved.proj(3,5): error MSB4004: The "MSBuildProjectName" property is reserved, and cannot be modified.
Environment and registry properties
Every environment variable with a valid property name becomes a property when the project is first evaluated:
$(PATH), $(HOME), $(GITHUB_ACTIONS), $(TF_BUILD). A property defined in the project with the same name
overrides it, and the environment is read once: later changes to the process environment are not seen by
properties. The documentation recommends passing values to spawned tools through a task’s EnvironmentVariables
parameter rather than relying on the inherited environment, and $([System.Environment]::GetEnvironmentVariable(...))
when the current value is really needed.
Registry properties, $(registry:HKEY_LOCAL_MACHINE\Software\…@Value), read the Windows registry. They work only in
msbuild.exe; the .NET SDK’s MSBuild does not support them, so they are not run here.
Global and local properties
A global property is set from outside the project: -p:Name=Value (--property: in the dotnet form), a
property passed to a child project by the MSBuild task’s Properties parameter, or one set by Visual Studio
(Configuration, Platform, DesignTimeBuild, …). Global properties override every assignment in the project and
its imports, environment properties included, and are forwarded to child projects (unless the parent lists them in
RemoveProperties). Reserved properties cannot be set even globally.
That is usually what you want (-p:Configuration=Release) and occasionally a trap: -p:Version=2.0.0 on a solution
build stamps every project, -p:TargetFramework=net8.0 forces a single TFM on projects that do not support it, and a
property that a .targets file computes from others is frozen at the command-line value. A project protects a property
with TreatAsLocalProperty="Name1;Name2" on its Project element: within that file and its later imports, assignments
win over the command line, as Region did above. Local properties are not forwarded to child projects; children receive
the original global value.
Properties created during execution
Outside targets, properties are set during evaluation. During execution they can be created or changed in three ways:
a PropertyGroup inside a target (evaluated when the target runs), a task Output element with PropertyName (as
CreatedDir above), and the CreateProperty task, which the documentation marks as deprecated in favour of the
first two. A property set inside a target is visible to targets that run after it, never to evaluation-time logic, and
setting it does not re-evaluate the project.
Reading a property from outside
-getProperty (MSBuild 17.8+) prints evaluated values without building. With one property the output is the bare value,
convenient in scripts; with several it is JSON:
dotnet msbuild props.proj -getProperty:Currency
dotnet msbuild props.proj -getProperty:Currency -getProperty:Region -p:Currency=USD
EUR
{
"Properties": {
"Currency": "USD",
"Region": "EU"
}
}
Without -t: the values are those of evaluation; with -t: they are the values after the targets have run. The same
switches work on dotnet build. For an SDK project, dotnet msbuild -getProperty:TargetPath or
-getProperty:OutputPath is the reliable way for scripts to find outputs.
The common properties
The properties that the common targets and the SDK read — AssemblyName, RootNamespace, OutputType,
Configuration, Platform, OutputPath, BaseIntermediateOutputPath, DefineConstants, TreatWarningsAsErrors,
NoWarn, WarningsAsErrors, DocumentationFile, LangVersion, … — are documented on two pages: the MSBuild
"common project properties" list (shared by all project types) and the .NET SDK property reference. C#-specific
reserved properties (Language, TargetFrameworkVersion in non-SDK projects) are on the reserved-properties page.
Common mistakes
-
Reading a property before it is defined (outside targets) — it is empty; nothing warns.
-
A global property overriding the project — check
-p:switches,Directory.Build.rspand the parentMSBuildtask; useTreatAsLocalPropertywhere needed. -
Assuming an environment variable wins over the project — it is the other way round.
-
$(MSBuildProjectDirectory)in an imported file — it is the importing project’s folder; use$(MSBuildThisFileDirectory)for paths next to the import. -
Registry properties in a
dotnet build— unsupported. -
CreateProperty— deprecated; use aPropertyGroupinside the target.