Share via


about_Classes_and_DSC

Breve descrizione

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

Descrizione lunga

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

Scenari supportati

Sono supportati gli scenari indicati di seguito:

  • 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 basato su classe e averlo salvato 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 per indicare 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 del modulo e del manifesto del modulo (MyDscResource.psm1 e MyDscResource.psd1) nella cartella MyDscResource.

A questo punto, creare ed eseguire 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 di Windows PowerShell Desired State Configuration.

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 di 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 interfacce implementate dopo i tipi di base o immediatamente dopo i due punti (:) se non è specificato alcun tipo di base. Usare le virgole per separare tutti i nomi di tipo. 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 di classe base da una sottoclasse, aggiungere la base parola chiave , 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 esplicita al costruttore, come illustrato.

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

Chiamare i metodi della classe base

È possibile eseguire l'override di 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 ([baseclass]$this) base 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à della classe è 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 class parola chiave è un vero tipo .NET Framework. I membri della classe sono pubblici.

class MyClass
{
}

Parola chiave enum ed enumerazioni

È stato aggiunto il supporto per la enum parola chiave ed è una modifica che causa un'interruzione. Il enum delimitatore è attualmente una nuova riga. Una soluzione alternativa per coloro che già usano 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 di enumeratore deve essere una costante in fase 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 nascosta

La hidden parola chiave, introdotta in Windows PowerShell 5.0, nasconde i membri della classe dai risultati predefinitiGet-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 venga eseguito 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 all'interno di PowerShell.

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

Import-DscResource

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

Proprietà

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

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

Campi con inizializzatori.

[int] $i = 5

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

static [int] $count = 0

Il tipo è facoltativo.

$s = "hello"

Tutti i membri sono pubblici. Le proprietà richiedono un carattere di 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. I costruttori possono essere sottoposti a overload. Sono supportati i 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 una sintassi per chiamare un costruttore da un altro costruttore, ad esempio la sintassi C#: ": this()"). La soluzione consiste nel definire un metodo Init comune.

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

  • Creazione di un'istanza con 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 correttamente al di fuori del modulo o dello script.

Il Get-Member parametro Statico elenca i costruttori, in modo da poter visualizzare gli overload come qualsiasi altro metodo. Le prestazioni di questa sintassi sono anche notevolmente più veloci rispetto a New-Object.

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

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

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

Metodi

Un metodo della classe di PowerShell viene implementato come ScriptBlock con solo un blocco finale. Tutti i metodi sono pubblici. L'esempio seguente illustra la definizione di un metodo denominato DoSomething.

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

Chiamata al metodo

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

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

Chiamata

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 non può essere scritto nella pipeline intenzionalmente o accidentalmente.

Ambito lessicale delle variabili

Di seguito viene riportato un esempio di come funziona l'assegnazione 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 per implementare un linguaggio del foglio di stile dinamico HTML (DSL). 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 }

Vedi anche

about_Enum

about_Hidden

about_Language_Keywords

about_Methods

Compilare risorse personalizzate di PowerShell Desired State Configuration