| name | dotnet-msbuild-tasks |
| description | Writes custom MSBuild tasks. ITask, ToolTask, IIncrementalTask, inline tasks, UsingTask. |
dotnet-msbuild-tasks
Guidance for authoring custom MSBuild tasks: implementing the ITask interface, extending ToolTask for CLI wrappers,
using IIncrementalTask (MSBuild 17.8+) for incremental execution, defining inline tasks with CodeTaskFactory,
registering tasks via UsingTask, declaring task parameters, debugging tasks, and packaging tasks as NuGet packages.
Version assumptions: .NET 8.0+ SDK (MSBuild 17.8+). IIncrementalTask requires MSBuild 17.8+ (VS 2022 17.8+, .NET 8
SDK). All examples use SDK-style projects. All C# examples assume using Microsoft.Build.Framework; and
using Microsoft.Build.Utilities; are in scope unless shown explicitly.
Scope
- ITask interface and Task base class implementation
- ToolTask for wrapping external CLI tools
- IIncrementalTask for engine-filtered incremental execution
- Inline tasks with CodeTaskFactory
- UsingTask registration and task parameters
- Task debugging and NuGet packaging
Out of scope
- MSBuild project system authoring (targets, props, items, conditions) -- see [skill:dotnet-msbuild-authoring]
Cross-references: [skill:dotnet-msbuild-authoring] for custom targets, import ordering, items, conditions, and property
functions.
ITask Interface
All MSBuild tasks implement Microsoft.Build.Framework.ITask. The simplest approach is to inherit from
Microsoft.Build.Utilities.Task, which provides default implementations for BuildEngine and HostObject.
Minimal Custom Task
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
public class GenerateFileHash : Task
{
[Required]
public string InputFile { get; set; } = string.Empty;
[Output]
public string Hash { get; set; } = string.Empty;
public override bool Execute()
{
if (!File.Exists(InputFile))
{
Log.LogError("Input file not found: {0}", InputFile);
return false;
}
using var stream = File.OpenRead(InputFile);
var bytes = System.Security.Cryptography.SHA256.HashData(stream);
Hash = Convert.ToHexString(bytes).ToLowerInvariant();
Log.LogMessage(MessageImportance.Normal,
"SHA-256 hash for {0}: {1}", InputFile, Hash);
return true;
}
}
```text
### ITask Contract
| Member | Purpose |
|---|---|
| `BuildEngine` | Provides logging, error reporting, and build context |
| `HostObject` | Host-specific data (rarely used) |
| `Execute()` | Runs the task. Return `true` for success, `false` for failure |
The `Task` base class exposes a `Log` () convenience methods:
| Method | When to use |
|---|---|
| `Log.()` | Informational () |
| `Log.()` | Non-fatal issues |
| `Log.()` | Fatal () |
| `Log.()` | Warning caught exception |
| `Log.()` | Error caught exception |
---
For detailed code (), see `examples.md` skill directory.
## Agent Gotchas
1. **Returning `` without logging an error.** If `()` returns `` but `Log.LogError` was never called, MSBuild reports a generic "task failed" no actionable message. Always log an error before returning ``.
1. **Using `Console.WriteLine` instead of `Log.LogMessage`.** Console output bypasses MSBuild's logging infrastructure may appear build logs, binary logs, IDE error lists. Always use `Log.LogMessage`, `Log.LogWarning`, `Log.LogError`.
1. **Referencing `IIncrementalTask` without version-gating.** This requires MSBuild 17.8+ (). Tasks referencing it will fail to load older MSBuild versions a `TypeLoadException`. If supporting older SDKs, use target-level `Inputs`/`Outputs` instead. If the task must support both old MSBuild, ship separate task assemblies per MSBuild version range use `#` conditional compilation a version constant.
1. **Placing task DLLs the NuGet `lib/` folder.** This adds the assembly a compile reference to consuming projects, polluting their type . Set `IncludeBuildOutput=` pack `tools/` instead.
**Forgetting `PrivateAssets=` MSBuild framework package references.** Without it, `Microsoft.Build.Framework` `Microsoft.Build.Utilities.Core` become transitive dependencies of consuming projects, causing version conflicts.
**Using `AssemblyFile` a path relative to the project.** In NuGet packages, the `.targets` a different location than the consuming project. Use `$(MSBuildThisFileDirectory)` to build paths relative to the `.targets` itself.
**Leaving `Debugger.Launch()` release builds.** Shipping a task unconditional `Debugger.Launch()` halts builds CI/CD servers. Guard `
**Inline tasks complex dependencies.** `CodeTaskFactory` compiles code at build time limited assembly references. For tasks that need NuGet packages complex type hierarchies, compile a standalone task assembly instead.
---
- [MSBuild Task Writing](https:
- [MSBuild Task Reference](https:
- [ToolTask Class](https:
- [MSBuild Inline Tasks](https:
- [UsingTask Element](https:
- [MSBuild Task Parameters](https:
- [Creating a NuGet Package MSBuild Tasks](https:
- [Debugging MSBuild Tasks](https: