Udostępnij za pośrednictwem


Pisanie niestandardowego zasobu DSC przy użyciu klas programu PowerShell

Dotyczy: Windows PowerShell 5.0

Wraz z wprowadzeniem klas programu PowerShell w programie Windows PowerShell 5.0 można teraz zdefiniować zasób DSC, tworząc klasę. Klasa definiuje zarówno schemat, jak i implementację zasobu, więc nie ma potrzeby tworzenia oddzielnego pliku MOF. Struktura folderów dla zasobu opartego na klasach jest również prostsza, ponieważ folder DSCResources nie jest konieczny.

W zasobie DSC opartym na klasie schemat jest definiowany jako właściwości klasy, które można modyfikować za pomocą atrybutów w celu określenia typu właściwości. Zasób jest implementowany przez metody Get(), Set()i Test() (równoważne funkcjom Get-TargetResource, Set-TargetResourcei Test-TargetResource w zasobie skryptu.

W tym artykule utworzymy prosty zasób o nazwie NewFile, który zarządza plikiem w określonej ścieżce.

Aby uzyskać więcej informacji na temat zasobów DSC, zobacz Build Custom Windows PowerShell Desired State Configuration Resources (Tworzenie niestandardowych zasobów konfiguracji żądanego stanu środowiska Windows PowerShell)

Nuta

Kolekcje ogólne nie są obsługiwane w zasobach opartych na klasach.

Struktura folderów dla zasobu klasy

Aby zaimplementować zasób niestandardowy DSC z klasą programu PowerShell, utwórz następującą strukturę folderów. Klasa jest zdefiniowana w MyDscResource.psm1, a manifest modułu jest zdefiniowany w MyDscResource.psd1.

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

Tworzenie klasy

Słowo kluczowe klasy służy do tworzenia klasy programu PowerShell. Aby określić, że klasa jest zasobem DSC, użyj atrybutu DscResource(). Nazwa klasy to nazwa zasobu DSC.

[DscResource()]
class NewFile {
}

Deklarowanie właściwości

Schemat zasobu DSC jest definiowany jako właściwości klasy. Deklarujemy trzy właściwości w następujący sposób.

[DscProperty(Key)]
[string] $path

[DscProperty(Mandatory)]
[ensure] $ensure

[DscProperty()]
[string] $content

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

Zwróć uwagę, że właściwości są modyfikowane przez atrybuty. Znaczenie atrybutów jest następujące:

  • DscProperty(Key): właściwość jest wymagana. Właściwość jest kluczem. Wartości wszystkich właściwości oznaczonych jako klucze muszą łączyć się w celu unikatowego zidentyfikowania wystąpienia zasobu w ramach konfiguracji.
  • DscProperty(Mandatory): właściwość jest wymagana.
  • DscProperty(NotConfigurable): właściwość jest tylko do odczytu. Właściwości oznaczone tym atrybutem nie mogą być ustawiane przez konfigurację, ale są wypełniane przez metodę Get(), gdy jest obecna.
  • DscProperty(): właściwość jest konfigurowalna, ale nie jest wymagana.

Właściwości $Path i $SourcePath są ciągami. $CreationTime jest właściwością DateTime. Właściwość $Ensure jest typem wyliczenia zdefiniowanym w następujący sposób.

enum Ensure
{
    Absent
    Present
}

Osadzanie klas

Jeśli chcesz dołączyć nowy typ ze zdefiniowanymi właściwościami, których można użyć w ramach zasobu, wystarczy utworzyć klasę z typami właściwości zgodnie z powyższym opisem.

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

    [DscProperty()]
    [string] $Phrase
}

Nuta

Klasa MyDscResourceReason jest deklarowana tutaj z nazwą modułu jako prefiksem. Chociaż można nadać klasom osadzonym dowolną nazwę, jeśli co najmniej dwa moduły definiują klasę o tej samej nazwie i są używane w konfiguracji, program PowerShell zgłasza wyjątek.

Aby uniknąć wyjątków spowodowanych konfliktami nazw w usłudze DSC, prefiks nazw klas osadzonych o nazwie modułu. Jeśli nazwa klasy osadzonej jest już mało prawdopodobna, można jej użyć bez prefiksu.

Jeśli zasób DSC jest przeznaczony do użycia z funkcją konfiguracji maszyny usługi Azure Automanage, zawsze prefiks nazwy klasy osadzonej tworzonej dla właściwości Reasons.

Funkcje publiczne i prywatne

Funkcje programu PowerShell można tworzyć w tym samym pliku modułu i używać ich wewnątrz metod zasobu klasy DSC. Funkcje muszą być zadeklarowane jako publiczne, jednak bloki skryptów w tych funkcjach publicznych mogą wywoływać funkcje, które są prywatne. Jedyną różnicą jest to, czy są one wymienione we właściwości FunctionsToExport manifestu modułu.

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

Implementowanie metod

Metody Get(), Set()i Test() są analogiczne do funkcji Get-TargetResource, Set-TargetResourcei Test-TargetResource w zasobie skryptu.

Najlepszym rozwiązaniem jest zminimalizowanie ilości kodu w ramach implementacji klasy. Zamiast tego przenieś większość kodu do funkcji publicznych w module, które następnie można niezależnie przetestować.

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

Pełny plik

Poniższy kompletny plik klasy.

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

Tworzenie manifestu

Aby udostępnić zasób oparty na klasie aparatowi DSC, należy dołączyć instrukcję DscResourcesToExport w pliku manifestu, który nakazuje modułowi wyeksportowanie zasobu. Nasz manifest wygląda następująco:

@{

    # 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

    }
}

Testowanie zasobu

Po zapisaniu plików klasy i manifestu w strukturze folderów zgodnie z wcześniejszym opisem można utworzyć konfigurację, która używa nowego zasobu. Aby uzyskać informacje na temat uruchamiania konfiguracji DSC, zobacz Uchwalenie konfiguracji. Poniższa konfiguracja sprawdzi, czy plik w /tmp/test.txt istnieje i czy zawartość jest zgodna z ciągiem dostarczonym przez właściwość "Content". Jeśli nie, cały plik zostanie zapisany.

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

Obsługa elementu PsDscRunAsCredential

[Uwaga] psDscRunAsCredential jest obsługiwana w programie PowerShell 5.0 lub nowszym.

Właściwość PsDscRunAsCredential może być używana w konfiguracjach DSC bloku zasobów, aby określić, że zasób powinien być uruchamiany w ramach określonego zestawu poświadczeń. Aby uzyskać więcej informacji, zobacz Running DSC with user credentials.

Wymagaj lub nie zezwalaj na użycie elementu PsDscRunAsCredential dla zasobu

Atrybut DscResource() przyjmuje opcjonalny parametr RunAsCredential. Ten parametr przyjmuje jedną z trzech wartości:

  • Optional PsDscRunAsCredential jest opcjonalna w przypadku konfiguracji wywołujących ten zasób. Jest to wartość domyślna.
  • Mandatory psDscRunAsCredential należy użyć dla każdej konfiguracji, która wywołuje ten zasób.
  • NotSupported Konfiguracje wywołujące ten zasób nie mogą używać PsDscRunAsCredential.
  • Default tak samo jak Optional.

Na przykład użyj następującego atrybutu, aby określić, że zasób niestandardowy nie obsługuje używania PsDscRunAsCredential:

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

Deklarowanie wielu zasobów klasy w module

Moduł może definiować wiele zasobów DSC opartych na klasach. Wystarczy zadeklarować wszystkie klasy w tym samym pliku .psm1 i dołączyć każdą nazwę w manifeście .psd1.

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

Uzyskiwanie dostępu do kontekstu użytkownika

Aby uzyskać dostęp do kontekstu użytkownika z poziomu zasobu niestandardowego, możesz użyć zmiennej automatycznej $global:PsDscContext.

Na przykład poniższy kod napisze kontekst użytkownika, w którym zasób jest uruchomiony do pełnego strumienia wyjściowego:

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

Zobacz też

tworzenie niestandardowych zasobów konfiguracji żądanego stanu programu Windows PowerShell