Написание пользовательского ресурса DSC с помощью классов PowerShell
Область применения: Windows PowerShell 5.0
Вводные сведения о классах PowerShell в Windows PowerShell 5.0 теперь можно определить ресурс DSC, создав класс. Класс определяет схему и реализацию ресурса, поэтому не требуется создавать отдельный MOF-файл. Структура папок для ресурса на основе классов также проще, так как папка DSCResources не требуется.
В ресурсе DSC на основе классов схема определяется как свойства класса, который можно изменить атрибутами, чтобы указать тип свойства. Ресурс реализуется методами Get()
, Set()
и Test()
(эквивалентными функциям Get-TargetResource
, Set-TargetResource
и Test-TargetResource
в ресурсе скрипта.
В этой статье мы создадим простой ресурс с именем NewFile, который управляет файлом в указанном пути.
Дополнительные сведения о ресурсах DSC см. в разделе Сборка настраиваемых ресурсов конфигурации требуемого состояния Windows PowerShell
Заметка
Универсальные коллекции не поддерживаются в ресурсах на основе классов.
Структура папок для ресурса класса
Чтобы реализовать пользовательский ресурс DSC с помощью класса PowerShell, создайте следующую структуру папок.
Класс определен в 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, всегда префиксируйте имя внедренного класса, созданного для свойства Причин.
Общедоступные и частные функции
Функции 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
[Примечание] psDscRunAsCredential поддерживается в PowerShell 5.0 и более поздних версиях.
Свойство 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";
}
См. также
сборка настраиваемых ресурсов конфигурации требуемого состояния Windows PowerShell