Escrevendo um recurso de DSC de instância única (melhor prática)Writing a single-instance DSC resource (best practice)

Observação: este tópico descreve a melhor prática para a definição de um recurso de DSC que permite apenas uma única instância em uma configuração.Note: This topic describes a best practice for defining a DSC resource that allows only a single instance in a configuration. Atualmente, não há nenhum recurso interno do DSC para fazer isso.Currently, there is no built-in DSC feature to do this. Isso pode mudar no futuro.That might change in the future.

Há situações em que você não deseja permitir que um recurso seja usado várias vezes em uma configuração.There are situations where you don't want to allow a resource to be used multiple times in a configuration. Por exemplo, um uma implementação anterior do recurso xTimeZone, uma configuração poderia chamar o recurso várias vezes, configurando o fuso horário para uma definição diferente em cada bloco de recurso:For example, in a previous implementation of the xTimeZone resource, a configuration could call the resource multiple times, setting the time zone to a different setting in each resource block:

Configuration SetTimeZone 
{ 
    Param 
    ( 
        [String[]]$NodeName = $env:COMPUTERNAME 

    ) 

    Import-DSCResource -ModuleName xTimeZone 


    Node $NodeName 
    { 
         xTimeZone TimeZoneExample 
         { 

            TimeZone = 'Eastern Standard Time' 
         } 

         xTimeZone TimeZoneExample2
         {

            TimeZone = 'Pacific Standard Time'

         }        

    } 
} 

Isso ocorre devido à forma como as chaves de recurso de DSC funcionam.This is because of the way DSC resource keys work. Um recurso deve ter pelo menos uma propriedade de chave.A resource must have at least one key property. Uma instância do recurso é considerada exclusiva se a combinação de valores de todas as suas propriedades de chave for exclusiva.A resource instance is considered unique if the combination of the values of all of its key properties is unique. Em sua implementação anterior, o recurso xTimeZone apresentava apenas uma propriedade --TimeZone, que obrigatoriamente tinha que ser uma chave.In its previous implementation, the xTimeZone resource had only one property--TimeZone, which was required to be a key. Por isso, uma configuração, como mostrado acima seria compilada e executada sem aviso.Because of this, a configuration such as the one above would compile and run without warning. Cada um do blocos de recurso de xTimeZone é considerado exclusivo.Each of the xTimeZone resource blocks is considered unique. Isso faria com que a configuração fosse aplicada várias vezes ao nó, alternando o fuso horário.This would cause the configuration to be repeatedly applied to the node, cycling the timezone back and forth.

Para garantir que uma configuração possa definir o fuso horário para um nó de destino somente uma vez, o recurso foi atualizado para adicionar uma segunda propriedade, IsSingleInstance, que se tornou a propriedade principal.To ensure that a configuration could set the time zone for a target node only once, the resource was updated to add a second property, IsSingleInstance, that became the key property. O IsSingleInstance foi limitado a um único valor, "Yes" (Sim) usando um ValueMap.The IsSingleInstance was limited to a single value, "Yes" by using a ValueMap. O esquema MOF antigo para o recurso foi:The old MOF schema for the resource was:

[ClassVersion("1.0.0.0"), FriendlyName("xTimeZone")]
class xTimeZone : OMI_BaseResource
{
    [Key, Description("Specifies the TimeZone.")] String TimeZone;
};

O esquema MOF atualizado para o recurso é:The updated MOF schema for the resource is:

[ClassVersion("1.0.0.0"), FriendlyName("xTimeZone")]
class xTimeZone : OMI_BaseResource
{
    [Key, Description("Specifies the resource is a single instance, the value must be 'Yes'"), ValueMap{"Yes"}, Values{"Yes"}] String IsSingleInstance;
    [Required, Description("Specifies the TimeZone.")] String TimeZone;
};

O script de recurso também foi atualizado para usar o novo parâmetro.The resource script was also updated to use the new parameter. Aqui está o script de recurso antigo:Here is the old resource script:

function Get-TargetResource
{
    [CmdletBinding()]
    [OutputType([Hashtable])]
    param
    (
        [parameter(Mandatory = $true)]
        [ValidateSet('Yes')]
        [String]
        $IsSingleInstance,

        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $TimeZone
    )

    #Get the current TimeZone
    $CurrentTimeZone = Get-TimeZone

    $returnValue = @{
        TimeZone = $CurrentTimeZone
        IsSingleInstance = 'Yes'
    }

    #Output the target resource
    $returnValue
}


function Set-TargetResource
{
    [CmdletBinding(SupportsShouldProcess=$true)]
    param
    (
        [parameter(Mandatory = $true)]
        [ValidateSet('Yes')]
        [String]
        $IsSingleInstance,

        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $TimeZone
    )

    #Output the result of Get-TargetResource function.
    $CurrentTimeZone = Get-TimeZone

    if($PSCmdlet.ShouldProcess("'$TimeZone'","Replace the System Time Zone"))
    {
        try
        {
            if($CurrentTimeZone -ne $TimeZone)
            {
                Write-Verbose -Verbose "Setting the TimeZone"
                Set-TimeZone -TimeZone $TimeZone}
            else
            {
                Write-Verbose -Verbose "TimeZone already set to $TimeZone"
            }
        }
        catch
        {
            $ErrorMsg = $_.Exception.Message
            Write-Verbose -Verbose $ErrorMsg
        }
    }
}


function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([Boolean])]
    param
    (
        [parameter(Mandatory = $true)]
        [ValidateSet('Yes')]
        [String]
        $IsSingleInstance, 

        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $TimeZone
    )

    #Output from Get-TargetResource
    $CurrentTimeZone = Get-TimeZone

    if($TimeZone -eq $CurrentTimeZone)
    {
        return $true
    }
    else
    {
        return $false
    }
}

Function Get-TimeZone {
    [CmdletBinding()]
    param()

    & tzutil.exe /g
}

Function Set-TimeZone {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        [System.String]
        $TimeZone
    )

    try
    {
        & tzutil.exe /s $TimeZone
    }
    catch
    {
        $ErrorMsg = $_.Exception.Message
        Write-Verbose $ErrorMsg
    }
}

Export-ModuleMember -Function *-TargetResource

Observe que a propriedade TimeZone propriedade não é mais uma chave.Notice that the TimeZone property is no longer a key. Agora, se uma configuração tentar definir o fuso horário duas vezes (usando dois blocos xTimeZone diferentes com valores de TimeZone diferentes), tentar compilar a configuração vai gerar um erro:Now, if a configuration attempts to set the time zone twice (by using two different xTimeZone blocks with different TimeZone values), attempting to compile the configuration will cause an error:

Test-ConflictingResources : A conflict was detected between resources '[xTimeZone]TimeZoneExample (::15::10::xTimeZone)' and 
'[xTimeZone]TimeZoneExample2 (::22::10::xTimeZone)' in node 'CONTOSO-CLIENT'. Resources have identical key properties but there are differences in the 
following non-key properties: 'TimeZone'. Values 'Eastern Standard Time' don't match values 'Pacific Standard Time'. Please update these property 
values so that they are identical in both cases.
At line:271 char:9
+         Test-ConflictingResources $keywordName $canonicalizedValue $k ...
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [Write-Error], InvalidOperationException
    + FullyQualifiedErrorId : ConflictingDuplicateResource,Test-ConflictingResources
Errors occurred while processing configuration 'SetTimeZone'.
At C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\PSDesiredStateConfiguration\PSDesiredStateConfiguration.psm1:3705 char:5
+     throw $ErrorRecord
+     ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (SetTimeZone:String) [], InvalidOperationException
    + FullyQualifiedErrorId : FailToProcessConfiguration