Disassembler
Disassembler

Reputation: 171

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.

Upvotes: 4

Views: 1118

Answers (2)

user4003407
user4003407

Reputation: 22132

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.

Upvotes: 2

Michael Sorens
Michael Sorens

Reputation: 36738

I am skeptical that you really got the results you expected even using just one custom attribute. The reason is that methods do not support attributes, at least that I have found. A method supports an optional return type that, in PowerShell, happens to look just like an attribute. That is you can define a method returning void...

MyMethod() {
    $foo = 5        
}

or a method returning something (like an int in this case)...

[int] MyMethod() {
    $foo = 5
    return $foo
}

(I'm hoping that someone can prove me wrong, though, and show that PowerShell classes do support attributes!)

Upvotes: 0

Related Questions