about_Classes_and_DSC

Kort beskrivning

Beskriver hur du kan använda klasser för att utveckla i PowerShell med Desired State Configuration (DSC).

Lång beskrivning

Från och med Windows PowerShell 5.0 lades språket till för att definiera klasser och andra användardefinierade typer med hjälp av formell syntax och semantik som liknar andra objektorienterade programmeringsspråk. Målet är att göra det möjligt för utvecklare och IT-proffs att använda PowerShell för ett bredare utbud av användningsfall, förenkla utvecklingen av PowerShell-artefakter som DSC-resurser och påskynda täckningen av hanteringsytor.

Scenarier som stöds

Följande scenarier stöds:

  • Definiera DSC-resurser och deras associerade typer med hjälp av PowerShell-språket.
  • Definiera anpassade typer i PowerShell med hjälp av välbekanta objektorienterade programmeringskonstruktioner, till exempel klasser, egenskaper, metoder och arv.
  • Felsöka typer med hjälp av PowerShell-språket.
  • Generera och hantera undantag med hjälp av formella mekanismer och på rätt nivå.

Definiera DSC-resurser med klasser

Förutom syntaxändringar är de största skillnaderna mellan en klassdefinierad DSC-resurs och en cmdlet DSC-resursprovider följande:

  • En MOF-fil (Management Object Format) krävs inte.
  • En DSCResource-undermapp i modulmappen krävs inte.
  • En PowerShell-modulfil kan innehålla flera DSC-resursklasser.

Skapa en klassdefinierad DSC-resursprovider

Följande exempel är en klassdefinierad DSC-resursprovider som sparas som en modul, MyDSCResource.psm1. Du måste alltid inkludera en nyckelegenskap i en klassdefinierad DSC-resursprovider.

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

Skapa ett modulmanifest

När du har skapat den klassdefinierade DSC-resursprovidern och sparat den som en modul skapar du ett modulmanifest för modulen. Om du vill göra en klassbaserad resurs tillgänglig för DSC-motorn måste du inkludera en DscResourcesToExport instruktion i manifestfilen som instruerar modulen att exportera resursen. I det här exemplet sparas följande modulmanifest som 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 = ''

}

Distribuera en DSC-resursprovider

Distribuera den nya DSC-resursprovidern genom att skapa en MyDscResource-mapp i $pshome\Modules eller $env:SystemDrive\ProgramFiles\WindowsPowerShell\Modules.

Du behöver inte skapa en DSCResource-undermapp. Kopiera modul- och modulmanifestfilerna (MyDscResource.psm1 och MyDscResource.psd1) till mappen MyDscResource.

Från och med nu skapar och kör du ett konfigurationsskript på samma sätt som med valfri DSC-resurs.

Skapa ett DSC-konfigurationsskript

När du har sparat klass- och manifestfilerna i mappstrukturen enligt beskrivningen ovan kan du skapa en konfiguration som använder den nya resursen. Följande konfiguration refererar till Modulen MyDSCResource. Spara konfigurationen som ett skript MyResource.ps1.

Information om hur du kör en DSC-konfiguration finns i Windows PowerShell Desired State Configuration Översikt.

Innan du kör konfigurationen skapar C:\test.txtdu . Konfigurationen kontrollerar om filen finns på c:\test\test.txt. Om filen inte finns kopierar konfigurationen filen från 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

Kör det här skriptet på samma sätt som med valfritt DSC-konfigurationsskript. Starta konfigurationen genom att köra följande i en upphöjd PowerShell-konsol:

PS C:\test> .\MyResource.ps1

Arv i PowerShell-klasser

Deklarera basklasser för PowerShell-klasser

Du kan deklarera en PowerShell-klass som bastyp för en annan PowerShell-klass, som du ser i följande exempel, där frukt är en bastyp för äpple.

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

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

Deklarera implementerade gränssnitt för PowerShell-klasser

Du kan deklarera implementerade gränssnitt efter bastyper eller omedelbart efter ett kolon (:) om ingen bastyp har angetts. Avgränsa alla typnamn med kommatecken. Detta liknar C#-syntax.

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

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

Anropa basklasskonstruktorer

Om du vill anropa en basklasskonstruktor från en underklass lägger du till nyckelordet base enligt följande exempel:

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

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

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

Om en basklass har en standardkonstruktor (inga parametrar) kan du utelämna ett explicit konstruktoranrop som visas.

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

Anropa basklassmetoder

Du kan åsidosätta befintliga metoder i underklasser. Om du vill göra åsidosättningen deklarerar du metoder med samma namn och signatur.

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

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

Om du vill anropa basklassmetoder från åsidosatta implementeringar omvandlar du till basklassen ([baseclass]$this) vid anrop.

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

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

Alla PowerShell-metoder är virtuella. Du kan dölja icke-virtuella .NET-metoder i en underklass med samma syntax som du gör för en åsidosättning: deklarera metoder med samma namn och signatur.

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

Aktuella begränsningar med klassarv

En begränsning med klassarv är att det inte finns någon syntax för att deklarera gränssnitt i PowerShell.

Definiera anpassade typer i PowerShell

Windows PowerShell 5.0 introducerade flera språkelement.

Klassnyckelord

Definierar en ny klass. Nyckelordet class är en sann .NET Framework typ. Klassmedlemmar är offentliga.

class MyClass
{
}

Uppräkningsnyckelord och uppräkningar

Stöd för nyckelordet enum har lagts till och är en icke-bakåtkompatibel ändring. Avgränsare enum är för närvarande en ny rad. En lösning för dem som redan använder enum är att infoga ett et-tecken (&) före ordet. Aktuella begränsningar: du kan inte definiera en uppräknare i sig själv, men du kan initiera enum i termer av en annan enum, som du ser i följande exempel:

Det går för närvarande inte att ange bastypen. Bastypen är alltid [int].

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

Ett uppräkningsvärde måste vara en parsningstidskonstant. Uppräkningsvärdet kan inte anges till resultatet av ett anropat kommando.

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

Enum stöder aritmetiska åtgärder, enligt följande exempel:

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

Dolt nyckelord

Nyckelordethidden, som introducerades i Windows PowerShell 5.0, döljer klassmedlemmar från standardresultatGet-Member. Ange den dolda egenskapen enligt följande rad:

hidden [type] $classmember = <value>

Dolda medlemmar visas inte med hjälp av tabbifyllning eller IntelliSense, såvida inte slutförandet sker i klassen som definierar den dolda medlemmen.

Ett nytt attribut, System.Management.Automation.HiddenAttribute, har lagts till så att C#-koden kan ha samma semantik i PowerShell.

Mer information finns i [about_Hidden[(/powershell/module/microsoft.powershell.core/about/about_hidden).

Import-DscResource

Import-DscResource är nu ett verkligt dynamiskt nyckelord. PowerShell parsar den angivna modulens rotmodul och söker efter klasser som innehåller attributet DscResource.

Egenskaper

Ett nytt fält, ImplementingAssembly, lades till i ModuleInfo. Om skriptet definierar klasser, eller om den inlästa sammansättningen för binära moduler är inställd på den dynamiska sammansättningen som skapats ImplementingAssembly för en skriptmodul. Den anges inte när ModuleType = Manifest.

Reflektion över fältet ImplementingAssembly identifierar resurser i en modul. Det innebär att du kan identifiera resurser som skrivits i PowerShell eller på andra hanterade språk.

Fält med initierare.

[int] $i = 5

Statisk stöds och fungerar som ett attribut som liknar typbegränsningarna, så det kan anges i valfri ordning.

static [int] $count = 0

En typ är valfri.

$s = "hello"

Alla medlemmar är offentliga. Egenskaper kräver antingen en ny linje eller semikolon. Om ingen objekttyp anges är egenskapstypen Objekt.

Konstruktorer och instansiering

PowerShell-klasser kan ha konstruktorer som har samma namn som klassen. Konstruktorer kan överbelastas. Statiska konstruktorer stöds. Egenskaper med initieringsuttryck initieras innan någon kod körs i en konstruktor. Statiska egenskaper initieras innan brödtexten i en statisk konstruktor och instansegenskaper initieras före brödtexten i den icke-statiska konstruktorn. För närvarande finns det ingen syntax för att anropa en konstruktor från en annan konstruktor, till exempel C#-syntaxen: ": this()"). Lösningen är att definiera en vanlig Init-metod.

Följande är sätt att instansiera klasser:

  • Instansiera med hjälp av standardkonstruktorn. Observera att New-Object stöds inte i den här versionen.

    $a = [MyClass]::new()

  • Anropa en konstruktor med en parameter.

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

  • Skicka en matris till en konstruktor med flera parametrar

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

För den här versionen är typnamnet endast synligt lexikalt, vilket innebär att det inte visas utanför modulen eller skriptet som definierar klassen. Funktioner kan returnera instanser av en klass som definierats i PowerShell, och instanser fungerar långt utanför modulen eller skriptet.

Den Get-Memberstatiska parametern visar konstruktorer, så att du kan visa överlagringar som vilken annan metod som helst. Prestandan för den här syntaxen är också betydligt snabbare än New-Object.

Den pseudostatiska metoden med namnet new fungerar med .NET-typer, som du ser i följande exempel. [hashtable]::new()

Nu kan du se konstruktoröverlagringar med Get-Member, eller som du ser i det här exemplet:

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

Metoder

En PowerShell-klassmetod implementeras som ett ScriptBlock som bara har ett slutblock. Alla metoder är offentliga. Nedan visas ett exempel på hur du definierar en metod med namnet DoSomething.

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

Metodanrop

Överlagrade metoder stöds. Överlagrade metoder namnges på samma sätt som en befintlig metod men särskiljs av deras angivna värden.

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

Åkallan

Se Metodanrop.

Attribut

Tre nya attribut har lagts till: DscResource, DscResourceKeyoch DscResourceMandatory.

Returtyper

Returtypen är ett kontrakt. Returvärdet konverteras till den förväntade typen. Om ingen returtyp anges ogiltigförklaras returtypen. Det finns ingen direktuppspelning av objekt och objekt kan inte skrivas till pipelinen avsiktligt eller av misstag.

Lexikal omfång för variabler

Nedan visas ett exempel på hur lexikal omfång fungerar i den här versionen.

$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

Exempel: Skapa anpassade klasser

I följande exempel skapas flera nya anpassade klasser för att implementera ett DSL (Dynamic Stylesheet Language) för HTML. Exemplet lägger till hjälpfunktioner för att skapa specifika elementtyper som en del av elementklassen, till exempel rubrikformat och tabeller, eftersom typer inte kan användas utanför omfånget för en modul.

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

Se även

about_Enum

about_Hidden

about_Language_Keywords

about_Methods

Skapa anpassade PowerShell-Desired State Configuration-resurser