Powershell: Add-Type reference assembly bug

Created on 3 Aug 2016  路  18Comments  路  Source: PowerShell/PowerShell

Steps to reproduce

$source = @"
using System;
namespace Foo {
public static class Bar {
public static void Test() {
Console.BackgroundColor = (Console.ConsoleColor) -1;
}
}
}
"@
Add-Type -TypeDefinition $source -Language CSharp -ReferencedAssemblies System.Console

Expected behavior

The type to be added.

Actual behavior

Add-Type : (5) : The type 'Console' exists in both 'System.Console, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' and
'System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e'
(4) : public static void Test() {
(5) : >>> System.Console.BackgroundColor = (System.Console.ConsoleColor) -1;
(6) : }
At line:1 char:1
+ Add-Type -TypeDefinition $source -Language CSharp -ReferencedAssembli ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidData: (Microsoft.Power...peCompilerError:AddTypeCompilerError) [Add-Type], Exception
    + FullyQualifiedErrorId : SOURCE_CODE_ERROR,Microsoft.PowerShell.Commands.AddTypeCommand

Add-Type : (5) : The type 'Console' exists in both 'System.Console, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' and
'System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e'
(4) : public static void Test() {
(5) : >>> System.Console.BackgroundColor = (System.Console.ConsoleColor) -1;
(6) : }
At line:1 char:1
+ Add-Type -TypeDefinition $source -Language CSharp -ReferencedAssembli ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidData: (Microsoft.Power...peCompilerError:AddTypeCompilerError) [Add-Type], Exception
    + FullyQualifiedErrorId : SOURCE_CODE_ERROR,Microsoft.PowerShell.Commands.AddTypeCommand

Add-Type : Cannot add type. Compilation errors occurred.
At line:1 char:1
+ Add-Type -TypeDefinition $source -Language CSharp -ReferencedAssembli ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [Add-Type], InvalidOperationException
    + FullyQualifiedErrorId : COMPILER_ERRORS,Microsoft.PowerShell.Commands.AddTypeCommand

Environment data

> $PSVersionTable
Name                           Value
----                           -----
PSVersion                      5.1.10032.0
PSEdition                      PowerShellCore
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   3.0.0.0
GitCommitId                    v6.0.0-alpha.7
CLRVersion
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

/cc @vors

Area-Cmdlets Blocked Issue-Bug Resolution-Fixed

Most helpful comment

A contract assembly is analogous to a header file in C++ - it defines the api implemented by the assembly, but it does not provide an implementation.

A facade assembly is a fake implementation that points to the real implementation.

For a concrete example, consider a commonly used type System.String:

When building against netstandard1, you will reference System.Runtime.dll. This is (or should be) a contract assembly that contains no implementation.

When you build your application (not a library, but an exe), you will target a specific runtime, e.g. net45 or netcoreapp1.0, the build system will copy the implementation assembly to your build directory and you will deploy that implementation assembly, not the contract assembly.

If your assembly targets netstandard*, the build system does not copy your referenced assemblies - it's assumed the application provides those because it knows which runtime you are targeting.

If your assembly targets netcoreapp*, the build system will copy System.Runtime.dll from .Net Core - and it will contain the implementation of System.String.

If your assembly targets net45, the build system will copy a facade assembly for System.Runtime.dll. This facade assembly has a type forward that says System.String is really in mscorlib.dll.

It turns out that the facade assembly System.Runtime.dll is installed on Windows 10, so maybe the build system won't copy this one, I haven't tried it. But many facade assemblies are not installed, so the build system will help you copy the necessary assemblies if your application targets a specific runtime and not netstandard*.

All 18 comments

That is really weird. 馃槩

PS>[Console].AssemblyQualifiedName
System.Console, System.Console, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a

There's even a test for that class being there

Which of course, means that if you don't use the -ReferencedAssemblies parameter (which is strangely plural), you get an error: 'Console' does not contain a definition for 'BackgroundColor' ... but...

PS> [Console]::BackgroundColor = "White"
PS> [Console]::BackgroundColor
White

Is there any _workaround_ for this? It's currently blocking my PowerLine module from working on Core :wink:

A similar issue is described in Roslyn at https://github.com/dotnet/roslyn/issues/13267

We're running into this problem as we expand our Puppet PowerShell module to be cross-platform, as we use a custom host.

In the 6.0 alpha 10 release, it was necessary to add more ReferencedAssemblies to the Add-Type call on OSX with -ReferencedAssemblies @('System.Collections', 'System.Console', 'System.Management.Automation', 'System.Globalization'). On Windows WMF 5.1, this was not necessary.

It's my understanding that System.Management.Automation should not be necessary to explicitly include based on the Add-Type documentation at https://technet.microsoft.com/en-us/library/hh849914.aspx

According to @tmat it's a bug in CoreFX.
They exporting the same public type from both assemblies.

There is a workaround: an internal Roslyn API that allows compiling such cases.
I will send a PR with it, but we need to discuss, do we want to use it or not.

Also, this API will become public at some point, this work is tracked in https://github.com/dotnet/roslyn/issues/5855

Some things we discussed with @tmat for future context.

  1. It's a bad idea to mix contract assemblies and implementation assemblies in the reference assmemblies. It will confuse Roslyn and things will break.
  2. In the PowerShell self-contained package, we don't ship any contract assemblies. We can consider doing it just for Add-Type but it's definitely will complicate our build process.
  3. The ideal solution would be to get https://github.com/dotnet/corefx/issues/5540 fixed and switch to the newer coreclr packages.

It's unclear based on the commentary in https://github.com/dotnet/coreclr/issues/4651 whether or not https://github.com/dotnet/coreclr/pull/9269 was / will be successful in fixing this issue completely or not.

If you could follow up on this thread when a new build based on the https://github.com/dotnet/coreclr/pull/9269 merge has shipped, that would be great @vors

Thanks!

@lzybkr @vors for my own knowledge, is a "contract assembly" the same as what @lzybkr has been referring to as a "facade assembly" (per #3095)?

A contract assembly is analogous to a header file in C++ - it defines the api implemented by the assembly, but it does not provide an implementation.

A facade assembly is a fake implementation that points to the real implementation.

For a concrete example, consider a commonly used type System.String:

When building against netstandard1, you will reference System.Runtime.dll. This is (or should be) a contract assembly that contains no implementation.

When you build your application (not a library, but an exe), you will target a specific runtime, e.g. net45 or netcoreapp1.0, the build system will copy the implementation assembly to your build directory and you will deploy that implementation assembly, not the contract assembly.

If your assembly targets netstandard*, the build system does not copy your referenced assemblies - it's assumed the application provides those because it knows which runtime you are targeting.

If your assembly targets netcoreapp*, the build system will copy System.Runtime.dll from .Net Core - and it will contain the implementation of System.String.

If your assembly targets net45, the build system will copy a facade assembly for System.Runtime.dll. This facade assembly has a type forward that says System.String is really in mscorlib.dll.

It turns out that the facade assembly System.Runtime.dll is installed on Windows 10, so maybe the build system won't copy this one, I haven't tried it. But many facade assemblies are not installed, so the build system will help you copy the necessary assemblies if your application targets a specific runtime and not netstandard*.

Got it. Thanks for the detailed explanation. We just had a great conversation with the .NET folks today, and this dovetails nicely with some similar info I got from them.

Apologies in asking this question!!

Is it a "Requirement" to install .NET Core in order for PowerShell Open Source to work in Linux distribution?? This is been bugging me for some time.

:)

@MaximoTrinidad in a sense, no, the PowerShell end-user packages are self-contained (they bring the components of .NET Core that are necessary along with themselves; _having_ those components is necessary, but you do not have to install them separately). It _is_ required in order to develop on PowerShell, but that's a separate story.

@andschwa , Thanks for the quick reply!

So, would it make any difference if I install .NET Core? I mean, It should be fine along side PowerShell. Right!

:)

@MaximoTrinidad installing .NET Core separately shouldn't affect PowerShell whatsoever 馃槃

Thanks @andschwa! Greatly Appreciated!
:)

Given that the repro in #3264 is working, I played around with the original example (note that I suspected that Console.ConsoleColor should have been System.ConsoleColor). I think it's working now (adding @SteveL-MSFT to verify):

$source = @"
using System;
namespace Foo {
public static class Bar {
public static void Test() {
Console.BackgroundColor = (System.ConsoleColor) (-1);
}
}
}
"@
Add-Type -TypeDefinition $source -Language CSharp -ReferencedAssemblies System.Console

Yes, it looks to be working.

Was this page helpful?
0 / 5 - 0 ratings