使用 PowerShell 类编写自定义 DSC 资源
适用于:Windows PowerShell 5.0
在 Windows PowerShell 5.0 中引入 PowerShell 类后,现在可以通过创建类来定义 DSC 资源。 该类定义资源的架构和实现,因此无需创建单独的 MOF 文件。 基于类的资源的文件夹结构也更简单,因为不需要 DSCResources 文件夹。
在基于类的 DSC 资源中,架构定义为类的属性,可以使用属性对其进行修改以指定属性类型。 资源由 Get()
、Set()
和 Test()
方法实现(相当于脚本资源中的 Get-TargetResource
、Set-TargetResource
和 Test-TargetResource
函数)。
在本文中,我们将创建一个名为 NewFile 的简单资源,用于管理指定路径中的文件。
有关 DSC 资源的详细信息,请参阅 生成自定义 Windows PowerShell Desired State Configuration 资源
注意
基于类的资源不支持泛型集合。
类资源的文件夹结构
若要使用 PowerShell 类实现 DSC 自定义资源,请创建以下文件夹结构。
该类在 MyDscResource.psm1
中定义,模块清单在 MyDscResource.psd1
中定义。
$env:ProgramFiles\WindowsPowerShell\Modules (folder)
|- MyDscResource (folder)
MyDscResource.psm1
MyDscResource.psd1
创建类
使用类关键字创建 PowerShell 类。 若要指定类是 DSC 资源,请使用 DscResource()
属性。 类的名称是 DSC 资源的名称。
[DscResource()]
class NewFile {
}
声明属性
DSC 资源架构定义为类的属性。 我们声明三个属性,如下所示。
[DscProperty(Key)]
[string] $path
[DscProperty(Mandatory)]
[ensure] $ensure
[DscProperty()]
[string] $content
[DscProperty(NotConfigurable)]
[MyDscResourceReason[]] $Reasons
请注意,属性由属性修改。 属性的含义如下所示:
- DscProperty(Key):属性是必需的。 该属性是一个键。 标记为键的所有属性的值必须组合在一起,以唯一标识配置中的资源实例。
- DscProperty(必需):属性是必需的。
-
DscProperty(NotConfigurable):属性为只读。 使用此属性标记的属性不能由配置设置,但存在时由
Get()
方法填充。 - DscProperty():该属性可配置,但不是必需的。
$Path
和 $SourcePath
属性都是字符串。
$CreationTime
是一个 DateTime 属性。
$Ensure
属性是一种枚举类型,定义如下。
enum Ensure
{
Absent
Present
}
嵌入类
如果要包含可在资源中使用的已定义属性的新类型,只需创建一个具有属性类型的类,如前所述。
class MyDscResourceReason {
[DscProperty()]
[string] $Code
[DscProperty()]
[string] $Phrase
}
注意
此处声明了 MyDscResourceReason
类,其名称为前缀。 虽然可以为嵌入式类提供任何名称,但如果两个或多个模块定义具有相同名称且在配置中使用的类,PowerShell 将引发异常。
为了避免 DSC 中名称冲突导致的异常,请将嵌入类的名称作为模块名称的前缀。 如果嵌入类的名称已不太可能发生冲突,则可以在没有前缀的情况下使用它。
如果 DSC 资源设计用于 Azure Automanage 的计算机配置功能,请始终为为 Reasons 属性创建的嵌入式类的名称添加前缀。
公共和专用函数
可以在同一模块文件中创建 PowerShell 函数,并在 DSC 类资源的方法中使用它们。 这些函数必须声明为公共函数,但是这些公共函数中的脚本块可以调用私有函数。 唯一的区别在于它们是否列在模块清单的 FunctionsToExport
属性中。
<#
Public Functions
#>
function Get-File {
param(
[ensure]$ensure,
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[String]$path,
[String]$content
)
$fileContent = [MyDscResourceReason]::new()
$fileContent.code = 'file:file:content'
$filePresent = [MyDscResourceReason]::new()
$filePresent.code = 'file:file:path'
$ensureReturn = 'Absent'
$fileExists = Test-path $path -ErrorAction SilentlyContinue
if ($true -eq $fileExists) {
$filePresent.phrase = "The file was expected to be: $ensure`nThe file exists at path: $path"
$existingFileContent = Get-Content $path -Raw
if ([string]::IsNullOrEmpty($existingFileContent)) {
$existingFileContent = ''
}
if ($false -eq ([string]::IsNullOrEmpty($content))) {
$content = $content | ConvertTo-SpecialChars
}
$fileContent.phrase = "The file was expected to contain: $content`nThe file contained: $existingFileContent"
if ($content -eq $existingFileContent) {
$ensureReturn = 'Present'
}
}
else {
$filePresent.phrase = "The file was expected to be: $ensure`nThe file does not exist at path: $path"
$path = 'file not found'
}
return @{
ensure = $ensureReturn
path = $path
content = $existingFileContent
Reasons = @($filePresent,$fileContent)
}
}
function Set-File {
param(
[ensure]$ensure = "Present",
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[String]$path,
[String]$content
)
Remove-Item $path -Force -ErrorAction SilentlyContinue
if ($ensure -eq "Present") {
New-Item $path -ItemType File -Force
if ([ValidateNotNullOrEmpty()]$content) {
$content | ConvertTo-SpecialChars | Set-Content $path -NoNewline -Force
}
}
}
function Test-File {
param(
[ensure]$ensure = "Present",
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[String]$path,
[String]$content
)
$test = $false
$get = Get-File @PSBoundParameters
if ($get.ensure -eq $ensure) {
$test = $true
}
return $test
}
<#
Private Functions
#>
function ConvertTo-SpecialChars {
param(
[parameter(Mandatory = $true,ValueFromPipeline)]
[ValidateNotNullOrEmpty()]
[string]$string
)
$specialChars = @{
'`n' = "`n"
'\\n' = "`n"
'`r' = "`r"
'\\r' = "`r"
'`t' = "`t"
'\\t' = "`t"
}
foreach ($char in $specialChars.Keys) {
$string = $string -replace ($char,$specialChars[$char])
}
return $string
}
实现方法
Get()
、Set()
和 Test()
方法类似于脚本资源中的 Get-TargetResource
、Set-TargetResource
和 Test-TargetResource
函数。
最佳做法是最大程度地减少类实现中的代码量。 而是将大部分代码移出模块中的公共函数,然后可以独立测试这些函数。
<#
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.
#>
[NewFile] Get() {
$get = Get-File -ensure $this.ensure -path $this.path -content $this.content
return $get
}
<#
This method is equivalent of the Set-TargetResource script function.
It sets the resource to the desired state.
#>
[void] Set() {
$set = Set-File -ensure $this.ensure -path $this.path -content $this.content
}
<#
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() {
$test = Test-File -ensure $this.ensure -path $this.path -content $this.content
return $test
}
完整文件
完整的类文件如下所示。
enum ensure {
Absent
Present
}
<#
This class is used within the DSC Resource to standardize how data
is returned about the compliance details of the machine. Note that
the class name is prefixed with the module name - this helps prevent
errors raised when multiple modules with DSC Resources define the
Reasons property for reporting when they're out-of-state.
#>
class MyDscResourceReason {
[DscProperty()]
[string] $Code
[DscProperty()]
[string] $Phrase
}
<#
Public Functions
#>
function Get-File {
param(
[ensure]$ensure,
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[String]$path,
[String]$content
)
$fileContent = [MyDscResourceReason]::new()
$fileContent.code = 'file:file:content'
$filePresent = [MyDscResourceReason]::new()
$filePresent.code = 'file:file:path'
$ensureReturn = 'Absent'
$fileExists = Test-path $path -ErrorAction SilentlyContinue
if ($true -eq $fileExists) {
$filePresent.phrase = "The file was expected to be: $ensure`nThe file exists at path: $path"
$existingFileContent = Get-Content $path -Raw
if ([string]::IsNullOrEmpty($existingFileContent)) {
$existingFileContent = ''
}
if ($false -eq ([string]::IsNullOrEmpty($content))) {
$content = $content | ConvertTo-SpecialChars
}
$fileContent.phrase = "The file was expected to contain: $content`nThe file contained: $existingFileContent"
if ($content -eq $existingFileContent) {
$ensureReturn = 'Present'
}
}
else {
$filePresent.phrase = "The file was expected to be: $ensure`nThe file does not exist at path: $path"
$path = 'file not found'
}
return @{
ensure = $ensureReturn
path = $path
content = $existingFileContent
Reasons = @($filePresent,$fileContent)
}
}
function Set-File {
param(
[ensure]$ensure = "Present",
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[String]$path,
[String]$content
)
Remove-Item $path -Force -ErrorAction SilentlyContinue
if ($ensure -eq "Present") {
New-Item $path -ItemType File -Force
if ([ValidateNotNullOrEmpty()]$content) {
$content | ConvertTo-SpecialChars | Set-Content $path -NoNewline -Force
}
}
}
function Test-File {
param(
[ensure]$ensure = "Present",
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[String]$path,
[String]$content
)
$test = $false
$get = Get-File @PSBoundParameters
if ($get.ensure -eq $ensure) {
$test = $true
}
return $test
}
<#
Private Functions
#>
function ConvertTo-SpecialChars {
param(
[parameter(Mandatory = $true,ValueFromPipeline)]
[ValidateNotNullOrEmpty()]
[string]$string
)
$specialChars = @{
'`n' = "`n"
'\\n' = "`n"
'`r' = "`r"
'\\r' = "`r"
'`t' = "`t"
'\\t' = "`t"
}
foreach ($char in $specialChars.Keys) {
$string = $string -replace ($char,$specialChars[$char])
}
return $string
}
<#
This resource manages the file in a specific path.
[DscResource()] indicates the class is a DSC resource
#>
[DscResource()]
class NewFile {
<#
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 is optional. When provided, the content of the file
will be overwridden by this value.
#>
[DscProperty()]
[string] $content
<#
This property reports the reasons the machine is or is not compliant.
[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)]
[MyDscResourceReason[]] $Reasons
<#
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.
#>
[NewFile] Get() {
$get = Get-File -ensure $this.ensure -path $this.path -content $this.content
return $get
}
<#
This method is equivalent of the Set-TargetResource script function.
It sets the resource to the desired state.
#>
[void] Set() {
$set = Set-File -ensure $this.ensure -path $this.path -content $this.content
}
<#
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() {
$test = Test-File -ensure $this.ensure -path $this.path -content $this.content
return $test
}
}
创建清单
若要使基于类的资源可供 DSC 引擎使用,必须在清单文件中包括一个 DscResourcesToExport
语句,该语句指示模块导出资源。 我们的清单如下所示:
@{
# Script module or binary module file associated with this manifest.
RootModule = 'NewFile.psm1'
# Version number of this module.
ModuleVersion = '1.0.0'
# ID used to uniquely identify this module
GUID = 'fad0d04e-65d9-4e87-aa17-39de1d008ee4'
# Author of this module
Author = 'Microsoft Corporation'
# Company or vendor of this module
CompanyName = 'Microsoft Corporation'
# Copyright statement for this module
Copyright = ''
# Description of the functionality provided by this module
Description = 'Create and set content of a file'
# Minimum version of the Windows PowerShell engine required by this module
PowerShellVersion = '5.0'
# Functions to export from this module
FunctionsToExport = @('Get-File','Set-File','Test-File')
# DSC resources to export from this module
DscResourcesToExport = @('NewFile')
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PSData = @{
# Tags applied to this module. These help with module discovery in online galleries.
# Tags = @(Power Plan, Energy, Battery)
# A URL to the license for this module.
# LicenseUri = ''
# A URL to the main website for this project.
# ProjectUri = ''
# A URL to an icon representing this module.
# IconUri = ''
# ReleaseNotes of this module
# ReleaseNotes = ''
} # End of PSData hashtable
}
}
测试资源
如前所述在文件夹结构中保存类和清单文件后,可以创建使用新资源的配置。 有关如何运行 DSC 配置的信息,请参阅 执行配置。 以下配置将检查文件 /tmp/test.txt
是否存在,以及内容是否与属性“Content”提供的字符串匹配。 否则,将写入整个文件。
Configuration MyConfig
{
Import-DSCResource -ModuleName NewFile
NewFile testFile
{
Path = "/tmp/test.txt"
Content = "DSC Rocks!"
Ensure = "Present"
}
}
MyConfig
支持 PsDscRunAsCredential
[注意] PowerShell 5.0 及更高版本中支持 PsDscRunAsCredential。
PsDscRunAsCredential 属性可用于 DSC 配置 资源块,以指定应在一组指定的凭据下运行资源。 有关详细信息,请参阅 使用用户凭据运行 DSC。
要求或禁止对资源使用 PsDscRunAsCredential
DscResource()
属性采用可选参数 RunAsCredential。 此参数采用三个值之一:
- 对于调用此资源的配置,
Optional
PsDscRunAsCredential 是可选的。 这是默认值。 -
Mandatory
PsDscRunAsCredential 必须用于调用此资源的任何配置。 - 调用此资源的
NotSupported
配置无法使用 PsDscRunAsCredential。 -
Default
与Optional
相同。
例如,使用以下属性指定自定义资源不支持使用 PsDscRunAsCredential:
[DscResource(RunAsCredential=NotSupported)]
class NewFile {
}
在模块中声明多个类资源
模块可以定义多个基于类的 DSC 资源。 只需在同一 .psm1
文件中声明所有类,并在 .psd1
清单中包含每个名称。
$env:ProgramFiles\WindowsPowerShell\Modules (folder)
|- MyDscResource (folder)
|- MyDscResource.psm1
MyDscResource.psd1
访问用户上下文
若要从自定义资源内访问用户上下文,可以使用自动变量 $global:PsDscContext
。
例如,以下代码将编写运行到详细输出流中的资源的用户上下文:
if (PsDscContext.RunAsUser) {
Write-Verbose "User: $global:PsDscContext.RunAsUser";
}