dotnet-consolidate
5.0.0
dotnet tool install --global dotnet-consolidate --version 5.0.0
dotnet new tool-manifest
dotnet tool install --local dotnet-consolidate --version 5.0.0
#tool dotnet:?package=dotnet-consolidate&version=5.0.0
nuke :add-package dotnet-consolidate --version 5.0.0
dotnet consolidate
.NET tool that verifies that all NuGet packages in a solution are consolidated.
Developers typically consider it bad practice to use different versions of the same NuGet package across different projects in the same solution.
The tool finds such discrepancies.
Installation
dotnet tool install dotnet-consolidate --global
Requires the .NET 8 runtime or newer. The .NET version your own projects target doesn't matter — the tool reads project files and never builds them.
Usage
Check a solution:
dotnet consolidate -s YourSolution.sln
Both .sln and .slnx are supported. With no -s, every solution in the working directory is checked:
dotnet consolidate
Check only certain packages:
dotnet consolidate -s YourSolution.sln -p Serilog Newtonsoft.Json
Check a whole family of packages, using wildcards:
dotnet consolidate -s YourSolution.sln -p "MyCompany.*"
Skip packages:
dotnet consolidate -s YourSolution.sln -e "MyCompany.Internal.*"
Skip prerelease versions:
dotnet consolidate -s YourSolution.sln --excludedVersionsRegex ".*-alpha$"
Check several solutions as one set:
dotnet consolidate -s Cms.sln TheSite.sln -c
Output when everything agrees:
All packages in YourSolution.sln are consolidated.
and when it doesn't:
Found 2 non-consolidated packages
----------------------------
Newtonsoft.Json
----------------------------
Sentry - 11.0.2
Sentry.Tests - 6.0.8
----------------------------
Microsoft.Extensions.Logging.Configuration
----------------------------
Sentry.Extensions.Logging - 2.1.0
Sentry.Extensions.Logging.Tests - 3.0.0
Options
| Option | Default | Description |
|---|---|---|
-s, --solutions |
all solutions in the working directory | Solutions to check, space separated. |
-p, --packageIds |
all packages | Check only these package IDs, space separated. |
-e, --excluded |
none | Package IDs to skip, space separated. |
--excludedVersionsRegex |
none | Regular expression matching versions to skip. |
-c, --crossSolution |
off | Check all given solutions as one set instead of one at a time. |
-d, --directoryBuildProps |
true |
Count packages declared in Directory.Build.props as references of the projects below it. |
-o, --reportOverridenDirectoryBuildProps |
true |
Report projects that override a version coming from their Directory.Build.props. |
--property |
none | MSBuild properties as Name=Value pairs, used when evaluating conditions in project files. |
-f, --format |
Text |
Output format, Text or Json. |
--help, --version |
Print usage or the tool version and exit successfully. |
-p and -e match package IDs case-insensitively, the way NuGet does, and both accept wildcards: * for any run of characters, ? for exactly one. An entry without a wildcard is matched in full, so -p Serilog does not check Serilog.Sinks.Console. Quote patterns in POSIX shells, otherwise * may expand to file names. The two options combine, -e narrowing what -p selected:
dotnet consolidate -s YourSolution.sln -p "MyCompany.*" -e "MyCompany.Internal.*"
-d and -o need an explicit value to be turned off — write -d false, not a bare -d, which reads whatever follows it as its value.
Exit codes
The tool exits with a non-success code when
- packages are not consolidated,
- a
-pentry matches no package in the solution (almost always a typo, and a pattern matching nothing means nothing was checked), - the command line can't be parsed, for example an unknown option, or one repeated where it takes a space-separated list. The complaint goes to stderr.
Directory.Build.props overrides are informational and never affect the exit code.
Multiple solutions
Several solutions are checked one at a time, each against itself. -c (--crossSolution) checks them as one set instead, so a package referenced at 1.0.0 in one solution and at 2.0.0 in another is reported even though neither solution disagrees with itself. There is a single report for the whole set, and a project belonging to more than one solution is counted once — the reading from the first solution on the command line wins, including the Directory.Build.props it inherited from.
One thing to know before putting -c in a build: -p then asks whether a package is referenced anywhere in the set, so an ID that only one of the solutions references is no longer reported as missing. That is the one way this flag can turn a failing run into a passing one.
Directory.Build.props
Packages declared in a Directory.Build.props count as references of every project underneath it. To compare project files alone, use -d false.
When a project declares a package that its Directory.Build.props already declares, the project file wins and the central version stops applying to it. -o reports that, with both versions and the props file to go and change:
Found 1 Directory.Build.props overrides
----------------------------
Serilog
----------------------------
ProjectB - 4.0.0 overrides 3.0.1 from C:\src\MySolution\Directory.Build.props
The overlap is reported even when the two versions match, since the copy in the project file silently stops following the props file the next time it is bumped.
Both ways of overriding are recognised:
<PackageReference Include="Serilog" Version="3.0.1" />
<PackageReference Include="Serilog" Version="4.0.0" />
<PackageReference Update="Serilog" Version="4.0.0" />
<PackageReference Remove="Serilog" /> is honoured as well: the project stops counting as a reference of the package altogether. A removal is not an override and is not reported.
Update and Remove only act on packages the project inherits, so -d false leaves them with nothing to change. Within a project file, MSBuild order applies — an Update or a Remove affects the PackageReference items declared above it and no others.
MSBuild conditions
Condition attributes on PropertyGroup, ItemGroup and PackageReference are evaluated, so a package reference that isn't actually active is left out of the check:
<ItemGroup Condition="'$(NuGetBuild)' == 'true'">
<PackageReference Include="MyPackage" Version="1.0.0" />
</ItemGroup>
Property values are supplied with --property, and they take precedence over anything the project file sets:
dotnet consolidate -s YourSolution.sln --property NuGetBuild=true Configuration=Release
$(...) references in Include and Version are expanded too, so Version="$(SerilogVersion)" is compared as the version it resolves to. When a property can't be resolved, the literal text is kept — except on an Update, which is dropped instead, leaving the inherited version standing. Overwriting a real version with the text $(SerilogVersion) would invent a discrepancy, and a property declared in the Directory.Build.props is unresolvable in the project file, which is parsed separately.
A project that multi-targets is evaluated once per entry in <TargetFrameworks> and the results are combined, so references guarded by '$(TargetFramework)' == '...' still take part in the check.
The supported part of the condition language is ==, !=, numeric comparisons, And, Or, !, parentheses, Exists() and HasTrailingSlash(). Anything beyond that, such as a property function like $([MSBuild]::VersionGreaterThan(...)), can't be evaluated; the tool says so and keeps the package references that condition guards, rather than dropping them. A Remove behind such a condition is discarded for the same reason, and an Update behind one is applied without displacing the inherited version, so both are reported. Import directives are not followed, so properties defined in an imported file are unknown (and therefore empty).
JSON output
-f json prints a single JSON document to stdout and nothing else. Progress messages are suppressed rather than moved to stderr, so dotnet consolidate -f json | ConvertFrom-Json works and CI systems that treat any stderr output as a failure stay happy. The one exception is a command line the parser rejects, whose complaint reaches stderr before any of this — that run fails on its exit code anyway. Anything the tool would have reported along the way (a project that couldn't be parsed, a condition it couldn't evaluate) is carried in warnings. Exit codes are the same as for the text format.
{
"warnings": [],
"solutions": [
{
"solutionFile": "YourSolution.sln",
"solutionFiles": ["YourSolution.sln"],
"isParsedWithoutIssues": true,
"packageIdsNotFound": [],
"nonConsolidatedPackages": [
{
"packageId": "Newtonsoft.Json",
"packageVersions": [
{ "projectName": "ProjectA", "version": "11.0.2" },
{ "projectName": "ProjectB", "version": "13.0.3" }
]
}
],
"directoryBuildPropsOverrides": [
{
"packageId": "Serilog",
"projectName": "ProjectB",
"version": "4.0.0",
"directoryBuildPropsVersion": "3.0.1",
"directoryBuildPropsFile": "C:\\src\\MySolution\\Directory.Build.props"
}
]
}
]
}
solutionFiles lists the solutions an entry covers and is always present. Ordinarily that is the one solution the entry is about, and solutionFile repeats it; with -c there is a single entry for the whole set, solutionFiles holds each path exactly and solutionFile is them joined for display.
A rejected command line produces the same document rather than nothing at all — the parser's complaint in warnings, an empty solutions, and a non-success exit code:
{
"warnings": [
"Option 'p, packageIds' is defined multiple times."
],
"solutions": []
}
Testing a development version of the tool locally from source
Run the following commands in src/DotNet.Consolidate:
dotnet build
dotnet pack
The package will be created under bin/Release.
Open the folder of the solution where you want to test the tool, then run:
dotnet tool install dotnet-consolidate --local --add-source <full path of bin/Release>
dotnet consolidate -s YourSolution.sln
When you're finished, you can also uninstall it to clean up:
dotnet tool uninstall dotnet-consolidate
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.
| Version | Downloads | Last Updated | |
|---|---|---|---|
| 5.0.0 | 3,182 | 9/4/2026 | |
| 4.2.0 | 443,514 | 12/13/2023 | |
| 4.1.0 | 9,858 | 11/10/2023 | |
| 4.0.1 | 27,075 | 7/28/2023 | |
| 4.0.0 | 1,518 | 7/27/2023 | |
| 3.0.2 | 119,697 | 2/24/2023 | |
| 3.0.1 | 1,773 | 2/22/2023 | |
| 3.0.0 | 1,452 | 2/22/2023 | |
| 2.1.0 | 47,710 | 12/11/2022 | |
| 2.0.0 | 104,643 | 8/30/2022 | |
| 1.2.2 | 30,656 | 12/13/2021 | |
| 1.2.1 | 52,283 | 1/4/2021 | |
| 1.2.0 | 1,662 | 12/29/2020 | |
| 1.1.0 | 1,846 | 11/5/2020 | |
| 1.0.0 | 4,245 | 4/5/2020 |