dotnet-msbuild-tasks
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;
}
}
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 property (TaskLoggingHelper) with convenience methods:
| Method | When to use |
|---|---|
Log.LogMessage(importance, msg) |
Informational output (Normal, High, Low) |
Log.LogWarning(msg) |
Non-fatal issues |
Log.LogError(msg) |
Fatal errors (causes build failure) |
Log.LogWarningFromException(ex) |
Warning from caught exception |
Log.LogErrorFromException(ex) |
Error from caught exception |
For detailed code examples (ToolTask, IIncrementalTask, task parameters, inline tasks, UsingTask, debugging, NuGet packaging), see examples.md in this skill directory.
Agent Gotchas
-
Returning
falsewithout logging an error. IfExecute()returnsfalsebutLog.LogErrorwas never called, MSBuild reports a generic "task failed" with no actionable message. Always log an error before returningfalse. -
Using
Console.WriteLineinstead ofLog.LogMessage. Console output bypasses MSBuild's logging infrastructure and may not appear in build logs, binary logs, or IDE error lists. Always useLog.LogMessage,Log.LogWarning, orLog.LogError. -
Referencing
IIncrementalTaskwithout version-gating. This interface requires MSBuild 17.8+ (.NET 8 SDK). Tasks referencing it will fail to load on older MSBuild versions with aTypeLoadException. If supporting older SDKs, use target-levelInputs/Outputsinstead. If the task must support both old and new MSBuild, ship separate task assemblies per MSBuild version range or use#ifconditional compilation with a version constant. -
Placing task DLLs in the NuGet
lib/folder. This adds the assembly as a compile reference to consuming projects, polluting their type namespace. SetIncludeBuildOutput=falseand pack intotools/instead. -
Forgetting
PrivateAssets="all"on MSBuild framework package references. Without it,Microsoft.Build.FrameworkandMicrosoft.Build.Utilities.Corebecome transitive dependencies of consuming projects, causing version conflicts. -
Using
AssemblyFilewith a path relative to the project. In NuGet packages, the.targetsfile is in a different location than the consuming project. Use$(MSBuildThisFileDirectory)to build paths relative to the.targetsfile itself. -
Leaving
Debugger.Launch()in release builds. Shipping a task with unconditionalDebugger.Launch()halts builds on CI/CD servers. Guard with#if DEBUGor remove before packaging. -
Inline tasks with complex dependencies.
CodeTaskFactorycompiles code at build time with limited assembly references. For tasks that need NuGet packages or complex type hierarchies, compile a standalone task assembly instead.
References
More from novotnyllc/dotnet-artisan
dotnet-csharp
Baseline C# skill loaded for every .NET code path. Guides language patterns (records, pattern matching, primary constructors, C# 8-15), coding standards, async/await, DI, LINQ, serialization, domain modeling, concurrency, Roslyn analyzers, globalization, native interop (P/Invoke, LibraryImport, ComWrappers), WASM interop (JSImport/JSExport), and type design. Spans 25 topics. Do not use for ASP.NET endpoint architecture, UI framework patterns, or CI/CD guidance.
127dotnet-ui
Builds .NET UI apps across Blazor (Server, WASM, Hybrid, Auto), MAUI (XAML, MVVM, Shell, Native AOT), Uno Platform (MVUX, Extensions, Toolkit), WPF (.NET 8+, Fluent theme), WinUI 3 (Windows App SDK, MSIX, Mica/Acrylic, adaptive layout), and WinForms (high-DPI, dark mode) with JS interop, accessibility (SemanticProperties, ARIA), localization (.resx, RTL), platform bindings (Java.Interop, ObjCRuntime), and framework selection. Spans 20 topic areas. Do not use for backend API design or CI/CD pipelines.
99dotnet-api
Builds ASP.NET Core APIs, EF Core data access, gRPC, SignalR, and backend services with middleware, security (OAuth, JWT, OWASP), resilience, messaging, OpenAPI, .NET Aspire, Semantic Kernel, HybridCache, YARP reverse proxy, output caching, Office documents (Excel, Word, PowerPoint), PDF, and architecture patterns. Spans 32 topic areas. Do not use for UI rendering patterns or CI/CD pipeline authoring.
90dotnet-testing
Defines .NET test strategy and implementation patterns across xUnit v3 (Facts, Theories, fixtures, IAsyncLifetime), integration testing (WebApplicationFactory, Testcontainers), Aspire testing (DistributedApplicationTestingBuilder), snapshot testing (Verify, scrubbing), Playwright E2E browser automation, BenchmarkDotNet microbenchmarks, code coverage (Coverlet), mutation testing (Stryker.NET), UI testing (page objects, selectors), and AOT WASM test compilation. Spans 13 topic areas. Do not use for production API architecture or CI workflow authoring.
86dotnet-advisor
Routes .NET/C# requests to the correct domain skill and loads coding standards as baseline for all code paths. Determines whether the task needs API, UI, testing, devops, tooling, or debugging guidance based on prompt analysis and project signals, then invokes skills in the right order. Always invoked after [skill:using-dotnet] detects .NET intent. Do not use for deep API, UI, testing, devops, tooling, or debugging implementation guidance.
60dotnet-debugging
Debugs Windows and Linux/macOS applications (native, .NET/CLR, mixed-mode) with WinDbg MCP (crash dumps, !analyze, !syncblk, !dlk, !runaway, !dumpheap, !gcroot, BSOD), dotnet-dump, lldb with SOS, createdump, and container diagnostics (Docker, Kubernetes). Hang/deadlock diagnosis, high CPU triage, memory leak investigation, kernel debugging, and dotnet-monitor for production. Spans 17 topic areas. Do not use for routine .NET SDK profiling, benchmark design, or CI test debugging.
57