By following this article, you will learn how to create NuGet Packages that utilize MSBuild ExecTask to run your Before or After specific Build Targets. This article (and the templates) only address C#, but can easily be applied to any .NET language because the important bits are language agnostic. The templates are written for .NET Core 3.1, but can easily be adapted to legacy .NET Frameworks or .NET 5. This is the same method used by NuGetDefense.
The Build Task NuGet Package Template sets up a cross-platform msbuild task that runs at build.
I started looking for a way to run a task at build when I started work on NuGetDefense. I needed to run a console app and emit warnings/errors to MSBuild as part of the Build. I found several articles detailing the pitfalls of trying to write a cross-platform task and settled instead on an MSBuild ExecTask run using a nuget Targets file.
Using the Code
Using the repository is as simple as installing the template and then writing your app.
dotnet new --install BuildTaskNuGetPackage.Template::*
dotnet new nugetbuildtask -n ProjectName -A "FirstName LastName"
If you prefer to use GitHub, you can start with the template repository.
- Click the Use This Template button
- Fill out the page including your repository name. DO NOT check the
Include all branches this would include the branch used to create the dotnet new template as well.
Regardless of which method you use, you will need to write your console app and update the nuspec to include any dependencies that are needed for your app to run.
This is how the files are included for NuGetDefense.
<file src="build/nugetdefense.targets" target="build/nugetdefense.targets" />
<file src="lib/net461/_._" target="lib/net461/_._" />
<file src="lib/netcoreapp3.1/_._" target="lib/netcoreapp3.1/_._" />
<file src="lib/netstandard2.0/_._" target="lib/netstandard2.0/_._" />
The Hard Way
So you want to do it the hard way? Maybe you like a challenge or want a deeper understanding of the code you're using.
Get the Console App
This should be fairly basic. You can use Visual Studio or the dotnet cli to create a new console app or use one that already exists. For the sake of this tutorial, I'm going to assume it's written in .NET Core 3.1 (so it is cross-platform) but with a few tweaks, you can use anything. Make sure to build the project so you have a list of all the files and dependencies required to run the app.
Create the Nuspec
The .nuspec file is the file that tells nuget how to put the package together. You can review the documentation for this file to see more of what it can do but we are only going to look at a few of the potential fields.
Create the file `yourProjectName.nuspec` somewhere in your repository. I generally put it right beside the project file, but you could potentially have it anywhere in your project/solution. Edit the contents to match the following:
<description>A Description of my Package</description>
You will want to replace the appropriate fields with values that match your project/organization. And you will want to keep the
id as a lowercase value (some NuGet sources may be case-sensitive) to avoid edge-case issues.
Now you need to look at the bin/Release/ folder and decide which files need to be included. Typically, this will be every item in that folder. Once you have a list, Add them to a files collection at the bottom of the Nuspec:
<description>A Description of my Package</description>
<file src="build/MyBuildTask.targets" target="build/MyBuildTask.targets" />
<file> element describes a file that you want to include in the package.
src is path to the file relative to the directory that contains the project file.
target is the location in the package that the files wil end up in. All files that are part of the console app should be placed in the tools directory to ensure they are not referenced in the project that installs this package (could cause numerous issues).
Dig into the Project File
We want this to build itself, then pack itself, but only when we are not trying to debug it. As it stands, trying to run `dotnet pack` on this will fail if the project is built in any configuration other than Release. And on top of that, having to drop to a console after using a hotkey to build is absurd when we can trigger the pack automatically.
Add these two lines to your project file (in the same
propertygroup that defines your target frameworks) to automatically pack the nuget package when built in Release.
Note: Many articles reference Replace Tokens to work around the configuration issue, but I've found that .NET Core on Linux replaces $Configuration$ with an empty string. If you working with Legacy .NET applications, or if this is fixed at a later date, you may wish to utilize that feature.
Create the Targets File and Add to the Nuspec
Create a directory name `build` and in this directory, create a file named `yourprojectname.targets`. It should look something like this:
<MyBuildTaskExe Condition="'$(OS)' == Unix">
<MyBuildTaskExe Condition="'$(OS)' == 'Windows_NT'">
<Target Name="MyBuildTask" AfterTargets="Build">
<Exec Command="$(MyBuildTaskExe) "$(MSBuildProjectFullPath)"
$(TargetFramework)" IgnoreExitCode="false" />
MyBuildTaskExe is defined based on the OS (because the path separators cause a problem otherwise). Defines command we are going to run with the
ExecTask (without the arguments)
MSBuildThisFileDirectory is the full path to the targets file.
is the path to the project file and can be used to find the directory and/or files for the build. It does not wrap itself in doube-quotes (so we wrap it with ")
AfterTargets could be replaced with
BeforeTargets and is used to determine when to run the task.
Points of Interest
- 9th July, 2020 - Initial publication
Software Developer specializing in Build/Test Automation. Adept Linux User and Cross Platform Developer.