260

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.

Pang
  • 9,564
  • 146
  • 81
  • 122
SteveC
  • 15,808
  • 23
  • 102
  • 173
  • 2
    You realize that breaking changes in packages you aren't using wouldn't affect you anyway... As for the question, I just remove all NuGet packages and re-add what the compiler tells me. – Ohad Schneider Jul 30 '17 at 16:11
  • 9
    @OhadSchneider Nope ... but the OCD in me doesn't want all the cruft of the unused packages, e.g. in the deployment – SteveC Jul 31 '17 at 08:20
  • 3
    @OhadSchneider Doing that can be a problem if you're intentionally not using the latest versions of certain packages. – Matthew Sep 27 '20 at 12:35
  • 11
    For future readers, VS2022 has this option built-in. Just right-click any project in Solution Explorer and choose `Remove Unused References`. This is mentioned in an answer below that hasn't received a lot of attention yet. – dotNET Feb 04 '22 at 05:28
  • I have VS2022, and I do not see any "Remove Unused References" option. Ver 17.2.3 – KWallace Jun 29 '22 at 15:52
  • 2
    @KWallace, see Frank Rosario's comment in [JeeShen Lee's answer](https://stackoverflow.com/a/67715612/2615878). I have VS 2022 with a solution containing projects targeting .NET Framework 4.8, .NET Standard 2.0, and .NET 5.0. "Remove Unused References" is only available for the .NET Standard 2.0 and .NET 5.0 projects. I have to use ReSharper for the .NET Framework projects. – Mike Grove aka Theophilus Jul 01 '22 at 19:43

10 Answers10

80

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)

ulex
  • 1,147
  • 9
  • 7
  • 2
    I have 2016.1 and R# is not removing unused nuget references.. I'm using project.json with nuget3 - is that a known issue? – Peter McEvoy Aug 01 '16 at 12:58
  • 2
    @PeterMcEvoy Yes, this is known issue. Thanks for pointing out. I have updated answer to clarify that. – ulex Aug 01 '16 at 15:01
  • 6
    I must be blind but I'm not seeing any way to actually run this tool – claudekennilol Mar 09 '17 at 20:08
  • Unfortunately still not available in R# 2017.1 for nuget3/project.json... – Peter McEvoy Apr 20 '17 at 15:38
  • 23
    @claudekennilol just figured it out. right click the project and there is an option for "Optimize References..." – Matt Sanders Apr 20 '17 at 23:14
  • 1
    Right click project > Refactor > Remove Unused References > Analyze Used References DOES NOT analyze nuget packages. You must right click project > Optimize references – Nick Gallimore Jun 15 '18 at 12:52
  • question is by visual studio, not in case of already using resharper. – kurtanamo Feb 01 '19 at 11:06
  • resharper is a VS extension you pay for correct? $299 per year right? – Fractal May 20 '19 at 18:56
  • 1
    @Fractal Correct. It's very (too) expensive for private users, but if you're in any type of workplace, then Resharper is well worth it for the vastly faster and cleaner workflow you get, compared to default VS. – Excludos Aug 05 '19 at 11:09
  • They also have a free student license. – hvaughan3 Sep 14 '19 at 18:27
  • @Excludos - I would pay for ReSharper without hesitation were my work to stop buying it for me. – bubbleking Apr 14 '21 at 01:34
  • @bubbleking You shouldn't expect everyone to be able to pay out for it simply because you can. Depending on where you live in the world, $300 a year can be a lot of money. – Excludos Apr 22 '21 at 08:40
  • @Excludos - Please point me to where I said I expected anything from anyone. I was merely providing testimony against your incorrect assertion that it’s “too expensive for private users.” Note, also, that you expect “any type of workplace” should be able pay for it, regardless of organization size or where they are in the world. I’m sure at least one workplace out there might feel insulted that you don’t count them as an actual workplace. – bubbleking Apr 22 '21 at 12:43
49

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

JeeShen Lee
  • 3,476
  • 5
  • 39
  • 59
  • 2
    I'm not seeing this option, did you have some other option enabled to show you experimental items? I'm using VS Pro 2019, 16.9.3 – debracey Jun 23 '21 at 20:11
  • Not sure about VS Pro. I'm using VS Community 16.9 earlier and now VS Community 16.10, both have shown the option. Anyone with VS Pro can help to verify this? – JeeShen Lee Jun 25 '21 at 01:48
  • 3
    @debracey VS version 16.10 has the remove unused reference feature available. Right-click on the project > Remove Unused References. – JeeShen Lee Jul 13 '21 at 12:49
  • 6
    FYI this feature appears to only be available for new csproj files; older projects don't have this option currently. https://github.com/dotnet/roslyn/issues/54801 – Frank Rosario Oct 24 '21 at 18:28
  • 1
    This just offers to let me remove any and all packages. It does not say which are not used! Vs2022 – Daniel Williams Nov 30 '21 at 21:21
  • Agree with Daniel - just tried it and it removed used references and broke the project... – Chris Nevill Aug 11 '22 at 11:40
29

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.

Pang
  • 9,564
  • 146
  • 81
  • 122
dknaack
  • 60,192
  • 27
  • 155
  • 202
  • This program is too eager when it comes to deleting libraries... Be careful, when using a website, you will notice that a lot of DLLs are necessary but have been gone. If a NuGet package requires another one, you should not delete that one even though YOU don't have a hard dependency on it. – jsgoupil Dec 15 '16 at 05:44
  • I've used this tool with bad results. It's removed (or at least suggested to remove) a lot of things that were actually in use. There have been a few times I've trusted the suggestions and had to spend precious time trying to recover what it's removed. – Ryan VandenHeuvel Dec 06 '17 at 13:29
  • 5
    [NOTE] THE description clearly states: `The tool is not tested to work with DotNet Web projects(Asp.Net, MVC), Windows CE, Silverlight project types. Use it at your own risk.` – Korayem Jun 30 '18 at 11:18
  • This tool is really nice! It's smart enough to not remove NUnit.ConsoleRunner etc even though you don't have direct reference to it in your code – OlegI Nov 20 '18 at 10:09
  • This tool is slow. I have solution with over 250 projects.. I waited.. and waited.. sigh – Piotr Kula Mar 20 '19 at 09:06
23

Right-click on the Dotnet core project in visual studio 2019 you will see an option for Remove unused references. enter image description here

Sumesh Es
  • 465
  • 3
  • 15
22

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.

Pang
  • 9,564
  • 146
  • 81
  • 122
Nick Gallimore
  • 1,222
  • 13
  • 31
  • 4
    In VS.Net 2019 with Resharper, I found this option under: _Solution Explorer > **References** > Optimize references..._ – R. Schreurs Jan 15 '20 at 10:22
  • "The Optimize References tool window displays assembly references that are both unused and used in the current project, and shows how exactly references are used." https://blog.jetbrains.com/dotnet/2012/01/03/optimizing-assembly-references-with-resharper-61/ – Nick Gallimore Jan 15 '20 at 14:42
9

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

PEK
  • 3,688
  • 2
  • 31
  • 49
6

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

lucdm
  • 93
  • 1
  • 8
1

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

Brad Rem
  • 6,036
  • 2
  • 25
  • 50
Nikhil Gupta
  • 1,708
  • 1
  • 23
  • 38
1

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.

angularsen
  • 8,160
  • 1
  • 69
  • 83
1

In JetBrains Rider IDE

  1. Right-click solution or project
  2. "Refactor This"
  3. "Remove Unused References"
  4. Next...

https://www.jetbrains.com/help/rider/Refactorings__Remove_Unused_References.html

Tim Abell
  • 11,186
  • 8
  • 79
  • 110