示例可见 CowSay

.Net Tool 需要使用 .net 6.0 及以上的版本

#创建 .Net Tool 项目

使用 dotnet new 命令创建一个新的 .Net Tool:

1
dotnet new console -n <ToolName> -f <Framework>

如下例创建了一个名为 Cowsay 的 .Net Tool,使用的是 .Net 8.0 的版本:

1
dotnet new console -n CowSay -f net8.0

当执行上述命令后,会在当前目录下创建一个名为 CowSay 的文件夹,其中包含了一个名为 Program.cs 的文件,该文件中包含了一个 Main 方法,该方法是 .Net Tool 的入口方法。

以及包含有一个 CowSay.csproj 的文件,该文件是 .Net Tool 的项目文件。

1
2
3
4
5
6
7
8
9
10
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>

可以看到其中约定的 TargetFramework8.0

#修改代码

Program.cs 中的代码修改为如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
using System.Reflection;

namespace CowSay;

internal static class Program
{
private static void Main(string[] args)
{
if (args.Length == 0)
{
string? versionString = Assembly.GetEntryAssembly()?
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?
.InformationalVersion;

Console.WriteLine($"Cow Say v{versionString}");
Console.WriteLine("-------------");
Console.WriteLine("\nUsage:");
Console.WriteLine(" Cow Say <message>");
return;
}

ShowCow(string.Join(' ', args));
}

private static void ShowCow(string message)
{
string bot = $"\n {message}";
bot += @"
__________________
\
\
(oo)\_______
(__) )\/\
||------||
|| ||
";
Console.WriteLine(bot);
}
}

其中 Main 方法中的代码是用来处理命令行参数的,如果没有参数,会打印一系列提示信息。如果有参数,则会将参数组合成一个 string 并作为输出的小牛的 ASCII 图案中的一部分内容。

#运行 Tool

此时在 CowSay 文件夹下执行 dotnet run 命令,会输出如下内容,因为此时并没有带上任何参数,所以会输出一系列提示信息:

1
2
3
4
5
6
❯ dotnet run
Cow Say v1.0.0+d0f6e0d23ad19769312547a08ad5db8cf35fa97d
-------------

Usage:
Cow Say <message>

如果运行 dotnet run Hello World 命令,则会输出如下内容。因为此时带上了参数,所以程序会将参数赋予给小牛并输出:

1
2
3
4
5
6
7
8
9
10
❯ dotnet run Hello World

Hello World
__________________
\
\
(oo)\_______
(__) )\/\
||------||
|| ||

#打包 Tool

在运行打包前,先修改 Cowsay.csproj 文件,在 <PropertyGroup> 标签中添加如下内容:

1
2
3
4
<PackAsTool>true</PackAsTool>
<ToolCommandName>cowsay</ToolCommandName>
<PackageOutputPath>./nupkg</PackageOutputPath>
<Version> 1.0.1 </Version>

其中:

  • <PackAsTool> 标签是用来指定是否将项目打包为一个 .Net Tool
  • <ToolCommandName> 标签是用来指定打包后的 .Net Tool 的名称,该名称会用于后续在 CLI 中调用
  • <PackageOutputPath> 标签是用来指定打包后的 .Net Tool 的输出路径。
  • <Version> 标签标识打包后的 .Net Tool 的版本

此时在 CowSay 文件夹下执行 pack 命令进行打包:

1
dotnet pack

此时在 CowSay 文件夹下创建一个名为 nupkg 的文件夹,其中包含的就是可安装的 tool 文件:

1
2
├───nupkg
│ CowSay.1.0.1.nupkg

#安装 Global Tool

打包 Tool 生成了一个可安装的 .Net Tool 后,就可以使用 dotnet tool install 命令来安装该 Tool:

1
dotnet tool install --global --add-source <sourcePath> <toolName> --version <version>

理论上 --version 不是必须的,但是在某些 .net 版本下,如 .net 8.0.203 中,不加上这个参数会出现错误,详见:Issue

如下在安装后,即可直接运行 coway 命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
❯ dotnet tool install --global --add-source .\nupkg CowSay --version 1.0.1
You can invoke the tool using the following command: cowsay
Tool 'cowsay' (version '1.0.1') was successfully installed.
❯ cowsay hello world

hello world
__________________
\
\
(oo)\_______
(__) )\/\
||------||
|| ||

后续如果想要删除该 tool,则可以运行 uninstall 命令:

1
dotnet tool uninstall -g <toolName>

#安装路径控制

在 Windows 下默认 Tool 安装的路径是:

1
2
C:\users\$env:username\.dotnet\tools
// C:\Users\$env:username\.dotnet\tools\cowsay.exe

在安装时,可以加上 --tool-path 参数来指定安装路径:

1
dotnet tool install --tool-path <path> --add-source <sourcePath> <toolName> --version <version>

对于指定路径的 tool,当卸载时也需要加上 --tool-path 参数来指定卸载路径:

1
dotnet tool uninstall --tool-path <path> <toolName>

#更新 Tool

如果修改了 Tool 并重新进行了 打包 Tool 操作,可以使用 update 进行 tool 的更新:

1
dotnet tool update --global --add-source <sourcePath> <toolName>

如我们将 CowSay 的版本更新为 1.0.2 后,并再次允许 dotnet pack,此时在 .nuget 目录下会生成一个新的 nupkg 文件夹,其中包含了新的 CowSay 的版本:

1
2
3
├── nupkg
│ ├── CowSay.1.0.1.nupkg
│ └── CowSay.1.0.2.nupkg

此时可运行 dotnet tool update 进行更新:

1
dotnet tool update --global --add-source .\nupkg CowSay --version 1.0.2

至此,一个最简单的 .Net Tool 就创建完成了。但如果要将其发布 上,还需要进行一些额外的操作。

#增加 License

在 XML 中添加以下的内容即可将 License 文件打包到 NuGet 包中:

1
2
3
4
5
6
7
<PropertyGroup>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
</PropertyGroup>

<ItemGroup>
<None Include="../LICENSE" Pack="true" PackagePath=""/>
</ItemGroup>

#增加 README

1
2
3
4
5
6
7
<PropertyGroup>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

<ItemGroup>
<None Include="../README.md" Pack="true" PackagePath=""/>
</ItemGroup>

#Reference

Tutorial: Create a .NET tool - .NET CLI | Microsoft Learn

(4) How to create your own .NET CLI tools to make your life easier - YouTube

NuGet pack and restore as MSBuild targets | Microsoft Learn

NuGet pack and restore as MSBuild targets | Microsoft Learn