about_Classes_and_DSC
簡短描述
描述如何使用類別在PowerShell中使用 Desired State Configuration (DSC) 進行開發。
完整描述
從 Windows PowerShell 5.0 開始,已新增語言來定義類別和其他使用者定義型別,方法是使用類似於其他面向物件程式設計語言的正式語法和語意。 目標是讓開發人員和IT專業人員能夠接受PowerShell以取得更廣泛的使用案例、簡化開發PowerShell成品,例如 DSC 資源,以及加速管理介面的涵蓋範圍。
支援的案例
支援下列案例:
- 使用 PowerShell 語言定義 DSC 資源及其相關聯的類型。
- 使用熟悉的對象導向程式設計建構在PowerShell中定義自定義類型,例如類別、屬性、方法和繼承。
- 使用 PowerShell 語言對類型進行偵錯。
- 使用正式機制,並在正確的層級產生和處理例外狀況。
使用類別定義 DSC 資源
除了語法變更之外,類別定義的 DSC 資源與 Cmdlet DSC 資源提供者之間的主要差異如下:
- 不需要管理物件格式 (MOF) 檔案。
- 模組資料夾中不需要 DSCResource 子資料夾。
- PowerShell 模組檔案可以包含多個 DSC 資源類別。
建立類別定義的 DSC 資源提供者
下列範例是類別定義的 DSC 資源提供者,會儲存為模組 MyDSCResource.psm1。 您必須一律在類別定義的 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 預期狀態設定概觀。
執行組態之前,請先建立 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類別的基底類型,如下列範例所示,其中
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的新屬性
如需詳細資訊,請參閱 [about_Hidden[(/powershell/module/microsoft.powershell.core/about/about_hidden)。
Import-DscResource
Import-DscResource
現在是真正的動態關鍵詞。 PowerShell 會剖析指定的模組根模組,並搜尋包含 DscResource 屬性的類別。
性能
已將新的欄位 ImplementingAssembly
新增至 ModuleInfo
。 如果腳本定義類別,或載入的二進位模組元件 ImplementingAssembly
會設定為為腳本模組建立的動態元件。 當 ModuleType = 指令清單時,不會設定它。
ImplementingAssembly
欄位的反映會探索模組中的資源。 這表示您可以探索以PowerShell或其他Managed語言撰寫的資源。
具有初始化表達式的欄位。
[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 }