/// <summary> /// Gets the output assembly of the given project. If the project /// was never built before, it's built before returning the output /// assembly. /// </summary> /// <param name="project">The project to get the output assembly from.</param> /// <param name="buildIfMissing">Whether to build the project if the output assembly is missing.</param> public static Task <Assembly> GetOutputAssembly(this IProjectNode project, bool buildIfMissing = true) { var fileName = (string)project.Properties.TargetFileName; var msBuild = project.Adapt().AsMsBuildProject(); if (msBuild == null) { throw new ArgumentException(Strings.IProjectNodeExtensions.NotMsBuildProject(project.DisplayName)); } // NOTE: we load from the obj/Debug|Release folder, which is // the one built in the background by VS continuously. var intermediateDir = msBuild.AllEvaluatedProperties .Where(p => p.Name == "IntermediateOutputPath") // If we grab the EvaluatedValue, it won't have the current // global properties overrides, like Configuration and Debug. .Select(p => msBuild.ExpandString(p.UnevaluatedValue)) .FirstOrDefault(); if (string.IsNullOrEmpty(fileName) || string.IsNullOrEmpty(intermediateDir) || string.IsNullOrEmpty(project.Properties.MSBuildProjectDirectory)) { tracer.Warn(Strings.IProjectNodeExtensions.NoTargetAssemblyName(project.DisplayName)); return(TaskHelpers.FromResult <Assembly>(null)); } var outDir = (string)Path.Combine(project.Properties.MSBuildProjectDirectory, intermediateDir); var assemblyFile = Path.Combine(outDir, fileName); if (!File.Exists(assemblyFile) && !buildIfMissing) { return(TaskHelpers.FromResult <Assembly>(null)); } return(Task.Factory.StartNew <Assembly>(() => { if (!File.Exists(assemblyFile)) { var success = project.Build().Result; if (success) { // Let the build finish writing the file for (int i = 0; i < 5; i++) { if (File.Exists(assemblyFile)) { break; } Thread.Sleep(200); } } if (!File.Exists(assemblyFile)) { tracer.Warn(Strings.IProjectNodeExtensions.NoBuildOutput(project.DisplayName, assemblyFile)); return null; } } var assemblyName = AssemblyName.GetAssemblyName(assemblyFile); var vsProject = project.As <IVsHierarchy>(); var localServices = project.As <IServiceProvider>(); var globalServices = GlobalServiceProvider.Instance; if (vsProject == null || localServices == null || globalServices == null) { tracer.Warn(Strings.IProjectNodeExtensions.InvalidVsContext); return null; } var openScope = globalServices.GetService <SVsSmartOpenScope, IVsSmartOpenScope>(); var dtar = localServices.GetService <SVsDesignTimeAssemblyResolution, IVsDesignTimeAssemblyResolution>(); // As suggested by Christy Henriksson, we reuse the type discovery service // but just for the IDesignTimeAssemblyLoader interface. The actual // assembly reading is done by the TFP using metadata only :) var dts = globalServices.GetService <DynamicTypeService>(); var ds = dts.GetTypeDiscoveryService(vsProject); var dtal = ds as IDesignTimeAssemblyLoader; if (openScope == null || dtar == null || dts == null || ds == null || dtal == null) { tracer.Warn(Strings.IProjectNodeExtensions.InvalidTypeContext); return null; } var provider = new VsTargetFrameworkProvider(dtar, dtal, openScope); return provider.GetReflectionAssembly(assemblyName); }, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default)); }