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.
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:
- Remove the .nuget folder from your solution. Make sure the folder itself is also removed from the solution workspace.
- 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:
- Recursively search for .csproj files in a path
- Remove the MSBuild target for NuGet package restore
- 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:
- Migrate away from MSBuild-based NuGet package restore (Xavier Decoster)
- The right way to restore NuGet packages (David Ebbo)