How can you find unused NuGet packages in solution?
Visual StudioNugetVisual Studio Problem Overview
How can you find the unused NuGet packages in a solution?
I've got a number of solutions where there are a lot of installed packages, and a large number of them are flagged as having updates.
However, I'm concerned there may be breaking changes, so I first want to clean up by removing any unused packages.
Visual Studio Solutions
Solution 1 - Visual Studio
ReSharper 2016.1 has a feature to remove unused NuGet.
It can be run on a solution and on each project in a solution and it does the following things:
- Analyze your code and collecting references to assemblies.
- Build NuGet usage graph based on usages of assemblies.
- Packages without content files, unused itself and without used dependencies are assumed as unused and suggested to remove.
Unfortunately, this doesn't work for project.json
projects (RSRP-454515) and ASP.NET core projects (RSRP-459076)
Solution 2 - Visual Studio
You can use the Visual Studio Extension ResolveUR - Resolve Unused References.
> Resolve unused references including nuget references in Visual Studio 2012/2013/2015 projects via menu item at solution and project nodes Solution Explorer Tool Window
It's not an easy task, so I suggest to make a backup and/or commit before, just in order to rollback if something went wrong.
Solution 3 - Visual Studio
You can accomplish this using ReSharper 2019.1.1.
Right click on the project > Refactor > Remove Unused References.
If your project is small, you can also use: project > Optimize Used References . . .
A window will pop up. Select all references and remove them all. Then go back and re-add the ones that give you a compiler error.
Solution 4 - Visual Studio
Visual Studio 2019 (version 16.9) has the remove-unused-packages function built-in, we will need to enable it manually now.
Go to Tools > Options > Text Editor > C# > Advanced > (Under the Analysis section) Tick Show "Removed Unused References" command
Visual Studio version 16.10 has the remove unused reference feature available. Right-click on the project > Remove Unused References.
Solution 5 - Visual Studio
Below is a little PowerShell script that finds redundant NuGet packages for .NET Core / .NET 5+ projects. For each project file it removes every reference once and checks if it compiles. This will take a lot of time. After this you get a summary of each reference that might be excluded. In the end it is up to you do decide what should be removed. Most likely you will not be able to remove everything it suggest (due dependencies), but it should give you a good starting point.
Save the script below as a ps1-file and replace the string C:\MySolutionDirectory in line 89 with the directory you want to scan on and then run the ps1-file. Do an backup first in case something goes wrong.
function Get-PackageReferences {
param($FileName, $IncludeReferences, $IncludeChildReferences)
$xml = [xml] (Get-Content $FileName)
$references = @()
if($IncludeReferences) {
$packageReferences = $xml | Select-Xml -XPath "Project/ItemGroup/PackageReference"
foreach($node in $packageReferences)
{
if($node.Node.Include)
{
if($node.Node.Version)
{
$references += [PSCustomObject]@{
File = (Split-Path $FileName -Leaf);
Name = $node.Node.Include;
Version = $node.Node.Version;
}
}
}
}
}
if($IncludeChildReferences)
{
$projectReferences = $xml | Select-Xml -XPath "Project/ItemGroup/ProjectReference"
foreach($node in $projectReferences)
{
if($node.Node.Include)
{
$childPath = Join-Path -Path (Split-Path $FileName -Parent) -ChildPath $node.Node.Include
$childPackageReferences = Get-PackageReferences $childPath $true $true
$references += $childPackageReferences
}
}
}
return $references
}
function Get-ProjectReferences {
param($FileName, $IncludeReferences, $IncludeChildReferences)
$xml = [xml] (Get-Content $FileName)
$references = @()
if($IncludeReferences) {
$projectReferences = $xml | Select-Xml -XPath "Project/ItemGroup/ProjectReference"
foreach($node in $projectReferences)
{
if($node.Node.Include)
{
$references += [PSCustomObject]@{
File = (Split-Path $FileName -Leaf);
Name = $node.Node.Include;
}
}
}
}
if($IncludeChildReferences)
{
$projectReferences = $xml | Select-Xml -XPath "Project/ItemGroup/ProjectReference"
foreach($node in $projectReferences)
{
if($node.Node.Include)
{
$childPath = Join-Path -Path (Split-Path $FileName -Parent) -ChildPath $node.Node.Include
$childProjectReferences = Get-ProjectReferences $childPath $true $true
$references += $childProjectReferences
}
}
}
return $references
}
$files = Get-ChildItem -Path C:\MySolutionDirectory -Filter *.csproj -Recurse
Write-Output "Number of projects: $($files.Length)"
$stopWatch = [System.Diagnostics.Stopwatch]::startNew()
$obseletes = @()
foreach($file in $files) {
Write-Output ""
Write-Output "Testing project: $($file.Name)"
$rawFileContent = [System.IO.File]::ReadAllBytes($file.FullName)
$childPackageReferences = Get-PackageReferences $file.FullName $false $true
$childProjectReferences = Get-ProjectReferences $file.FullName $false $true
$xml = [xml] (Get-Content $file.FullName)
$packageReferences = $xml | Select-Xml -XPath "Project/ItemGroup/PackageReference"
$projectReferences = $xml | Select-Xml -XPath "Project/ItemGroup/ProjectReference"
$nodes = @($packageReferences) + @($projectReferences)
foreach($node in $nodes)
{
$previousNode = $node.Node.PreviousSibling
$parentNode = $node.Node.ParentNode
$parentNode.RemoveChild($node.Node) > $null
if($node.Node.Include)
{
$xml.Save($file.FullName)
if($node.Node.Version)
{
$existingChildInclude = $childPackageReferences | Where-Object { $_.Name -eq $node.Node.Include -and $_.Version -eq $node.Node.Version } | Select-Object -First 1
if($existingChildInclude)
{
Write-Output "$($file.Name) references package $($node.Node.Include) ($($node.Node.Version)) that is also referenced in child project $($existingChildInclude.File)."
continue
}
else
{
Write-Host -NoNewline "Building $($file.Name) without package $($node.Node.Include) ($($node.Node.Version))... "
}
}
else
{
$existingChildInclude = $childProjectReferences | Where-Object { $_.Name -eq $node.Node.Include } | Select-Object -First 1
if($existingChildInclude)
{
Write-Output "$($file.Name) references project $($node.Node.Include) that is also referenced in child project $($existingChildInclude.File)."
continue
}
else
{
Write-Host -NoNewline "Building $($file.Name) without project $($node.Node.Include)... "
}
}
}
else
{
continue
}
dotnet build $file.FullName > $null
if($LastExitCode -eq 0)
{
Write-Output "Building succeeded."
if($node.Node.Version)
{
$obseletes += [PSCustomObject]@{
File = $file;
Type = 'Package';
Name = $node.Node.Include;
Version = $node.Node.Version;
}
}
else
{
$obseletes += [PSCustomObject]@{
File = $file;
Type = 'Project';
Name = $node.Node.Include;
}
}
}
else
{
Write-Output "Building failed."
}
if($null -eq $previousNode)
{
$parentNode.PrependChild($node.Node) > $null
}
else
{
$parentNode.InsertAfter($node.Node, $previousNode.Node) > $null
}
# $xml.OuterXml
$xml.Save($file.FullName)
}
[System.IO.File]::WriteAllBytes($file.FullName, $rawFileContent)
dotnet build $file.FullName > $null
if($LastExitCode -ne 0)
{
Write-Error "Failed to build $($file.FullName) after project file restore. Was project broken before?"
return
}
}
Write-Output ""
Write-Output "-------------------------------------------------------------------------"
Write-Output "Analyse completed in $($stopWatch.Elapsed.TotalSeconds) seconds"
Write-Output "$($obseletes.Length) reference(s) could potentially be removed."
$previousFile = $null
foreach($obselete in $obseletes)
{
if($previousFile -ne $obselete.File)
{
Write-Output ""
Write-Output "Project: $($obselete.File.Name)"
}
if($obselete.Type -eq 'Package')
{
Write-Output "Package reference: $($obselete.Name) ($($obselete.Version))"
}
else
{
Write-Output "Project refence: $($obselete.Name)"
}
$previousFile = $obselete.File
}
You find more information here: https://devblog.pekspro.com/posts/finding-redundant-project-references
Solution 6 - Visual Studio
Right-click on the Dotnet core project in visual studio 2019 you will see an option for Remove unused references.
Solution 7 - Visual Studio
I don't think there is a default way to find this out. The primary reason being the variety of things these packages can do from referencing an assembly to injecting source code to your project. You may want to check the Nuget.Extensions though. The following thread on codeplex talks about an audit report of nuget packages.
http://nuget.codeplex.com/discussions/429694
(NuGet has been moved from Codeplex to GitHub. Archive of the above link:) https://web.archive.org/web/20171212202557/http://nuget.codeplex.com:80/discussions/429694
Solution 8 - Visual Studio
This is manual labor, but it works.
-
Use ReSharper or similar code analysis tool to identify any unused references in your projects and uninstall the nuget in the corresponding projects.
-
Sometimes uninstalled nugets still linger in the Installed packages and Updates lists in the Manage NuGet Packages dialog. Close Visual Studio then delete the
packages
folder, then reopen the solution and restore your nugets.
Solution 9 - Visual Studio
In Visual Studio 2019 starting from the latest versions and Visual Studio 2022 you can remove unused packages as reported in previous comments, but only for SDK style projects. If you try on old projects, like .Net Framework, you won't see this option. As workaround, to verify, you can create two simply console apps: one using .Net Core or later, and one .Net Framework 4.7 or 4.8.
Please refer to: Remove Unused References