As announced recently in the .NET Core 2.1 Roadmap,
the .NET Core 2.1.300 SDK will add a feature called “.NET Core Global Tools”. This announcement contains a brief snippet of how the tools will work. As this feature is new, there are some rough edges.
In this post, I’ll go over the basic design of how global CLI tool should work, some of the gotchas,
and how to make it all work.
:warning: (Update May 12, 2018) This post was written for 2.1 Preview 1 and is now obsolete. See this post for an updated version
For those who want to get started on code right away, checkout the project templates
You will need to download a pre-release build of the .NET Core CLI that supports these features.
At the time of writing this post, this feature is still in progress and is subject to change.
Tip: For a real-world example of creating global tools, see https://github.com/aspnet/DotNetTools/, which contains
the source code for a handful of .NET Core global tools created by the ASP.NET Core team.
A .NET Core global tool is a special NuGet package that contains a console application.
When installing a tool, .NET Core CLI will download the package, extract it to disk, and make your console
tool available as a new command in your shell by adding to the PATH environment variable.
Users install tools by executing “dotnet install tool”:
Once installed, the command contained in the “awesome-tool” package are on PATH.
Creating your own package
To simplify getting started, I’ve created a project templates.
Install the templates package
Create a new project
Once you have this project, you can create your tool package like this:
This creates a file named awesome-tool.1.0.0.nupkg
You can install your package like this:
When you are ready to share with world, upload the package to https://nuget.org.
Under the hood
The NuGet package that contains a global tool must completely contain the application. Unlike project tools (aka DotNetCliToolReference), you produce a package by executing dotnet publish, and putting everything that is
in the publish output into the NuGet package.
When dotnet install tool executes, it…
uses dotnet restore with special parameters to fetch the package.
extracts the package into %USERPROFILE%.dotnettoolspkgs (Windows) or $HOME/.dotnet/toolspkgs (macOS/Linux).
Generates an executable shim from the extract packge into %USERPROFILE%.dotnettools or $HOME/.dotnet/tools
The executable shim generation is controlled by a file named DotnetToolSettings.xml. It’s basic format looks like this.
<Command Name=“my-command-name” EntryPoint=“MyApp.dll” Runner=“dotnet” />
At the time of writing this post, this feature has some restrictions and unexpected behaviors. These may change as the feature evolves.
Gotcha 1 – there is no uninstall (yet)
There is no uninstall after dotnet install tool.
I’m willing to bet this will change by RTM. But for previews, you can manually uninstall tools by deleting this files:
Gotcha 2 – PATH
awesome-tool: command not found
awesome-tool : The term ‘awesome-tool’ is not recognized as the name of a cmdlet, function, script file, or operable program.
Global tools are installed to %USERPROFILE%.dotnettools (Windows) or $HOME/.dotnet/tools (macOS/Linux). The .NET Core CLI tool makes a best effort to help you ensure this is in your PATH environment variable. However, this
can easily be broken. For instance:
if you have set the DOTNET_SKIP_FIRST_TIME_EXPERIENCE environment variable to speed up intial runs of .NET Core, then your PATH may not be updated on first use
macOS: if you install the CLI via .tar.gz and not .pkg, you’ll be missing the /etc/paths.d/dotnet-cli-tool file that configures PATH.
Linux: you will need to edit your shell environment file. e.g. ~/.bash_profile or ~/.zshrc
(May require restarting your shell.)
Gotcha 3 – tools are user-specific, not machine global
The .NET Core CLI installs global tools to $HOME/.dotnet/tools (Linux/macOS) or %USERPROFILE%.dotnettools (Windows).
This means you cannot install a global tool for the entire machine using dotnet install tool –global.
Installed tools are only available to the user who installed them.
Package authoring and SDK
The best experience for authoring a global tool requires a .NET Core SDK version 2.1.300-preview1-008000 or newer.
This SDK provides a few simple settings that adjust package layout to match the requirements for .NET Core global tools.
You only need to add two properties to enable packing the project as a global tool.
Tip: Until .NET Core 2.1 is released, you may see build warnings when calling dotnet pack on this project. To workaround this, add the following:
I recommend this for now as it will also prevent an install error for users that would look like this:
Deep-dive: package requirements
There are some very specific requirements for CLI global tools. The SDK takes care of most of these for you
when you specify PackAsTool=true.
If you cannot yet upgrade to a nightly build of the .NET Core SDK but want to try this out, you can workaround these restrictions. The templates package McMaster.DotNet.GlobalTool.Templates, version 2.1.300-preview-build7
contains a template that workarounds this for older SDKs.
Publish output into pack
As mentioned above, the tools package must contain all your apps dependencies.
This can be collected into one place by using dotnet publish.
By default, dotnet pack only contains the output of dotnet build.
This output does not normally contain third-party assemblies, static files, and the DotnetToolSettings.xml file,
which is why you need to publish, not just build.
Early version of the SDK don’t support packing global tools. You can workaround this by chaining
publish before dotnet-pack, and using a .nuspec file.
<Content Include=“DotnetToolSettings.xml” CopyToPublishDirectory=“PreserveNewest” />
<Target Name=“PackGlobalTool” BeforeTargets=“GenerateNuspec” DependsOnTargets=“Publish”>
<!– … –>
<file src=“$publishdir$” target=“tools/netcoreapp2.0/any/” />
Error NU1202 and “Microsoft.NETCore.Platforms”
This is only a requirement for 2.1.300-preview1. It will be fixed in 2.1.300-preview2 when that becomes available.
A global tool package must specify a dependency to “Microsoft.NETCore.Platforms”. This is required because
dotnet-install-tool does some magic stuff. Ask me about this in the comments if you want me to explain. Without this dependency, the package will fail to install with
To workaround, add this to your nuspec:
<!– … –>
<dependency id=“Microsoft.NETCore.Platforms” version=“2.0.1” />
<!– … –>
See this GitHub issue for details.
Restrictions on DotnetToolSettings.xml
The schema for this file looks like this:
<Command Name=“$name” EntryPoint=“$file” Runner=“$runner” />
Lots of restrictions here:
The file must exist in the NuGet package under tools/$targetframework/$runtimeidentifier/. If your application is portable to all platforms, use any as the $runtimeidentifier. Example: tools/netcoreapp2.0/any/DotnetToolSettings.xml
You may only specify one DotnetToolSettings.xml file per package.
You may only specify one <Command> per DotnetToolSettings.xml file.
The only allowed value for Runner is “dotnet”.
The value for EntryPoint must be a .dll file that sits next to DotnetToolSettings.xml in the package.
Error NU1212 and package type
Installation may fail with this error
This error message is not very clear (see https://github.com/dotnet/cli/issues/8698 for improvement).
What this means is that dotnet-install-tool is currently restricted to only installing a .NET Core package that has specific metadata. That metadata can be defined in your nuspec file and must be set as follows:
<!– This piece is required –>
<packageType name=“DotnetTool” />
<!– … –>
You must redistribute any of your dependencies in your tools package. Dependencies define in the <dependencies> metadata of your NuGet package are not
honored by dotnet-install-tool.
This is an awesome feature. Super happy the .NET Core CLI team is working on it. Can’t wait to see what people make.