Disassembler Disassembler - 3 months ago 23
PowerShell Question

PowerShell methods with multiple custom attributes

I have been unable to work with more than one custom attribute (decorator) on PowerShell 5.0 class method. In C#, I'm able do to following:

public class SomeAttribute : Attribute {
public string Text { get; set; }
}

public class OtherAttribute : Attribute {
public string Text { get; set; }
}

public class MyClass {
[SomeAttribute(Text = "sometext")]
[OtherAttribute(Text = "othertext")]
public void MyMethod() {
// ...
}
}


and then somewhere else call

object[] customAttributes = typeof(MyClass).GetMethod("MyMethod").GetCustomAttributes(false);


which gives me an array of all custom attributes associated to the method without any problems.

In PowerShell 5.0, I'm trying to use analogically:

class SomeAttribute : Attribute {
[string]$Text
}

class OtherAttribute : Attribute {
[string]$Text
}

class MyClass {
[SomeAttribute(Text="sometext")]
[OtherAttribute(Text="othertext")]
MyMethod() {
# ...
}
}


which seems to be the proper syntax as PowerShell happily accepts it. But listing the attributes via

[MyClass].GetMethod("MyMethod").GetCustomAttributes($false)


returns following error:

Exception calling "GetCustomAttributes" with "1" argument(s): "Could not load type 'OtherAttribute' from assembly 'â§ıpowershell, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'."
At line:1 char:1
+ [MyClass].GetMethod("MyMethod").GetCustomAttributes($false)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : TypeLoadException


and

[MyClass].GetMethod("MyMethod").CustomAttributes


simply returns
$null
.

However when I use only one custom attribute, everything works as expected and the attribute is correctly returned using the snippets above.

How do I properly define multiple custom attributes for PowerShell 5.0 class method?

Update - Example of behavior using only one custom attribute



Let's assume only the first custom attribute from the original question.

class SomeAttribute : Attribute {
[string]$Text
}

class MyClass {
[SomeAttribute(Text="sometext")]
MyMethod() {
# ...
}
}


Then

[MyClass].GetMethod("MyMethod").GetCustomAttributes($false)


gives following output

Text TypeId
---- ------
sometext SomeAttribute


and

[MyClass].GetMethod("MyMethod").CustomAttributes


gives following output

AttributeType Constructor ConstructorArguments NamedArguments
------------- ----------- -------------------- --------------
SomeAttribute Void .ctor() {} {Text = "sometext"}


Which leads me to believe that one attribute indeed works as expected.

Answer

It seems that PowerShell (.NET, IMHO) have troubles referencing two different assemblies with same full name (something tell me, that it is not possible in .NET).

PS> class A {}
PS> class B {}
PS> [A].Assembly -eq [B].Assembly
False
PS> [A].Assembly.FullName -eq [B].Assembly.FullName
True
PS> class CA { [A] $A }; class CB { [B] $B }
PS> [CA]::new().A #work fine
PS> [CB]::new().B #error

Solution would be to define classes A and B in the same command, this would put them in the same generated assembly:

PS> class A {}; class B {}
PS> [A].Assembly -eq [B].Assembly
True
PS> class CA { [A] $A }; class CB { [B] $B }
PS> [CA]::new().A #work fine
PS> [CB]::new().B #work fine

So, this work fine for me:

PS> class SomeAttribute : Attribute {
>>>     [string]$Text
>>> }
>>> class OtherAttribute : Attribute {
>>>     [string]$Text
>>> }
PS> class MyClass {
>>>     [SomeAttribute(Text="sometext")]
>>>     [OtherAttribute(Text="othertext")]
>>>     MyMethod() {
>>>         # ...
>>>     }
>>> }
PS> [MyClass].GetMethod("MyMethod").GetCustomAttributes($false)

Text      TypeId
----      ------
sometext  SomeAttribute
othertext OtherAttribute

Other solution would be to not define classes in interactive session but in script files. In that case, obfuscated file name will be part of generated assembly name, thus you will not have this trouble even when attributes defines in different script files.