ILMerge assembly not merged in correctly - c#

I am using ILMerge version 2.11.1103.0 to merge 4 DLL's into a new assembly. Assuming they are called A, B, C, and D, their relationships are as follows:
B -> A
C -> A
D -> A
D -> C
When I run ILMerge on A, B, and C, it all works perfectly fine. However when I add D I receive the following error:
An exception occurred during merging:
ILMerge.Merge: The assembly 'A' was not merged in correctly.
It is still listed as an external reference in the target assembly.
at ILMerging.ILMerge.Merge()
at ILMerging.ILMerge.Main(String[] args)
I have tried using the /closed option as indicated here ILMerge DLL: Assembly not merged in correctly, still listed as an external reference but unfortunately it does not help:
In order to close the target assembly, the number of assemblies to be added to the input is 0.
When I change the order of the assemblies, I receive the following exception:
An exception occurred during merging:
Index was outside the bounds of the array.
at System.Compiler.ISymUnmanagedWriter.CloseMethod()
at System.Compiler.Ir2md.VisitMethodBody(Method method)
at System.Compiler.Ir2md.VisitMethod(Method method)
at System.Compiler.Ir2md.Visit(Node node)
at System.Compiler.Ir2md.VisitClass(Class Class)
at System.Compiler.Ir2md.Visit(Node node)
at System.Compiler.Ir2md.VisitModule(Module module)
at System.Compiler.Ir2md.SetupMetadataWriter(String debugSymbolsLocation)
at System.Compiler.Ir2md.WritePE(Module module, String debugSymbolsLocation, BinaryWriter writer)
at System.Compiler.Writer.WritePE(String location, Boolean writeDebugSymbols, Module module, Boolean delaySign, String keyFileName, String keyName)
at System.Compiler.Writer.WritePE(CompilerParameters compilerParameters, Module module)
at System.Compiler.Module.WriteModule(String location, CompilerParameters options)
at ILMerging.ILMerge.Merge()
at ILMerging.ILMerge.Main(String[] args)
The full command line I'm using is:
ILMerge.exe /targetplatform:v4,"C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0" /log:ilmerge.log /target:library /union /xmldocs /copyattrs /allowMultiple /internalize /closed /keyfile:keyfile.snk /out:Merged.dll A B C D
Can anyone see what am I doing wrong? Thanks.

As suggested by #HansPassant in the comment above, I contacted Mike Barnett of Microsoft fame. He was able to help me fix this by:
Upgrading to the latest version of ilmerge (2.12.0803) from http://nuget.org/packages/ilmerge
Removing the /union switch from the command (this was previously added to resolve an issue running gendarme on merged assemblies that had been rewritten by the Code Contracts rewriter. Thankfully due to a change in the build process this was no longer required).

I've encountered issues with ILMerge not working before. I suppose they're just bugs... Try merging in multiple phases, e.g. Merge B and A in to A', then merge A' with C and D. Maybe that will produce different results/errors that might lead to a solution.

Related

Strange behavior when loading assemblies and its dependencies programatically

The following experimental codes/projects are using netcore 2.0 and netstandard 2.0 in VS2017. Let's say I have two versions of a third party dll v1.0.0.0 and v2.0.0.0, which contains only one class Constants.cs.
//ThirdPartyDependency.dll v1.0.0.0
public class Constants
{
public static readonly string TestValue = "test value v1.0.0.0";
}
//ThirdPartyDependency.dll v2.0.0.0
public class Constants
{
public static readonly string TestValue = "test value v2.0.0.0";
}
Then I created my own solution named AssemblyLoadTest, which contains:
Wrapper.Abstraction: class library with no project references
namespace Wrapper.Abstraction
{
public interface IValueLoader
{
string GetValue();
}
public class ValueLoaderFactory
{
public static IValueLoader Create(string wrapperAssemblyPath)
{
var assembly = Assembly.LoadFrom(wrapperAssemblyPath);
return (IValueLoader)assembly.CreateInstance("Wrapper.Implementation.ValueLoader");
}
}
}
Wrapper.V1: class library with project reference Wrapper.Abstractions and dll reference ThirdPartyDependency v1.0.0.0
namespace Wrapper.Implementation
{
public class ValueLoader : IValueLoader
{
public string GetValue()
{
return Constants.TestValue;
}
}
}
Wrapper.V2: class library with project reference Wrapper.Abstractions and dll reference ThirdPartyDependency v2.0.0.0
namespace Wrapper.Implementation
{
public class ValueLoader : IValueLoader
{
public string GetValue()
{
return Constants.TestValue;
}
}
}
AssemblyLoadTest: console application with project reference Wrapper.Abstraction
class Program
{
static void Main(string[] args)
{
AppDomain.CurrentDomain.AssemblyResolve += (s, e) =>
{
Console.WriteLine($"AssemblyResolve: {e.Name}");
if (e.Name.StartsWith("ThirdPartyDependency, Version=1.0.0.0"))
{
return Assembly.LoadFrom(#"v1\ThirdPartyDependency.dll");
}
else if (e.Name.StartsWith("ThirdPartyDependency, Version=2.0.0.0"))
{
//return Assembly.LoadFrom(#"v2\ThirdPartyDependency.dll");//FlagA
return Assembly.LoadFile(#"C:\FULL-PATH-TO\v2\ThirdPartyDependency.dll");//FlagB
}
throw new Exception();
};
var v1 = ValueLoaderFactory.Create(#"v1\Wrapper.V1.dll");
var v2 = ValueLoaderFactory.Create(#"v2\Wrapper.V2.dll");
Console.WriteLine(v1.GetValue());
Console.WriteLine(v2.GetValue());
Console.Read();
}
}
STEPS
Build AssemblyLoadTest in DEBUG
Build Wrapper.V1 project in DEBUG, copy files in Wrapper.V1\bin\Debug\netstandard2.0\ to AssemblyLoadTest\bin\Debug\netcoreapp2.0\v1\
Build Wrapper.V2 project in DEBUG, copy files in Wrapper.V2\bin\Debug\netstandard2.0\ to AssemblyLoadTest\bin\Debug\netcoreapp2.0\v2\
Replace FULL-PATH-TO in AssemblyLoadTest.Program.Main with the correct absolute v2 path that you copied in step 3
Run AssemblyLoadTest - Test1
Comment FlagB line and uncomment FlagA line, run AssemblyLoadTest - Test2
Comment AppDomain.CurrentDomain.AssemblyResolve, run AssemblyLoadTest - Test3
My results and questions:
Test1 succeeds and prints v1.0.0.0 and v2.0.0.0 as expected
Test2 throws exception at v2.GetValue()
System.IO.FileLoadException: 'Could not load file or assembly
'ThirdPartyDependency, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=null'. Could not find or load a specific file.
(Exception from HRESULT: 0x80131621)'
Question1: Why LoadFile with absolute path works as expected, while LoadFrom with relative path not working, meanwhile LoadFrom with relative path works for v1.0.0.0 in the first if statement?
Test3 fails with the same exception above at the same place, here my understanding is CLR locates the assemblies with the following priority rule:
Rule1: Check if AppDomain.AssemblyResolve is registered (highest priority)
Rule2: Otherwise check if the assembly is loaded.
Rule3: Otherwise search the assembly in folders(can be configured in probing and codeBase.
Here in Test3 where AssemblyResolve is not registered, v1.GetValue works because Rule1 and Rule2 is N/A, AssemblyLoadTest\bin\Debug\netcoreapp2.1\v1 is in Rule3 scan candidates. When executing v2.GetValue, Rule1 is still N/A, however Rule2 is applied here (if Rule3 is applied, why exceptions?)
Question2: Why the version is ignored even Wrapper.V2 reference ThirdPartyDependency.dll using
<Reference Include="ThirdPartyDependency, Version=2.0.0.0">
<HintPath>..\lib\ThirdPartyDependency\2.0.0.0\ThirdPartyDependency.dll</HintPath>
</Reference>
Great answer from Vitek Karas, original link here.
Kind of unfortunately all of the behavior you describe is currently as designed. That doesn't mean it's intuitive (which it's totally not). Let me try to explain.
Assembly binding happens based on AssemblyLoadContext (ALC). Each ALC can have only one version of any given assembly loaded (so only one assembly of a given simple name, ignoring versions, culture, keys and so on). You can create a new ALC which then can have any assemblies loaded again, with same or different versions. So ALCs provide binding isolation.
Your .exe and related assemblies are loaded into a Default ALC - one which is created at the start of the runtime.
Assembly.LoadFrom will try to load the specified file into the Default ALC - always. Let me stress the "try" word here. If the Default ALC already loaded assembly with the same name, and the already loaded assembly is equal or higher version, then the LoadFrom will succeed, but it will use the already loaded assembly (effectively ignoring the path you specified). If on the other hand the already loaded assembly is of a lower version then the one you're trying to load - this will fail (we can't load the same assembly for the second time into the same ALC).
Assembly.LoadFile will load the specified file into a new ALC - always creates a new ALC. So the load will effectively always succeed (there's no way this can collide with anything since it's in its own ALC).
So now to your scenarios:
Test1
This works because your ResolveAssembly event handler loads the two assemblies into separate ALCs (LoadFile will create a new one, so the first assembly goes to the default ALC, and the second one goes into its own).
Test2
This fails because LoadFrom tries to load the assembly into the Default ALC. The failure actually occurs in the AssemblyResolve handler when it calls the second LoadFrom. First time it loaded v1 into Default, the second time it tries to load v2 into Default - which fails because Default already has v1 loaded.
Test3
This fails the same way because it internally does basically exactly what Test2 does. Assembly.LoadFrom also registers event handler for AssemblyResolve and that makes sure that dependent assemblies can be loaded from the same folder. So in your case v1\Wrapper.V1.dll will resolve its dependency to v1\ThirdPartyDependency.dll because it's next to it on the disk. Then for v2 it will try to do the same, but v1 is already loaded, so it fails just like in Test2. Remember that LoadFrom loads everything into the Default ALC, so collisions are possible.
Your questions:
Question1
LoadFile works because it loads the assembly into its own ALC, which provides full isolation and thus there are never any conflicts. LoadFrom loads the assembly into the Default ALC, so if that already has assembly with the same name loaded, there might be conflicts.
Question2
The version is actually not ignored. The version is honored which is why Test2 and Test3 fail. But I might not understand this question correctly - it's not clear to me in which context you're asking it.
CLR binding order
The order of Rules you describe is different.
It's basically:
Rule2 - if it's already loaded - use it (including if higher version is already loaded, then use that)
Rule1 - if everything fails - as a last resort - call AppDomain.AssemblyResolve
Rule 3 actually doesn't exist. .NET Core doesn't have a notion of probing paths or code base. It sort of does for the assemblies which are statically referenced by the app, but for dynamically loaded assemblies no probing is performed (with the exception of LoadFrom loading dependent assemblies from the same folder as the parent as described above).
Solutions
To make this fully work, you would need to do either:
Use the LoadFile along with your AssemblyResolve handler. But the problem here is that if you LoadFile an assembly which itself has other dependencies, you will need to handle those in your handler as well (you lose the "nice" behavior of LoadFrom which loads dependencies from the same folder)
Implement your own ALC which handles all dependencies. This is technically the cleaner solution, but potentially more work. And it's similar in that regard that you still have to implement the loading from the same folder if needed.
We are actively working on making scenarios like this easy. Today they are doable, but pretty hard. The plan is to have something which solves this for .NET Core 3. We're also very aware of the lack of documentation/guidance in this area. And last but not least, we are working on improving the error messages, which are currently very confusing.

ReflectionTypeLoadException when loading external dll

Imagine I have a solution with some projects. In the project Proj1.BL I have the abstract class MyAbstractClass, and (strong requirement) I should inherit from it only in another solutions. All of them will contain only one class (e.g. DerivedClass : MyAbstractClass), will have references to corresponding projects from the first solution(Proj1.BL, Proj1.DataModel, etc.) and will be compiled to DLLs (Class Libraries). And in conclusion, these DLLs (another strong requirement) will be stored in $(OutDir of first solution)\Externals.
My question is: how should I load these assemblies within the first solution correctly? I've tried someway like this:
public static Type LoadType(string fileName)
{
if (fileName == null) throw new ArgumentNullException("fileName");
fileName = Path.Combine("Externals", fileName);
if (!File.Exists(fileName)) throw new ApplicationException("File does not exist.");
return
Assembly.LoadFrom(fileName)
.GetTypes()
.SingleOrDefault(_ => _.IsSubclassOf(typeof (MyAbstractClass)));
}
But I'm continuing to get
ReflectionTypeLoadException. Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information.
LoaderException is about that CLR couldn't load type Proj1.BL.MyAbstractClass from Proj1.BL, Version=*.*.*.*, Culture=neutral, PublicKeyToken=null.
But code that I'm running is in Proj1.BL. What's wrong?
Thanks for any suggestions.
Eh, the answer was easily than expected. There was a version mismatch when Proj1 assemblies' versions' values were autoincremented after each rebuild, but DLL expected to find Proj1 assemblies' versions' that were specified at compile-time.

Executable fails with weird exception

I am using ILMerge and Quartz.NET in a C# .NET 4.0 Windows Service application. The app runs fine without using ILMerge, but now that we're nearing shipping release, I wanted to combine all DLLs into a single executable.
Problem is, that ILMerge seems to work fine, but when I run the combined executable, it throws this exception:
Unhandled Exception: Quartz.SchedulerException: ThreadPool type 'Quartz.Simpl.SimpleThreadPool' could not be instantiated. ---> System.InvalidCastException: Unable to cast object of type 'Quartz.Simpl.SimpleThreadPool' to type 'Quartz.Spi.IThreadPool'.
at Quartz.Util.ObjectUtils.InstantiateType[T](Type type) in :line 0
at Quartz.Impl.StdSchedulerFactory.Instantiate() in :line 0
--- End of inner exception stack trace ---
at Quartz.Impl.StdSchedulerFactory.Instantiate() in :line 0
at Quartz.Impl.StdSchedulerFactory.GetScheduler() in :line 0
Does anyone have any idea why this is? I have been wasting over 4 hours already and I can't figure it out. If I don't combine with ILMerge, then everything runs fine (with the Quartz.dll and Common.Logging.dll in the same directory).
I'm sure someone must have tried packaging Quartz.net up like this before, any ideas?
Disclaimer: I don't know Quartz.NET at all, although I spent some time struggling with ILMerge. When I finally understood its limitations... I stopped using it.
ILMerge'd application tends to have problems with everything which contains the word "reflection".
I can guess (I've never used Quartz.NET) that some classes are resolved using reflection and driven by configuration files.
Class is not only identified by its name (with namespace) but also by assembly it is coming from (unfortunatelly it doesn't get displayed in exception message).
So, let's assume you had (before ILMerging) two assemblies A (for you Application) and Q (for Quartz.NET).
Assembly 'A' was referencing assembly 'Q' and was using a class 'Q:QClass' which was implementing 'Q:QIntf'.
After merging, those classes became 'A:QClass' and 'A:QIntf' (they were moved from assembly Q to A) and all the references in code has been replaced to use those (completely) new classes/interfaces, so "A:QClass" is implementing "A:QIntf" now.
But, it did not change any config files/embedded strings which may still reference "Q:QClass".
So when application is reading those not-updated config files it still loads "Q:QClass" (why it CAN find it is a different question, maybe you left assembly 'Q' in current folder or maybe it is in GAC - see 1).
Anyway, "Q:QClass" DOES NOT implement "A:QIntf", it still implements "Q:QIntf" even if they are binary identical - so you can't cast 'Q:QClass' to 'A:QIntf'.
The not-ideal-but-working solution is to "embed" assemblies instead of "merging" them. I wrote a open-source tool which does it (embedding instead of merging) but it is not related to this question. So if you decide to embed just ask me.
You can test it by removing (hiding, whatever works for you) every single instance of Q.dll on your PC. If I'm right, the exception should say now 'FileNotFound'.
You could try creating your own ISchedulerFactory and avoid using reflection to load all of your types.
The StdSchedulerFactory uses this code to creat a threadpool. It's where your error is happening and would be the place to start looking at making changes:
Type tpType = loadHelper.LoadType(cfg.GetStringProperty(PropertyThreadPoolType)) ?? typeof(SimpleThreadPool);
try
{
tp = ObjectUtils.InstantiateType<IThreadPool>(tpType);
}
catch (Exception e)
{
initException = new SchedulerException("ThreadPool type '{0}' could not be instantiated.".FormatInvariant(tpType), e);
throw initException;
}
The ObjectUtils.InstantiateType method that is called is this one, and the last line is the one throwing your exception:
public static T InstantiateType<T>(Type type)
{
if (type == null)
{
throw new ArgumentNullException("type", "Cannot instantiate null");
}
ConstructorInfo ci = type.GetConstructor(Type.EmptyTypes);
if (ci == null)
{
throw new ArgumentException("Cannot instantiate type which has no empty constructor", type.Name);
}
return (T) ci.Invoke(new object[0]);
}
Right after this section in the factory, datasources are loaded using the same pattern and then the jobs themselves are also loaded dynamically which means you'd also have to write your own JobFactory. Since Quartz.Net loads a bunch of bits and pieces dynamically at runtime going down this road means you might end up rewriting a fair amount of things.

ASP.net exception "FileNotFoundException" after idle when assembly file is present in the bin folder

The file is present, correctly named and not corrupted. If I move it out and back in from the "Bin", it works again, for about 5 minutes, then the error bellow comes back. Any operation that refreshes the file is fine, publishing it the anew, renaming or moving makes the site work again, for a moment.
{"Message":"Could not load file or assembly \u0027Ouranos,
Version=1.0.0.0, Culture=fr-CA, PublicKeyToken=null\u0027 or one of
its dependencies. Le fichier spécifié est introuvable.","StackTrace":"
at Services.Asynchrone(String DimensionX, String DimensionY, String
Action, String Culture, String Utilisateur, String Interface, String
Source, String Champ, String Valeur, String Classement, String
Direction, StriFileNotFoundExceptionng Page, String
Itérations)","ExceptionType":"System.IO."}
Fusion did give me an error code (0x80070002), which pointed me to get Process Monitor. Which lead me to the temporary assembly folder. Now I may be wrong about this. Comparing the cache files from an healthy website and the sick one, I noticed something odd.
Healthy website as all the DLL from the BIN in cache.
The sick website is missing two DLL in the cache that are present in the BIN.
Now, I know that ASP.net tends to say that the main library is missing when it's in fact one of the referenced library that is missing. In this current situation I don't know what I could do to fix that problem. The two DLL are not set in the cache, thus when it tries to load the main DLL it fails locating the two others from the cache and throws a file not found on the main DLL.
The two culprits are:
PresentationCore.dll
WindowsBase.dll
To troubleshot this kinf of errors, you can use Fusion log, instructions about how to enable it and how to use it can be found here: How to enable assembly bind failure logging (Fusion) in .NET.
It would seem that the following code actually fixes the problem. It checks for all the required assemblies for the assembly and loads the missing. I had such a code before and it did not work, because without the !(Assemblée is System.Reflection.Emit.AssemblyBuilder) && (Assemblée.GetType().FullName != "System.Reflection.Emit.InternalAssemblyBuilder") was not present and has the code causing an exception in .net 4.0 and over. It's not elegant, but it does the job.
public static void Chargeur()
{
var Assemblées_Chargées = (from Assembly Assemblée in AppDomain.CurrentDomain.GetAssemblies() where !(Assemblée is System.Reflection.Emit.AssemblyBuilder) && (Assemblée.GetType().FullName != "System.Reflection.Emit.InternalAssemblyBuilder") && (!Assemblée.GlobalAssemblyCache) && (Assemblée.CodeBase != Assembly.GetExecutingAssembly().CodeBase) select Assemblée).ToList();
var Chemins_Chargés = Assemblées_Chargées.Select(Assemblée => Assemblée.Location).ToArray();
var Chemins_Référencés = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll");
var Assemblées_NonChargées = Chemins_Référencés.Where(Références => !Chemins_Chargés.Contains(Références, StringComparer.InvariantCultureIgnoreCase)).ToList();
Assemblées_NonChargées.ForEach(path => Assemblées_Chargées.Add(AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(path))));
}

Mimicking assembly resolution of the msbuild process

I am writing a validation tool that checks the versions of files referenced in a project. I want to use the same resolution process that MSBuild uses.
For example, Assembly.Load(..) requires a fully-qualified assembly name. However, in the project file, we may only have something like "System.Xml". MSBuild probably uses the project's target framework version and some other heuristics to decide which version of System.Xml to load.
How would you go about mimicking (or directly using) msbuild's assembly resolution process?
In other words, at run-time, I want to take the string "System.Xml", along with other info found in a .csproj file and find the same file that msbuild would find.
I had this problem today, and I found this old blog post on how to do it:
http://blogs.msdn.com/b/jomo_fisher/archive/2008/05/22/programmatically-resolve-assembly-name-to-full-path-the-same-way-msbuild-does.aspx
I tried it out, works great! I modified the code to find 4.5.1 versions of assemblies when possible, this is what I have now:
#if INTERACTIVE
#r "Microsoft.Build.Engine"
#r "Microsoft.Build.Framework"
#r "Microsoft.Build.Tasks.v4.0"
#r "Microsoft.Build.Utilities.v4.0"
#endif
open System
open System.Reflection
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 = {
referencePaths:string array
referenceDependencyPaths:string array
relatedPaths:string array
referenceSatellitePaths:string array
referenceScatterPaths:string array
referenceCopyLocalPaths:string array
suggestedBindingRedirects:string array
}
let resolve (references:string array, outputDirectory:string) =
let x = { new IBuildEngine with
member be.BuildProjectFile(projectFileName, targetNames, globalProperties, targetOutputs) = 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.IgnoreVersionForFrameworkReferences <- true
rar.TargetFrameworkVersion <- "v4.5.1"
rar.TargetedRuntimeVersion <- "v4.5.1"
rar.TargetFrameworkDirectories <- [||] //[|#"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\"|]
rar.Assemblies <- [|for r in references -> new Microsoft.Build.Utilities.TaskItem(r) :> ITaskItem|]
rar.AutoUnify <- true
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 |]
}
[<EntryPoint>]
let main argv =
try
let s = resolve([| "System"
"System.Data"
"System.Core, Version=4.0.0.0"
"Microsoft.SqlServer.Replication" |], "")
printfn "%A" s.referencePaths
finally
ignore (System.Console.ReadKey())
0
If you target the Framework version you want to be compatible with instead of targeting 3.5, Visual Studio 2008 SP1 and FxCop 1.36 RTM added rule CA 1903: Use only API from targeted framework to ensure you stay compatible with the target framework version. Turning that rule on and treating it as an error will fail your Build and provide the behavior you want.
Here is sample code demonstrating a violation when you are targeting framework version 2:
using System.Runtime;
class Program
{
static void Main()
{
GCSettings.LatencyMode = GCLatencyMode.LowLatency;
}
}
This should show you how to do what you really want, but I think you should use the FXCop answer I provided.
static void Main()
{
string targetFile = #"test.csproj";
XDocument xmlDoc = XDocument.Load(targetFile);
XNamespace ns = "http://schemas.microsoft.com/developer/msbuild/2003";
var references = from reference in xmlDoc.Descendants(ns + "ItemGroup").Descendants(ns + "Reference")
select reference.Attribute("Include").Value;
foreach (var reference in references)
{
Assembly.LoadWithPartialName(reference);
}
foreach (var item in AppDomain.CurrentDomain.GetAssemblies())
{
var assemblyVersion = ((AssemblyFileVersionAttribute)item.GetCustomAttributes(typeof(AssemblyFileVersionAttribute), true)[0]).Version.ToString();
Console.WriteLine("\r\nFullname:\t{0}\r\nFileVersion:\t{1}", item.FullName, assemblyVersion);
}
Console.WriteLine("\r\nPress any key to continue");
Console.ReadKey();
}
Why not just call msbuild against your project or solution file, pass it the /v:d extension, and parse the output file for the information you want? For instance, you'll see something like the following for each assembly resolution:
Primary reference "System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089".
Resolved file path is "c:\WINNT\Microsoft.NET\Framework\v2.0.50727\System.Data.dll".
Reference found at search path location "{TargetFrameworkDirectory}".
For SearchPath "{TargetFrameworkDirectory}".
Considered "C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\System.Data.exe", but it didn't exist.
Considered "C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\System.Data.dll", but it didn't exist.
Considered "C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\System.Data.exe", but it didn't exist.
Considered "C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\System.Data.dll", but it didn't exist.
Considered "c:\WINNT\Microsoft.NET\Framework\v3.5\System.Data.exe", but it didn't exist.
Considered "c:\WINNT\Microsoft.NET\Framework\v3.5\System.Data.dll", but it didn't exist.
Considered "c:\WINNT\Microsoft.NET\Framework\v3.0\System.Data.exe", but it didn't exist.
Considered "c:\WINNT\Microsoft.NET\Framework\v3.0\System.Data.dll", but it didn't exist.
Considered "c:\WINNT\Microsoft.NET\Framework\v2.0.50727\System.Data.exe", but it didn't exist.
This reference is not "CopyLocal" because it's a prerequisite file.
Alternatively, MSBuild delegates the task of resolving assemblies to the Microsoft.Build.Tasks.ResolveAssemblyReference class from the Microsoft.Build.Tasks.v3.5 assembly (in my case, building against the 3.5 framework). You can parse the project file and supply an instance of ResolveAssemblyReference with the appropriate (meta)data, and let it perform the resolution for you - seems perfect, since that's exactly what MSBuild does.
If you get yourself a free copy of Reflector, you can examine the internals of the MSBuild.exe file itself. I notice there is a class
Microsoft.Build.Shared.TypeLoader
that has a method called
internal LoadedType Load(string typeName, AssemblyLoadInfo assembly);
which may help?
Anyway, with reflector you can get the code, and hopefully reuse the system directly.
To directly mimic the CLR resolution process you could write a custom MSBuild task although I don't see what it would achieve.
MSBuild doesn't resolve assemblies. They are resolved by the CLR. This article describes how the runtime resolves assemblies: http://msdn.microsoft.com/en-us/library/yx7xezcf.aspx
When you are in Visual Studio the System assemblies come from the filesystem, but when they are loaded at runtime they come from the GAC. http://p3net.mvps.org/Topics/Basics/IntegratingGACWithVS.aspx
If you still have questions please clarify.
This might help: Resolving Binary References in MSBuild

Categories