Properties

This section documents MSBuild 18.9, the .NET SDK 10.0.402 (with its dotnet CLI) and NuGet 7.9, as published in the MSBuild, .NET and NuGet documentation on learn.microsoft.com, which is the reference these pages are written and verified against. Material about .NET 11 / NuGet 8.0 is still in preview and is always flagged as such — never presented as baseline.

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

MSBuildProjectDirectory, MSBuildProjectFile, MSBuildProjectName, MSBuildProjectExtension, MSBuildProjectFullPath, MSBuildProjectDirectoryNoRoot

the project being built (no trailing slash on the directory)

MSBuildThisFile, MSBuildThisFileDirectory, MSBuildThisFileFullPath, MSBuildThisFileName, MSBuildThisFileExtension, MSBuildThisFileDirectoryNoRoot

the file that contains the reference — a .props or .targets file when used there (the directory ends with a separator)

MSBuildBinPath, MSBuildToolsPath, MSBuildExtensionsPath, MSBuildSDKsPath, MSBuildToolsVersion

where MSBuild and its extensions are (inside the SDK for dotnet msbuild)

MSBuildVersion, MSBuildSemanticVersion, MSBuildAssemblyVersion, MSBuildFileVersion, MSBuildRuntimeType

the engine version (18.9.15) and runtime (Core for dotnet, Full for msbuild.exe)

MSBuildStartupDirectory, MSBuildNodeCount, MSBuildLastTaskResult, MSBuildProjectDefaultTargets, MSBuildInteractive

the invocation: where MSBuild was started, how many nodes, the last task’s result

MSBuildWarningsAsErrors, MSBuildWarningsAsMessages, MSBuildWarningsNotAsErrors

codes promoted or demoted for this project

MSBuildDisableFeaturesFromVersion, MSBuildUserExtensionsPath, MSBuildOverrideTasksPath, MSBuildFrameworkToolsPath

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.rsp and the parent MSBuild task; use TreatAsLocalProperty where 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 a PropertyGroup inside the target.