about_Classes_Methods
Short description
Describes how to define methods for PowerShell classes.
Long description
Methods define the actions that a class can perform. Methods can take parameters that specify input data. Methods always define an output type. If a method doesn't return any output, it must have the Void output type. If a method doesn't explicitly define an output type, the method's output type is Void.
In class methods, no objects get sent to the pipeline except those specified in
the return
statement. There's no accidental output to the pipeline from the
code.
Note
This is fundamentally different from how PowerShell functions handle output, where everything goes to the pipeline.
Nonterminating errors written to the error stream from inside a class method
aren't passed through. You must use throw
to surface a terminating error.
Using the Write-*
cmdlets, you can still write to PowerShell's output streams
from within a class method. The cmdlets respect the preference variables
in the calling scope. However, you should avoid using the Write-*
cmdlets so
that the method only outputs objects using the return
statement.
Class methods can reference the current instance of the class object by using
the $this
automatic variable to access properties and other methods defined
in the current class. The $this
automatic variable isn't available in static
methods.
Class methods can have any number of attributes, including the hidden and static attributes.
Syntax
Class methods use the following syntaxes:
One-line syntax
[[<attribute>]...] [hidden] [static] [<output-type>] <method-name> ([<method-parameters>]) { <body> }
Multiline syntax
[[<attribute>]...]
[hidden]
[static]
[<output-type>] <method-name> ([<method-parameters>]) {
<body>
}
Examples
Example 1 - Minimal method definition
The GetVolume()
method of the ExampleCube1 class returns the volume of
the cube. It defines the output type as a floating number and returns the
result of multiplying the Height, Length, and Width properties of
the instance.
class ExampleCube1 {
[float] $Height
[float] $Length
[float] $Width
[float] GetVolume() { return $this.Height * $this.Length * $this.Width }
}
$box = [ExampleCube1]@{
Height = 2
Length = 2
Width = 3
}
$box.GetVolume()
12
Example 2 - Method with parameters
The GeWeight()
method takes a floating number input for the density of the
cube and returns the weight of the cube, calculated as volume multiplied by
density.
class ExampleCube2 {
[float] $Height
[float] $Length
[float] $Width
[float] GetVolume() { return $this.Height * $this.Length * $this.Width }
[float] GetWeight([float]$Density) {
return $this.GetVolume() * $Density
}
}
$cube = [ExampleCube2]@{
Height = 2
Length = 2
Width = 3
}
$cube.GetWeight(2.5)
30
Example 3 - Method without output
This example defines the Validate()
method with the output type as
System.Void. This method returns no output. Instead, if the validation
fails, it throws an error. The GetVolume()
method calls Validate()
before
calculating the volume of the cube. If validation fails, the method terminates
before the calculation.
class ExampleCube3 {
[float] $Height
[float] $Length
[float] $Width
[float] GetVolume() {
$this.Validate()
return $this.Height * $this.Length * $this.Width
}
[void] Validate() {
$InvalidProperties = @()
foreach ($Property in @('Height', 'Length', 'Width')) {
if ($this.$Property -le 0) {
$InvalidProperties += $Property
}
}
if ($InvalidProperties.Count -gt 0) {
$Message = @(
'Invalid cube properties'
"('$($InvalidProperties -join "', '")'):"
"Cube dimensions must all be positive numbers."
) -join ' '
throw $Message
}
}
}
$Cube = [ExampleCube3]@{ Length = 1 ; Width = -1 }
$Cube
$Cube.GetVolume()
Height Length Width
------ ------ -----
0.00 1.00 -1.00
Exception:
Line |
20 | throw $Message
| ~~~~~~~~~~~~~~
| Invalid cube properties ('Height', 'Width'): Cube dimensions must
| all be positive numbers.
The method throws an exception because the Height and Width properties are invalid, preventing the class from calculating the current volume.
Example 4 - Static method with overloads
The ExampleCube4 class defines the static method GetVolume()
with two
overloads. The first overload has parameters for the dimensions of the cube and
a flag to indicate whether the method should validate the input.
The second overload only includes the numeric inputs. It calls the first
overload with $Static
as $true
. The second overload gives users a way to
call the method without always having to define whether to strictly validate
the input.
The class also defines GetVolume()
as an instance (nonstatic) method. This
method calls the second static overload, ensuring that the instance
GetVolume()
method always validates the cube's dimensions before returning
the output value.
class ExampleCube4 {
[float] $Height
[float] $Length
[float] $Width
static [float] GetVolume(
[float]$Height,
[float]$Length,
[float]$Width,
[boolean]$Strict
) {
$Signature = "[ExampleCube4]::GetVolume({0}, {1}, {2}, {3})"
$Signature = $Signature -f $Height, $Length, $Width, $Strict
Write-Verbose "Called $Signature"
if ($Strict) {
[ValidateScript({$_ -gt 0 })]$Height = $Height
[ValidateScript({$_ -gt 0 })]$Length = $Length
[ValidateScript({$_ -gt 0 })]$Width = $Width
}
return $Height * $Length * $Width
}
static [float] GetVolume([float]$Height, [float]$Length, [float]$Width) {
$Signature = "[ExampleCube4]::GetVolume($Height, $Length, $Width)"
Write-Verbose "Called $Signature"
return [ExampleCube4]::GetVolume($Height, $Length, $Width, $true)
}
[float] GetVolume() {
Write-Verbose "Called `$this.GetVolume()"
return [ExampleCube4]::GetVolume(
$this.Height,
$this.Length,
$this.Width
)
}
}
$VerbosePreference = 'Continue'
$Cube = [ExampleCube4]@{ Height = 2 ; Length = 2 }
$Cube.GetVolume()
VERBOSE: Called $this.GetVolume()
VERBOSE: Called [ExampleCube4]::GetVolume(2, 2, 0)
VERBOSE: Called [ExampleCube4]::GetVolume(2, 2, 0, True)
MetadataError:
Line |
19 | [ValidateScript({$_ -gt 0 })]$Width = $Width
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| The variable cannot be validated because the value 0 is not a valid
| value for the Width variable.
The verbose messages in the method definitions show how the initial call to
$this.GetVolume()
calls the static method.
Calling the static method directly with the Strict parameter as $false
returns 0
for the volume.
[ExampleCube4]::GetVolume($Cube.Height, $Cube.Length, $Cube.Width, $false)
VERBOSE: Called [ExampleCube4]::GetVolume(2, 2, 0, False)
0
Method signatures and overloads
Every class method has a unique signature that defines how to call the method. The method's output type, name, and parameters define the method signature.
When a class defines more than one method with the same name, the definitions of that method are overloads. Overloads for a method must have different parameters. A method can't define two implementations with the same parameters, even if the output types are different.
The following class defines two methods, Shuffle()
and Deal()
. The Deal()
method defines two overloads, one without any parameters and the other with the
Count parameter.
class CardDeck {
[string[]]$Cards = @()
hidden [string[]]$Dealt = @()
hidden [string[]]$Suits = @('Clubs', 'Diamonds', 'Hearts', 'Spades')
hidden [string[]]$Values = 2..10 + @('Jack', 'Queen', 'King', 'Ace')
CardDeck() {
foreach($Suit in $this.Suits) {
foreach($Value in $this.Values) {
$this.Cards += "$Value of $Suit"
}
}
$this.Shuffle()
}
[void] Shuffle() {
$this.Cards = $this.Cards + $this.Dealt | Where-Object -FilterScript {
-not [string]::IsNullOrEmpty($_)
} | Get-Random -Count $this.Cards.Count
}
[string] Deal() {
if ($this.Cards.Count -eq 0) { throw "There are no cards left." }
$Card = $this.Cards[0]
$this.Cards = $this.Cards[1..$this.Cards.Count]
$this.Dealt += $Card
return $Card
}
[string[]] Deal([int]$Count) {
if ($Count -gt $this.Cards.Count) {
throw "There are only $($this.Cards.Count) cards left."
} elseif ($Count -lt 1) {
throw "You must deal at least 1 card."
}
return (1..$Count | ForEach-Object { $this.Deal() })
}
}
Method output
By default, methods don't have any output. If a method signature includes an
explicit output type other than Void, the method must return an object of
that type. Methods don't emit any output except when the return
keyword
explicitly returns an object.
Method parameters
Class methods can define input parameters to use in the method body. Method parameters are enclosed in parentheses and are separated by commas. Empty parentheses indicate that the method requires no parameters.
Parameters can be defined on a single line or multiple lines. The following blocks show the syntax for method parameters.
([[<parameter-type>]]$<parameter-name>[, [[<parameter-type>]]$<parameter-name>])
(
[[<parameter-type>]]$<parameter-name>[,
[[<parameter-type>]]$<parameter-name>]
)
Method parameters can be strongly typed. If a parameter isn't typed, the method accepts any object for that parameter. If the parameter is typed, the method tries to convert the value for that parameter to the correct type, throwing an exception if the input can't be converted.
Method parameters can't define default values. All method parameters are mandatory.
Method parameters can't have any other attributes. This prevents methods from
using parameters with the Validate*
attributes. For more information about
the validation attributes, see about_Functions_Advanced_Parameters.
You can use one of the following patterns to add validation to method parameters:
- Reassign the parameters to the same variables with the required validation attributes. This works for both static and instance methods. For an example of this pattern, see Example 4.
- Use
Update-TypeData
to define aScriptMethod
that uses validation attributes on the parameters directly. This only works for instance methods. For more information, see the Defining instance methods with Update-TypeData section.
Automatic variables in methods
Not all automatic variables are available in methods. The following list includes automatic variables and suggestions for whether and how to use them in PowerShell class methods. Automatic variables not included in the list aren't available to class methods.
$?
- Access as normal.$_
- Access as normal.$args
- Use the explicit parameter variables instead.$ConsoleFileName
- Access as$Script:ConsoleFileName
instead.$Error
- Access as normal.$EnabledExperimentalFeatures
- Access as$Script:EnabledExperimentalFeatures
instead.$Event
- Access as normal.$EventArgs
- Access as normal.$EventSubscriber
- Access as normal.$ExecutionContext
- Access as$Script:ExecutionContext
instead.$false
- Access as normal.$foreach
- Access as normal.$HOME
- Access as$Script:HOME
instead.$Host
- Access as$Script:Host
instead.$input
- Use the explicit parameter variables instead.$IsCoreCLR
- Access as$Script:IsCoreCLR
instead.$IsLinux
- Access as$Script:IsLinux
instead.$IsMacOS
- Access as$Script:IsMacOS
instead.$IsWindows
- Access as$Script:IsWindows
instead.$LASTEXITCODE
- Access as normal.$Matches
- Access as normal.$MyInvocation
- Access as normal.$NestedPromptLevel
- Access as normal.$null
- Access as normal.$PID
- Access as$Script:PID
instead.$PROFILE
- Access as$Script:PROFILE
instead.$PSBoundParameters
- Don't use this variable. It's intended for cmdlets and functions. Using it in a class may have unexpected side effects.$PSCmdlet
- Don't use this variable. It's intended for cmdlets and functions. Using it in a class may have unexpected side effects.$PSCommandPath
- Access as normal.$PSCulture
- Access as$Script:PSCulture
instead.$PSEdition
- Access as$Script:PSEdition
instead.$PSHOME
- Access as$Script:PSHOME
instead.$PSItem
- Access as normal.$PSScriptRoot
- Access as normal.$PSSenderInfo
- Access as$Script:PSSenderInfo
instead.$PSUICulture
- Access as$Script:PSUICulture
instead.$PSVersionTable
- Access as$Script:PSVersionTable
instead.$PWD
- Access as normal.$Sender
- Access as normal.$ShellId
- Access as$Script:ShellId
instead.$StackTrace
- Access as normal.$switch
- Access as normal.$this
- Access as normal. In a class method,$this
is always the current instance of the class. You can access the class properties and methods with it. It's not available in static methods.$true
- Access as normal.
For more information about automatic variables, see about_Automatic_Variables.
Hidden methods
You can hide methods of a class by declaring them with the hidden
keyword.
Hidden class methods are:
- Not included in the list of class members returned by the
Get-Member
cmdlet. To show hidden methods withGet-Member
, use the Force parameter. - Not displayed in tab completion or IntelliSense unless the completion occurs in the class that defines the hidden method.
- Public members of the class. They can be called and inherited. Hiding a method doesn't make it private. It only hides the method as described in the previous points.
Note
When you hide any overload for a method, that method is removed from
IntelliSense, completion results, and the default output for Get-Member
.
For more information about the hidden
keyword, see about_Hidden.
Static methods
You can define a method as belonging to the class itself instead of instances
of the class by declaring the method with the static
keyword. Static class
methods:
- Are always available, independent of class instantiation.
- Are shared across all instances of the class.
- Are always available.
- Can't access instance properties of the class. They can only access static properties.
- Live for the entire session span.
Derived class methods
When a class derives from a base class, it inherits the methods of the base class and their overloads. Any method overloads defined on the base class, including hidden methods, are available on the derived class.
A derived class can override an inherited method overload by redefining it in the class definition. To override the overload, the parameter types must be the same as for the base class. The output type for the overload can be different.
Unlike constructors, methods can't use the : base(<parameters>)
syntax to
invoke a base class overload for the method. The redefined overload on the
derived class completely replaces the overload defined by the base class.
The following example shows the behavior for static and instance methods on derived classes.
The base class defines:
- The static methods
Now()
for returning the current time andDaysAgo()
for returning a date in the past. - The instance property TimeStamp and a
ToString()
instance method that returns the string representation of that property. This ensures that when an instance is used in a string it converts to the datetime string instead of the class name. - The instance method
SetTimeStamp()
with two overloads. When the method is called without parameters, it sets the TimeStamp to the current time. When the method is called with a DateTime, it sets the TimeStamp to that value.
class BaseClass {
static [datetime] Now() {
return Get-Date
}
static [datetime] DaysAgo([int]$Count) {
return [BaseClass]::Now().AddDays(-$Count)
}
[datetime] $TimeStamp = [BaseClass]::Now()
[string] ToString() {
return $this.TimeStamp.ToString()
}
[void] SetTimeStamp([datetime]$TimeStamp) {
$this.TimeStamp = $TimeStamp
}
[void] SetTimeStamp() {
$this.TimeStamp = [BaseClass]::Now()
}
}
The next block defines classes derived from BaseClass:
- DerivedClassA inherits from BaseClass without any overrides.
- DerivedClassB overrides the
DaysAgo()
static method to return a string representation instead of the DateTime object. It also overrides theToString()
instance method to return the timestamp as an ISO8601 date string. - DerivedClassC overrides the parameterless overload of the
SetTimeStamp()
method so that setting the timestamp without parameters sets the date to 10 days before the current date.
class DerivedClassA : BaseClass {}
class DerivedClassB : BaseClass {
static [string] DaysAgo([int]$Count) {
return [BaseClass]::DaysAgo($Count).ToString('yyyy-MM-dd')
}
[string] ToString() {
return $this.TimeStamp.ToString('yyyy-MM-dd')
}
}
class DerivedClassC : BaseClass {
[void] SetTimeStamp() {
$this.SetTimeStamp([BaseClass]::Now().AddDays(-10))
}
}
The following block shows the output of the static Now()
method for the
defined classes. The output is the same for every class, because the derived
classes don't override the base class implementation of the method.
"[BaseClass]::Now() => $([BaseClass]::Now())"
"[DerivedClassA]::Now() => $([DerivedClassA]::Now())"
"[DerivedClassB]::Now() => $([DerivedClassB]::Now())"
"[DerivedClassC]::Now() => $([DerivedClassC]::Now())"
[BaseClass]::Now() => 11/06/2023 09:41:23
[DerivedClassA]::Now() => 11/06/2023 09:41:23
[DerivedClassB]::Now() => 11/06/2023 09:41:23
[DerivedClassC]::Now() => 11/06/2023 09:41:23
The next block calls the DaysAgo()
static method of each class. Only the
output for DerivedClassB is different, because it overrode the base
implementation.
"[BaseClass]::DaysAgo(3) => $([BaseClass]::DaysAgo(3))"
"[DerivedClassA]::DaysAgo(3) => $([DerivedClassA]::DaysAgo(3))"
"[DerivedClassB]::DaysAgo(3) => $([DerivedClassB]::DaysAgo(3))"
"[DerivedClassC]::DaysAgo(3) => $([DerivedClassC]::DaysAgo(3))"
[BaseClass]::DaysAgo(3) => 11/03/2023 09:41:38
[DerivedClassA]::DaysAgo(3) => 11/03/2023 09:41:38
[DerivedClassB]::DaysAgo(3) => 2023-11-03
[DerivedClassC]::DaysAgo(3) => 11/03/2023 09:41:38
The following block shows the string presentation of a new instance for each
class. The representation for DerivedClassB is different because it
overrode the ToString()
instance method.
"`$base = [BaseClass]::New() => $($base = [BaseClass]::New(); $base)"
"`$a = [DerivedClassA]::New() => $($a = [DerivedClassA]::New(); $a)"
"`$b = [DerivedClassB]::New() => $($b = [DerivedClassB]::New(); $b)"
"`$c = [DerivedClassC]::New() => $($c = [DerivedClassC]::New(); $c)"
$base = [BaseClass]::New() => 11/6/2023 9:44:57 AM
$a = [DerivedClassA]::New() => 11/6/2023 9:44:57 AM
$b = [DerivedClassB]::New() => 2023-11-06
$c = [DerivedClassC]::New() => 11/6/2023 9:44:57 AM
The next block calls the SetTimeStamp()
instance method for each instance,
setting the TimeStamp property to a specific date. Each instance has the
same date, because none of the derived classes override the parameterized
overload for the method.
[datetime]$Stamp = '2024-10-31'
"`$base.SetTimeStamp(`$Stamp) => $($base.SetTimeStamp($Stamp) ; $base)"
"`$a.SetTimeStamp(`$Stamp) => $($a.SetTimeStamp($Stamp); $a)"
"`$b.SetTimeStamp(`$Stamp) => $($b.SetTimeStamp($Stamp); $b)"
"`$c.SetTimeStamp(`$Stamp) => $($c.SetTimeStamp($Stamp); $c)"
$base.SetTimeStamp($Stamp) => 10/31/2024 12:00:00 AM
$a.SetTimeStamp($Stamp) => 10/31/2024 12:00:00 AM
$b.SetTimeStamp($Stamp) => 2024-10-31
$c.SetTimeStamp($Stamp) => 10/31/2024 12:00:00 AM
The last block calls SetTimeStamp()
without any parameters. The output shows
that the value for the DerivedClassC instance is set to 10 days before the
others.
"`$base.SetTimeStamp() => $($base.SetTimeStamp() ; $base)"
"`$a.SetTimeStamp() => $($a.SetTimeStamp(); $a)"
"`$b.SetTimeStamp() => $($b.SetTimeStamp(); $b)"
"`$c.SetTimeStamp() => $($c.SetTimeStamp(); $c)"
$base.SetTimeStamp() => 11/6/2023 9:53:58 AM
$a.SetTimeStamp() => 11/6/2023 9:53:58 AM
$b.SetTimeStamp() => 2023-11-06
$c.SetTimeStamp() => 10/27/2023 9:53:58 AM
Defining instance methods with Update-TypeData
Beyond declaring methods directly in the class definition, you can define
methods for instances of a class in the static constructor using the
Update-TypeData
cmdlet.
Use this snippet as a starting point for the pattern. Replace the placeholder text in angle brackets as needed.
class <ClassName> {
static [hashtable[]] $MemberDefinitions = @(
@{
MemberName = '<MethodName>'
MemberType = 'ScriptMethod'
Value = {
param(<method-parameters>)
<method-body>
}
}
)
static <ClassName>() {
$TypeName = [<ClassName>].Name
foreach ($Definition in [<ClassName>]::MemberDefinitions) {
Update-TypeData -TypeName $TypeName @Definition
}
}
}
Tip
The Add-Member
cmdlet can add properties and methods to a class in
non-static constructors, but the cmdlet runs every time the constructor is
called. Using Update-TypeData
in the static constructor ensures that the
code for adding the members to the class only needs to run once in a session.
Defining methods with default parameter values and validation attributes
Methods defined directly in a class declaration can't define default values or validation attributes on the method parameters. To define class methods with default values or validation attributes, they must be defined as ScriptMethod members.
In this example, the CardDeck class defines a Draw()
method that uses
both a validation attribute and a default value for the Count parameter.
class CookieJar {
[int] $Cookies = 12
static [hashtable[]] $MemberDefinitions = @(
@{
MemberName = 'Eat'
MemberType = 'ScriptMethod'
Value = {
param(
[ValidateScript({ $_ -ge 1 -and $_ -le $this.Cookies })]
[int] $Count = 1
)
$this.Cookies -= $Count
if ($Count -eq 1) {
"You ate 1 cookie. There are $($this.Cookies) left."
} else {
"You ate $Count cookies. There are $($this.Cookies) left."
}
}
}
)
static CookieJar() {
$TypeName = [CookieJar].Name
foreach ($Definition in [CookieJar]::MemberDefinitions) {
Update-TypeData -TypeName $TypeName @Definition
}
}
}
$Jar = [CookieJar]::new()
$Jar.Eat(1)
$Jar.Eat()
$Jar.Eat(20)
$Jar.Eat(6)
You ate 1 cookie. There are 11 left.
You ate 1 cookie. There are 10 left.
MethodInvocationException:
Line |
36 | $Jar.Eat(20)
| ~~~~~~~~~~~~
| Exception calling "Eat" with "1" argument(s): "The attribute
| cannot be added because variable Count with value 20 would no
| longer be valid."
You ate 6 cookies. There are 4 left.
Note
While this pattern works for validation attributes, notice that the exception is misleading, referencing an inability to add an attribute. It might be a better user experience to explicitly check the value for the parameter and raise a meaningful error instead. That way, users can understand why they're seeing the error and what to do about it.
Limitations
PowerShell class methods have the following limitations:
Method parameters can't use any attributes, including validation attributes.
Workaround: Reassign the parameters in the method body with the validation attribute or define the method in the static constructor with the
Update-TypeData
cmdlet.Method parameters can't define default values. The parameters are always mandatory.
Workaround: Define the method in the static constructor with the
Update-TypeData
cmdlet.Methods are always public, even when they're hidden. They can be overridden when the class is inherited.
Workaround: None.
If any overload of a method is hidden, every overload for that method is treated as hidden too.
Workaround: None.