2

Using PowerShell 5.1.14393.3866 on Windows 10, I am trying to define and use custom attributes on Powershell class properties as in the following:

class MarkedAttribute : System.Attribute { }

class Person {
    [Marked()]
    [string]$Name
}

$p = [Person]::new()
$p.Name = "Hoho"

Write-Host $p.Name

When I run the previous script I get the following error:

At C:\Users\xxxxxxxx\Desktop\FinishLine\issue.ps1:6 char:6
+     [Marked()]
+      ~~~~~~
Cannot find the type for custom attribute 'Marked'. Make sure that the assembly that contains this type is loaded.
    + CategoryInfo          : ParserError: (:) [], ParseException
    + FullyQualifiedErrorId : CustomAttributeTypeNotFound

It is as if the custom attribute was never defined.

I am able to use 'standard' attributes such as the ones defined in System.ComponentModel but no chance with custom attributes.

I tried the following without success:

  • Using the full attribute name [MarkedAttribute()]
  • Defining the attribute in C# and executing Add-Type -TypeDefinition $theCsCode

According to some posts on the web, it should be possible to use custom attribute from PowerShell.

What am I missing?

1 Answer 1

4

There are two problems (written as of PowerShell 7.1):

  • Class definitions are processed at parse time, at which point any referenced types must already be defined; that is, the [MarkedAttribute] type must be defined before the [Person] type can be declared with a reference to it.

  • Once that requirement is met, additionally - for reasons unknown to me - the explicit Attribute suffix is needed to refer to the type.

class MarkedAttribute : System.Attribute { }

# Use Invoke-Expression so you can refer to [MarkedAttribute] if defined
# in the same script, 
# and be sure to use the 'Attribute' suffix explicitly.
Invoke-Expression @'
  class Person {
    [MarkedAttribute()]
    [string]$Name
  }
'@

$p = [Person]::new()
$p.Name = "Hoho"

$p.Name # output

If you want to avoid the need for Invoke-Expression, outsource the [Person] class definition to a separate file that you can then load into the current scope with ., the dot-sourcing operator:

First, create a PersonClass.ps1 file containing the Person class definition in the same directory as the script that will be using it:

@'
class Person {
  [MarkedAttribute()]
  [string]$Name
}
'@ > PersonClass.ps1

Then define your main script as follows:

class MarkedAttribute : System.Attribute { }

# Dot-source the script that defines the [Person] class, from the
# same dir. where this script resides:
. $PSScriptRoot/PersonClass.ps1

$p = [Person]::new()
$p.Name = "Hoho"

$p.Name # output

This approach ensures that class [MarkedAttribute] is already defined by the time PersonClass.ps1 is parsed, and the definition of [Person] therefore succeeds.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.