MSBuild tasks with dependencies
You could wrestle with MSBuild's task loading mechanism, or just don't.
A few months ago, I wrote demos and a blog post about writing MSBuild tasks and shipping them in a NuGet package. The question I’ve been asked most frequently since then is “how to I make a task with external dependencies work?” i.e. “my task needs to connect to MySql, Postgres, load SQLite”, or something like that. When I started writing a post to answer these questions, I intended to show you the way to make all this work.
But then, as the list of workarounds grew, I realized that there is a simpler solution that works just as well and avoids the headaches. So in response to this, I wrote Bundling .NET build tools in NuGet. This approach shows you how to use a regular console app instead of an MSBuild task to accomplish the same kinds of things.
This is a follow up to Shipping a cross-platform MSBuild task in a NuGet package.
TL;DR Just don’t. Use a console tool instead. MSBuild tasks have limitations. It’s typically easier to write a console tool and start a new process from MSBuild using
If you need access to MSBuild’s object model so you can use logging, items, properties, and task outputs, you will still need to write a task. But even in this case, I recommend making a task that acts as a wrapper for invoking the console tool.
The ToolTask API in MSBuild is designed for exactly this. See an example implementation here: ToolTask implementation.
But if you insist…
MSBuild tasks aren’t well suited for dependencies beyond the task assembly itself. Although there is an issue open on MSBuild to improve this (see Microsoft/MSBuild#1312), if you’re reading this, you probably can’t wait for a future version of MSBuild.
Some issues you might run into:
Third-party dependencies and NuGet
Ideally, you would be able to put your MSBuild task in an NuGet library and let
PackageReference do its magic to ensure your dependencies are installed.
But that’s not supported. See Microsoft/MSBuild#1755.
So instead, you have to make a “fat package”. This means you ship your third-party dependencies in your own NuGet package. See “BuildBundlerMinifier” as an example:
Binding redirects and version conflicts
Visual Studio and MSBuild.exe run on .NET Framework. If your assembly uses a dependency also used by MSBuild itself, you can end of with
MissingMethodException when the runtime fails to load the right version. Normally, this is resolved with binding redirects.
This is not supported by MSBuild.
To workaround this, you have to hook into assembly resolving hooks.
See this example: AssemblyResolver.cs
Native dependencies from tasks on .NET Framework
This is not supported by MSBuild either. See Microsoft/MSBuild#1887.
You can workaround by using a P/Invoke call to
LoadLibraryEx to load a native library into memory. See this example: NativeLibraryLoader.cs.
Native dependencies from tasks on .NET Core
On .NET Core, you can workaround by implementing AssemblyLoadContext and overwriting
LoadUnmanagedDll. See this example: ContextAwareTask.cs
Aligning versions with MSBuild
If you need a dependency that is also used in MSBuild itself, you have to align with the version MSBuild uses. This includes
Newtonsoft.Json, and others. See example: dependencies.props
My recommendation: consider making your build task a console tool instead and use
Exec to invoke it.
For info on this approach, see Bundling build tools in MSBuild packages.