NuGet packages inventory across multiple solutions
Recently, I ran across a situation where a suite of application had multiple Visual Studio Solutions (which I think is a very common scenario) and each solution was managed by different team. There were some project reference dependencies between projects in different Visual Studio solutions. In this case, I wanted to view the NuGet references across all Visual Studio solutions so that I can verify if multiple versions of a package were used in different projects in different solutions.
Visual Studio 2015 gives us an option to Manage NuGet packages for not just a single project but for entire solution as well. This option works best when you want to update the NuGet packages or consolidate i.e. upgrade/downgrade NuGet packages to same version across the solution. If you see discrepancy in the package versions, you can install the correct version and fix the discrepancy. Simple as it sounds!
But there isn't any way to get inventory of NuGet packages for multiple Visual Studio solutions and run a consolidation of packages. So I resorted to writing a small utility program in C# that could help me with inventory and I could have teams work on evaluating right package version and consolidating there references.
There are 2 approaches to this problem
- The proposed program should be able to read through the Visual Studio Solutions and its Project files. For each Visual Studio Project, it should read the metadata and scan the NuGet Packages and then create an inventory in CSV file.
- The proposed program should be able to scan through available packages.config file recursively. For each packages.config file, the program should extract content and create an inventory in CSV file.
In the first approach, I get a mapping of project and the packages referenced in the project. In the second approach, I get a mapping of packages.config (which is unique per project) and the packages listed in it. The second approach gives fairly accurate information required with lesser code.
NuGet Core API - PackageReferenceFile
For the second approach, the proposed program requires referencing NuGet.Core package to use PackageReferenceFile API that abstracts the management of packages.config file for us. You can do a lot more than just reading the packages.config file like add/delete entries, create package file from a project, etc with this API.
Scan Utility
Below is a console program that scans through all packages.config in the folder specified and creates a list of SearchResults object. SearchResults object holds the package references, path of packages.config and version used in each packages.config
At the end of the program, list of SearchResults can be filtered for packages whose multiple versions have been referenced in the solutions or to get full inventory of NuGet packages in all solutions.
class Program
{
static void Main(string[] args)
{
string path = @"C:\Work\Repository\TFS\Research";
string[] files = Directory.GetFiles(path, "packages.config", SearchOption.AllDirectories);
const string format = "{0},{1},{2}";
StringBuilder builder = new StringBuilder();
List<SearchResults> results = new List<SearchResults>();
foreach (var fileName in files)
{
var file = new PackageReferenceFile(fileName);
foreach (PackageReference packageReference in file.GetPackageReferences())
{
SearchResults currentResult = results.FirstOrDefault(x => x.PackageId == packageReference.Id);
if (currentResult == null)
{
currentResult = new SearchResults
{
PackageId = packageReference.Id
};
results.Add(currentResult);
}
SearchVersion currentVersion = currentResult.Versions.FirstOrDefault(x => x.PackageVersion == packageReference.Version.ToString());
if (currentVersion == null)
{
currentVersion = new SearchResults.SearchVersion
{
PackageVersion = packageReference.Version.ToString()
};
currentResult.Versions.Add(currentVersion);
}
currentVersion.Path.Add(fileName);
}
}
results.ForEach(result =>
{
if (result.Versions.Count > 1) // multiple versions of same package
{
result.Versions.ForEach(version =>
{
version.Path.ForEach(packagePath =>
{
builder.AppendFormat(format, result.PackageId, packagePath, version.PackageVersion);
builder.AppendLine();
});
});
}
});
File.WriteAllText(Path.Combine(path, "packages-duplicates.csv"), builder.ToString());
}
}
public class SearchResults
{
public string PackageId { get; set; }
public List<SearchVersion> Versions { get; set; }
public SearchResults()
{
Versions = new List<SearchResults.SearchVersion>();
}
public class SearchVersion
{
public string PackageVersion { get; set; }
public List<string> Path { get; set; }
public SearchVersion()
{
Path = new List<string>();
}
}
}
In the above utility program, the CSV file only has packages with different versions as they are the right candidates for consolidation. You can apply a Pivot in Excel to detect duplicate packages and this gives good insight to the developers to evaluate upgrade/downgrade options.
The next step is to consolidate the NuGet packages in each Visual Studio solution based on the baseline versions of these packages and test the applications for any breaks.
You can also integrate this program in your build process and generate report to regulate the versions of packages used across solutions
Happy coding!