PowerShell クラスを使用したカスタム DSC リソースの記述

適用対象: Windows PowerShell 5.0

Windows PowerShell 5.0 での PowerShell クラスの導入により、クラスを作成して DSC リソースを定義できるようになりました。 このクラスは、リソースのスキーマと実装の両方を定義するため、別の MOF ファイルを作成する必要はありません。 DSCResources フォルダーは必要ないため、クラス ベースのリソースのフォルダー構造も簡単です。

クラス ベースの DSC リソースでは、スキーマはクラスのプロパティとして定義されます。このプロパティは、プロパティの種類を指定するために属性で変更できます。 リソースは、Get()Set()、および Test() メソッド (スクリプト リソース内の Get-TargetResourceSet-TargetResource、および Test-TargetResource 関数と同等) によって実装されます。

この記事では、指定したパス内のファイル 管理する NewFile という名前の単純なリソースを作成します。

DSC リソースの詳細については、「カスタム Windows PowerShell Desired State Configuration Resources をビルドする」を参照してください。

手記

ジェネリック コレクションは、クラス ベースのリソースではサポートされていません。

クラス リソースのフォルダー構造

PowerShell クラスを使用して DSC カスタム リソースを実装するには、次のフォルダー構造を作成します。 クラスは MyDscResource.psm1 で定義され、モジュール マニフェストは MyDscResource.psd1で定義されます。

$env:ProgramFiles\WindowsPowerShell\Modules (folder)
    |- MyDscResource (folder)
        MyDscResource.psm1
        MyDscResource.psd1

クラスを作成する

class キーワードを使用して PowerShell クラスを作成します。 クラスが DSC リソースであることを指定するには、DscResource() 属性を使用します。 クラスの名前は、DSC リソースの名前です。

[DscResource()]
class NewFile {
}

プロパティを宣言する

DSC リソース スキーマは、クラスのプロパティとして定義されます。 次の 3 つのプロパティを宣言します。

[DscProperty(Key)]
[string] $path

[DscProperty(Mandatory)]
[ensure] $ensure

[DscProperty()]
[string] $content

[DscProperty(NotConfigurable)]
[MyDscResourceReason[]] $Reasons

プロパティが属性によって変更されていることに注意してください。 属性の意味は次のとおりです。

  • DscProperty(Key): プロパティは必須です。 プロパティはキーです。 キーとしてマークされているすべてのプロパティの値を組み合わせて、構成内のリソース インスタンスを一意に識別する必要があります。
  • DscProperty(Mandatory): プロパティは必須です。
  • DscProperty(NotConfigurable): プロパティは読み取り専用です。 この属性でマークされたプロパティは構成では設定できませんが、Get() メソッドが存在する場合は設定されます。
  • DscProperty(): プロパティは構成できますが、必須ではありません。

$Path プロパティと $SourcePath プロパティは両方とも文字列です。 $CreationTime は、DateTime プロパティです。 $Ensure プロパティは列挙型であり、次のように定義されます。

enum Ensure
{
    Absent
    Present
}

クラスの埋め込み

リソース内で使用できるプロパティが定義された新しい型を含める場合は、前述のようにプロパティ型を持つクラスを作成するだけです。

class MyDscResourceReason {
    [DscProperty()]
    [string] $Code

    [DscProperty()]
    [string] $Phrase
}

手記

ここで、MyDscResourceReason クラスは、モジュールの名前をプレフィックスとして使用して宣言されます。 埋め込みクラスには任意の名前を付けることができますが、2 つ以上のモジュールが同じ名前のクラスを定義し、両方とも構成で使用されている場合、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-TargetResourceSet-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 を要求または禁止する

属性は、RunAsCredential省略可能なパラメーターを受け取ります。 このパラメーターは、次の 3 つの値のいずれかを受け取ります。

  • PsDscRunAsCredential 、このリソースを呼び出す構成では省略可能です。 これが既定値です。
  • PsDscRunAsCredential 、このリソースを呼び出す構成に使用する必要があります。
  • このリソースを呼び出す構成では、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 Desired State Configuration Resources をビルドする