Freigeben über


Schreiben einer benutzerdefinierten DSC-Ressource mit PowerShell-Klassen

Gilt für: Windows PowerShell 5.0

Mit der Einführung der PowerShell-Klassen in Windows PowerShell 5.0 können Sie jetzt eine DSC-Ressourcen durch Erstellen einer Klasse definieren. Die Klasse definiert das Schema und die Implementierung der Ressource, daher besteht keine Notwendigkeit, eine separate MOF-Datei zu erstellen. Die Ordnerstruktur für eine klassenbasierte Ressource ist auch einfacher, da kein DSCResources-Ordner erforderlich ist.

In einer klassenbasierten DSC-Ressource wird das Schema als Eigenschaften der -Klasse definiert, die mit Attributen geändert werden können, um den Eigenschaftentyp anzugeben. Die Ressource wird mit den Methoden Get() , Set() und Test() implementiert (entspricht den Funktionen Get-TargetResource, Set-TargetResource und Test-TargetResource in einer Skriptressource.

In diesem Artikel erstellen wir eine einfache Ressource namens NewFile , die eine Datei in einem angegebenen Pfad verwaltet.

Weitere Informationen zu DSC-Ressourcen finden Sie unter Erstellen von benutzerdefinierten Windows PowerShell DSC-Ressourcen.

Hinweis

Generische Sammlungen werden nicht in auf Klassen basierenden Ressourcen unterstützt.

Ordnerstruktur für eine Klassenressource

Um eine benutzerdefinierte DSC-Ressource mit einer PowerShell-Klasse zu implementieren, erstellen Sie die folgende Ordnerstruktur. Die Klasse wird in MyDscResource.psm1 und das Modulmanifest in MyDscResource.psd1 definiert.

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

Erstellen einer Klasse

Sie verwenden das Schlüsselwort „class“ zum Erstellen einer PowerShell-Klasse. Um anzugeben, dass eine Klasse eine DSC-Ressource ist, verwenden Sie das Attribut DscResource() . Der Name der Klasse ist der Name der DSC-Ressource.

[DscResource()]
class NewFile {
}

Deklarieren von Eigenschaften

Das DSC-Ressourcenschema wird als Eigenschaften der Klasse definiert. Es wurden die folgenden drei Eigenschaften deklariert.

[DscProperty(Key)]
[string] $path

[DscProperty(Mandatory)]
[ensure] $ensure

[DscProperty()]
[string] $content

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

Beachten Sie, dass die Eigenschaften durch Attribute geändert werden. Die Attribute haben folgende Bedeutungen:

  • DscProperty(Key) : Die Eigenschaft ist erforderlich. Die Eigenschaft ist ein Schlüssel. Die Werte aller als Schlüssel gekennzeichneten Eigenschaften in Kombination müssen eine Ressourceninstanz in einer Konfiguration eindeutig identifizieren.
  • DscProperty(Mandatory) : Die Eigenschaft ist erforderlich.
  • DscProperty(NotConfigurable) : Die Eigenschaft ist schreibgeschützt. Eigenschaften, die mit diesem Attribut gekennzeichnet sind, können nicht von einer Konfiguration festgelegt werden, sondern werden durch die Methode Get() , wenn vorhanden, aufgefüllt.
  • DscProperty() : Die Eigenschaft ist konfigurierbar, aber nicht erforderlich.

Die Eigenschaften $Path und $SourcePath stellen Zeichenfolgen dar. $CreationTime ist eine DateTime-Eigenschaft. Die Eigenschaft $Ensure ist ein Enumerationstyp, der wie folgt definiert ist:

enum Ensure
{
    Absent
    Present
}

Einbetten von Klassen

Wenn Sie einen neuen Typ mit definierten Eigenschaften einbeziehen möchten, den Sie in Ihrer Ressource verwenden können, erstellen Sie einfach eine Klasse mit Eigenschaftstypen wie oben beschrieben.

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

    [DscProperty()]
    [string] $Phrase
}

Hinweis

Die MyDscResourceReason -Klasse wird hier mit dem Modulnamen als Präfix deklariert. Sie können eingebetteten Klassen zwar einen beliebigen Namen geben, aber wenn zwei oder mehr Module eine Klasse mit demselben Namen definieren und beide in einer Konfiguration verwendet werden, löst PowerShell eine Ausnahme aus.

Um Ausnahmen zu vermeiden, die durch Namenskonflikte in DSC verursacht werden, stellen Sie den Namen Ihrer eingebetteten Klassen den Modulnamen voran. Wenn der Name Ihrer eingebetteten Klasse bereits unwahrscheinlich ist, dass ein Konflikt besteht, können Sie ihn ohne Präfix verwenden.

Wenn Ihre DSC-Ressource für die Verwendung mit dem Azure Automanage-Computerkonfigurationsfeature konzipiert ist, präfixieren Sie immer den Namen der eingebetteten Klasse, die Sie für die Reasons-Eigenschaft erstellen.

Öffentliche und private Funktionen

Sie können PowerShell-Funktionen innerhalb derselben Moduldatei erstellen und in den Methoden Ihrer DSC-Klassenressource verwenden. Die Funktionen müssen als öffentlich deklariert werden, aber die Skriptblöcke innerhalb dieser öffentlichen Funktionen können private Funktionen aufrufen. Der einzige Unterschied besteht darin, ob sie in der FunctionsToExport-Eigenschaft des Modulmanifests aufgeführt sind.

<#
   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
}

Implementieren der Methoden

Die Methoden Get() , Set() und Test() werden analog zu den Funktionen Get-TargetResource, Set-TargetResource und Test-TargetResource in einer Skriptressource implementiert.

Als bewährte Methode sollten Sie den Umfang des Codes innerhalb der Klassenimplementierung minimieren. Verschieben Sie stattdessen den Großteil Ihres Codes in öffentliche Funktionen im Modul, die dann unabhängig getestet werden können.

<#
    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
}

Die vollständige Datei

Die vollständige Klassendatei folgt.

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
    }
}

Erstellen eines Manifests

Um eine klassenbasierte Ressource für die DSC-Engine verfügbar zu machen, müssen Sie eine DscResourcesToExport-Anweisung zur Manifestdatei hinzufügen, die die Engine anweist, die Ressource zu exportieren. Unser Manifest sieht folgendermaßen aus:

@{

    # 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

    }
}

Testen der Ressource

Nachdem Sie die Klasse und die Manifestdateien, wie zuvor beschrieben, in der Ordnerstruktur gespeichert haben, können Sie eine Konfiguration erstellen, die die neue Ressource verwendet. Informationen dazu, wie Sie eine DSC-Konfiguration ausführen, finden Sie unter Inkraftsetzung von Konfigurationen. Die folgende Konfiguration überprüft, ob die Datei unter /tmp/test.txt vorhanden ist und ob der Inhalt mit der Zeichenfolge übereinstimmt, die von der Eigenschaft „Content“ bereitgestellt wird. Wenn nicht, wird die gesamte Datei geschrieben.

Configuration MyConfig
{
    Import-DSCResource -ModuleName NewFile
    NewFile testFile
    {
        Path = "/tmp/test.txt"
        Content = "DSC Rocks!"
        Ensure = "Present"
    }
}
MyConfig

Unterstützung von PsDscRunAsCredential

Hinweis: PsDscRunAsCredential wird ab PowerShell 5.0 unterstützt.

Mithilfe der Eigenschaft PsDscRunAsCredential kann im Ressourcenblock DSC configurations angegeben werden, dass die Ressource mit einem festgelegten Satz an Anmeldeinformationen ausgeführt werden soll. Weitere Informationen finden Sie unter Ausführen von DSC mit Benutzeranmeldeinformationen.

Anfordern oder Untersagen von PsDscRunAsCredential für Ihre Ressource

Das Attribut DscResource() verwendet einen optionalen Parameter RunAsCredential. Dieser Parameter kann einen von drei Werten annehmen:

  • OptionalPsDscRunAsCredential ist optional für Konfigurationen, die diese Ressource aufrufen. Dies ist der Standardwert.
  • MandatoryPsDscRunAsCredential muss für jede Konfiguration verwendet werden, die diese Ressource aufruft.
  • NotSupportedKonfigurationen, die diese Ressource aufrufen, können PsDscRunAsCredential nicht verwenden.
  • Default Identisch mit Optional.

Verwenden Sie beispielsweise das folgende Attribut, um anzugeben, dass Ihre benutzerdefinierte Ressource die Verwendung von PsDscRunAsCredential nicht unterstützt:

[DscResource(RunAsCredential=NotSupported)]
class NewFile {
}

Deklarieren von Ressourcen mit mehreren Klassen in einem Modul

Ein Modul kann mehrere klassenbasierte DSC-Ressourcen definieren. Sie müssen nur alle Klassen in derselben .psm1 Datei deklarieren und jeden Namen in das .psd1 Manifest einschließen.

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

Zugriff auf den Benutzerkontext

Um aus einer benutzerdefinierten Ressource auf den Benutzerkontext zuzugreifen, können Sie die automatische Variable $global:PsDscContext verwenden.

Beispielsweise wird mit dem folgenden Code der Benutzerkontext für die Ressourcenausführung in den ausführlichen Ausgabestream geschrieben:

if (PsDscContext.RunAsUser) {
    Write-Verbose "User: $global:PsDscContext.RunAsUser";
}

Weitere Informationen

Erstellen von benutzerdefinierten Windows PowerShell DSC-Ressourcen