about_Classes_and_DSC
简短说明
介绍如何使用类通过 Desired State Configuration(DSC)在 PowerShell 中开发。
长说明
从 Windows PowerShell 5.0 开始,已添加语言来定义类和其他用户定义的类型,方法是使用类似于其他面向对象的编程语言的正式语法和语义。 目标是使开发人员和 IT 专业人员能够接受适用于更广泛的用例的 PowerShell、简化开发 PowerShell 项目(如 DSC 资源)以及加速管理图面的覆盖。
支持的方案
支持以下方案:
- 使用 PowerShell 语言定义 DSC 资源及其关联类型。
- 使用熟悉的面向对象的编程构造(如类、属性、方法和继承)在 PowerShell 中定义自定义类型。
- 使用 PowerShell 语言调试类型。
- 使用正式机制并在正确的级别生成和处理异常。
使用类定义 DSC 资源
除了语法更改之外,类定义的 DSC 资源与 cmdlet DSC 资源提供程序之间的主要区别如下:
- 不需要管理对象格式 (MOF) 文件。
- 不需要模块文件夹中的 DSCResource 子文件夹。
- PowerShell 模块文件可以包含多个 DSC 资源类。
创建类定义的 DSC 资源提供程序
以下示例是作为模块 MyDSCResource.psm1 保存的类定义的 DSC 资源提供程序。 必须始终在类定义的 DSC 资源提供程序中包含键属性。
enum Ensure
{
Absent
Present
}
<#
This resource manages the file in a specific path.
[DscResource()] indicates the class is a DSC resource
#>
[DscResource()]
class FileResource
{
<#
This property is the fully qualified path to the file that is
expected to be present or absent.
The [DscProperty(Key)] attribute indicates the property is a
key and its value uniquely identifies a resource instance.
Defining this attribute also means the property is required
and DSC will ensure a value is set before calling the resource.
A DSC resource must define at least one key property.
#>
[DscProperty(Key)]
[string]$Path
<#
This property indicates if the settings should be present or absent
on the system. For present, the resource ensures the file pointed
to by $Path exists. For absent, it ensures the file point to by
$Path does not exist.
The [DscProperty(Mandatory)] attribute indicates the property is
required and DSC will guarantee it is set.
If Mandatory is not specified or if it is defined as
Mandatory=$false, the value is not guaranteed to be set when DSC
calls the resource. This is appropriate for optional properties.
#>
[DscProperty(Mandatory)]
[Ensure] $Ensure
<#
This property defines the fully qualified path to a file that will
be placed on the system if $Ensure = Present and $Path does not
exist.
NOTE: This property is required because [DscProperty(Mandatory)] is
set.
#>
[DscProperty(Mandatory)]
[string] $SourcePath
<#
This property reports the file's create timestamp.
[DscProperty(NotConfigurable)] attribute indicates the property is
not configurable in DSC configuration. Properties marked this way
are populated by the Get() method to report additional details
about the resource when it is present.
#>
[DscProperty(NotConfigurable)]
[Nullable[datetime]] $CreationTime
<#
This method is equivalent of the Set-TargetResource script function.
It sets the resource to the desired state.
#>
[void] Set()
{
$fileExists = $this.TestFilePath($this.Path)
if($this.ensure -eq [Ensure]::Present)
{
if(-not $fileExists)
{
$this.CopyFile()
}
}
else
{
if($fileExists)
{
Write-Verbose -Message "Deleting the file $($this.Path)"
Remove-Item -LiteralPath $this.Path -Force
}
}
}
<#
This method is equivalent of the Test-TargetResource script
function. It should return True or False, showing whether the
resource is in a desired state.
#>
[bool] Test()
{
$present = $this.TestFilePath($this.Path)
if($this.Ensure -eq [Ensure]::Present)
{
return $present
}
else
{
return -not $present
}
}
<#
This method is equivalent of the Get-TargetResource script function.
The implementation should use the keys to find appropriate
resources. This method returns an instance of this class with the
updated key properties.
#>
[FileResource] Get()
{
$present = $this.TestFilePath($this.Path)
if ($present)
{
$file = Get-ChildItem -LiteralPath $this.Path
$this.CreationTime = $file.CreationTime
$this.Ensure = [Ensure]::Present
}
else
{
$this.CreationTime = $null
$this.Ensure = [Ensure]::Absent
}
return $this
}
<#
Helper method to check if the file exists and it is correct file
#>
[bool] TestFilePath([string] $location)
{
$present = $true
$item = Get-ChildItem -LiteralPath $location -ea Ignore
if ($null -eq $item)
{
$present = $false
}
elseif( $item.PSProvider.Name -ne "FileSystem")
{
throw "Path $($location) is not a file path."
}
elseif($item.PSIsContainer)
{
throw "Path $($location) is a directory path."
}
return $present
}
<#
Helper method to copy file from source to path
#>
[void] CopyFile()
{
if(-not $this.TestFilePath($this.SourcePath))
{
throw "SourcePath $($this.SourcePath) is not found."
}
[System.IO.FileInfo]
$destFileInfo = new-object System.IO.FileInfo($this.Path)
if (-not $destFileInfo.Directory.Exists)
{
$FullName = $destFileInfo.Directory.FullName
$Message = "Creating directory $FullName"
Write-Verbose -Message $Message
#use CreateDirectory instead of New-Item to avoid code
# to handle the non-terminating error
[System.IO.Directory]::CreateDirectory($FullName)
}
if(Test-Path -LiteralPath $this.Path -PathType Container)
{
throw "Path $($this.Path) is a directory path"
}
Write-Verbose -Message "Copying $this.SourcePath to $this.Path"
#DSC engine catches and reports any error that occurs
Copy-Item -Path $this.SourcePath -Destination $this.Path -Force
}
}
创建模块清单
创建类定义的 DSC 资源提供程序并将其保存为模块后,请为该模块创建模块清单。 若要使基于类的资源可供 DSC 引擎使用,必须在清单文件中包括一个 DscResourcesToExport
语句,该语句指示模块导出资源。 在此示例中,以下模块清单保存为 MyDscResource.psd1。
@{
# Script module or binary module file associated with this manifest.
RootModule = 'MyDscResource.psm1'
DscResourcesToExport = 'FileResource'
# Version number of this module.
ModuleVersion = '1.0'
# ID used to uniquely identify this module
GUID = '81624038-5e71-40f8-8905-b1a87afe22d7'
# Author of this module
Author = 'Microsoft Corporation'
# Company or vendor of this module
CompanyName = 'Microsoft Corporation'
# Copyright statement for this module
Copyright = '(c) 2014 Microsoft. All rights reserved.'
# Description of the functionality provided by this module
# Description = ''
# Minimum version of the PowerShell engine required by this module
PowerShellVersion = '5.0'
# Name of the PowerShell host required by this module
# PowerShellHostName = ''
}
部署 DSC 资源提供程序
通过在 $pshome\Modules
或 $env:SystemDrive\ProgramFiles\WindowsPowerShell\Modules
中创建 MyDscResource 文件夹来部署新的 DSC 资源提供程序。
无需创建 DSCResource 子文件夹。 将模块和模块清单文件(MyDscResource.psm1 和 MyDscResource.psd1)复制到 MyDscResource 文件夹。
从此开始,可以像使用任何 DSC 资源一样创建和运行配置脚本。
创建 DSC 配置脚本
如前所述在文件夹结构中保存类和清单文件后,可以创建使用新资源的配置。 以下配置引用 MyDSCResource 模块。 将配置另存为脚本,MyResource.ps1。
有关如何运行 DSC 配置的信息,请参阅 Windows PowerShell Desired State Configuration Overview。
在运行配置之前,请创建 C:\test.txt
。 配置检查文件是否存在于 c:\test\test.txt
。 如果该文件不存在,则配置将从 C:\test.txt
复制该文件。
Configuration Test
{
Import-DSCResource -ModuleName MyDscResource
FileResource file
{
Path = "C:\test\test.txt"
SourcePath = "C:\test.txt"
Ensure = "Present"
}
}
Test
Start-DscConfiguration -Wait -Force Test
像运行任何 DSC 配置脚本一样运行此脚本。 若要启动配置,请在提升的 PowerShell 控制台中运行以下命令:
PS C:\test> .\MyResource.ps1
PowerShell 类中的继承
声明 PowerShell 类的基类
可以将 PowerShell 类声明为另一个 PowerShell 类的基类型,如以下示例所示,其中 水果 是 apple的基类型。
class fruit
{
[int]sold() {return 100500}
}
class apple : fruit {}
[apple]::new().sold() # return 100500
声明 PowerShell 类的实现接口
可以在基类型之后声明已实现的接口,或者在没有指定基类型的情况下立即声明冒号(:
)。 使用逗号分隔所有类型名称。 这类似于 C# 语法。
class MyComparable : system.IComparable
{
[int] CompareTo([object] $obj)
{
return 0;
}
}
class MyComparableTest : test, system.IComparable
{
[int] CompareTo([object] $obj)
{
return 0;
}
}
调用基类构造函数
若要从子类调用基类构造函数,请添加 base
关键字,如以下示例所示:
class A {
[int]$a
A([int]$a)
{
$this.a = $a
}
}
class B : A
{
B() : base(103) {}
}
[B]::new().a # return 103
如果基类具有默认构造函数(无参数),则可以省略显式构造函数调用,如下所示。
class C : B
{
C([int]$c) {}
}
调用基类方法
可以在子类中替代现有方法。 若要执行重写,请使用同一名称和签名声明方法。
class baseClass
{
[int]days() {return 100500}
}
class childClass1 : baseClass
{
[int]days () {return 200600}
}
[childClass1]::new().days() # return 200600
若要从重写的实现调用基类方法,请强制转换为基类 ([baseclass]$this)
调用。
class childClass2 : baseClass
{
[int]days()
{
return 3 * ([baseClass]$this).days()
}
}
[childClass2]::new().days() # return 301500
所有 PowerShell 方法都是虚拟的。 可以使用与重写相同的语法隐藏子类中的非虚拟 .NET 方法:声明具有相同名称和签名的方法。
class MyIntList : system.collections.generic.list[int]
{
# Add is final in system.collections.generic.list
[void] Add([int]$arg)
{
([system.collections.generic.list[int]]$this).Add($arg * 2)
}
}
$list = [MyIntList]::new()
$list.Add(100)
$list[0] # return 200
类继承的当前限制
类继承的限制是 PowerShell 中没有声明接口的语法。
在 PowerShell 中定义自定义类型
Windows PowerShell 5.0 引入了多种语言元素。
类关键字
定义一个新类。
class
关键字是真正的 .NET Framework 类型。
类成员是公共的。
class MyClass
{
}
枚举关键字和枚举
添加了对 enum
关键字的支持,这是一项重大更改。
enum
分隔符当前是换行符。 对于已经在使用 enum
的人来说,一种解决方法是在单词前面插入安和(&
)。 当前限制:不能在自身方面定义枚举器,但可以在另一个 enum
中初始化 enum
,如以下示例所示:
当前无法指定基类型。 基类型始终为 [int]。
enum Color2
{
Yellow = [Color]::Blue
}
枚举器值必须是分析时间常量。 枚举器值不能设置为已调用命令的结果。
enum MyEnum
{
Enum1
Enum2
Enum3 = 42
Enum4 = [int]::MaxValue
}
Enum
支持算术运算,如以下示例所示:
enum SomeEnum { Max = 42 }
enum OtherEnum { Max = [SomeEnum]::Max + 1 }
Hidden 关键字
Windows PowerShell 5.0 中引入的 hidden
关键字在默认 Get-Member
结果中隐藏类成员。 指定隐藏属性,如以下行所示:
hidden [type] $classmember = <value>
除非完成发生在定义隐藏成员的类中,否则隐藏成员不会使用 Tab 完成或 IntelliSense 显示。
添加了 System.Management.Automation.HiddenAttribute的新属性,以便 C# 代码可以在 PowerShell 中具有相同的语义。
有关详细信息,请参阅 [about_Hidden[(/powershell/module/microsoft.powershell.core/about/about_hidden)。
Import-DscResource
Import-DscResource
现在是真正的动态关键字。 PowerShell 分析指定的模块的根模块,搜索包含 DscResource 属性的类。
性能
向 ModuleInfo
添加了一个新字段 ImplementingAssembly
。 如果脚本定义类,或二进制模块的加载程序集 ImplementingAssembly
设置为为脚本模块创建的动态程序集。 ModuleType = Manifest 时未设置它。
ImplementingAssembly
字段的反射可发现模块中的资源。 这意味着可以发现以 PowerShell 或其他托管语言编写的资源。
具有初始值设定项的字段。
[int] $i = 5
静态受支持,可以像属性一样工作,类似于类型约束,因此可以按任意顺序指定它。
static [int] $count = 0
类型是可选的。
$s = "hello"
所有成员都是公共成员。 属性需要换行符或分号。 如果未指定对象类型,则属性类型 Object。
构造函数和实例化
PowerShell 类可以具有与其类同名的构造函数。 构造函数可以重载。 支持静态构造函数。
在构造函数中运行任何代码之前,先初始化具有初始化表达式的属性。 静态属性在静态构造函数的主体之前初始化,实例属性在非静态构造函数的主体之前初始化。 目前,没有从另一个构造函数(如 C# 语法)调用构造函数的语法:": this()")
。 解决方法是定义常见的 Init 方法。
以下是实例化类的方法:
使用默认构造函数实例化。 请注意,此版本中不支持
New-Object
。$a = [MyClass]::new()
使用参数调用构造函数。
$b = [MyClass]::new(42)
将数组传递给具有多个参数的构造函数
$c = [MyClass]::new(@(42,43,44), "Hello")
对于此版本,类型名称仅在词法上可见,这意味着它在定义类的模块或脚本之外不可见。 函数可以返回在 PowerShell 中定义的类的实例,并且实例在模块或脚本之外运行良好。
Get-Member
Static 参数列出构造函数,因此你可以像查看任何其他方法一样查看重载。 此语法的性能也远远快于 New-Object
。
名为 新 的伪静态方法适用于 .NET 类型,如以下示例所示。 [hashtable]::new()
现在可以看到具有 Get-Member
的构造函数重载,如以下示例所示:
[hashtable]::new
OverloadDefinitions
-------------------
hashtable new()
hashtable new(int capacity)
hashtable new(int capacity, float loadFactor)
方法
PowerShell 类方法作为仅具有结束块的 ScriptBlock 实现。 所有方法都是公共的。 下面演示了定义名为 DoSomething 的方法的示例。
class MyClass
{
DoSomething($x)
{
$this._doSomething($x) # method syntax
}
private _doSomething($a) {}
}
方法调用
支持重载的方法。 重载方法的名称与现有方法相同,但按其指定值进行区分。
$b = [MyClass]::new()
$b.DoSomething(42)
调用
请参阅 方法调用。
属性
添加了三个新属性:DscResource
、DscResourceKey
和 DscResourceMandatory
。
返回类型
返回类型为协定。 返回值将转换为预期类型。 如果未指定返回类型,则返回类型为 void。 无法有意或意外地将对象流式传输到管道。
变量的词法范围
下面演示了本版本中词法范围的工作原理的示例。
$d = 42 # Script scope
function bar
{
$d = 0 # Function scope
[MyClass]::DoSomething()
}
class MyClass
{
static [object] DoSomething()
{
return $d # error, not found dynamically
return $script:d # no error
$d = $script:d
return $d # no error, found lexically
}
}
$v = bar
$v -eq $d # true
示例:创建自定义类
以下示例创建几个新的自定义类来实现 HTML 动态样式表语言(DSL)。 该示例添加帮助程序函数,以创建特定元素类型作为元素类的一部分,例如标题样式和表,因为类型不能在模块范围之外使用。
# Classes that define the structure of the document
#
class Html
{
[string] $docType
[HtmlHead] $Head
[Element[]] $Body
[string] Render()
{
$text = "<html>`n<head>`n"
$text += $Head
$text += "`n</head>`n<body>`n"
$text += $Body -join "`n" # Render all of the body elements
$text += "</body>`n</html>"
return $text
}
[string] ToString() { return $this.Render() }
}
class HtmlHead
{
$Title
$Base
$Link
$Style
$Meta
$Script
[string] Render() { return "<title>$Title</title>" }
[string] ToString() { return $this.Render() }
}
class Element
{
[string] $Tag
[string] $Text
[hashtable] $Attributes
[string] Render() {
$attributesText= ""
if ($Attributes)
{
foreach ($attr in $Attributes.Keys)
{
$attributesText = " $attr=`"$($Attributes[$attr])`""
}
}
return "<${tag}${attributesText}>$text</$tag>`n"
}
[string] ToString() { return $this.Render() }
}
#
# Helper functions for creating specific element types on top of the classes.
# These are required because types aren't visible outside of the module.
#
function H1 {[Element] @{Tag = "H1"; Text = $args.foreach{$_} -join " "}}
function H2 {[Element] @{Tag = "H2"; Text = $args.foreach{$_} -join " "}}
function H3 {[Element] @{Tag = "H3"; Text = $args.foreach{$_} -join " "}}
function P {[Element] @{Tag = "P" ; Text = $args.foreach{$_} -join " "}}
function B {[Element] @{Tag = "B" ; Text = $args.foreach{$_} -join " "}}
function I {[Element] @{Tag = "I" ; Text = $args.foreach{$_} -join " "}}
function HREF
{
param (
$Name,
$Link
)
return [Element] @{
Tag = "A"
Attributes = @{ HREF = $link }
Text = $name
}
}
function Table
{
param (
[Parameter(Mandatory)]
[object[]]
$Data,
[Parameter()]
[string[]]
$Properties = "*",
[Parameter()]
[hashtable]
$Attributes = @{ border=2; cellpadding=2; cellspacing=2 }
)
$bodyText = ""
# Add the header tags
$bodyText += $Properties.foreach{TH $_}
# Add the rows
$bodyText += foreach ($row in $Data)
{
TR (-join $Properties.Foreach{ TD ($row.$_) } )
}
$table = [Element] @{
Tag = "Table"
Attributes = $Attributes
Text = $bodyText
}
$table
}
function TH {([Element] @{Tag="TH"; Text=$args.foreach{$_} -join " "})}
function TR {([Element] @{Tag="TR"; Text=$args.foreach{$_} -join " "})}
function TD {([Element] @{Tag="TD"; Text=$args.foreach{$_} -join " "})}
function Style
{
return [Element] @{
Tag = "style"
Text = "$args"
}
}
# Takes a hash table, casts it to and HTML document
# and then returns the resulting type.
#
function Html ([HTML] $doc) { return $doc }