about_Classes_and_DSC

Brève description

Décrit comment utiliser des classes pour développer dans PowerShell avec Desired State Configuration (DSC).

Description longue

À compter de Windows PowerShell 5.0, le langage a été ajouté pour définir des classes et d’autres types définis par l’utilisateur, à l’aide d’une syntaxe formelle et d’une sémantique similaire à d’autres langages de programmation orientés objet. L’objectif est de permettre aux développeurs et aux professionnels de l’informatique d’adopter PowerShell pour un large éventail de cas d’usage, de simplifier le développement d’artefacts PowerShell tels que les ressources DSC et d’accélérer la couverture des surfaces de gestion.

Scénarios pris en charge

Les scénarios suivants sont pris en charge :

  • Définissez les ressources DSC et leurs types associés à l’aide du langage PowerShell.
  • Définissez des types personnalisés dans PowerShell à l’aide de constructions de programmation familières orientées objet, telles que les classes, les propriétés, les méthodes et l’héritage.
  • Déboguer des types à l’aide du langage PowerShell.
  • Générez et gérez des exceptions à l’aide de mécanismes formels et au niveau approprié.

Définir des ressources DSC avec des classes

Outre les modifications de syntaxe, les principales différences entre une ressource DSC définie par une classe et un fournisseur de ressources DSC d’applet de commande sont les éléments suivants :

  • Un fichier MOF (Management Object Format) n’est pas obligatoire.
  • Un sous-dossier DSCResource dans le dossier du module n’est pas obligatoire.
  • Un fichier de module PowerShell peut contenir plusieurs classes de ressources DSC.

Créer un fournisseur de ressources DSC défini par une classe

L’exemple suivant est un fournisseur de ressources DSC défini par la classe qui est enregistré en tant que module, MyDSCResource.psm1. Vous devez toujours inclure une propriété de clé dans un fournisseur de ressources DSC défini par la 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
    }
}

Créer un manifeste de module

Après avoir créé le fournisseur de ressources DSC défini par la classe et l’avoir enregistré en tant que module, créez un manifeste de module pour le module. Pour rendre une ressource basée sur une classe disponible pour le moteur DSC, vous devez inclure une instruction DscResourcesToExport dans le fichier manifeste qui indique au module d’exporter la ressource. Dans cet exemple, le manifeste de module suivant est enregistré en tant que 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 = ''

}

Déployer un fournisseur de ressources DSC

Déployez le nouveau fournisseur de ressources DSC en créant un dossier MyDscResource dans $pshome\Modules ou $env:SystemDrive\ProgramFiles\WindowsPowerShell\Modules.

Vous n’avez pas besoin de créer un sous-dossier DSCResource. Copiez les fichiers de manifeste de module et de module (MyDscResource.psm1 et MyDscResource.psd1) dans le dossier MyDscResource.

À partir de ce stade, vous créez et exécutez un script de configuration comme vous le feriez avec n’importe quelle ressource DSC.

Créer un script de configuration DSC

Après avoir enregistré les fichiers de classe et de manifeste dans la structure de dossiers comme décrit précédemment, vous pouvez créer une configuration qui utilise la nouvelle ressource. La configuration suivante fait référence au module MyDSCResource. Enregistrez la configuration en tant que script, MyResource.ps1.

Pour plus d’informations sur l’exécution d’une configuration DSC, consultez Vue d’ensemble de windows PowerShell Desired State Configuration.

Avant d’exécuter la configuration, créez C:\test.txt. La configuration vérifie si le fichier existe à c:\test\test.txt. Si le fichier n’existe pas, la configuration copie le fichier à partir de 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

Exécutez ce script comme vous le feriez pour n’importe quel script de configuration DSC. Pour démarrer la configuration, dans une console PowerShell avec élévation de privilèges, exécutez ce qui suit :

PS C:\test> .\MyResource.ps1

Héritage dans les classes PowerShell

Déclarer des classes de base pour les classes PowerShell

Vous pouvez déclarer une classe PowerShell comme type de base pour une autre classe PowerShell, comme illustré dans l’exemple suivant, dans lequel fruit est un type de base pour apple.

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

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

Déclarer des interfaces implémentées pour les classes PowerShell

Vous pouvez déclarer des interfaces implémentées après les types de base, ou immédiatement après un signe deux-points (:) s’il n’existe aucun type de base spécifié. Séparez tous les noms de types à l’aide de virgules. Ceci est similaire à la syntaxe C#.

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

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

Appeler des constructeurs de classe de base

Pour appeler un constructeur de classe de base à partir d’une sous-classe, ajoutez le mot clé base, comme illustré dans l’exemple suivant :

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

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

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

Si une classe de base a un constructeur par défaut (aucun paramètre), vous pouvez omettre un appel de constructeur explicite, comme indiqué.

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

Appeler des méthodes de classe de base

Vous pouvez remplacer les méthodes existantes dans les sous-classes. Pour effectuer la substitution, déclarez des méthodes à l’aide du même nom et de la même signature.

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

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

Pour appeler des méthodes de classe de base à partir d’implémentations substituées, effectuez un cast vers la classe de base ([baseclass]$this) lors de l’appel.

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

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

Toutes les méthodes PowerShell sont virtuelles. Vous pouvez masquer les méthodes .NET non virtuelles dans une sous-classe à l’aide de la même syntaxe que pour une substitution : déclarez des méthodes portant le même nom et la même signature.

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

Limitations actuelles avec l’héritage de classe

Une limitation avec l’héritage de classe est qu’il n’existe aucune syntaxe pour déclarer des interfaces dans PowerShell.

Définition de types personnalisés dans PowerShell

Windows PowerShell 5.0 a introduit plusieurs éléments de langage.

Mot clé de classe

Définit une nouvelle classe. Le mot clé class est un type .NET Framework vrai. Les membres de classe sont publics.

class MyClass
{
}

Énumérations et mots clés enum

La prise en charge du mot clé enum a été ajoutée et constitue un changement cassant. Le délimiteur enum est actuellement une nouvelle ligne. Une solution de contournement pour ceux qui utilisent déjà enum consiste à insérer un ampersand (&) avant le mot. Limitations actuelles : vous ne pouvez pas définir d’énumérateur en termes de lui-même, mais vous pouvez initialiser enum en termes d’une autre enum, comme illustré dans l’exemple suivant :

Le type de base ne peut pas être spécifié. Le type de base est toujours [int].

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

Une valeur d’énumérateur doit être une constante d’analyse de temps. La valeur d’énumérateur ne peut pas être définie sur le résultat d’une commande appelée.

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

Enum prend en charge les opérations arithmétiques, comme illustré dans l’exemple suivant :

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

Mot clé masqué

Le mot clé hidden, introduit dans Windows PowerShell 5.0, masque les membres de classe des résultats par défaut Get-Member. Spécifiez la propriété masquée comme indiqué dans la ligne suivante :

hidden [type] $classmember = <value>

Les membres masqués ne sont pas affichés à l’aide de la saisie semi-automatique de tabulation ou d’IntelliSense, sauf si la saisie semi-automatique se produit dans la classe qui définit le membre masqué.

Un nouvel attribut, System.Management.Automation.HiddenAttribute, a été ajouté, afin que le code C# puisse avoir la même sémantique dans PowerShell.

Pour plus d’informations, consultez [about_Hidden[(/powershell/module/microsoft.powershell.core/about/about_hidden).

Import-DscResource

Import-DscResource est maintenant un vrai mot clé dynamique. PowerShell analyse le module racine du module spécifié, en recherchant les classes qui contiennent l’attribut DscResource.

Propriétés

Un nouveau champ, ImplementingAssembly, a été ajouté à ModuleInfo. Si le script définit des classes ou si l’assembly chargé pour les modules binaires ImplementingAssembly est défini sur l’assembly dynamique créé pour un module de script. Il n’est pas défini lorsque ModuleType = Manifest.

La réflexion sur le champ ImplementingAssembly découvre les ressources dans un module. Cela signifie que vous pouvez découvrir des ressources écrites dans PowerShell ou d’autres langages managés.

Champs avec initialiseurs.

[int] $i = 5

Static est pris en charge et fonctionne comme un attribut, similaire aux contraintes de type, afin qu’il puisse être spécifié dans n’importe quel ordre.

static [int] $count = 0

Un type est facultatif.

$s = "hello"

Tous les membres sont publics. Les propriétés nécessitent une nouvelle ligne ou un point-virgule. Si aucun type d’objet n’est spécifié, le type de propriété est Object.

Constructeurs et instanciation

Les classes PowerShell peuvent avoir des constructeurs qui ont le même nom que leur classe. Les constructeurs peuvent être surchargés. Les constructeurs statiques sont pris en charge. Les propriétés avec des expressions d’initialisation sont initialisées avant d’exécuter du code dans un constructeur. Les propriétés statiques sont initialisées avant le corps d’un constructeur statique et les propriétés d’instance sont initialisées avant le corps du constructeur non statique. Actuellement, il n’existe aucune syntaxe pour appeler un constructeur à partir d’un autre constructeur, tel que la syntaxe C# : ": this()"). La solution de contournement consiste à définir une méthode Init commune.

Voici les méthodes d’instanciation des classes :

  • Instanciation à l’aide du constructeur par défaut. Notez que New-Object n’est pas pris en charge dans cette version.

    $a = [MyClass]::new()

  • Appel d’un constructeur avec un paramètre.

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

  • Passage d’un tableau à un constructeur avec plusieurs paramètres

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

Pour cette version, le nom du type n’est visible que lexiquement, ce qui signifie qu’il n’est pas visible en dehors du module ou du script qui définit la classe. Les fonctions peuvent retourner des instances d’une classe définie dans PowerShell, et les instances fonctionnent bien en dehors du module ou du script.

Les constructeurs de listes de paramètres statiques peuvent donc afficher des surcharges comme n’importe quelle autre méthode. Les performances de cette syntaxe sont également considérablement plus rapides que New-Object.

La méthode pseudo-statique nommée nouvelle fonctionne avec les types .NET, comme illustré dans l’exemple suivant. [hashtable]::new()

Vous pouvez maintenant voir les surcharges de constructeur avec Get-Member, ou comme illustré dans cet exemple :

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

Méthode

Une méthode de classe PowerShell est implémentée en tant que ScriptBlock qui n’a qu’un bloc de fin. Toutes les méthodes sont publiques. L’exemple suivant montre comment définir une méthode nommée DoSomething.

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

Appel de méthode

Les méthodes surchargées sont prises en charge. Les méthodes surchargées sont nommées de la même façon qu’une méthode existante, mais différenciées par leurs valeurs spécifiées.

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

Invocation

Voir appel de méthode.

Attributs

Trois nouveaux attributs ont été ajoutés : DscResource, DscResourceKeyet DscResourceMandatory.

Types de retour

Le type de retour est un contrat. La valeur de retour est convertie en type attendu. Si aucun type de retour n’est spécifié, le type de retour est void. Il n’y a pas de streaming d’objets et d’objets qui ne peuvent pas être écrits dans le pipeline intentionnellement ou par accident.

Étendue lexicale des variables

L’exemple suivant montre comment fonctionne l’étendue lexicale dans cette version.

$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

Exemple : Créer des classes personnalisées

L’exemple suivant crée plusieurs classes personnalisées pour implémenter un langage de feuille de style dynamique HTML (DSL). L’exemple ajoute des fonctions d’assistance pour créer des types d’éléments spécifiques dans le cadre de la classe d’élément, telles que les styles de titre et les tables, car les types ne peuvent pas être utilisés en dehors de l’étendue d’un module.

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

Voir aussi

about_Enum

about_Hidden

about_Language_Keywords

about_Methods

créer des ressources de configuration d’état souhaité PowerShell personnalisées