Condividi tramite


about_Classes_and_DSC

Breve descrizione

Viene descritto come usare le classi per lo sviluppo in PowerShell con DSC (Desired State Configuration).

Descrizione lunga

A partire da Windows PowerShell 5.0, è stato aggiunto il linguaggio per definire classi e altri tipi definiti dall'utente, usando sintassi formale e semantica simili ad altri linguaggi di programmazione orientati agli oggetti. L'obiettivo è consentire agli sviluppatori e ai professionisti IT di adottare PowerShell per un'ampia gamma di casi d'uso, semplificare lo sviluppo di artefatti di PowerShell come le risorse DSC e accelerare la copertura delle superfici di gestione.

Scenari supportati

Sono supportati gli scenari seguenti:

  • Definire le risorse DSC e i relativi tipi associati usando il linguaggio di PowerShell.
  • Definire tipi personalizzati in PowerShell usando costrutti di programmazione familiari orientati agli oggetti, ad esempio classi, proprietà, metodi ed ereditarietà.
  • Eseguire il debug dei tipi usando il linguaggio di PowerShell.
  • Generare e gestire le eccezioni usando meccanismi formali e a livello corretto.

Definire le risorse DSC con le classi

Oltre alle modifiche della sintassi, le principali differenze tra una risorsa DSC definita dalla classe e un provider di risorse DSC cmdlet sono gli elementi seguenti:

  • Non è necessario un file MOF (Management Object Format).
  • Non è necessaria una sottocartella DSCResource nella cartella del modulo.
  • Un file di modulo di PowerShell può contenere più classi di risorse DSC.

Creare un provider di risorse DSC definito dalla classe

L'esempio seguente è un provider di risorse DSC definito dalla classe salvato come modulo MyDSCResource.psm1. È sempre necessario includere una proprietà chiave in un provider di risorse DSC definito dalla classe.

enum Ensure
{
    Absent
    Present
}

<#
    This resource manages the file in a specific path.
    [DscResource()] indicates the class is a DSC resource
#>

[DscResource()]
class FileResource
{
    <#
        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 defines the fully qualified path to a file that will
        be placed on the system if $Ensure = Present and $Path does not
        exist.

        NOTE: This property is required because [DscProperty(Mandatory)] is
        set.
    #>
    [DscProperty(Mandatory)]
    [string] $SourcePath

    <#
        This property reports the file's create timestamp.

        [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)]
    [Nullable[datetime]] $CreationTime

    <#
        This method is equivalent of the Set-TargetResource script function.
        It sets the resource to the desired state.
    #>
    [void] Set()
    {
        $fileExists = $this.TestFilePath($this.Path)
        if($this.ensure -eq [Ensure]::Present)
        {
            if(-not $fileExists)
            {
                $this.CopyFile()
            }
        }
        else
        {
            if($fileExists)
            {
                Write-Verbose -Message "Deleting the file $($this.Path)"
                Remove-Item -LiteralPath $this.Path -Force
            }
        }
    }

    <#

        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()
    {
        $present = $this.TestFilePath($this.Path)

        if($this.Ensure -eq [Ensure]::Present)
        {
            return $present
        }
        else
{
            return -not $present
        }
    }

    <#
        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.
    #>
    [FileResource] Get()
    {
        $present = $this.TestFilePath($this.Path)

        if ($present)
        {
            $file = Get-ChildItem -LiteralPath $this.Path
            $this.CreationTime = $file.CreationTime
            $this.Ensure = [Ensure]::Present
        }
        else
        {
            $this.CreationTime = $null
            $this.Ensure = [Ensure]::Absent
        }
        return $this
    }

    <#
        Helper method to check if the file exists and it is correct file
    #>
    [bool] TestFilePath([string] $location)
    {
        $present = $true

        $item = Get-ChildItem -LiteralPath $location -ea Ignore
        if ($null -eq $item)
        {
            $present = $false
        }
        elseif( $item.PSProvider.Name -ne "FileSystem")
        {
            throw "Path $($location) is not a file path."
        }
        elseif($item.PSIsContainer)
        {
            throw "Path $($location) is a directory path."
        }
        return $present
    }

    <#
        Helper method to copy file from source to path
    #>
    [void] CopyFile()
    {
        if(-not $this.TestFilePath($this.SourcePath))
        {
            throw "SourcePath $($this.SourcePath) is not found."
        }

        [System.IO.FileInfo]
        $destFileInfo = new-object System.IO.FileInfo($this.Path)

        if (-not $destFileInfo.Directory.Exists)
        {
            $FullName = $destFileInfo.Directory.FullName
            $Message = "Creating directory $FullName"

            Write-Verbose -Message $Message

            #use CreateDirectory instead of New-Item to avoid code
            # to handle the non-terminating error
            [System.IO.Directory]::CreateDirectory($FullName)
        }

        if(Test-Path -LiteralPath $this.Path -PathType Container)
        {
            throw "Path $($this.Path) is a directory path"
        }

        Write-Verbose -Message "Copying $this.SourcePath to $this.Path"

        #DSC engine catches and reports any error that occurs
        Copy-Item -Path $this.SourcePath -Destination $this.Path -Force
    }
}

Creare un manifesto del modulo

Dopo aver creato il provider di risorse DSC definito dalla classe e salvarlo come modulo, creare un manifesto del modulo per il modulo. 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. In questo esempio il manifesto del modulo seguente viene salvato come MyDscResource.psd1.

@{

# Script module or binary module file associated with this manifest.
RootModule = 'MyDscResource.psm1'

DscResourcesToExport = 'FileResource'

# Version number of this module.
ModuleVersion = '1.0'

# ID used to uniquely identify this module
GUID = '81624038-5e71-40f8-8905-b1a87afe22d7'

# Author of this module
Author = 'Microsoft Corporation'

# Company or vendor of this module
CompanyName = 'Microsoft Corporation'

# Copyright statement for this module
Copyright = '(c) 2014 Microsoft. All rights reserved.'

# Description of the functionality provided by this module
# Description = ''

# Minimum version of the PowerShell engine required by this module
PowerShellVersion = '5.0'

# Name of the PowerShell host required by this module
# PowerShellHostName = ''

}

Distribuire un provider di risorse DSC

Distribuire il nuovo provider di risorse DSC creando una cartella MyDscResource in $pshome\Modules o $env:SystemDrive\ProgramFiles\WindowsPowerShell\Modules.

Non è necessario creare una sottocartella DSCResource. Copiare i file manifesto del modulo e del modulo (MyDscResource.psm1 e MyDscResource.psd1) nella cartella MyDscResource.

A questo punto, si crea ed esegue uno script di configurazione come si farebbe con qualsiasi risorsa DSC.

Creare uno script di configurazione DSC

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. La configurazione seguente fa riferimento al modulo MyDSCResource. Salvare la configurazione come script MyResource.ps1.

Per informazioni su come eseguire una configurazione DSC, vedere Panoramica della configurazione dello stato desiderato di Windows PowerShell.

Prima di eseguire la configurazione, creare C:\test.txt. La configurazione controlla se il file esiste in c:\test\test.txt. Se il file non esiste, la configurazione copia il file da C:\test.txt.

Configuration Test
{
    Import-DSCResource -ModuleName MyDscResource
    FileResource file
    {
        Path = "C:\test\test.txt"
        SourcePath = "C:\test.txt"
        Ensure = "Present"
    }
}
Test
Start-DscConfiguration -Wait -Force Test

Eseguire questo script come qualsiasi script di configurazione DSC. Per avviare la configurazione, eseguire le operazioni seguenti in una console di PowerShell con privilegi elevati:

PS C:\test> .\MyResource.ps1

Ereditarietà nelle classi di PowerShell

Dichiarare le classi di base per le classi di PowerShell

È possibile dichiarare una classe PowerShell come tipo di base per un'altra classe di PowerShell, come illustrato nell'esempio seguente, in cui fruit è un tipo di base per apple.

class fruit
{
    [int]sold() {return 100500}
}

class apple : fruit {}
    [apple]::new().sold() # return 100500

Dichiarare le interfacce implementate per le classi di PowerShell

È possibile dichiarare le interfacce implementate dopo i tipi di base o immediatamente dopo i due punti (:) se non è specificato alcun tipo di base. Separare tutti i nomi dei tipi usando le virgole. Si tratta di una sintassi simile a C#.

class MyComparable : system.IComparable
{
    [int] CompareTo([object] $obj)
    {
        return 0;
    }
}

class MyComparableTest : test, system.IComparable
{
    [int] CompareTo([object] $obj)
    {
        return 0;
    }
}

Chiamare costruttori di classi di base

Per chiamare un costruttore della classe base da una sottoclasse, aggiungere la parola chiave base, come illustrato nell'esempio seguente:

class A {
    [int]$a
    A([int]$a)
    {
        $this.a = $a
    }
}

class B : A
{
    B() : base(103) {}
}

    [B]::new().a # return 103

Se una classe base ha un costruttore predefinito (nessun parametro), è possibile omettere una chiamata di costruttore esplicita, come illustrato.

class C : B
{
    C([int]$c) {}
}

Chiamare i metodi della classe base

È possibile eseguire l'override dei metodi esistenti nelle sottoclassi. Per eseguire l'override, dichiarare i metodi usando lo stesso nome e la stessa firma.

class baseClass
{
    [int]days() {return 100500}
}
class childClass1 : baseClass
{
    [int]days () {return 200600}
}

    [childClass1]::new().days() # return 200600

Per chiamare i metodi della classe base dalle implementazioni sottoposte a override, eseguire il cast alla classe base ([baseclass]$this) alla chiamata.

class childClass2 : baseClass
{
    [int]days()
    {
        return 3 * ([baseClass]$this).days()
    }
}

    [childClass2]::new().days() # return 301500

Tutti i metodi di PowerShell sono virtuali. È possibile nascondere metodi .NET non virtuali in una sottoclasse usando la stessa sintassi eseguita per un override: dichiarare metodi con lo stesso nome e firma.

class MyIntList : system.collections.generic.list[int]
{
    # Add is final in system.collections.generic.list
    [void] Add([int]$arg)
    {
        ([system.collections.generic.list[int]]$this).Add($arg * 2)
    }
}

$list = [MyIntList]::new()
$list.Add(100)
$list[0] # return 200

Limitazioni correnti con l'ereditarietà della classe

Una limitazione con l'ereditarietà delle classi è che non esiste alcuna sintassi per dichiarare le interfacce in PowerShell.

Definizione di tipi personalizzati in PowerShell

Windows PowerShell 5.0 ha introdotto diversi elementi del linguaggio.

Parola chiave Class

Definisce una nuova classe. La parola chiave class è un vero tipo .NET Framework. I membri della classe sono pubblici.

class MyClass
{
}

Enumerazione e parole chiave

È stato aggiunto il supporto per la parola chiave enum ed è una modifica che causa un'interruzione. Il delimitatore enum è attualmente una nuova riga. Una soluzione alternativa per coloro che usano già enum consiste nell'inserire una e commerciale (&) prima della parola. Limitazioni correnti: non è possibile definire un enumeratore in termini di se stesso, ma è possibile inizializzare enum in termini di un altro enum, come illustrato nell'esempio seguente:

Impossibile specificare il tipo di base. Il tipo di base è sempre [int].

enum Color2
{
    Yellow = [Color]::Blue
}

Un valore dell'enumeratore deve essere una costante del tempo di analisi. Il valore dell'enumeratore non può essere impostato sul risultato di un comando richiamato.

enum MyEnum
{
    Enum1
    Enum2
    Enum3 = 42
    Enum4 = [int]::MaxValue
}

Enum supporta operazioni aritmetiche, come illustrato nell'esempio seguente:

enum SomeEnum { Max = 42 }
enum OtherEnum { Max = [SomeEnum]::Max + 1 }

Parola chiave Hidden

La parola chiave hidden introdotta in Windows PowerShell 5.0 nasconde i membri della classe dai risultati predefiniti Get-Member. Specificare la proprietà nascosta come illustrato nella riga seguente:

hidden [type] $classmember = <value>

I membri nascosti non vengono visualizzati utilizzando il completamento tramite tabulazione o IntelliSense, a meno che il completamento non si verifichi nella classe che definisce il membro nascosto.

È stato aggiunto un nuovo attributo System.Management.Automation.HiddenAttribute, in modo che il codice C# possa avere la stessa semantica in PowerShell.

Per altre informazioni, vedere [about_Hidden[(/powershell/module/microsoft.powershell.core/about/about_hidden).

Import-DscResource

Import-DscResource è ora una parola chiave dinamica vera. PowerShell analizza il modulo radice del modulo specificato, cercando classi che contengono l'attributo DscResource.

Proprietà

È stato aggiunto un nuovo campo ImplementingAssemblya ModuleInfo. Se lo script definisce classi o l'assembly caricato per i moduli binari ImplementingAssembly è impostato sull'assembly dinamico creato per un modulo script. Non è impostato quando ModuleType = Manifest.

La reflection sul campo ImplementingAssembly individua le risorse in un modulo. Ciò significa che è possibile individuare le risorse scritte in PowerShell o in altri linguaggi gestiti.

Campi con inizializzatori.

[int] $i = 5

Statico è supportato e funziona come un attributo, simile ai vincoli di tipo, in modo che possa essere specificato in qualsiasi ordine.

static [int] $count = 0

Un tipo è facoltativo.

$s = "hello"

Tutti i membri sono pubblici. Le proprietà richiedono una nuova riga o un punto e virgola. Se non viene specificato alcun tipo di oggetto, il tipo di proprietà è Object.

Costruttori e creazione di istanze

Le classi di PowerShell possono avere costruttori con lo stesso nome della classe. È possibile eseguire l'overload dei costruttori. Sono supportati costruttori statici. Le proprietà con espressioni di inizializzazione vengono inizializzate prima di eseguire qualsiasi codice in un costruttore. Le proprietà statiche vengono inizializzate prima del corpo di un costruttore statico e le proprietà dell'istanza vengono inizializzate prima del corpo del costruttore non statico. Attualmente non esiste alcuna sintassi per chiamare un costruttore da un altro costruttore, ad esempio la sintassi C#: ": this()"). La soluzione alternativa consiste nel definire un metodo Init comune.

Di seguito sono riportati i modi per creare un'istanza delle classi:

  • Creazione di un'istanza usando il costruttore predefinito. Si noti che New-Object non è supportato in questa versione.

    $a = [MyClass]::new()

  • Chiamata di un costruttore con un parametro .

    $b = [MyClass]::new(42)

  • Passaggio di una matrice a un costruttore con più parametri

    $c = [MyClass]::new(@(42,43,44), "Hello")

Per questa versione, il nome del tipo è visibile solo in modo lessicale, ovvero non è visibile all'esterno del modulo o dello script che definisce la classe. Le funzioni possono restituire istanze di una classe definita in PowerShell e le istanze funzionano bene al di fuori del modulo o dello script.

Il parametro Get-Memberstatico elenca i costruttori, quindi è possibile visualizzare overload come qualsiasi altro metodo. Anche le prestazioni di questa sintassi sono notevolmente più veloci di New-Object.

Il metodo pseudo-statico denominato nuova funziona con i tipi .NET, come illustrato nell'esempio seguente. [hashtable]::new()

È ora possibile visualizzare gli overload del costruttore con Get-Membero come illustrato in questo esempio:

[hashtable]::new
OverloadDefinitions
-------------------
hashtable new()
hashtable new(int capacity)
hashtable new(int capacity, float loadFactor)

Metodi

Un metodo di classe di PowerShell viene implementato come ScriptBlock con solo un blocco finale. Tutti i metodi sono pubblici. Di seguito viene illustrato un esempio di definizione di un metodo denominato DoSomething.

class MyClass
{
    DoSomething($x)
    {
        $this._doSomething($x)       # method syntax
    }
    private _doSomething($a) {}
}

Chiamata al metodo

Sono supportati metodi di overload. I metodi di overload sono denominati come un metodo esistente, ma differenziati in base ai valori specificati.

$b = [MyClass]::new()
$b.DoSomething(42)

Invocazione

Vedere chiamata al metodo.

Attributi

Sono stati aggiunti tre nuovi attributi: DscResource, DscResourceKeye DscResourceMandatory.

Tipi restituiti

Il tipo restituito è un contratto. Il valore restituito viene convertito nel tipo previsto. Se non viene specificato alcun tipo restituito, il tipo restituito è void. Non esiste alcun flusso di oggetti e oggetti che non possono essere scritti nella pipeline intenzionalmente o per errore.

Ambito lessicale delle variabili

Di seguito viene illustrato un esempio di funzionamento dell'ambito lessicale in questa versione.

$d = 42  # Script scope

function bar
{
    $d = 0  # Function scope
    [MyClass]::DoSomething()
}

class MyClass
{
    static [object] DoSomething()
    {
        return $d  # error, not found dynamically
        return $script:d # no error

        $d = $script:d
        return $d # no error, found lexically
    }
}

$v = bar
$v -eq $d # true

Esempio: Creare classi personalizzate

Nell'esempio seguente vengono create diverse classi personalizzate nuove per implementare un linguaggio DSL (Dynamic Stylesheet Language) HTML. Nell'esempio vengono aggiunte funzioni helper per creare tipi di elemento specifici come parte della classe di elemento, ad esempio stili di intestazione e tabelle, perché i tipi non possono essere usati all'esterno dell'ambito di un modulo.

# Classes that define the structure of the document
#
class Html
{
    [string] $docType
    [HtmlHead] $Head
    [Element[]] $Body

    [string] Render()
    {
        $text = "<html>`n<head>`n"
        $text += $Head
        $text += "`n</head>`n<body>`n"
        $text += $Body -join "`n" # Render all of the body elements
        $text += "</body>`n</html>"
        return $text
    }
    [string] ToString() { return $this.Render() }
}

class HtmlHead
{
    $Title
    $Base
    $Link
    $Style
    $Meta
    $Script
    [string] Render() { return "<title>$Title</title>" }
    [string] ToString() { return $this.Render() }
}

class Element
{
    [string] $Tag
    [string] $Text
    [hashtable] $Attributes
    [string] Render() {
        $attributesText= ""
        if ($Attributes)
        {
            foreach ($attr in $Attributes.Keys)
            {
                $attributesText = " $attr=`"$($Attributes[$attr])`""
            }
        }

        return "<${tag}${attributesText}>$text</$tag>`n"
    }
    [string] ToString() { return $this.Render() }
}

#
# Helper functions for creating specific element types on top of the classes.
# These are required because types aren't visible outside of the module.
#
function H1 {[Element] @{Tag = "H1"; Text = $args.foreach{$_} -join " "}}
function H2 {[Element] @{Tag = "H2"; Text = $args.foreach{$_} -join " "}}
function H3 {[Element] @{Tag = "H3"; Text = $args.foreach{$_} -join " "}}
function P  {[Element] @{Tag = "P" ; Text = $args.foreach{$_} -join " "}}
function B  {[Element] @{Tag = "B" ; Text = $args.foreach{$_} -join " "}}
function I  {[Element] @{Tag = "I" ; Text = $args.foreach{$_} -join " "}}
function HREF
{
    param (
        $Name,
        $Link
    )

    return [Element] @{
        Tag = "A"
        Attributes = @{ HREF = $link }
        Text = $name
    }
}
function Table
{
    param (
        [Parameter(Mandatory)]
        [object[]]
            $Data,
        [Parameter()]
        [string[]]
            $Properties = "*",
        [Parameter()]
        [hashtable]
            $Attributes = @{ border=2; cellpadding=2; cellspacing=2 }
    )

    $bodyText = ""
    # Add the header tags
    $bodyText +=  $Properties.foreach{TH $_}
    # Add the rows
    $bodyText += foreach ($row in $Data)
                {
                            TR (-join $Properties.Foreach{ TD ($row.$_) } )
                }

    $table = [Element] @{
                Tag = "Table"
                Attributes = $Attributes
                Text = $bodyText
            }
    $table
}
function TH  {([Element] @{Tag="TH"; Text=$args.foreach{$_} -join " "})}
function TR  {([Element] @{Tag="TR"; Text=$args.foreach{$_} -join " "})}
function TD  {([Element] @{Tag="TD"; Text=$args.foreach{$_} -join " "})}

function Style
{
    return  [Element]  @{
        Tag = "style"
        Text = "$args"
    }
}

# Takes a hash table, casts it to and HTML document
# and then returns the resulting type.
#
function Html ([HTML] $doc) { return $doc }

Vedere anche

about_Enum

about_Hidden

about_Language_Keywords

about_Methods

creare risorse di Configurazione stato desiderate di PowerShell personalizzate