PowerShell 面向对象

PowerShell 面向对象
狂犬主子PowerShell 面向对象
PowerShell 是一种强大的脚本语言,主要用于基于 Windows 的系统管理自动化。它支持面向对象编程(OOP),允许开发者创建类、实例化对象以及使用继承等特性来构建复杂的程序结构。
PowerShell 从版本 5.0 开始支持类的定义和其他用户定义类型的正式语法。这允许开发人员和 IT 专业人士使用面向对象编程的概念来定义自定义类型,从而扩大 PowerShell 的适用范围。
本文为个人的学习笔记,参考官方文档,加入了一些理解,示例为本人实验时写的代码,可能不太严谨。如果有错误内容的话,还请多多指正!
概念
-
类(Class) 是抽象的模板,定义了对象的结构和行为。
-
对象(Object) 是根据类(Class)创建出来的实例(Instance),对象是类的具体实例。
-
实例化是根据类(Class)创建对象(Object)的过程。
-
实例成员是类(Class)的实例属性和实例方法,其中包含多个属性(Property)、构造函数(Constructor)、和方法(Method),它们的状态随着每个对象实例的不同而不同。
每个实例都有独立的成员,各个实例的同名字段互不影响。
也就是说,每个对象实例都有其自己的一套实例成员的副本。
只能通过类的创建实例访问属性和方法。
-
类属性(Class_Property) 是在类范围中声明的变量。
属性可以是任何内置类型,也可以是另一个类的实例。
类可以具有零个或多个属性。
类属性没有数量限制。
-
类构造函数(Class_Constructor) 是用于根据类(Class)初始化新创建的对象(Object)的函数。
构造函数使你能够在创建类实例时设置默认值并验证对象逻辑。
构造函数与类具有相同的名称。
构造函数可能具有参数来初始化新对象的数据成员。
限制:
- 构造函数没有输出类型。 他们无法使用
return
返回值。 - 构造函数始终与类同名。
- 无法直接调用构造函数。 它们仅在创建实例时运行。
- 构造函数永远不会出现在 cmdlet 的
Get-Member
输出中。
一个类可以定义零个或多个构造函数。
如果未定义构造函数,则为该类提供默认的无参数构造函数。此构造函数将所有成员初始化为其默认值。 对象类型和字符串被赋予 null 值。
定义构造函数时,不会创建默认的无参数构造函数。如果需要,请创建无参数构造函数。
- 构造函数没有输出类型。 他们无法使用
-
类方法(Class_Methods) 是定义类可以执行的操作(函数)。
方法可以采用指定输入数据的参数。
方法始终定义输出类型。
如果方法未返回任何输出,则它必须具有 Void 输出类型。 如果方法未显式定义输出类型,则该方法的输出类型为 Void。
在类方法中,除语句中指定的
return
对象外,不会向管道发送任何对象。代码不会意外输出到管道。这与 PowerShell 函数处理输出的方式有根本的不同,在 PowerShell 中,所有内容都进入管道。
-
-
hidden
关键字 用于隐藏类成员。该成员仍可供用户访问,并且可在对象可用的所有范围内使用。
隐藏成员在
Get-Member
cmdlet 中隐藏(可以用Get-Member -Force
显示),不能在类定义之外使用 Tab 补全或 IntelliSense 显示。static
关键字仅适用于类成员,而不适用于类本身。 -
static
关键字 用于定义类成员为静态。静态属性始终可用,独立于类实例化。也就是说,静态属性可以在不创建对象的情况下直接访问。
静态属性在类的所有实例之间共享。也就是说,静态属性可以作为全局变量使用。
静态方法始终可用。也就是说,静态方法可以在不创建对象的情况下直接调用。
所有静态属性在整个会话范围内都有效。
自动
$this
变量在静态方法中不可用。PowerShell 中定义的类的静态属性是可修改的(
Update-TypeData
cmdlet),默认情况下是不可修改的。无法定义不可变的静态属性。static
关键字仅适用于类成员,而不适用于类本身。 -
继承(Class_Inheritance) 是一种机制,它允许一个类继承另一个类的属性和方法。
PowerShell类支持继承,这使你可以定义可重用(继承)、扩展或修改父类行为的子类。
成员被继承的类称为基类(父类)。 继承基类成员的类称为派生类(子类)。可以通过创建派生自现有类的新类来扩展类。 派生类继承基类的属性和方法。可以根据需要添加或替代基类成员。
PowerShell 仅支持单一继承。 类只能继承自单个类。 不过,继承是可传递的。这样一来,就可以为一组类型定义继承层次结构。 换句话说,类型 D 可以继承自类型 C,该类型继承自类型 B,后者继承自基类类型 A。由于继承是可传递的,A 类型的成员可用于类型 D。
派生类可以访问基类的所有成员,但无法访问基类的私有成员。
派生类不会继承基类的所有成员:不会继承静态构造函数、实例构造函数。
语法
定义类
1 | class <类名> [ : [ <基类> ][ , <接口列表> ]] { [[<属性>] [hidden] [static] <属性定义> ...] [<类名>([<构造函数参数列表>]) {<构造函数语句列表>} ...] [[<属性>] [hidden] [static] <方法定义> ...] } |
说明:
- 类名:定义一个类的名字。定义类名一般使用首字母大写的驼峰式命名。
- 基类:可选,指定该类继承自哪个基类。
- 接口列表:可选,指定该类实现哪些接口。
- 属性:修饰符,如访问级别控制(public, private, protected)或其他特性。
- hidden:表示隐藏父类中的同名成员。
- static:表示静态成员,不属于任何实例。
- 属性定义:定义类的属性或字段。
- 构造函数:定义类的构造函数,用于初始化新创建的对象。
- 构造函数参数列表:构造函数接受的参数列表。
- 构造函数语句列表:构造函数内部执行的语句。
- 方法定义:定义类的方法。
实例化类
-
New-Object
cmdlet:1
2[$<变量名> =] New-Object -TypeName <类名> [ [-ArgumentList] <构造函数参数列表>]
- 变量名:创建的对象将被赋值给这个变量。
- New-Object:创建一个新的对象实例。
- -TypeName:指定要创建的对象的类名。
- -ArgumentList:可选参数列表,用于传递给构造函数。
-
[类名]::new()
:1
[$<变量名> =] [<类名>]::new([<构造函数参数列表>])
- 变量名:创建的对象将被赋值给这个变量。
- ::new:调用类的构造函数。
- 构造函数参数列表:构造函数的参数列表。
使用
[类名]::new()
语法时,类名两边必须有括号([]
和()
)。括号表示 PowerShell 的类型定义。 -
类名哈希表:
1
[$<变量名> =] [<类名>]@{[<类属性哈希表>]}
- 变量名:创建的对象将被赋值给这个变量。
- 类名:类名。
- 类属性哈希表:使用哈希表来初始化类的属性。
哈希表语法仅适用于具有不需要任何参数的默认构造函数的类。 它使用默认构造函数创建类的实例,然后将键值对分配给实例属性。 如果哈希表中的任何键不是有效的属性名称,PowerShell 将引发错误。
类属性
单行语法
1 | [[<属性>]...] [<属性类型>] $<属性名称> [= <默认值>] |
多行语法
1 | [[<属性>]...] [<属性类型>] $<属性名称> [= <默认值>] |
- 属性(Attributes):
[[<attribute>]...]
:表示一个或多个属性(attributes)。这些属性可以用于修饰属性,提供额外的信息或行为。- 例如:
[NoRunspaceAffinity()]
或[ValidateRange(1, 10)]
。
- 属性类型(Property Type):
<property-type>
:表示属性的数据类型。- 例如:
[int]
、[string]
、[DateTime]
等。
- 属性名称(Property Name):
$<property-name>
:表示属性的名称。- 例如:
$MyProperty
。
- 默认值(Default Value)(可选):
= <default-value>
:表示属性的默认值。- 例如:
= 0
、= "default"
等。
类方法
单行语法
1 | [[<属性>]...] [hidden] [static] [<输出类型>] <方法名称> ([<方法参数>]) { <方法体> } |
多行语法
1 | [[<属性>]...] [hidden] [static] [<输出类型>] <方法名称> ([<方法参数>]) { <方法体> } |
- 属性(Attributes):
[[<attribute>]...]
:表示一个或多个属性(attributes)。这些属性可以用于修饰方法,提供额外的信息或行为。- 例如:
[NoRunspaceAffinity()]
或[ValidateRange(1, 10)]
。
- 隐藏(Hidden):
[hidden]
:表示该方法是隐藏的,不会显示在对象的公共成员列表中。
- 静态(Static):
[static]
:表示该方法是静态方法,可以直接通过类名调用,不需要创建类的实例。
- 输出类型(Output Type):
[<output-type>]
:表示方法的返回类型。- 例如:
[int]
、[string]
、[void]
等。
- 方法名称(Method Name):
<method-name>
:表示方法的名称。- 例如:
MyMethod
。
- 方法参数(Method Parameters):
([<method-parameters>])
:表示方法的参数列表。- 例如:
([int] $param1, [string] $param2)
。
- 方法体(Body):
{ <body> }
:表示方法的具体实现代码块。- 例如:
{ Write-Host "Hello, World!" }
。
示例
最小定义
1 | # 定义一个名为 MadDog 的类 class MadDog { [string]$Name } # 实例化一个 MadDog 对象 $dog = [MadDog]::new() # 给 dog 对象的 Name 属性赋值 $dog.Name = "Yaya" # 输出 dog 对象 $dog # 输出: Name ---- Yaya |
包含实例成员的类
此示例定义了一个 MadDog 类,其中包含多个属性、构造函数和方法。 每个定义的成员都是实例成员,而不是静态成员。只能通过类的创建实例访问属性和方法。
1 | class MadDog { # 类属性 [string]$Name [datetime]$Birthday # 默认构造函数 MadDog() { $this.Init(@{}) } # 哈希表构造函数 MadDog([hashtable]$Properties) { $this.Init($Properties) } # 名字和生日构造函数 MadDog([string]$Name, [datetime]$Birthday) { $this.Init(@{Name = $Name; Birthday = $Birthday }) } # 共享实例化方法 [void] Init([hashtable]$Properties) { foreach ($Property in $Properties.Keys) { $this.$Property = $Properties.$Property } } # 计算年龄 [timespan] GetAge() { if ( $null -eq $this.Birthday -or $this.Birthday -eq [datetime]::MinValue ) { throw "Birthday 未定义" } return (Get-Date) - $this.Birthday } # 返回字符串表示 [string] ToString() { return "$($this.Name) 于 $($this.Birthday.Year) 年 $($this.Birthday.Month) 月 $($this.Birthday.Day) 日生,现在已经生活了 $($this.GetAge()) 天。" } } |
实例化 MadDog 类的 dog 对象:
1 | $dog = [MadDog]::new("Yaya", "2017-01-01") $dog # Name Birthday # ---- -------- # Yaya 2017/1/1 0:00:00 $dog.GetAge() # Days : 2817 # Hours : 7 # Minutes : 10 # Seconds : 0 # Milliseconds : 801 # Ticks : 2434146008012020 # TotalDays : 2817.29862038428 # TotalHours : 67615.1668892228 # TotalMinutes : 4056910.01335337 # TotalSeconds : 243414600.801202 # TotalMilliseconds : 243414600801.202 $dog.ToString() # Yaya 于 2017 年 1 月 1 日生,现在已经生活了 2817.07:11:35.7569411 天。 |
具有静态成员的类
此示例中的 DogList 类基于示例 2 中的 MadDog 类。虽然 DogList 类不能标记为静态本身,但实现仅定义 Dogs 静态属性和一组用于管理该属性的静态方法。
1 | class DogList { # 静态属性 Dogs,用于保存所有的狂犬列表 static [System.Collections.Generic.List[MadDog]] $Dogs # 静态方法,用于初始化狂犬列表。在其他静态方法中调用以避免显式初始化。 # 这样做的目的是确保在调用任何其他静态方法之前,书籍列表已经被正确初始化。这使得调用者无需担心初始化问题,从而简化了使用过程。 static [void] Initialize() { # Initialize() 方法是一个简单的调用 Initialize($false) 的方法 [DogList]::Initialize($false) } static [bool] Initialize([bool]$force) { # Initialize([bool]$force) 方法根据 $force 参数决定是否强制重新初始化书籍列表。 if ([DogList]::Dogs.Count -gt 0 -and -not $force) { return $false } [DogList]::Dogs = [System.Collections.Generic.List[MadDog]]::new() return $true } # 确保狂犬有效,可以添加到列表中。 static [void] Validate([MadDog]$Dog) { $Prefix = @( '狂犬验证失败:书籍必须定义 Name 和 Birthday 属性,但是' ) -join ' ' if ($null -eq $Dog.Name) { throw "$Prefix Name 属性未定义。" } if ($null -eq $Dog.Birthday) { throw "$Prefix Birthday 属性未定义。" } } # 静态方法,用于管理狂犬列表 # 如果该狂犬尚未在列表中,则将其添加到列表中。 static [void] Add([MadDog]$Dog) { [DogList]::Initialize() [DogList]::Validate($Dog) if ([DogList]::Dogs.Contains($Dog)) { throw "狂犬 $Dog 已存在于列表中。" } [DogList]::Dogs.Add($Dog) } # 静态方法,用于清空狂犬列表 static [void] Clear() { [DogList]::Initialize($true) [DogList]::Dogs.Clear() } # 使用过滤脚本块查找特定狂犬 static [MadDog] Find([scriptblock]$Filter) { [DogList]::Initialize() return [DogList]::Dogs.Find($Filter) } # 查找所有匹配过滤脚本块的狂犬 static [MadDog[]] FindAll([scriptblock]$Filter) { [DogList]::Initialize() return [DogList]::Dogs.FindAll($Filter) } # 删除特定狂犬 static [void] Remove([MadDog]$Dog) { [DogList]::Initialize() [DogList]::Dogs.Remove($Dog) } # 通过属性值删除狂犬 static [void] RemoveBy([string]$Property, [object]$Value) { [DogList]::Initialize() $Index = [DogList]::Dogs.FindIndex({ $args[0].$Property -eq $Value }.GetNewClosure()) if ($Index -ge 0) { [DogList]::Dogs.RemoveAt($Index) } } } |
定义 DogList 类后,可以将上一示例中的狂犬添加到列表中。
1 | # 验证列表是否为空 $null -eq [DogList]::Dogs # True # 添加狂犬 [DogList]::Add($dog) # 重复添加狂犬,验证 Validate() 方法 [DogList]::Add($dog) # Exception: D:\temp\class.ps1:78:13 # Line | # 78 | throw "狂犬 $Dog 已存在于列表中。" # | ~~~~~~~~~~~~~~~~~~~~~~~~ # | 狂犬 Yaya 于 2017 年 1 月 1 日生,现在已经生活了 2817.08:15:06.0637525 天。 已存在于列表中。 [DogList]::Dogs # Name Birthday # ---- -------- # Yaya 2017/1/1 0:00:00 |
以下代码片段调用类的静态方法。
1 | [DogList]::Add([MadDog]::new(@{ Name = "ToWong" Birthday = "2015-01-01" })) [DogList]::Find({ $args[0].Birthday -gt '2016-01-01' }).ToString() # Yaya 于 2017 年 1 月 1 日生,现在已经生活了 2817.08:22:47.7292221 天。 [DogList]::FindAll({ $args[0].Birthday -gt '2014-01-01' }).Name # Yaya # ToWong [DogList]::Remove($dog) [DogList]::Dogs.Name # ToWong [DogList]::RemoveBy('Name', 'ToWong') "Names: $([DogList]::Dogs.Name)" # Names: [DogList]::Add($dog) [DogList]::Add($dog) # Exception: D:\temp\class.ps1:78:13 # Line | # 78 | throw "狂犬 $Dog 已存在于列表中。" # | ~~~~~~~~~~~~~~~~~~~~~~~~ # | 狂犬 Yaya 于 2017 年 1 月 1 日生,现在已经生活了 2817.08:26:24.6769138 天。 已存在于列表中。 |
包含和不使用 Runspace 相关性的类定义
PowerShell 允许定义类,而不考虑它们是否与当前运行空间相关。下面的示例定义了一个类,该类可以与任何 PowerShell 运行空间相关。
UnsafeClass
示例
ShowRunspaceId()
报告不同的线程 ID,但相同的运行空间 ID 的方法 [UnsafeClass]
。 最终,会话状态已损坏,导致错误,例如 Global scope cannot be removed
。
1 | # 有运行空间相关性的类(默认) class UnsafeClass { static [object] ShowRunspaceId($val) { return [PSCustomObject]@{ ThreadId = [Threading.Thread]::CurrentThread.ManagedThreadId RunspaceId = [runspace]::DefaultRunspace.Id } } } $unsafe = [UnsafeClass]::new() while ($true) { 1..10 | ForEach-Object -Parallel { Start-Sleep -ms 100 ($using:unsafe)::ShowRunspaceId($_) } } # ThreadId RunspaceId # -------- ---------- # 26 4 # 29 4 # 29 4 # 31 4 # 31 4 # 32 4 # 32 4 # 35 4 # 28 4 # 28 4 # 24 4 # 24 4 # 35 4 # 32 4 # WriteError: # Line | # 3 | ($using:unsafe)::ShowRunspaceId($_) # | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # | Global scope cannot be removed. # 41 4 # 42 4 # 42 4 # 40 4 # 40 4 # ...... |
此示例无限循环运行。 输入 Ctrl+C 可停止执行。
SafeClass
示例
[SafeClass]
的 ShowRunspaceId()
方法报告不同的线程 Runspace ID。
1 | # 没有运行空间相关性(NoRunspaceAffinity)的类 [NoRunspaceAffinity()] class SafeClass { static [object] ShowRunspaceId($val) { return [PSCustomObject]@{ ThreadId = [Threading.Thread]::CurrentThread.ManagedThreadId RunspaceId = [runspace]::DefaultRunspace.Id } } } $safe = [SafeClass]::new() while ($true) { 1..10 | ForEach-Object -Parallel { Start-Sleep -ms 100 ($using:safe)::ShowRunspaceId($_) } } # ThreadId RunspaceId # -------- ---------- # 26 5 # 27 6 # 30 9 # 28 7 # 29 8 # 31 6 # 33 8 # 32 5 # 35 7 # 34 9 # 35 11 # 36 10 # 34 12 # 33 13 # 32 14 |
此示例无限循环运行。 输入 Ctrl+C 可停止执行。
具有自定义类型的类属性
1 | enum LogLevel { Debug Info Warning Error } class Logger { [string]$Message [LogLevel]$Level = [LogLevel]::Info [void] Write() { Write-Host "[$($this.Level)]: $($this.Message)" } } $logger = [Logger]::new() $logger.Message = "Hello, World!" $logger.Level = [LogLevel]::Debug $logger.Write() # [Debug]: Hello, World! |
具有验证类型的类属性
using namespace System.Management.Automation
1 | # 定义 InstallPackage 类 class InstallPackage { [ValidateNotNullOrEmpty()] [string]$Path } # 创建 installer 实例 $installer = [InstallPackage]::new() # 尝试设置“Path”为空值 $installer.Path = "" # 报错:设置“Path”时发生异常:“参数为 Null 或空。请提供一个不为 Null 或空的参数,然后重试该命令。” # 所在位置 行:1 字符: 1 # + $installer.Path = "" # + ~~~~~~~~~~~~~~~~~~~~ # + CategoryInfo : NotSpecified: (:) [], SetValueInvocationException # + FullyQualifiedErrorId : ExceptionWhenSetting $installer.Path = $null # 报错同上 $installer.Path = "C:\Program Files" $installer # Path # ---- # C:\Program Files |
具有显式默认值的类属性
1 | class MadDog { [string]$Name = "Yaya" # 假设狗子是现在出生的 [DateTime]$Birthday = (Get-Date).Date } [MadDog]::new() # Name Birthday # ---- -------- # Yaya 2024/9/18 0:00:00 [MadDog]::new().Birthday -eq (Get-Date).Date # True |
隐藏类属性
不支持 PowerShell 5.1。
1 | class MadDog { [string]$Name = "Yaya" [DateTime]$Birthday = (Get-Date).Date hidden [string]$Guid = (New-Guid).Guid } $dog = [MadDog]::new() $dog # Name Birthday # ---- -------- # Yaya 2024/9/18 0:00:00 $dog.Guid # d3b72879-dd18-40ca-8248-31375e486e76 $dog | Get-Member -MemberType Properties # TypeName: MadDog # Name MemberType Definition # ---- ---------- ---------- # Birthday Property datetime Birthday {get;set;} # Name Property string Name {get;set;} # 使用 -Force 参数显示隐藏的类属性 $dog | Get-Member -MemberType Properties -Force # TypeName: MadDog # Name MemberType Definition # ---- ---------- ---------- # pstypenames CodeProperty System.Collections.ObjectModel.Collection`1[[System.String, System.Private.CoreLib, Version=8… # Birthday Property datetime Birthday {get;set;} # Guid Property string Guid {get;set;} # Name Property string Name {get;set;} |
Static 类属性
1 | class MadDog { [string]$Name [DateTime]$Birthday = (Get-Date).Date hidden [string]$Guid = (New-Guid).Guid static [MadDog[]]$Dogs = @() MadDog() { # 将当前实例添加到静态属性中 [MadDog]::Dogs += $this } } "Dog Count: $([MadDog]::Dogs.Count)" # Dog Count: 0 $dog1 = [MadDog] @{ Name = "Yaya" } $dog2 = [MadDog] @{ Name = "ToWong" } [MadDog]::Dogs | Select-Object -Property Name, Guid # Name Guid # ---- ---- # Yaya d8d2fd8c-24d3-49e6-bcef-86e43062a63b # ToWong 68b2ac8e-8904-421a-8cfa-903d9737cbf7 |
派生类属性(继承)
1 | # 土狗类 class RuralDog { static [string]$Breed = "Rural" [string]$Name = "XiaoHua" } # 中华田园犬类=土狗类 class ChineseDog : RuralDog {} # 秋田犬类=土狗类 class Akita : RuralDog { [string]$Name = "Sakura" } # 狼=未定义品种土狗类 class Wolf : RuralDog { [string] $Breed [string] $Name } # 狂犬=超级土狗 class MadDog : ChineseDog { static [string]$Breed = "SuperRural" [string]$Name = "Yaya" } "土狗: $([RuralDog]::new().Name) - $([RuralDog]::Breed) " # 土狗: XiaoHua - Rural "中华田园犬: $([ChineseDog]::new().Name) - $([ChineseDog]::Breed) " # 中华田园犬: XiaoHua - Rural "秋田犬: $([Akita]::new().Name) - $([Akita]::Breed) " # 秋田犬: Sakura - Rural "狼: $([Wolf]::new().Name) - $([Wolf]::Breed) " # 狼: - "狂犬: $([MadDog]::new().Name) - $([MadDog]::Breed) " # 狂犬: Yaya - SuperRural |
具有参数的方法
1 | class McDog { [float] $长 [float] $宽 [float] $高 [float] 获取体积() { return $this.长 * $this.宽 * $this.高 } [float] 获取质量([float]$密度) { return $this.获取体积() * $密度 } } $Mc_YDog = [McDog]@{ 长 = 2 宽 = 2 高 = 3 } $Mc_YDog.获取质量(1.25) # 15 |
不带输出的方法
1 | class McDog { [float] $长 [float] $宽 [float] $高 [float] 获取体积() { $this.检验尺寸() return $this.长 * $this.宽 * $this.高 } [void] 检验尺寸() { $不合规的属性 = @() foreach ($属性 in @("长", "宽", "高")) { if ($this.$属性 -lt 0) { $不合规的属性 += $属性 } } if ($不合规的属性.Count -gt 0) { $错误信息 = @( '不合规的属性' "('$($不合规的属性 -join "', '")'):" "狗的尺寸必须为正数!" ) -join ' ' throw $错误信息 } } } $Mc_YDog = [McDog]@{ 长 = 2 宽 = -1 } $Mc_YDog # 长 宽 高 # -- -- -- # 2.00 -1.00 0.00 $Mc_YDog.获取体积() # Exception: # Line | # 25 | throw $错误信息 # | ~~~~~~~~~~~ # | 不合规的属性 ('宽'): 狗的尺寸必须为正数! |
具有重载的静态方法
略
方法签名和重载
略