Programmatically Resolve Assembly Name to Full Path the Same Way MSBuild Does
Jomo Fisher—Every once in a while I find I need to turn and assembly name like “System” or “System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL” into a path to the actual file for that assembly. This is a tricky problem considering the assembly name might be for a product install in Program Files or elsewhere. MSBuild already has rules for doing this resolution. Here’s how to call that code directly (written in F#):
#light
#r "c:\Windows\Microsoft.NET\Framework\v2.0.50727\Microsoft.Build.Tasks.dll"
#r "c:\Windows\Microsoft.NET\Framework\v2.0.50727\Microsoft.Build.Utilities.dll"
#r "c:\Windows\Microsoft.NET\Framework\v2.0.50727\Microsoft.Build.Framework.dll"
#r "c:\Windows\Microsoft.NET\Framework\v2.0.50727\Microsoft.Build.Engine.dll"
open Microsoft.Build.Tasks
open Microsoft.Build.Utilities
open Microsoft.Build.Framework
open Microsoft.Build.BuildEngine
/// Reference resolution results. All paths are fully qualified.
type ResolutionResults = {
/// Paths to primary references
referencePaths:string array
/// Paths to dependencies
referenceDependencyPaths:string array
/// Paths to related files (like .xml and .pdb)
relatedPaths:string array
/// Paths to satellite assemblies used for localization.
referenceSatellitePaths:string array
/// Additional files required to support multi-file assemblies.
referenceScatterPaths:string array
/// Paths to files that reference resolution recommend be copied to the local directory
referenceCopyLocalPaths:string array
/// Binding redirects that reference resolution recommends for the app.config file.
suggestedBindingRedirects:string array
}
let Resolve(references:string array, outputDirectory:string) =
let x = { new IBuildEngine with
member be.BuildProjectFile(projectFileName, targetNames, globalProperties, argetOutputs) = true
member be.LogCustomEvent(e) = ()
member be.LogErrorEvent(e) = ()
member be.LogMessageEvent(e) = ()
member be.LogWarningEvent(e) = ()
member be.ColumnNumberOfTaskNode with get() = 1
member be.ContinueOnError with get() = true
member be.LineNumberOfTaskNode with get() = 1
member be.ProjectFileOfTaskNode with get() = "" }
let rar = new ResolveAssemblyReference()
rar.BuildEngine <- x
rar.TargetFrameworkDirectories <- [|@"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\"|]
rar.AllowedRelatedFileExtensions <- [| ".pdb"; ".xml"; ".optdata" |]
rar.FindRelatedFiles <- true
rar.Assemblies <- [|for r in references -> new Microsoft.Build.Utilities.TaskItem(r):>ITaskItem|]
rar.SearchPaths <- [| "{CandidateAssemblyFiles}"
"{HintPathFromItem}"
"{TargetFrameworkDirectory}"
"{Registry:Software\Microsoft\.NetFramework,v3.5,AssemblyFoldersEx}"
"{AssemblyFolders}"
"{GAC}"
"{RawFileName}"
outputDirectory |]
rar.AllowedAssemblyExtensions <- [| ".exe"; ".dll" |]
rar.TargetProcessorArchitecture <- "x86"
if not (rar.Execute()) then
failwith "Could not resolve"
{
referencePaths = [| for p in rar.ResolvedFiles -> p.ItemSpec |]
referenceDependencyPaths = [| for p in rar.ResolvedDependencyFiles -> p.ItemSpec |]
relatedPaths = [| for p in rar.RelatedFiles -> p.ItemSpec |]
referenceSatellitePaths = [| for p in rar.SatelliteFiles -> p.ItemSpec |]
referenceScatterPaths = [| for p in rar.ScatterFiles -> p.ItemSpec |]
referenceCopyLocalPaths = [| for p in rar.CopyLocalFiles -> p.ItemSpec |]
suggestedBindingRedirects = [| for p in rar.SuggestedRedirects -> p.ItemSpec |]
}
try
let s = Resolve([| "System"
"System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL"
"Microsoft.SqlServer.Replication"
|], "")
printfn "%A" s
finally
ignore (System.Console.ReadKey())
The result of running the code looks like this:
{referencePaths =
[|"C:\\Program Files\\Microsoft SQL Server\\90\\SDK\\Assemblies\\Microsoft.SqlServer.Replication.dll";
"C:\\Program Files\\Reference Assemblies\\Microsoft\\Framework\\v3.5\\System.Core.dll";
"C:\\Windows\\assembly\\GAC_MSIL\\System\\2.0.0.0__b77a5c561934e089\\System.dll"|];
referenceDependencyPaths =
[|"C:\\Windows\\assembly\\GAC_MSIL\\System.DirectoryServices\\2.0.0.0__b03f5f7f11d50a3a\\System.DirectoryServices.dll";
"C:\\Windows\\assembly\\GAC_MSIL\\System.Xml\\2.0.0.0__b77a5c561934e089\\System.Xml.dll";
"C:\\Windows\\assembly\\GAC_32\\Microsoft.SqlServer.BatchParser\\9.0.242.0__89845dcd8080cc91\\Microsoft.SqlServer.BatchParser.dll";
"C:\\Windows\\assembly\\GAC_MSIL\\System.Runtime.Remoting\\2.0.0.0__b77a5c561934e089\\System.Runtime.Remoting.dll";
"C:\\Windows\\assembly\\GAC_MSIL\\Microsoft.Vsa\\8.0.0.0__b03f5f7f11d50a3a\\Microsoft.Vsa.dll";
"C:\\Windows\\assembly\\GAC_32\\System.Data.OracleClient\\2.0.0.0__b77a5c561934e089\\System.Data.OracleClient.dll";
"C:\\Program Files\\Microsoft SQL Server\\90\\SDK\\Assemblies\\Microsoft.SqlServer.ConnectionInfo.dll";
"C:\\Windows\\assembly\\GAC_MSIL\\Accessibility\\2.0.0.0__b03f5f7f11d50a3a\\Accessibility.dll";
"C:\\Windows\\assembly\\GAC_32\\System.Transactions\\2.0.0.0__b77a5c561934e089\\System.Transactions.dll";
"C:\\Windows\\assembly\\GAC_MSIL\\System.DirectoryServices.Protocols\\2.0.0.0__b03f5f7f11d50a3a\\System.DirectoryServices.Protocols.dll";
"C:\\Windows\\assembly\\GAC_MSIL\\System.Drawing\\2.0.0.0__b03f5f7f11d50a3a\\System.Drawing.dll";
"C:\\Windows\\assembly\\GAC_MSIL\\System.Deployment\\2.0.0.0__b03f5f7f11d50a3a\\System.Deployment.dll";
"C:\\Program Files\\Microsoft SQL Server\\90\\SDK\\Assemblies\\Microsoft.SqlServer.Smo.dll";
"C:\\Windows\\assembly\\GAC_32\\System.EnterpriseServices\\2.0.0.0__b03f5f7f11d50a3a\\System.EnterpriseServices.dll";
"C:\\Windows\\assembly\\GAC_MSIL\\System.Web.RegularExpressions\\2.0.0.0__b03f5f7f11d50a3a\\System.Web.RegularExpressions.dll";
"C:\\Program Files\\Microsoft SQL Server\\90\\SDK\\Assemblies\\Microsoft.SqlServer.WmiEnum.dll";
"C:\\Program Files\\Microsoft SQL Server\\90\\SDK\\Assemblies\\Microsoft.SqlServer.ServiceBrokerEnum.dll";
"C:\\Windows\\assembly\\GAC_MSIL\\System.Configuration\\2.0.0.0__b03f5f7f11d50a3a\\System.Configuration.dll";
"C:\\Windows\\assembly\\GAC_MSIL\\System.Web.Services\\2.0.0.0__b03f5f7f11d50a3a\\System.Web.Services.dll";
"C:\\Windows\\assembly\\GAC_MSIL\\System.Management\\2.0.0.0__b03f5f7f11d50a3a\\System.Management.dll";
"C:\\Windows\\assembly\\GAC_MSIL\\System.Design\\2.0.0.0__b03f5f7f11d50a3a\\System.Design.dll";
"C:\\Windows\\assembly\\GAC_32\\System.Web\\2.0.0.0__b03f5f7f11d50a3a\\System.Web.dll";
"C:\\Windows\\assembly\\GAC_MSIL\\System.Windows.Forms\\2.0.0.0__b77a5c561934e089\\System.Windows.Forms.dll";
"C:\\Windows\\assembly\\GAC_MSIL\\System.Security\\2.0.0.0__b03f5f7f11d50a3a\\System.Security.dll";
"C:\\Program Files\\Microsoft SQL Server\\90\\SDK\\Assemblies\\Microsoft.SqlServer.SmoEnum.dll";
"C:\\Windows\\assembly\\GAC_MSIL\\System.Runtime.Serialization.Formatters.Soap\\2.0.0.0__b03f5f7f11d50a3a\\System.Runtime.Serialization.Formatters.Soap.dll";
"C:\\Windows\\assembly\\GAC_MSIL\\Microsoft.JScript\\8.0.0.0__b03f5f7f11d50a3a\\Microsoft.JScript.dll";
"C:\\Windows\\assembly\\GAC_MSIL\\System.Configuration.Install\\2.0.0.0__b03f5f7f11d50a3a\\System.Configuration.Install.dll";
"C:\\Windows\\assembly\\GAC_MSIL\\Microsoft.VisualC\\8.0.0.0__b03f5f7f11d50a3a\\Microsoft.VisualC.dll";
"C:\\Program Files\\Microsoft SQL Server\\90\\SDK\\Assemblies\\Microsoft.SqlServer.SqlEnum.dll";
"C:\\Windows\\assembly\\GAC_32\\System.Data\\2.0.0.0__b77a5c561934e089\\System.Data.dll";
"C:\\Windows\\assembly\\GAC_MSIL\\System.Drawing.Design\\2.0.0.0__b03f5f7f11d50a3a\\System.Drawing.Design.dll";
"C:\\Program Files\\Microsoft SQL Server\\90\\SDK\\Assemblies\\Microsoft.SqlServer.Rmo.dll";
"C:\\Windows\\assembly\\GAC_MSIL\\System.Data.SqlXml\\2.0.0.0__b77a5c561934e089\\System.Data.SqlXml.dll";
"C:\\Windows\\assembly\\GAC_MSIL\\System.ServiceProcess\\2.0.0.0__b03f5f7f11d50a3a\\System.ServiceProcess.dll";
"C:\\Program Files\\Microsoft SQL Server\\90\\SDK\\Assemblies\\Microsoft.SqlServer.RegSvrEnum.dll"|];
relatedPaths = [||];
referenceSatellitePaths = [||];
referenceScatterPaths =
[|"C:\\Windows\\assembly\\GAC_32\\System.EnterpriseServices\\2.0.0.0__b03f5f7f11d50a3a\\System.EnterpriseServices.Wrapper.dll"|];
referenceCopyLocalPaths = [||];
suggestedBindingRedirects = [||];}
This posting is provided "AS IS" with no warranties, and confers no rights.
Comments
Anonymous
May 29, 2008
Hi Jomo! This was a very timely post, thank you so much. Of course, as luck would have it, I'm not using F#... I need to do exactly what you've done here in C#. I think I understand most of your code. The one thing that I don't get is this part: IBuildEngine buildengine = new Microsoft.Build.Framework. let x = { new IBuildEngine with member be.BuildProjectFile(projectFileName, targetNames, globalProperties, argetOutputs) = true member be.LogCustomEvent(e) = () member be.LogErrorEvent(e) = () member be.LogMessageEvent(e) = () member be.LogWarningEvent(e) = () member be.ColumnNumberOfTaskNode with get() = 1 member be.ContinueOnError with get() = true member be.LineNumberOfTaskNode with get() = 1 member be.ProjectFileOfTaskNode with get() = "" } To me that reads, "create a new IBuildEngine with the following properties." Is that right? Is this creating an anonymous type that implements IBuildEngine? Do you know how one creates an IBuildEngine normally? I can't even find a concrete type that implements IBuildEngine when I search in Reflector (and MSDN). Any guidance would be greatly appreciated! :)Anonymous
May 31, 2008
Matt, That's exactly right. F# lets you anonymously implement an interface with an 'object expression' like you see above. In C# or VB you'll need to create a named type (onymous?) that inherits from IBuildEngine and then new it up inside the resolution function.Anonymous
June 02, 2008
Jomo, Thanks for the reply! While I was investigating the above, I figured I'd temporarily try to interop directly. I compiled your code with the latest F# compiler and referenced it from my project, and of course it works perfectly. Viva la CLR. I'm looking forward to a fully supported release of F# so I can justify spending more real work time on it. As an aside, I'm surprised there isn't a framework class available that implements IBuildEngine. I expected to be able to do something like: task.BuildEngine = Engine.GlobalEngine; Keep up the good work, I really enjoy reading your blog. MattAnonymous
August 29, 2008
The comment has been removedAnonymous
September 01, 2008
Jomo Fisher— Last time I wrote about getting started with F# scripts. They’re the fastest way to justAnonymous
January 08, 2009
Mood: Please verify that your code is referencing v2.0 of the MSBuild assemblies. I was able to reproduce the raising of the EntryPointNotFoundException when I referenced the corresponding v3.5 assemblies.Anonymous
January 08, 2009
Any idea why the related XML files don't appear in the result set? I would expect to see at least the XML file for System.Core as it coexists with the assembly in the same directory.