Reputation: 3051
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:
[MarkedAttribute()]
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?
Upvotes: 2
Views: 819
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.
Invoke-Expression
, though do note that Invoke-Expression
should generally be avoidedOnce 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