Scripting .NET project migration to Automatic NuGet Package Restore

Background on NuGet Package Restore and Automatic Package Restore

NuGet Package Restore allows you to reference NuGet packages in your project without shipping them with your source code or committing them to source control. The general idea is that the packages are restored - that is, downloaded and installed - into your project when it is build. This offers a number of benefits, including better interaction with source control and smaller code distributions.

NuGet Package Restore was originally implemented using an MSBuild task, so whenever you ran a build in Visual Studio the build step would handle the package restore before continuing with the next steps in the build process. This worked, but had a few downsides. For one, it required MSBuild to work, which conflicts with cross-platform development, build servers, etc. Another issue is that it required a separate .nuget directory be added to your Visual Studio solution, including a copy of NuGet.exe and a targets file.

NuGet MSBuild directory

Migrating Automatic Package Restore

NuGet 2.7 (and later) added support for Automatic NuGet Package Restore without requiring MSBuild. For new projects, this all just automatically works. However, if you had an older project for which you'd manually configured NuGet Package Restore, there is a manual process to be followed:

    1. Remove the .nuget folder from your solution. Make sure the folder itself is also removed from the solution workspace.
    2. Edit each project file (e.g., .csproj, .vbproj) in the solution and remove any references to the NuGet.targets file. To do so, search for Nuget.targets and remove the entire <Import Project> line where it is referenced.

This is really easy - it just takes seconds to do on one project. However, if you've got a lot of projects, it's painful busywork. I was making some updates on the Web Camps Training Kit which has a lot of projects (several demos and hands on labs, each with begin and end state solutions). I wanted a recursive script that dug through all the subfolders and fixed up my projects for me automatically. I asked around on Twitter and heard about IFix, from Terje Sandstrom. It's a nice solution that handles this problem, but I wanted a recursive script, and if I was going to automate IFix with a recursive script I might as well just write a script that handled everything.

Enough Talk, Show Me The Script

Warning! This script edits all projects and deletes all .nuget and packages directories recursively for the specified path. That means if you ran it from C:\ it would modify every project on your C drive. Obviously, be careful about the current directory and make sure your affected projects are under source control.

Here's what it does:

  1. Recursively search for .csproj files in a path
  2. Remove the MSBuild target for NuGet package restore
  3. Recurse through the directory structure, removing .nuget and packages directories

A few admissions / excuses

Yes, it could be more terse. Get-ChildItem could be gci, Set-Content could be sc, all the switches could use the single letter equivalents. I used to be embarrassed posting verbose PowerShell, but I've decided that this is a lot easier to read and there's no real point to minifying PowerShell.

Yes, this is just doing a simple string replace. I looked at doing things the XML way (XPath or DOM manipulations) or using regular expressions, but decided that I want this to be completely explicit. I don't want to match any lines unless they're exactly matching what was written by Visual Studio when I enabled package restore. Because of that, I only want an exact string match. I think that this is a little faster since it's just a string replace, but I didn't test that and don't really care if it isn't.

Yes, this leaves a blank line in the .csproj file. That makes no difference in the way Visual Studio handles it and it was easier than worrying about it.

Yes, that $find declaration is weird. The string I'm searching for (<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />) contains a lot of characters that need to be escaped in PowerShell, and even more if doing a Regex replace in PowerShell (which I tested a bit). So instead I used a here-string, which is kind of like a C# verbatim string literal (e.g. @\\server\share\file.txt).

For more information on why you should migrate away from MSBuild based Automatic Package Restore, see these posts:

2 Comments

  • Hi !

    Thanks for referring to IFix !
    Just a small correction - IFix IS recursive :-)
    It will scan and fix all solutions and csproj from the current directory and down through all subfolders. Sorry if that was not quite clear - I will make the docs more explicit on that point.

    Also, regarding your PS script - the solution files needs to be fixed up as well, and also a bit more I think - look at the source code for IFix here: https://github.com/OsirisTerje/IFix/blob/master/RemoveOldNugetRestore/RemoveOldNugetRestore.cs

    regards
    Terje

  • This isn't a good solution in it's current form:
    1 In my .csprojs the import line is often like this:
    <Import Project="$(SolutionDir)\.nuget\nuget.targets" />

    2 You've missed out on the <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> ... </Target> in the .csproj's.

    3 I got a lot of errors about trying to remove non-empty package directories

    4 if you happen to have a directory called "packages" which doesn't store nuget stuff then it'll get deleted by the script

    It seems like the are a number of options for using nuget restore - even in the "best current practice" - i.e. whether you're using source control or not, and whether you want to manually run "nuget restore" yourself or wrap it in some pre-msbuild condition (and you then have the problem of where to put the nuget.exe)

    I think it's best to encourage people to do this process manually after they've figured out which option is appropriate to them. From there doing a:
    Find all "nuget.targets", Subfolders, Find Results 1, "C:\netreturn\Main", "*.csproj"

    is no harder than running some powershell script which you've downloaded off a blog

Comments have been disabled for this content.