A fast .NET SDK version manager. dv installs Microsoft's official .NET SDKs
in seconds — verified, side by side, no admin rights — and resolves which one
a command runs under from the directory you are standing in.
dv install 10.0 # newest 10.0 SDK, or 10, 10.0.4xx, 10.0.402, lts
dv pin 8.0 # writes global.json here
dotnet --version # 8.0.426There is no new version file. global.json is what the dotnet host,
Visual Studio, Rider and CI images already read, so it is what dv pin writes
and what dv resolves from — applying rollForward with the same rules as the
host's own resolver, ported from its source.
dv does not replace NuGet or dotnet tool. It is the SDK half: getting the
right SDKs onto the machine and pointing dotnet at the right one.
curl -fsSL https://raw.githubusercontent.com/adrum/dv/main/install.sh | shOn Windows, in PowerShell:
powershell -ExecutionPolicy Bypass -c "irm https://raw.githubusercontent.com/adrum/dv/main/install.ps1 | iex"Either downloads the build for your platform, verifies it against the
published sha256, and puts dv in ~/.local/bin (override with DV_BIN_DIR).
Neither touches your shell profile — that line is yours to paste, below.
Or from source:
cargo build --release
install -m 0755 target/release/dv /usr/local/bin/dvThen put the shims in front of everything else on PATH:
eval "$(dv init zsh)" # or bash, sh, fish, powershell — add this to your shell profiledv doctor confirms it worked, and says plainly what to fix when it did not.
dv install [version] |
install 10, 10.0, 10.0.4xx, 10.0.402, latest or lts — or, with no version, what global.json here asks for |
dv uninstall <version> |
remove an installed SDK |
dv sdk install|uninstall|list |
the same as install, uninstall, list |
dv runtime install <component@version> |
install a runtime without an SDK — 8.0, aspnetcore@8.0; uninstall and list too |
dv update |
install the newest SDK every tracked line and global.json accepts, remove what nothing needs (--dry-run, --no-prune) |
dv list |
installed SDKs, active one marked |
dv list --remote |
what can be installed, beside what is (--all for EOL channels and every patch, --refresh to skip the 10-minute cache, --cached to never touch the network) |
dv pin <version> |
write global.json here |
dv default <version> |
SDK used when nothing pins one |
dv which [command] |
the binary that would run here, and nothing else |
dv run <command> … |
run dotnet, dnx, a global tool or anything on PATH under the resolved SDK |
dv resolve |
the SDK that would be used here (--source says why) |
dv init <shell> |
shell setup for PATH and DOTNET_ROOT (--hook adds cd-time switching) |
dv rehash |
rebuild the shims and install roots |
dv cache status|prune|clear |
inspect or clear downloaded archives |
dv doctor |
check the installation |
--format json |
machine-readable output for install, list, pin, resolve, which, doctor, … |
dv self update |
replace this binary with the newest build |
First match wins:
DV_DOTNET_VERSION- the nearest
global.json, when it names an SDK version — read with thedotnethost's ownrollForwardrules - the version set with
dv default - the newest installed release (a preview only when nothing else is installed)
dv which and dv resolve are read-only: they resolve and print, and never
install, write or touch the working directory. When nothing pins a version, dv
falls back to the newest installed SDK rather than failing.
An app built for net8.0 needs the 8.0 runtime even when SDK 10 builds it.
dv runs each SDK from an install root holding that SDK and every installed
runtime and targeting pack, so a project targeting net8.0;net10.0 builds
and runs on both with nothing extra to install. The roots are hard links, so
they cost no extra disk.
~/.local/share/dv/ (DV_HOME; %LOCALAPPDATA%\dv on Windows)
versions/10.0.402/ SDK archives, extracted exactly as published
roots/10.0.402/ this SDK + every runtime — what the shim runs
dotnet/ everything — what DOTNET_ROOT names
shims/dotnet real files on PATH
cache/ downloaded archives
tracked.json what was asked for — what `dv update` moves forward
config.toml default version, release index location
Shims are real files rather than a shell hook because dotnet has to resolve
correctly outside an interactive shell — editors, language servers, CI
runners and git hooks inherit PATH and nothing else. Each shim carries a
# managed-by: dv marker, and dv never removes a file it did not write.
dv has no built-in list of versions. It reads Microsoft's release metadata
when you install or ask for --remote, so every SDK, preview and patch
Microsoft publishes is installable the day it ships, with no dv update. The
metadata is cached: --remote reuses a copy from the last 10 minutes
(--refresh checks now), installs always check, and both fall back to the
cache when offline.
Microsoft signs its release metadata, and dv verifies that signature before
it trusts a single hash in it: a detached CMS signature that must chain to
DigiCert Trusted Root G4 (built into dv), belong to Microsoft, and not be past
the expiration Microsoft signs into each file. Every archive is then checked
against the sha512 in that verified metadata before it is extracted. Both
fail closed — unsigned metadata, a bad signature or a missing hash is an
error, never a reason to skip the check. Extraction refuses absolute paths,
.. and links, and dv doctor reports an installed tree that changed.
Certificate revocation is not checked.
macOS, Linux (glibc and musl) and Windows, on x64 and Arm64 — anywhere Microsoft publishes an SDK. The client is a single Rust binary with no runtime dependencies.
Full docs at https://adrum.github.io/dv — guides, the command reference,
global.json and rollForward in detail, and how install roots work.
The site lives in docs/ (Astro + Starlight):
cd docs && npm install && npm run devMIT — see LICENSE. The SDKs dv installs are Microsoft's, under
Microsoft's licenses, which ship inside each SDK.
dv reports nothing anywhere. It talks to the network when you install,
update, or ask for --remote, and not otherwise. The SDKs it installs have
their own telemetry, which DOTNET_CLI_TELEMETRY_OPTOUT=1 turns off.