How can you find unused NuGet packages in solution?

Visual StudioNuget

Visual 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:

  1. Analyze your code and collecting references to assemblies.
  2. Build NuGet usage graph based on usages of assemblies.
  3. 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.

enter image description here

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. enter image description here

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.

  1. Use ReSharper or similar code analysis tool to identify any unused references in your projects and uninstall the nuget in the corresponding projects.

  2. 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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSteveCView Question on Stackoverflow
Solution 1 - Visual StudioulexView Answer on Stackoverflow
Solution 2 - Visual StudiodknaackView Answer on Stackoverflow
Solution 3 - Visual StudioNick GallimoreView Answer on Stackoverflow
Solution 4 - Visual StudioJeeShen LeeView Answer on Stackoverflow
Solution 5 - Visual StudioPEKView Answer on Stackoverflow
Solution 6 - Visual StudioSumesh EsView Answer on Stackoverflow
Solution 7 - Visual StudioNikhil GuptaView Answer on Stackoverflow
Solution 8 - Visual StudioangularsenView Answer on Stackoverflow
Solution 9 - Visual StudiolucdmView Answer on Stackoverflow