Here’s something interesting that I started a few days ago and a colleague of mine just managed to finish. We are building a program which requires functionality similar to the Add Reference dialogue in Visual Studio, in the sense that I wanted something tha can enumerate all available assemblies and index them somewhere ready for insertion as references. Sounds easy, right? Well, it took a bit of effort.
My first implementation used particular registry keys that Visual Studio initialized. This worked, but it was silly for two reasons: it had to be platform-adaptive (32-bit and 64-bit systems used different keys), and it was incomplete. For example, you’d get something like half the assemblies that needed to be there. Why was that? Because only part of those assemblies that VS referes to in registry are available in the Add Reference dialogue.
So, where does VS get them from? I got the idea while looking at SharpDevelop – it’s an open-source VS-alike IDE, and I was able to glean from the sources (it wasn’t easy) that SharpDevelop actually enumerated the GAC. How amazing! I thought, knowing full well that you cannot reference a GAC assembly in a VS project. But apparently, MSBuild has a particular build step where you can map assembly paths in GAC to local assembly paths in your .Net Framework installation folder, Reference Assemblies folder as well as those other tricky folders that you probably wouldn’t find mentioned anywhere else.
I present the code below. Basically, given an assembly name, the method returns the physical location of the DLL(s) which contain the assembly. This lets you use practically anything in the GAC without much effort, though of course you’ll still run into a situation where a GAC DLL will resolve into a GAC DLL, meaning you can’t use it for a Visual Studio project. But those cases are typically very easy to pick up.
public static IList<string> GetLocations(string assemblyName)
{
var engine = new Engine(ToolsetDefinitionLocations.Registry |
ToolsetDefinitionLocations.ConfigurationFile);
var project = engine.CreateNewProject();
project.AddNewItem("Reference", assemblyName, true);
project.AddNewImport("$(MSBuildToolsPath)\\Microsoft.CSharp.targets", null);
project.SetProperty("TargetFrameworkVersion", "v3.5");
project.SetProperty("BuildingProject", "false");
project.SetProperty("Platform", "AnyCPU");
project.SetProperty("OutputPath", ".");
project.SetProperty("Configuration", "Release");
//ConsoleLogger logger = new ConsoleLogger();
//engine.RegisterLogger(logger);
engine.DefaultToolsVersion = "3.5";
if (!project.Build("ResolveAssemblyReferences"))
throw new Exception("Could not resolve location of " + assemblyName);
var result = new List<string>();
foreach (BuildItem bi in project.GetEvaluatedItemsByName("_ResolveAssemblyReferenceResolvedFiles"))
result.Add(bi.FinalItemSpec);
return result;
}