Unified cross-shell wrappers to run common .NET repo tasks from PowerShell, CMD, or Bash using a single entrypoint: dotnet-tools.
These thin wrappers dispatch to PowerShell command scripts that live alongside them, so you get a consistent CLI across shells for build, test (with coverage), version bump, clean, and tag.
dotnet-tools/
├─ dotnet-tools # Bash wrapper (Git Bash/WSL/Linux)
├─ dotnet-tools.cmd # Windows CMD/PowerShell wrapper
├─ commands/ # PowerShell command scripts (the implementation)
│ ├─ build.ps1
│ ├─ bump.ps1
│ ├─ clean.ps1
│ ├─ init-lib.ps1
│ ├─ init-min.ps1
│ ├─ init-proj.ps1
│ ├─ tag.ps1
│ └─ test.ps1
├─ parameters/ # Default parameters for init commands
│ └─ init-parameters.json
├─ setup/ # Install helpers to place wrappers on PATH
│ ├─ install.ps1
│ └─ install.sh
├─ templates/ # Scaffolding templates used by init commands
│ ├─ LICENSE.MIT.template
│ ├─ project.minimal.csproj.template
│ ├─ project.nuget.csproj.template
│ ├─ project.Tests.csproj.template
│ └─ solution.sln.template
└─ tests/ # E2E test harness and mock projects
├─ run.ps1
└─ mock/
├─ cmd-test-lib/
│ └─ MyLibCmd/ ...
└─ cmd-test-min/
└─ MyProjCmd/ ...
- The wrappers must stay sibling to the
commands/folder; they resolve scripts by relative path.
Once the repo directory is on your PATH, call dotnet-tools <command> [args] from any shell.
Examples:
- Build a solution (auto-detects single
.slnundersrcor specify a path):dotnet-tools build [<path-to-src>] [--no-restore] [--solution Name.sln] [--configuration Debug|Release]
- Bump project version in a
.csproj:dotnet-tools bump <version|--major|--minor|--patch> [<csproj-or-directory>]
- Clean all
bin/andobj/undersrc(or a specified path):dotnet-tools clean [<path-to-src>]
- Create a git tag from csproj
<Version>(optionally push to remote):dotnet-tools tag [<path>] [--push] [--remote <name>]
- Run tests with code coverage (HTML report):
dotnet-tools test [<tests-root>]
-- Scaffold a new library repository (solution, project, metadata):
dotnet-tools init-lib [--root <RootName|Path>] [--solution Name] [--project Name] [--author "Full Name"] [--company "Company"] [--description "NuGet package description"] [--version 0.1.0] [--packageId Id] [--repositoryUrl URL] [--json path/to/params.json]- Example (flags only):
dotnet-tools init-lib --root MyLib --solution MyLib --author "Jane Doe" --company "Acme" --description "High performance utilities" --version 0.1.0 --packageId Acme.MyLib --repositoryUrl https://github.com/acme/mylib - Example (JSON only):
dotnet-tools init-lib --json parameters/init-parameters.json - Note: Do not mix
--jsonwith other flags; use one or the other.
-- Scaffold a minimal library repository (solution + project only):
-
dotnet-tools init-min [--root <RootName|Path>] [--solution Name] [--project Name] [--author "Full Name"] [--description "Project description"] [--json path/to/params.json] -
Example:
dotnet-tools init-min --root MyLib --solution MyLib --project MyLib --author "Jane Doe" --description "High performance utilities" -
Behavior: Generates solution and a minimal
.csproj(no NuGet metadata). Does not create a test project; addstests/.gitkeepinstead. Uses the same.editorconfig,.gitignore, MITLICENSE, and README generation asinit. -- Scaffold a single project folder: -
dotnet-tools init-proj --name <ProjectName> [--min|--nuget] -
Example (minimal default):
dotnet-tools init-proj --name Utils -
Example (explicit minimal):
dotnet-tools init-proj --name Utils --min -
Example (nuget blank metadata):
dotnet-tools init-proj --name Package --nuget -
Behavior: Creates
<ProjectName>/containing<ProjectName>.csproj. With--min(or no flag) copiesproject.minimal.csproj.template. With--nugetcopiesproject.nuget.csproj.templatebut blanks all NuGet metadata property values (properties retained with empty content).
- .NET SDK installed (
dotnet --version) - For coverage HTML report: ReportGenerator (optional)
- Install as a global .NET tool:
dotnet tool install -g dotnet-reportgenerator-globaltool - Ensure
~/.dotnet/tools(or Windows equivalent) is on yourPATH
- Install as a global .NET tool:
You can install the wrappers into a tools directory on your PATH using the provided scripts.
Run from the repo root:
Set-ExecutionPolicy -Scope Process Bypass
./setup/install.ps1 # auto-picks a writable PATH dir or $HOME/bin
# or specify a target directory explicitly
./setup/install.ps1 -Target "$env:USERPROFILE\bin"After installation, open a new shell and run dotnet-tools build to verify.
Run from the repo root:
chmod +x ./setup/install.sh
./setup/install.sh # auto-picks a writable PATH dir or ~/.local/bin or specify a target directory explicitly
./setup/install.sh /usr/local/bin # may require: sudo ./setup/install.sh /usr/local/binThe installer ensures the commands/ folder sits alongside dotnet-tools in the chosen directory and attempts to add the fallback directory to your PATH (e.g., ~/.local/bin) if needed.
You have two options: add the repo folder to PATH (recommended), or copy the wrapper + commands/ folder into an existing tools directory already on PATH.
Important: dotnet-tools/dotnet-tools.cmd and commands/ must live together in the same directory you place on PATH.
- Add for the current session only:
$p = 'D:\\Repositories\\dotnet-tools'
$env:Path = "$env:Path;$p"- Persist for the current user (new shells will pick it up):
$p = 'D:\\Repositories\\dotnet-tools'
$u = [Environment]::GetEnvironmentVariable('Path', 'User')
if (-not $u.Split(';') -contains $p) {
[Environment]::SetEnvironmentVariable('Path', ($u.TrimEnd(';') + ';' + $p), 'User')
}Then open a new PowerShell/CMD and run e.g. dotnet-tools build.
Git Bash can call the same dotnet-tools wrapper. Ensure the repo folder is in PATH within your bash profile.
echo 'export PATH="$PATH:/d/Repositories/dotnet-tools"' >> ~/.bashrc
source ~/.bashrc
# Optional: ensure executable bit is set for the bash wrapper
chmod +x /d/Repositories/dotnet-tools/dotnet-toolsIf you’re on Linux or WSL with PowerShell 7 (pwsh) installed, the bash wrapper will call pwsh directly. Add the repo folder to your PATH:
# Example: clone into ~/.local/share/dotnet-tools
mkdir -p ~/.local/share
cd ~/.local/share
git clone https://example.com/your/dotnet-tools.git
# Add to PATH via profile (adjust path as needed)
echo 'export PATH="$PATH:$HOME/.local/share/dotnet-tools"' >> ~/.bashrc
source ~/.bashrc
# Ensure the bash wrapper is executable
chmod +x "$HOME/.local/share/dotnet-tools/dotnet-tools"If powershell.exe is available (on WSL), the wrapper prefers it; otherwise it falls back to pwsh.
- Execution policy: wrappers invoke PowerShell with
-ExecutionPolicy Bypass, so you typically do not need to change your system policy. - Multiple solutions/projects: commands auto-discover where practical, but will ask you to specify a path or solution if multiple are found.
- Coverage report:
dotnet-tools testwrites coverage intodocs/coverage-report(when running with a repo root containingdocs) or into<tests-root>/coverage-reportif you pass a base path. - Errors about missing
reportgenerator: install it viadotnet tool install -g dotnet-reportgenerator-globaltooland ensure your tools path is onPATH.
build: Restore (optional) and build the solution in Debug/Releasebump: Insert or update<Version>in a.csproj(supports--major|--minor|--patch)clean: Removebin/andobj/undersrcor a specified pathtag: Createv<Version>git tag from.csprojand optionally pushtest: Run tests in discovered test projects and generate HTML coverage reportinit-lib: Scaffold a new library repo: creates root +src/ docs/ tests/folders,.editorconfig,.gitignore,.wakatime-project,LICENSE(MIT),README.md, solution and class library project with NuGet metadata (PackageId, Version, Authors, Description, License, RepositoryUrl, README).init-min: Scaffold a minimal library repo: same folders and top-level files asinit, but creates a minimal class library.csprojwithout any NuGet packaging metadata and does not create a test project (addstests/.gitkeep).init-proj: Scaffold a single project folder choosing between minimal (--mindefault) or NuGet template (--nugetblanks metadata values but keeps tags present).
End-to-end tests are orchestrated by tests/run.ps1 and exercised via entrypoint commands. They scaffold temporary mock projects, run the toolchain (init/build/bump/tag/test/clean), and print a concise summary.
# Windows (CMD/PowerShell wrapper)
dotnet-tools cmd-test-all # run both CMD suites
dotnet-tools cmd-test-lib # only init-lib suite (CMD)
dotnet-tools cmd-test-min # only init-min suite (CMD)
# Bash wrapper (Git Bash/WSL/Linux)
./dotnet-tools bash-test-all # run both Bash suites
./dotnet-tools bash-test-lib # only init-lib suite (Bash)
./dotnet-tools bash-test-min # only init-min suite (Bash)- init:
init-lib(NuGet metadata project) andinit-min(minimal project) - build: builds the generated solution under
src/ - bump: updates
<Version>in.csproj- init-lib:
--patch - init-min: sets an explicit initial version (e.g.,
0.1.0)
- init-lib:
- tag: creates
v<Version>git tag based on.csproj - test: runs tests and generates coverage (if ReportGenerator is installed)
- clean: removes
bin/andobj/
- Each step logs with
[TEST], followed by[SUCCESS]or[FAIL]per command. - Final line shows:
[SUMMARY] <passed> of <total> tests passed.
- Location:
tests/mock/<suite>/... - Cleanup:
- Always cleaned before a run starts.
- For
*-test-all, the first suite cleans the mock root; subsequent suites in the same run do not clean again so both mock projects remain. - For single-suite runs (
*-test-libor*-test-min), the mock root is cleaned before that suite.
- Tests initialize a nested git repo under each mock project and scope all git commands with
git -C <repoRoot>. - A safety check aborts if
git rev-parse --show-toplevelis outsidetests/mock. - Line-ending warnings are suppressed locally via repo config (
core.autocrlf=false,core.safecrlf=false).
dotnetSDK installed.gitavailable on PATH.- Coverage (optional):
dotnet tool install -g dotnet-reportgenerator-globaltool. - For Bash suites: Git Bash/WSL/Linux environment capable of invoking PowerShell (
powershell.exeorpwsh).
Scaffolds a new repository structure for a class library.
Usage:
dotnet-tools init-lib [--root <RootName|Path>] [--solution Name] [--project Name] \
[--author "Full Name"] [--company "Company"] [--description "NuGet package description"] \
[--version 0.1.0] [--packageId Id] [--repositoryUrl URL] [--json path/to/params.json]Parameters:
--root <RootName|Path>: Root folder to create (must not exist). If omitted, value defaults from JSONRootFolder.--solution: Solution name (default: JSONSolutionNameorGetFileName(--root)).--project: Project name (default: JSONProjectNameor--solution).--author: NuGetAuthors(default: JSONAuthoror environment user).--company: NuGetCompany(default: JSONCompanyor--author).--description: NuGet description (default: JSONDescriptionor "<Project> library").--version: Initial<Version>(default: JSONVersionor 0.1.0).--packageId: NuGetPackageId(default: JSONPackageIdor--project).--repositoryUrl: NuGetRepositoryUrl(default: JSONRepositoryUrlorgit remote get-url originif available).--json: Path to parameters JSON. Mutually exclusive with other flags.
Behavior and precedence:
- Flags are optional. When a flag is not provided, values are read from
parameters/init-parameters.jsonby default (or the file given via--json). - Do not mix
--jsonwith other flags. Provide either a JSON file or use flags; mixing them returns an error. - Root path must come from
--rootor JSONRootFolder. Positional arguments are not supported. - The script validates the parameters JSON exists and includes all required keys:
RootFolder,SolutionName,ProjectName,Author,Company,Description,PackageId,RepositoryUrl,PackageLicenseExpression,Version.
Generated structure:
<Root>/
.editorconfig
.gitignore
.wakatime-project
LICENSE # MIT (current year + author)
README.md # Header: solution name, body: description
src/
<Solution>.sln
<Project>/
<Project>.csproj # With NuGet metadata
README.md # Copied for NuGet packaging
tests/
NuGet metadata added to <Project>.csproj:
PackageId, Version, Authors, Company, Description, PackageLicenseExpression, RepositoryUrl (if git remote origin available or provided), PackageReadmeFile.
Templates:
LICENSE(MIT) is rendered fromtemplates/LICENSE.MIT.templatewith current year and author.- Solution is rendered from
templates/solution.sln.template. - Project is rendered from either
templates/project.nuget.csproj.template(NuGet metadata) ortemplates/project.minimal.csproj.template(minimal). - Test project (when applicable) is rendered from
templates/project.Tests.csproj.template.
Scaffolds the same repository structure as init-lib, but generates a minimal project with no NuGet metadata and no test project.
Usage:
dotnet-tools init-min [--root <RootName|Path>] [--solution Name] [--project Name] \
[--author "Full Name"] [--description "Project description"] [--json path/to/params.json]Behavior and notes:
- Uses the same parameter resolution rules as
init-lib. If--jsonis provided, do not mix it with flags. Only a minimal subset of JSON keys is required:RootFolder,SolutionName,ProjectName,Author,Description. - Generates:
.editorconfig,.gitignore,.wakatime-project,LICENSE(MIT),README.md,src/<Solution>.sln,src/<Project>.csproj(minimal),docs/.gitkeep,tests/.gitkeep. - Templates used:
templates/solution.sln.templateandtemplates/project.minimal.csproj.template.
This project is licensed under the MIT License. A copy of the license is available at LICENSE in the repository.