PowerShell Core: resolving assembly conflicts - c#

I have found an interesting case which I can't fully understand.
My runtime - PowerShell 7.2.4
I have two PowerShell Core modules: Net6PowerShellModule and NetstandardPowerShellModule.
Net6PowerShellModule.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<None Remove="Net6PowerShellModule.psd1" />
</ItemGroup>
<ItemGroup>
<Content Include="Net6PowerShellModule.psd1">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
<PackageReference Include="System.Management.Automation" Version="7.2.4" />
</ItemGroup>
</Project>
Test-Net6Cmdlet.cs
using System.Management.Automation;
namespace Net6PowerShellModule
{
[Cmdlet("Test", "Net6Cmdlet")]
public class TestNet6Cmdlet : PSCmdlet
{
protected override void ProcessRecord()
{
var type = typeof(Microsoft.Extensions.DependencyInjection.IServiceCollection);
WriteObject("Net6 Cmdlet Run.");
}
}
}
Net6PowerShellModule.psd1
#{
RootModule = 'Net6PowerShellModule.dll'
ModuleVersion = '0.0.1'
GUID = '8c1bd929-32bd-44c3-af6b-d9dd261e34f3'
CmdletsToExport = 'Test-Net6Cmdlet'
}
NetstandardPowerShellModule.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<None Remove="NetstandardPowerShellModule.psd1" />
</ItemGroup>
<ItemGroup>
<Content Include="NetstandardPowerShellModule.psd1">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.2.0" />
<PackageReference Include="System.Management.Automation" Version="6.1.2" />
</ItemGroup>
</Project>
Test-NetstandardCmdlet.cs
using System.Management.Automation;
namespace NetstandardPowerShellModule
{
[Cmdlet("Test", "NetstandardCmdlet")]
public class TestNetstandardCmdlet : PSCmdlet
{
protected override void ProcessRecord()
{
var type = typeof(Microsoft.Extensions.DependencyInjection.IServiceCollection);
WriteObject("Netstandard 2.0 Cmdlet Run.");
}
}
}
NetstandardPowerShellModule.psd1
#{
RootModule = 'NetstandardPowerShellModule.dll'
ModuleVersion = '0.0.1'
GUID = 'eef615d0-aecf-4e89-8f4c-53174f8c99d6'
CmdletsToExport = 'Test-NetstandardCmdlet'
}
Next, I have two possible use cases for those modules:
Import Net6PowerShellModule
Import NetstandardPowerShellModule
Run Test-Net6Cmdlet
Run Test-NetstandardCmdlet
Result - everything runs correctly. The only loaded assembly is Microsoft.Extensions.DependencyInjection.Abstractions version 6.0.0.
Import Net6PowerShellModule
Import NetstandardPowerShellModule
Run Test-NetstandardCmdlet
Run Test-Net6Cmdlet.
Result - error: Test-Net6Cmdlet: Could not load file or assembly 'Microsoft.Extensions.DependencyInjection.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'. Could not find or load a specific file. (0x80131621)
Based on this question, this is not an expected behavior:
Subsequent attempts to load a different version of the assembly:
cause a statement-terminating error in Powershell (Core) 7+
Assembly with same name is already loaded.
Why does the first scenario work then?
If I add the RequiredAssemblies = #('Microsoft.Extensions.DependencyInjection.Abstractions.dll') line in both module manifests, I got an expected behavior: regardless of which module is loaded first, I still got an error: Assembly with same name is already loaded.
Update 1
Articles that I've already looked at:
https://learn.microsoft.com/en-us/powershell/scripting/dev-cross-plat/resolving-dependency-conflicts
PowerShell: why am I able to load multiple versions of the same .NET assembly in one session and also "bypass Dependency hell"?
https://devblogs.microsoft.com/powershell/resolving-powershell-module-assembly-dependency-conflicts/ - this article explains a lot about Assembly Load Contexts, but I still cannot understand the difference between having RequiredAssemblies defined in .psd1 files or using the dependencies in code.

tl;dr
When authoring a module, it is best to use a module manifest (.psd1 file) with a RequiredAssemblies key to declare dependent (helper) assemblies, because it makes loading of such assemblies predictable, because it happens at the time of module import.
In Windows PowerShell, attempts to load a different version of an already-loaded assembly are quietly ignored and the newly imported module is made to use the already-loaded assembly too, which may or may not work.
In PowerShell (Core) v6+, attempts to load a different version of an already-loaded assembly are categorically prevented.
If you use implicit loading of dependent assemblies, based on .NET's mechanisms, the timing of when these dependencies are loaded isn't guaranteed, and, in PowerShell Core, you'll bypass the check that prevents attempts to load a different version of an already-loaded assembly - which may or may not work and is only allowed if the later load attempt requests a lower version than the one already loaded.
Non-trivial workarounds are needed to reliably load different versions of a given assembly side by side.
Background information
One of your links, Resolving PowerShell module assembly dependency conflicts, contains all relevant information, but it's a lengthy read with complex, in-depth information, and it doesn't clarify the way in which PowerShell (Core) (v6+) overrides the behavior of the underlying .NET (Core) / .NET 5+ framework.
Terminology: For simplicity, I'll use the following terms:
Windows PowerShell is the legacy, ships-with-Windows Windows-only edition of PowerShell whose latest and final version is 5.1.x.
It is built on top of .NET Framework, the legacy, Windows-only edition of .NET, whose latest and last version is v4.8.x
PowerShell Core (officially now just PowerShell) is the modern, cross-platform, install-on-demand PowerShell edition, whose first (but now obsolete) version was v6 and whose current version as of this writing is v7.2.6.
It is built on top of .NET Core (officially since v5 just .NET), the modern, cross-platform, install-on-demand edition of .NET.
The underlying problem:
At the .NET framework / CLR (Common Language Runtime) level, the fundamental problem is the inability to load different versions of a given assembly side by side into the same application domain (.NET Framework) / ALC (assembly-load context, .NET Core): the version that is loaded first into a session quietly wins, with .NET Core imposing the additional restriction that only attempts to load lower versions succeed (see below).
PowerShell uses only a single application domain / ALC, including for modules, necessitating workarounds to enable side-by-side loading of different versions of a (dependent) assembly. The linked article details various workarounds; a robust, generic, in-process workaround requires PowerShell Core and is limited to binary (compiled) modules and is nontrivial; limited workarounds, some out-of-process, are available for Windows PowerShell and cross-PowerShell-edition code.
Behavior without a workaround:
Windows PowerShell and .NET Framework:
.NET Framework quietly ignores subsequent attempts to load a different version of an already-loaded assembly, and makes the caller use the already-loaded version, irrespective of whether the requested version is higher or lower than the already loaded one.
Curiously, when the caller uses reflection with typeof to inspect the assembly and its types it just loaded (whether implicitly or explicitly), the requested assembly version is reported - even though it is the already loaded assembly and its types that are actually used.
Windows PowerShell does not overlay this behavior with custom functionality, although whether or not you use a module manifest (.psd1 file) with a RequiredAssemblies entry can make a difference with respect to when dependent assemblies are loaded (see below).
Upshot:
Attempts to load a different version of an already-loaded assembly never fail and quietly make the caller use the already-loaded version.
This may or may not work, and you'll only notice at the time the types from the assembly in question are actually used (construction of an instance, property access, method calls, ...).
Notably, it is likely to fail if a later caller attempts to load a higher version of an assembly, as it may rely on features that the already-loaded lower version doesn't have, but even the inverse scenario isn't guaranteed to succeed, given that a higher version may have introduced breaking changes.
PowerShell Core and .NET Core:
.NET Core:
Quietly ignores subsequent attempts to load a lower version of an already-loaded assembly, and makes the caller use the already-loaded version
Throws an exception for attempts to load a higher version: Could not find or load a specific file. (0x80131621)
The rationale appears to be: an already-loaded higher version is at least likely to be backward-compatible with the requested lower version - although, in the absence of semantic versioning for .NET assemblies - this is not guaranteed.
When no exception occurs, and the caller uses reflection with typeof to inspect the assembly and its types it just loaded (whether implicitly or explicitly), it does see the actual, already-loaded assembly version - unlike in .NET Framework.
PowerShell Core does overlay this behavior with custom functionality, unlike Windows PowerShell:
It defines a custom dependency handler for the plugin-oriented [System.Reflection.Assembly]::LoadFrom() .NET Core API, which it uses behind the scenes in the following contexts:
When loading a module with a module manifest (.psd1) that declares dependent assemblies via its RequiredAssemblies entry.
Calls to Add-Type with -Path / -LiteralPath.
in using assembly statements.
This custom dependency resolution handler categorically prevents attempts to load a different version of an already-loaded version, irrespective of which version number is higher.
A statement-terminating Assembly with same name is already loaded error occurs (in the case of using assembly, loading a script breaks in the parsing stage with Cannot load assembly '...').
Upshot:
In PowerShell Core, attempts to load a different version of an already-loaded assembly always fail if you let PowerShell make the load attempt.
Only if you use .NET Core's implicit dependency loading is there a chance that the load attempt succeeds, namely if an attempt is made to load a lower version later. Implicit dependency loading happens automatically, via the [System.Reflection.Assembly]::Load() .NET API, which is based on supplying a dependent assembly's (location-independent) full name and default probing paths for looking for the file hosting it, which notably includes the directory of the calling assembly itself.
This may or may not work, given that versioning of .NET assemblies doesn't use semantic versioning, so a higher version of an assembly isn't guaranteed to be backward-compatible.
Also, the timing of implicit loading of dependencies isn't predictable, and usually doesn't happen until the types from the assembly in question are actually used (construction of an instance, property access, method calls, ...). Thus, if you rely on your modules to use implicit dependency loading, there is no guaranteed relationship between when a module is imported vs. when actual dependency loading is attempted, so it may fail if a dependency with a lower version happens to be loaded first in a given session (but, as stated, even if the higher version is loaded first, you may see a later error when the types are actually used).

Related

C# CodeDom System.TypeLoadException XamMac2 Xamarin macOS

I'm coding a complex application that makes use of CodeDom to instantiate objects with reflection. It is, however, mixed between netstandard2.0 (the wrapper in between that also supports net461) and net461 projects.
As I've read, it is very possible to make reference to previous versions of the framework, this case the following:
{System.TypeLoadException: Could not resolve type with token 01000067 from typeref (expected class 'System.CodeDom.Compiler.CompilerParameters' in assembly 'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089')
Disregarding the System reference that gets loaded in memory for the Xamarin Mac executable.
What doesn't fit is the code looking for the a System assembly that maybe gets overwritten with the Xamarin System assembly.
Do you know any workaround for this? Other than migrating entirely the remaining set of projects to hybrid between netstandard2.0 and net461.
Thanks.
Indeed.
There was a mismatch between the System.CodeDom assembly that is packed inside System.dll and the one that must be referenced for the project to build.
This got solved by targeting netstandard2.0 with both net461 set.
<PropertyGroup>
<TargetFrameworks>net461;netstandard2.0</TargetFrameworks>
</PropertyGroup>
Like so.

Custom msbuild task tries to load dependency with wrong version

I have custom publish process which firstly merges some assemblies into one via ILRepack, then performs some other steps and finally cleans up publish directory - removes merged dependencies from APP_NAME.deps.json, relevant assemblies and symbol files.
In order to implement the last step, I've created a NuGet package with a custom MsBuild task.
According to Nate's blog post, I've set PrivateAssets="All" in order to ship all task's dependencies within the package:
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="15.9.20" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Update="#(PackageReference)" PrivateAssets="All" />
</ItemGroup>
Package layout looks like:
Suddenly, during publish, this step fails with error:
task failed unexpectedly. Could not load file or assembly
'Newtonsoft.Json, Version=12.0.0.0, Culture=neutral,
PublicKeyToken=30ad4fe6b2a6aeed'. Could not find or load a specific
file. (0x80131621)
I can't understand why task tries to load version 12.0.0.0 while I have Newtonsoft.Json 12.0.2 (as specified in PackageReference).
Thank you for any help!
Updated:
According to this msbuild spec currently MSBuild tasks have serious limitations:
Tasks in MSBuild are dynamically loaded assemblies with potentially separate and colliding dependency trees. Currently MSBuild on .NET Core has no isolation between tasks and as such only one version of any given assembly can be loaded. Prime example of this is Newtonsoft.Json which has multiple versions, but all the tasks must agree on it to work.
As Lex Li mentioned in the comment, 12.0.2 is the NuGet package version which corresponds to the 12.0.0.0 assembly version. So the system attempts to load the right version of the assembly.
According to task-isolation-and-dependencies.md, related issue etc. custom MsBuild tasks have serious limitations.
MsBuild itself includes a dependency on Newtonsoft.Json 11.0.1, so custom tasks can't load any other version of this dependency.
Workarounds:
Use the same dependency version as MsBuild. I guess this approach is fragile and should not be used.
Create and package console application instead of a custom MsBuild task. I've chosen this approach because it is easily extensible and allows us to use any dependency version. Nate's blog post gives an overview of the approach.
As Martin Ullrich mentioned in the comment, we could use task with isolation boundaries. ContextAwareTask.cs demonstrates the approach.

Loading an external .NET Standard 2.0 assembly with blazor

In a Blazor app, I want to load an external assembly and execute a method. For this, I have created a new ASP.Net Core webapplication using the Blazor template.
Then, in a Razor Page (which will be compiled and executed by browser/wasm) I use reflection to load the assembly and run the method (based on code found here)
// download external assembly from server
HttpClient client = new HttpClient();
var bytes = await client.GetByteArrayAsync("http://localhost:62633/_framework/MyCustomLib.dll");
//load assembly
var assembly = System.Reflection.Assembly.Load(bytes);
// get type/method info
var type = assembly.GetType("MyCustomLib.MyCustomClass");
var method = type.GetMethod("WriteSomething");
// instantiate object and run method
object classInstance = Activator.CreateInstance(type, null);
method.Invoke(classInstance, null);
The method WriteSomething contains a single Console.WriteLine() which prints something in the browser's console, thanks to blazor/mono.wasm goodness. The complete code in this library is:
namespace MyCustomLib
{
public class MyCustomClass
{
public void WriteSomething()
{
System.Console.WriteLine("This is printed from a loaded dll 3 !");
}
}
}
Result:
As you can see, this works great when MyCustomLib.dll is built as a .NET Framework Class Library. However, I want to use a .NET Standard Class Library.
When I build a MyCustomLib.dll as a .NET Standard 2.0 library, and execute the same blazor app, I get the following error in the browser's console:
Could not load file or assembly 'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' or one of its dependencies.
I would expect mono.wasm would have loaded the necessary dependencies to support .NET Standard assemblies.
Loading the assembly into the AppDomain yields the same result.
var assembly = AppDomain.CurrentDomain.Load(bytes);
Switching down to netstandard 1.6 gives me a similar error, this time about System.Runtime (because mono.wasm expects Mono.Runtime I assume).
Maybe there's a way to perform LoadAssembly on the assemblies referenced by the netstandard2.0 package, but I wouldn't know how.
How can I load a .NET Standard 2.0 into the browser environment using Blazor?
After doing some further investigation, I've concluded that my problem is that my external library is not properly linked to the mono.net dependencies. This is why, when you build a Blazor app, it is compiled a second time to /dist/_framework/_bin.
I've found three possible solutions to this problem:
1. Turn the external class library into a Blazor Web app
This way, your app will automatically be converted to a mono-compatible assembly when built. A simple peek in to a Blazor .csproj shows the dependencies needed to achieve this. For it to work, I had to change the .csproj of my external assembly:
from a default netstandard library:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
</Project>
into a web app:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RunCommand>dotnet</RunCommand>
<LangVersion>7.3</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Blazor.Build" Version="0.7.0" PrivateAssets="all" />
</ItemGroup>
</Project>
These are the only dependencies needed. On build, the compatible assembly will be found in the /dist/_framework/_bin folder. It can then be loaded using the methods described in the question.
This works, but feels a bit hacky because the only reason we're turning the library into a web app is so that it can compile itself into a properly linked assembly.
2. Load the netstandard2.0 mono facade
Another solution is to unzip the Nuget Package from Microsoft.AspNetCore.Blazor.Build and grab the netstandard.dll. It's found in the tools\mono\bcl\Facades folder. Now, when doing the following in the main Blazor app:
var netstandard = await client.GetByteArrayAsync("http://localhost:62633/_framework/netstandard.dll");
var externallib = await client.GetByteArrayAsync("http://localhost:62633/_framework/MyCustomLib.dll");
AppDomain.CurrentDomain.Load(netstandard);
var assembly = AppDomain.CurrentDomain.Load(externallib);
then the unmodified netstandard 2.0 library MyCustomLib will be loaded without errors.
No need to change it to a web app
This works, but it feels even hackier than the first solution, unsure whether this will fail later along the way...
3. Use the Blazor Build tools
The Blazor Build tools, currently found here, they have a ResolveRuntimeDependenciesCommand command for the CLI which seems to do exactly what a blazor web app is doing when it spits output to /_framework/_bin.
I'm still looking at how this could be used to convert a "non blazor-webapp" assembly into a mono-compatible one.
Feel free to comment or answer with additional information. I'm leaving this question open until a "cleaner" solution is found.
This is caused by the blazor linker stripping out the dependency to netstandard since it's not explicitly used in the main WebAssemblyHost project.
When dynamically loading a DLL that uses netstandard, it will assume that it was already loaded and crash.
If you plan to load assemblies dynamically, I recommend using
<BlazorWebAssemblyEnableLinking>false</BlazorWebAssemblyEnableLinking>
to ensure non of the base dependencies is stripped out.

How .NET applications compiled with old .NET Framework Assemblies still be able resolve newer .NET Framework assemblies

I have been looking for an answer to that but so far I have not been able to. For those who are not familiar with the basis of my question, here is some explanation to that.
Context
You can have two types of assembly in .NET when it comes to naming your assemblies:
Strong-named Assemblies
Simple-named Assemblies (aka Weakly-named Assemblies as coined by Jeffrey Richter in his CLR via C# book).
Unlike Simple-named Assemblies, the Strong-named assemblies are signed with a public/private key. Related to my question, one of the benefits of signing the assembly is the following:
Now the version number of your assembly becomes important. Assemblies
contain a static reference to other assemblies that they use the types of. When they reference a strong-named assembly, you must give them
that same exact assembly with the exact signature (name, version,
culture, and public key token). Otherwise, the fusion assembly loader
will fail at resolving it.
Also, strong-named assemblies can only reference other strong-named assemblies but not simple-named assemblies.
Question
Based on what I have described, I also expect the same rules to be applied to referencing .NET Framework assemblies. Meaning that my assemblies will search for exact versions of the referenced assemblies that are shipped with a .NET Framework that I originally used the assemblies of in my compiled application. However, when you upgrade from .NET 4.5 to 4.7, without re-compiling your application, your application is still working and supposed to be working. How is that possible and how does Microsoft manages that? Do they apply an exception to their assemblies in the fusion loader so that it always loads the latest .NET Framework assemblies for user-developed applications? Does it use a bindingRedirect internally for each possible old version to new version? Do they use publishers policies?
I feel like the answer is hidden in this text:
The runtime uses the following steps to resolve an assembly reference:
Determines the correct assembly version by examining applicable
configuration files, including the application configuration file,
publisher policy file, and machine configuration file. If the
configuration file is located on a remote machine, the runtime must
locate and download the application configuration file first.
... -- Microsoft Doc
Other notes:
Specific Version
For some, there is a misconception about Specific Version = true flag, which can be changed in Visual Studio when referencing an assembly. This flag is for compile-time checking only and has nothing to do with the runtime assembly resolution. In fact, the assembly will still have the strong name including assembly name, version, and public key token (if exists).
An Extra Quick Question: By Default?
By default, the runtime attempts to bind with the exact version of an
assembly that the application was built with.
says, Microsoft. It confuses me a little bit. Does it mean that version number is important for both weakly-named and strong-named assemblies to get resolved? For instance, if I drop a weakly-named assembly with an incremented version number, then will it not be resolved by other assemblies that are referencing the older version of it?

How do I create and use a .NET metadata-only 'Reference Assembly'?

Since version 3.0, .NET installs a bunch of different 'reference assemblies' under C:\Program Files\Reference Assemblies\Microsoft...., to support different profiles (say .NET 3.5 client profile, Silverlight profile). Each of these is a proper .NET assembly that contains only metadata - no IL code - and each assembly is marked with the ReferenceAssemblyAttribute. The metadata is restricted to those types and member available under the applicable profile - that's how intellisense shows a restricted set of types and members. The reference assemblies are not used at runtime.
I learnt a bit about it from this blog post.
I'd like to create and use such a reference assembly for my library.
How do I create a metadata-only assembly - is there some compiler flag or ildasm post-processor?
Are there attributes that control which types are exported to different 'profiles'?
How does the reference assembly resolution at runtime - if I had the reference assembly present in my application directory instead of the 'real' assembly, and not in the GAC at all, would probing continue and my AssemblyResolve event fire so that I can supply the actual assembly at runtime?
Any ideas or pointers to where I could learn more about this would be greatly appreciated.
Update: Looking around a bit, I see the .NET 3.0 'reference assemblies' do seem to have some code, and the Reference Assembly attribute was only added in .NET 4.0. So the behaviour might have changed a bit with the new runtime.
Why? For my Excel-DNA ( http://exceldna.codeplex.com ) add-in library, I create single-file .xll add-in by packing the referenced assemblies into the .xll file as resources. The packed assemblies include the user's add-in code, as well as the Excel-DNA managed library (which might be referenced by the user's assembly).
It sounds rather complicated, but works wonderfully well most of the time - the add-in is a single small file, so no installation of distribution issues. I run into (not unexpected) problems because of different versions - if there is an old version of the Excel-DNA managed library as a file, the runtime will load that instead of the packed one (I never get a chance to interfere with the loading).
I hope to make a reference assembly for my Excel-DNA managed part that users can point to when compiling their add-ins. But if they mistakenly have a version of this assembly at runtime, the runtime should fail to load it, and give me a chance to load the real assembly from resources.
To create a reference assembly, you would add this line to your AssemblyInfo.cs file:
[assembly: ReferenceAssembly]
To load others, you can reference them as usual from your VisualStudio project references, or dynamically at runtime using:
Assembly.ReflectionOnlyLoad()
or
Assembly.ReflectionOnlyLoadFrom()
If you have added a reference to a metadata/reference assembly using VisualStudio, then intellisense and building your project will work just fine, however if you try to execute your application against one, you will get an error:
System.BadImageFormatException: Cannot load a reference assembly for execution.
So the expectation is that at runtime you would substitute in a real assembly that has the same metadata signature.
If you have loaded an assembly dynamically with Assembly.ReflectionOnlyLoad() then you can only do all the reflection operations against it (read the types, methods, properties, attributes, etc, but can not dynamically invoke any of them).
I am curious as to what your use case is for creating a metadata-only assembly. I've never had to do that before, and would love to know if you have found some interesting use for them...
If you are still interested in this possibility, I've made a fork of the il-repack project based on Mono.Cecil which accepts a "/meta" command line argument to generate a metadata only assembly for the public and protected types.
https://github.com/KarimLUCCIN/il-repack/tree/xna
(I tried it on the full XNA Framework and its working afaik ...)
Yes, this is new for .NET 4.0. I'm fairly sure this was done to avoid the nasty versioning problems in the .NET 2.0 service packs. Best example is the WaitHandle.WaitOne(int) overload, added and documented in SP2. A popular overload because it avoids having to guess at the proper value for *exitContext" in the WaitOne(int, bool) overload. Problem is, the program bombs when it is run on a version of 2.0 that's older than SP2. Not a happy diagnostic either. Isolating the reference assemblies ensures that this can't happen again.
I think those reference assemblies were created by starting from a copy of the compiled assemblies (like it was done in previous versions) and running them through a tool that strips the IL from the assembly. That tool is however not available to us, nothing in the bin/netfx 4.0 tools Windows 7.1 SDK subdirectory that could do this. Not exactly a tool that gets used often so it is probably not production quality :)
You might have luck with the Cecil Library (from Mono); I think the implementation allows ILMerge functionality, it might just as well write metadata only assemblies.
I have scanned the code base (documentation is sparse), but haven't found any obvious clues yet...
YYMV

Categories

Resources