Chedy2149
Chedy2149

Reputation: 3051

How to define and use a Custom Attribute in PowerShell 5?

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:

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

What am I missing?

Upvotes: 2

Views: 819

Answers (1)

mklement0
mklement0

Reputation: 439133

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.

Upvotes: 4

Related Questions