Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
Guidance for authoring MSBuild project system elements: custom targets with
BeforeTargets/AfterTargets/DependsOnTargets, incremental build with Inputs/Outputs, props vs targets import
ordering, items and item metadata (Include/Exclude/Update/Remove), conditions, property functions, well-known
metadata, and advanced Directory.Build.props/Directory.Build.targets patterns.
Version assumptions: .NET 8.0+ SDK (MSBuild 17.8+). All examples use SDK-style projects.
Scope
Custom targets with BeforeTargets/AfterTargets/DependsOnTargets
Incremental build with Inputs/Outputs
Props vs targets import ordering
Items and item metadata (Include/Exclude/Update/Remove)
Conditions and property functions
Advanced Directory.Build.props/targets patterns
MSBuild well-known metadata and item batching
Out of scope
Solution layout and shared configuration (CPM, .editorconfig) -- see [skill:dotnet-project-structure]
MSBuild error interpretation and CI drift diagnosis -- see [skill:dotnet-build-analysis]
Reading and modifying .csproj files -- see [skill:dotnet-csproj-reading]
Cross-references: [skill:dotnet-project-structure] for solution layout and basic Directory.Build.props structure,
[skill:dotnet-build-analysis] for interpreting MSBuild errors and CI drift.
Custom Targets
Targets are the unit of execution in MSBuild. Each target runs a sequence of tasks and can declare ordering
relationships with other targets.
Defining a Custom Target
<TargetName="PrintBuildInfo"BeforeTargets="Build"><MessageImportance="high"Text="Building $(MSBuildProjectName) v$(Version) for $(TargetFramework)" /></Target>
```text
### Ordering: BeforeTargets, AfterTargets, DependsOnTargets
Three mechanisms control target execution order:
| Mechanism | Effect | Use when |
|---|---|---|
| `BeforeTargets="X"` | Runs this target before `X` | Injecting into an existing pipeline (e.g., run before `Build`) |
| `AfterTargets="X"` | Runs this target after `X` | Post-processing (e.g., copy output after `Publish`) |
| `DependsOnTargets="A;B"` | Ensures `A` and `B` run before this target | Declaring prerequisite targets within your own target graph |
```xml
```text
**Prefer `BeforeTargets`/`AfterTargets` over `DependsOnTargets`** for injecting into the standard build pipeline. `DependsOnTargets` is best for orchestrating your own custom target graph.
### Extending Existing DependsOn Lists
SDK targets expose `*DependsOn` properties for extension. Append your target name rather than replacing the list:
```xml
$(BuildDependsOn);GenerateVersionInfo
```csharp
---
## Incremental Build with Inputs/Outputs
Targets with `Inputs` and `Outputs` only run when outputs are missing or older than inputs. This is critical for build performance.
```xml
```csharp
**How incrementality works:**
1. MSBuild compares timestamps of `Inputs` items against `Outputs` items.
2. If all outputs exist and are newer than all inputs, the target is skipped entirely.
3. If any input is newer than any output, the full target runs.
**Common incrementality failures:**
- **Missing `Outputs`:** Target runs every build. Always pair `Inputs` with `Outputs`.
- **Volatile outputs:** If another target writes to the output path mid-build, timestamps reset and trigger unnecessary rebuilds.
- **Generator side effects:** Code generators that write unconditionally (even when content unchanged) break incrementality. Write to a temp file and copy only if content differs.
- **File copy timestamps:** `Copy` task with `SkipUnchangedFiles="true"` preserves timestamps; without it, every copy updates the timestamp.
---
## Props vs Targets: Import Ordering
MSBuild evaluates project files in a specific order. Understanding this is essential for correct customization.
### Evaluation Order
```text
1. Directory.Build.props (imported by SDK early)
2. (SDK props imported)
3. Explicit in project (your .props imports)
4. Project body , (project-level properties)
5. SDK targets imported (SDK targets)
6. Directory.Build.targets (imported by SDK late)
7. Explicit .targets imports (your .targets imports)
```text
### Rules
- **`.props` files** set default property values and define items. They run **before** the project body, so project-level properties can override them.
- **`.targets` files** define targets and finalize item lists. They run **after** the project body, so they see all project-level settings.
```xml
true
enable
```text
```xml
```text
**Key rule:** Properties in `.props` files should use `Condition="'$(Prop)' == ''"` to allow project-level overrides. Properties in `.targets` files are evaluated last and cannot be overridden by the project.
---
## Items and Item Metadata
Items are named collections of files or values. Each item can carry metadata (key-value pairs).
### Item Operations
```xml
PreserveNewest
true
contentFiles/any/any/
```csharp
**SDK-style projects auto-include `*.cs` files.** Do not add a `` -- it causes `NETSDK1022` duplicate items. Use `Remove` first, then `Include` for conditional compilation scenarios:
```xml
```csharp
### Well-Known Metadata
Every item has built-in metadata accessible via `%(ItemName.MetadataName)`:
| Metadata | Value | Example for `src/Models/Order.cs` |
|---|---|---|
| `%(FullPath)` | Absolute path | `/repo/src/Models/Order.cs` |
| `%(RootDir)` | Root directory | `/` |
| `%(Directory)` | Directory relative to root | `repo/src/Models/` |
| `%(Filename)` | File name without extension | `Order` |
| `%(Extension)` | File extension | `.cs` |
| `%(RecursiveDir)` | Part matched by `**` in glob | `Models/` (if glob was `src/**/*.cs`) |
| `%(Identity)` | Item spec as declared | `src/Models/Order.cs` |
### Item Metadata and Batching
Custom metadata enables per-item behavior through MSBuild batching:
```xml
main
audit
```bash
`%(Metadata)` in a task attribute triggers batching. MSBuild groups items by the metadata value and invokes the task once per group.
---
## Conditions
Conditions control whether properties, items, targets, and tasks are evaluated.
### Property Conditions
```xml
latest
$(DefineConstants);NET8_OR_GREATER
true
win-x64
```text
### Item and Target Conditions
```xml
```bash
### Condition Operators
| Operator | Example |
|---|---|
| `==` / `!=` | `'$(Config)' == 'Release'` |
| `AND` / `OR` | `'$(A)' == '1' AND '$(B)' != ''` |
| `!` (negation) | `!Exists('$(OutDir)')` |
| `Exists()` | `Exists('$(SolutionDir)global.json')` |
| `HasTrailingSlash()` | `HasTrailingSlash('$(OutputPath)')` |
**Always single-quote both sides of comparisons.** `'$(Prop)' == 'value'` is correct. Unquoted comparisons fail when the property is empty.
---
## Property Functions
MSBuild properties can call .NET static methods and MSBuild intrinsic functions inline.
### .NET Static Method Calls
```xml
$([System.String]::Copy('$(PackageId)').ToLowerInvariant())
$([System.IO.Path]::Combine('$(MSBuildThisFileDirectory)', 'tools', 'analyzer.dll'))
$([System.Guid]::NewGuid().ToString('N'))
$([System.Text.RegularExpressions.Regex]::Replace('$(Version)', '-.*$', ''))
$([System.Environment]::GetEnvironmentVariable('CI'))
```text
### MSBuild Intrinsic Functions
```xml
$([MSBuild]::IsOSPlatform('Windows'))
$([MSBuild]::IsOSPlatform('Linux'))
$([MSBuild]::IsOSPlatform('OSX'))
$([MSBuild]::Add($(PatchVersion), 1))
$([MSBuild]::VersionGreaterThanOrEquals('$(NETCoreSdkVersion)', '8.0.100'))
$([MSBuild]::StableStringHash('$(InputFile)'))
$([MSBuild]::GetPathOfFileAbove('SharedConfig.props', '$(MSBuildProjectDirectory)'))
$([MSBuild]::NormalizePath('$(MSBuildProjectDirectory)', '..', 'shared'))
```text
### Useful MSBuild Properties
| Property | Value |
|---|---|
| `$(MSBuildProjectDirectory)` | Directory containing the current `.csproj` |
| `$(MSBuildThisFileDirectory)` | Directory containing the current `.props`/`.targets` file |
| `$(MSBuildProjectName)` | Project name without extension |
| `$(IntermediateOutputPath)` | `obj/` output path |
| `$(OutputPath)` | `bin/` output path |
| `$(TargetFramework)` | Current TFM (e.g., `net10.0`) |
| `$(TargetFrameworks)` | Multi-TFM list (e.g., `net8.0;net10.0`) |
| `$(Configuration)` | `Debug` or `Release` |
| `$(SolutionDir)` | Solution directory (only set when building from solution) |
**Use `$(MSBuildThisFileDirectory)` in `.props`/`.targets` files**, not `$(MSBuildProjectDirectory)`. The former resolves to the file's own location, which is correct when the file is imported from a NuGet package or a different directory.
---
## Directory.Build.props/targets Advanced Patterns
Basic Directory.Build layout is covered in [skill:dotnet-project-structure]. This section covers advanced patterns for multi-repo and monorepo scenarios.
### Import Chain with GetPathOfFileAbove
In monorepos with nested directories, each level can define its own `Directory.Build.props` that chains to the parent:
```xml
repo/
Directory.Build.props (repo-wide defaults)
src/
Directory.Build.props (src-specific overrides)
MyApp/
MyApp.csproj
tests/
Directory.Build.props (test-specific overrides)
MyApp.Tests/
MyApp.Tests.csproj
```csharp
```xml
$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)..'))
true
```text
```xml
$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)..'))
false
true
```text
### Condition Guards
Prevent property values from being overridden by child imports:
```xml
true
enable
enable
```text
**Rule:** Properties in `.props` files should use the `Condition="'$(Prop)' == ''"` guard so that inner `.props` files and project-level properties can override them. Properties you want to enforce unconditionally belong in `Directory.Build.targets` (which evaluates last).
### Preventing Double Imports
When multiple `Directory.Build.props` files chain upward, a shared import could be evaluated twice. Use a sentinel property to guard against this:
```xml
true
My Company
My Company
Copyright (c) My Company. All rights reserved.
```text
The sentinel and content properties must be in the **same** `PropertyGroup` with the `!= 'true'` condition. Putting content in a separate block with `== 'true'` does not prevent re-evaluation -- it runs on every import because the sentinel is already set.
A cleaner approach uses `Condition` on the `` element:
```xml
```xml
### Enforcing Settings in Directory.Build.targets
Properties set in `.targets` files cannot be overridden by project-level `PropertyGroup` elements because they evaluate after the project body:
```xml
true
latest-recommended
true
```text
---
## Agent Gotchas
1. **Unquoted condition comparisons.** Always quote both sides: `'$(Prop)' == 'value'`. Unquoted `$(Prop) == value` fails silently when the property is empty or contains spaces.
1. **Using `$(MSBuildProjectDirectory)` in shared `.props`/`.targets` files.** This resolves to the importing project's directory, not the file's own directory. Use `$(MSBuildThisFileDirectory)` to reference paths relative to the `.props`/`.targets` file itself.
1. **Setting properties in `.targets` and expecting project overrides.** Properties in `.targets` evaluate after the project body and override project-level values. If a property should be overridable, set it in `.props` with a `Condition="'$(Prop)' == ''"` guard.
1. **Adding `` in SDK-style projects.** SDK-style projects auto-include all `*.cs` files. Explicit inclusion causes `NETSDK1022` duplicate items. Use `Remove` then `Include` for conditional scenarios.
1. **Missing `Outputs` on targets with `Inputs`.** A target with `Inputs` but no `Outputs` runs every build. Always pair them for incremental behavior.
1. **Using `$(SolutionDir)` in `.props`/`.targets` files.** This property is only set when building through a solution file. Command-line `dotnet build MyProject.csproj` leaves it empty. Use `$([MSBuild]::GetPathOfFileAbove('*.sln', '$(MSBuildProjectDirectory)'))` or pass paths relative to `$(MSBuildThisFileDirectory)`.
1. **Putting items in `PropertyGroup` or properties in `ItemGroup`.** Items (using `Include=`) must be in ``. Properties (using element value) must be in ``. Mixing them produces silent evaluation failures.
1. **Forgetting `Condition` guard on parent import chain.** `GetPathOfFileAbove` returns empty string when no file is found. The `` must have `Condition="Exists('$(ResolvedPath)')"` or the build fails with a file-not-found error.
---
## References
- [MSBuild Reference](https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-reference)
- [MSBuild Targets](https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-targets)
- [MSBuild Items](https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-items)
- [MSBuild Conditions](https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-conditions)
- [MSBuild Property Functions](https://learn.microsoft.com/en-us/visualstudio/msbuild/property-functions)
- [MSBuild Well-Known Item Metadata](https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-well-known-item-metadata)
- [Customize Your Build](https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-your-build)
- [Directory.Build.props and Directory.Build.targets](https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-by-directory)