This guide provides detailed instructions for creating a .NET tool using DotnetToolWrapper.
DotnetToolWrapper enables you to package native executables as .NET Tools, making them distributable through NuGet
and installable via the dotnet tool install command. This guide walks you through the complete process.
- .NET SDK 8.0, 9.0, or 10.0
- NuGet CLI or dotnet pack command
- Your native executable(s) for target platforms
To create a .NET tool for an existing application, follow these steps:
- Create a .nuspec file - Defines the NuGet package metadata
- Create DotnetToolSettings.xml - Specifies the tool command name and entry point
- Create DotnetToolWrapper.json - Maps platforms to native executables
- Copy DotnetToolWrapper files - Add the wrapper application files
- Add your native executables - Include platform-specific binaries
- Package the NuGet package - Create the final distributable package
The following is the recommended folder structure for a tool. Note that you should include folders for all target .NET frameworks (net8.0, net9.0, and net10.0) to ensure compatibility:
root/
├── tool.nuspec
├── README.md
├── win-x64/
│ └── my-tool.exe
├── linux-x64/
│ └── my-tool
├── osx-x64/
│ └── my-tool
└── tools/
├── net8.0/
│ └── any/
│ ├── DotnetToolSettings.xml
│ ├── DotnetToolWrapper.json
│ ├── DemaConsulting.DotnetToolWrapper.deps.json
│ ├── DemaConsulting.DotnetToolWrapper.dll
│ └── DemaConsulting.DotnetToolWrapper.runtimeconfig.json
├── net9.0/
│ └── any/
│ ├── DotnetToolSettings.xml
│ ├── DotnetToolWrapper.json
│ ├── DemaConsulting.DotnetToolWrapper.deps.json
│ ├── DemaConsulting.DotnetToolWrapper.dll
│ └── DemaConsulting.DotnetToolWrapper.runtimeconfig.json
└── net10.0/
└── any/
├── DotnetToolSettings.xml
├── DotnetToolWrapper.json
├── DemaConsulting.DotnetToolWrapper.deps.json
├── DemaConsulting.DotnetToolWrapper.dll
└── DemaConsulting.DotnetToolWrapper.runtimeconfig.json
Note: While the example above shows net8.0, net9.0, and net10.0 folders, you can choose to include only the framework versions you want to support. However, including all three ensures maximum compatibility across different .NET SDK versions that users might have installed.
Create a .nuspec file that defines your package metadata:
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>My.Tool.Package</id>
<version>1.0.0</version>
<title>My Tool</title>
<authors>Your Name</authors>
<license type="expression">MIT</license>
<readme>docs/README.md</readme>
<description>Description of your tool</description>
<projectUrl>https://github.com/yourusername/your-tool</projectUrl>
<repository type="git" url="https://github.com/yourusername/your-tool.git" />
<tags>dotnet-tool cli native</tags>
<packageTypes>
<packageType name="DotnetTool" />
</packageTypes>
</metadata>
<files>
<file src="README.md" target="docs/README.md" />
<file src="tools/**/*" target="tools" />
</files>
</package>Key points:
- Set a unique
<id>for your package - Use semantic versioning for
<version> - Include
<packageType name="DotnetTool" />to mark this as a .NET tool - Adjust file paths in the
<files>section to match your structure
Refer to the .nuspec File Reference for more details.
Create a DotnetToolSettings.xml file in each framework folder (e.g., tools/net8.0/any/):
<?xml version="1.0" encoding="utf-8"?>
<DotNetCliTool Version="1">
<Commands>
<Command Name="my-tool" EntryPoint="DemaConsulting.DotnetToolWrapper.dll" Runner="dotnet" />
</Commands>
</DotNetCliTool>Key points:
- Replace
my-toolwith the command name users will type to run your tool - Keep
EntryPointasDemaConsulting.DotnetToolWrapper.dll - Keep
Runnerasdotnet
Create a DotnetToolWrapper.json file in each framework folder that maps platform identifiers to executables:
{
"win-x64": {
"program": "../../../win-x64/my-tool.exe"
},
"win-x86": {
"program": "../../../win-x86/my-tool.exe"
},
"linux-x64": {
"program": "../../../linux-x64/my-tool"
},
"linux-arm64": {
"program": "../../../linux-arm64/my-tool"
},
"osx-x64": {
"program": "../../../osx-x64/my-tool"
},
"osx-arm64": {
"program": "../../../osx-arm64/my-tool"
}
}Platform Identifiers:
The platform identifier format is {os}-{architecture}:
Supported Operating Systems:
win- Windowslinux- Linuxfreebsd- FreeBSDosx- macOS
Supported Architectures:
x86- 32-bit Intel/AMDx64- 64-bit Intel/AMDarm- 32-bit ARMarm64- 64-bit ARM (Apple Silicon, etc.)wasm- WebAssembly (architecture detected, but typically requires special runtime handling)s390x- IBM System z
Key points:
- Only include platforms you support
- Paths are relative to the DotnetToolWrapper.json file location
- Environment variables in paths will be expanded (e.g.,
%USERPROFILE%,$HOME) - The wrapper will automatically select the correct executable based on the runtime platform
For each framework version you're supporting, copy the following files from the DotnetToolWrapper release
into your tools/{framework}/any/ folder:
DemaConsulting.DotnetToolWrapper.dllDemaConsulting.DotnetToolWrapper.deps.jsonDemaConsulting.DotnetToolWrapper.runtimeconfig.json
You can obtain these files by:
- Downloading from the releases page
- Building from source (see Building from Source)
Place your native executables at the root of the package in subdirectories matching the platform identifiers. For example:
root/
├── win-x64/
│ └── my-tool.exe
├── linux-x64/
│ └── my-tool
└── osx-arm64/
└── my-tool
Important:
- Ensure Linux and macOS executables have execute permissions
- Test executables on their target platforms before packaging
- Consider including debug symbols or stripped binaries based on your needs
Once all files are in place, create the NuGet package:
nuget pack tool.nuspec -Version 1.0.0Or using dotnet pack if you have a .csproj:
dotnet pack -c Release -p:PackageVersion=1.0.0This creates a .nupkg file that can be:
- Published to NuGet.org
- Published to a private NuGet feed
- Installed locally for testing
dotnet tool install -g My.Tool.Packagedotnet tool install --tool-path ./tools My.Tool.Packagemy-tool --help
my-tool [arguments]dotnet tool uninstall -g My.Tool.PackageYou can use environment variables in the program path within DotnetToolWrapper.json:
{
"win-x64": {
"program": "%LOCALAPPDATA%/MyTool/my-tool.exe"
},
"linux-x64": {
"program": "$HOME/.local/share/mytool/my-tool"
}
}This is useful when your tool needs to be installed in a user-specific location.
If you want to support multiple .NET framework versions:
- Create separate folders for each framework:
net8.0,net9.0,net10.0 - Copy the appropriate DotnetToolWrapper binaries for each framework
- Place your native executables at the root of the package (they are shared across all frameworks)
- Update your .nuspec to include all framework folders
Users with different .NET SDK versions will automatically use the appropriate framework version.
Before publishing, test your package locally:
# Pack the package
nuget pack tool.nuspec -Version 1.0.0-test
# Install from local file
dotnet tool install -g My.Tool.Package --add-source ./
# Test the tool
my-tool --version
# Uninstall when done
dotnet tool uninstall -g My.Tool.PackageTo build DotnetToolWrapper from source:
# Clone the repository
git clone https://github.com/demaconsulting/DotnetToolWrapper.git
cd DotnetToolWrapper
# Restore dependencies
dotnet restore
# Build for all target frameworks
dotnet build --configuration Release
# Find the output files in:
# src/DemaConsulting.DotnetToolWrapper/bin/Release/net8.0/
# src/DemaConsulting.DotnetToolWrapper/bin/Release/net9.0/
# src/DemaConsulting.DotnetToolWrapper/bin/Release/net10.0/Problem: After running dotnet tool install, the command is not found.
Solutions:
- Ensure the .NET tools directory is in your PATH
- Windows:
%USERPROFILE%\.dotnet\tools - Linux/macOS:
~/.dotnet/tools
- Windows:
- Restart your terminal or run
dotnet tool list -gto verify installation
Problem: "This tool does not support the {platform} target"
Solutions:
- Verify the platform identifier in
DotnetToolWrapper.jsonmatches your system - Add support for your platform by including the appropriate native executable
- Check that the platform identifier format is correct (
{os}-{architecture})
Problem: "Missing configuration file DotnetToolWrapper.json"
Solutions:
- Ensure
DotnetToolWrapper.jsonis in the same directory asDemaConsulting.DotnetToolWrapper.dll - Check file permissions and ensure the file is readable
- Verify the file is included in the NuGet package
Problem: Permission denied when running the tool on Linux or macOS
Solutions:
-
Ensure the native executable has execute permissions before packaging:
chmod +x my-tool
-
If the executable is already packaged, extract it, fix permissions, and repackage
Problem: The tool runs the wrong executable or fails to find the right one.
Solutions:
- Double-check the platform identifier in
DotnetToolWrapper.json - Verify the paths are correct and relative to the json file location
- Test the configuration by examining what platform the wrapper detects:
- The wrapper constructs the platform as
{GetOs()}-{GetArchitecture()}
- The wrapper constructs the platform as
For real-world examples of tools using DotnetToolWrapper:
- Browse repositories on GitHub with the dotnettoolwrapper topic
- Check the examples in the DotnetToolWrapper repository (if available)
- Issues: Report bugs or request features via GitHub Issues
- Discussions: Ask questions via GitHub Discussions
- Documentation: See README.md for project overview
- Architecture: See ARCHITECTURE.md for design details
- Contributing: See CONTRIBUTING.md for contribution guidelines