Automate Visual Studio project management from Package Manager Console

Visual Studio 2012 and above comes with the Package Manager Console, PowerShell-based tool used to automate NuGet package management from the command line.  But you can use it to access and change solutions and projects loaded into your Visual Studio using the DTE API, same interface Visual Studio exposes to its Addins.

In this post, I will demonstrate how you can replace all references to binaries (dll or exe files) with references to projects loaded into your solution. Such operation is necessary when you need to debug the code inside the dependencies. This use case is a frequent occurrence in development shops that split their code base into core, more technie, low-level functionality and higher level business-level code. The lower-level code is encapsulated into one or more dlls and the higher-level developers reference those dlls when developing their client-facing solutions.

Usually, the lower-level code has been well designed, developed and properly unit tested to make them work as expected. But sometimes something is not right, and it’s up to the app developer, who is on the deadline to figure out why. So the app developer would check out the code for the lower-level API and would load the lower-level API  solution into their existing higher-level app solution. You can do this by

  1. right-clicking on the existing loaded solution
  2. picking Add -> Existing Project,
  3. changing the dropdown in the file picker to Solution Files (*.sln)
  4. picking the dependency solution in the dialog

The next step, would be for developers to manually replace references in your higher-level projects, removing references to the binary dll or exe files and pointing them to their equivalent projects that are now part of your solution.  This operation is tedious and error prone and is a perfect candidate for Powershell automation.   Let’s get started.

Before we begin, however, lets make it easier to separate the two sets of projects into the lower-level projects and the higher level projects.  The best way to segregate inside a single solution is to use solution folders.  So let’s create a folder called “Dependencies” and drag all the lower-level API projects under it.  You can create a solution folder by right-clicking on your solution and picking Add -> New Solution Folder.  We are ready now!

Switch to or open the Package Manager Console.  In VS2012  you can open it from TOOLS -> NuGet Package Manger -> Package Manager Console.  You should now be in the command prompt.  Here’s a script to perform such replacements.

function Update-References() 
{
	#find the solution folder called "Dependencies" where the dependency projects live
	$depFolder = $dte.Solution.Projects | where { $_.Kind -eq "{66A26720-8FB5-11D2-AA7E-00C04F688DDE}" -and $_.Name -eq 'Dependencies' }
	if(-not $depFolder)
	{
		Write-Error "Did not find a Dependencies folder in your solution.  It must be a top-level folder";
		return -1
	}

	#Load a list of all the dependency projects into a hashtable
	$depProjects = @{}
	$depFolder.ProjectItems | % { $_.Object }  | % { $depProjects.add($_.Name, $_) }

	#load a list of all high-level projects (my projects)
	$myProjects = $dte.Solution.Projects | where { $_.Kind -eq '{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}' }

	#iterate through references in my projects and remember those that match the dependency projects.  Once matched, remove the old reference
	$projectToRefName = @{}
	foreach ($project in $myProjects.Object) 
	{ 	
		$lst = new-object system.collections.arraylist
		$projectToRefName.Add($project, $lst)
		foreach($reference in $project.References  | where { $depProjects.Contains($_.Identity) } )	 
		{ 
			$lst.add($reference.Identity)
			$reference.Remove() 
		}
	}

	#replace add a project reference for all the binary references we removed
	foreach ($project in $myProjects.Object) 
	{ 	
		$lst = $projectToRefName[$project]
        $depProjects.Values | where { $lst.Contains($_.Name) }  | % { $project.References.AddProject($_) } 
	}
}

Export-ModuleMember Update-References 

You can save this script into a script module file (*.psm1) somewhere and then use Import-Module command to load it. The Import-Module command takes the full path to the psm1 file like this:

Import-Module C:\MyPowershellModules\UpdateReferences.psm1

#if you need to make a change to the file, you will need to remove module first, because importing it again
Remove-Module UpdateReferences

Leave a comment