Condividi tramite


Scrittura di una risorsa DSC personalizzata con classi di PowerShell

Si applica a: Windows PowerShell 5.0

Con l'introduzione delle classi di PowerShell in Windows PowerShell 5.0, è ora possibile definire una risorsa DSC creando una classe . La classe definisce sia lo schema che l'implementazione della risorsa, quindi non è necessario creare un file MOF separato. Anche la struttura di cartelle per una risorsa basata su classi è più semplice, perché non è necessaria una cartella DSCResources .

In una risorsa DSC basata su classi, lo schema viene definito come proprietà della classe che può essere modificata con attributi per specificare il tipo di proprietà. La risorsa viene implementata dai metodi Get(), Set()e Test() (equivalenti alle funzioni Get-TargetResource, Set-TargetResourcee Test-TargetResource in una risorsa script.

In questo articolo verrà creata una risorsa semplice denominata NewFile che gestisce un file in un percorso specificato.

Per altre informazioni sulle risorse DSC, vedere Creare risorse di Configurazione dello stato desiderate di Windows PowerShell personalizzate

Nota

Le raccolte generiche non sono supportate nelle risorse basate su classi.

Struttura di cartelle per una risorsa di classe

Per implementare una risorsa personalizzata DSC con una classe PowerShell, creare la struttura di cartelle seguente. La classe è definita in MyDscResource.psm1 e il manifesto del modulo è definito in MyDscResource.psd1.

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

Creare la classe

Usare la parola chiave class per creare una classe di PowerShell. Per specificare che una classe è una risorsa DSC, usare l'attributo DscResource(). Il nome della classe è il nome della risorsa DSC.

[DscResource()]
class NewFile {
}

Dichiarare le proprietà

Lo schema delle risorse DSC viene definito come proprietà della classe . Dichiariamo tre proprietà come indicato di seguito.

[DscProperty(Key)]
[string] $path

[DscProperty(Mandatory)]
[ensure] $ensure

[DscProperty()]
[string] $content

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

Si noti che le proprietà vengono modificate dagli attributi. Il significato degli attributi è il seguente:

  • DscProperty(Key): la proprietà è obbligatoria. La proprietà è una chiave. I valori di tutte le proprietà contrassegnate come chiavi devono essere combinati per identificare in modo univoco un'istanza di risorsa all'interno di una configurazione.
  • dscProperty(obbligatorio): la proprietà è obbligatoria.
  • DscProperty(NotConfigurable): la proprietà è di sola lettura. Le proprietà contrassegnate con questo attributo non possono essere impostate da una configurazione, ma vengono popolate dal metodo Get() quando presente.
  • DscProperty(): la proprietà è configurabile, ma non è obbligatoria.

Le proprietà $Path e $SourcePath sono entrambe stringhe. Il $CreationTime è una proprietà dateTime. La proprietà $Ensure è un tipo di enumerazione, definito come segue.

enum Ensure
{
    Absent
    Present
}

Classi di incorporamento

Se si vuole includere un nuovo tipo con proprietà definite che è possibile usare all'interno della risorsa, è sufficiente creare una classe con tipi di proprietà come descritto in precedenza.

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

    [DscProperty()]
    [string] $Phrase
}

Nota

La classe MyDscResourceReason viene dichiarata qui con il nome del modulo come prefisso. Anche se è possibile assegnare un nome qualsiasi alle classi incorporate, se due o più moduli definiscono una classe con lo stesso nome e vengono entrambi usati in una configurazione, PowerShell genera un'eccezione.

Per evitare eccezioni causate da conflitti di nomi in DSC, anteporre i nomi delle classi incorporate con il nome del modulo. Se il nome della classe incorporata non è già in conflitto, è possibile usarlo senza un prefisso.

Se la risorsa DSC è progettata per l'uso con la funzionalità di configurazione del computer di Gestione automatica di Azure, anteporre sempre il nome della classe incorporata creata per la proprietà Reasons .

Funzioni pubbliche e private

È possibile creare funzioni di PowerShell all'interno dello stesso file di modulo e usarle all'interno dei metodi della risorsa di classe DSC. Le funzioni devono essere dichiarate come pubbliche, ma i blocchi di script all'interno di tali funzioni pubbliche possono chiamare funzioni private. L'unica differenza è se sono elencati nella proprietà FunctionsToExport del manifesto del modulo.

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

Implementazione dei metodi

I metodi Get(), Set()e Test() sono analoghi alle funzioni Get-TargetResource, Set-TargetResourcee Test-TargetResource in una risorsa script.

Come procedura consigliata, ridurre al minimo la quantità di codice all'interno dell'implementazione della classe. Spostare invece la maggior parte del codice in funzioni pubbliche nel modulo, che può quindi essere testato in modo indipendente.

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

File completo

Di seguito è indicato il file di classe completo.

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

Creare un manifesto

Per rendere disponibile una risorsa basata su classi per il motore DSC, è necessario includere un'istruzione DscResourcesToExport nel file manifesto che indica al modulo di esportare la risorsa. Il manifesto è simile al seguente:

@{

    # 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

    }
}

Testare la risorsa

Dopo aver salvato i file di classe e manifesto nella struttura di cartelle come descritto in precedenza, è possibile creare una configurazione che usa la nuova risorsa. Per informazioni su come eseguire una configurazione DSC, vedere Applicazione delle configurazioni. La configurazione seguente verificherà se il file in /tmp/test.txt esiste e se il contenuto corrisponde alla stringa fornita dalla proprietà 'Content'. In caso contrario, viene scritto l'intero file.

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

Supporto di PsDscRunAsCredential

[Nota] PsDscRunAsCredential è supportato in PowerShell 5.0 e versioni successive.

La proprietà PsDscRunAsCredential può essere usata nelle configurazioni DSC blocco di risorse per specificare che la risorsa deve essere eseguita in un set specificato di credenziali. Per altre informazioni, vedere Esecuzione di DSC con credenziali utente.

Richiedere o non consentire PsDscRunAsCredential per la risorsa

L'attributo DscResource() accetta un parametro facoltativo RunAsCredential. Questo parametro accetta uno dei tre valori seguenti:

  • Optional PsDscRunAsCredential è facoltativo per le configurazioni che chiamano questa risorsa. Questo è il valore predefinito.
  • Mandatory PsDscRunAsCredential deve essere usato per qualsiasi configurazione che chiama questa risorsa.
  • NotSupported Configurazioni che chiamano questa risorsa non possono usare PsDscRunAsCredential.
  • Default Uguale a Optional.

Ad esempio, usare l'attributo seguente per specificare che la risorsa personalizzata non supporta l'uso di PsDscRunAsCredential:

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

Dichiarazione di più risorse di classe in un modulo

Un modulo può definire più risorse DSC basate su classi. È sufficiente dichiarare tutte le classi nello stesso file .psm1 e includere ogni nome nel manifesto .psd1.

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

Accedere al contesto utente

Per accedere al contesto utente dall'interno di una risorsa personalizzata, è possibile usare la variabile automatica $global:PsDscContext.

Ad esempio, il codice seguente scriverà il contesto utente in cui la risorsa è in esecuzione nel flusso di output dettagliato:

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

Vedere anche

Compilare risorse personalizzate di Windows PowerShell DSC (Desired State Configuration)