about_Classes_and_DSC

Krótki opis

Opisuje sposób tworzenia klas w programie PowerShell przy użyciu Desired State Configuration (DSC).

Długi opis

Począwszy od Windows PowerShell 5.0, język został dodany do definiowania klas i innych typów zdefiniowanych przez użytkownika przy użyciu składni formalnej i semantyki, które są podobne do innych języków programowania obiektowego. Celem jest umożliwienie deweloperom i specjalistom IT wdrożenia programu PowerShell w szerszym zakresie przypadków użycia, uproszczenie opracowywania artefaktów programu PowerShell, takich jak zasoby DSC i przyspieszenie pokrycia powierzchni zarządzania.

Obsługiwane scenariusze

Obsługiwane są następujące scenariusze:

  • Zdefiniuj zasoby DSC i skojarzone z nimi typy przy użyciu języka programu PowerShell.
  • Zdefiniuj typy niestandardowe w programie PowerShell przy użyciu znanych konstrukcji programowania zorientowanych na obiekty, takich jak klasy, właściwości, metody i dziedziczenie.
  • Debugowanie typów przy użyciu języka programu PowerShell.
  • Generowanie i obsługa wyjątków przy użyciu mechanizmów formalnych i na odpowiednim poziomie.

Definiowanie zasobów DSC przy użyciu klas

Oprócz zmian składni główne różnice między zasobem DSC zdefiniowanym przez klasę a dostawcą zasobów DSC poleceń cmdlet to następujące elementy:

  • Plik MOF (Management Object Format) nie jest wymagany.
  • Podfolder DSCResource w folderze modułu nie jest wymagany.
  • Plik modułu programu PowerShell może zawierać wiele klas zasobów DSC.

Tworzenie dostawcy zasobów DSC zdefiniowanego przez klasę

Poniższy przykład to dostawca zasobów DSC zdefiniowany przez klasę, który jest zapisywany jako moduł MyDSCResource.psm1. Zawsze należy uwzględnić właściwość klucza u dostawcy zasobów DSC zdefiniowanego przez klasę.

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

Tworzenie manifestu modułu

Po utworzeniu dostawcy zasobów DSC zdefiniowanego przez klasę i zapisaniu go jako modułu utwórz manifest modułu dla modułu. Aby udostępnić zasób oparty na klasie aparatowi DSC, należy dołączyć instrukcję DscResourcesToExport w pliku manifestu, który nakazuje modułowi wyeksportowanie zasobu. W tym przykładzie następujący manifest modułu jest zapisywany jako 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 = ''

}

Wdrażanie dostawcy zasobów DSC

Wdróż nowego dostawcę zasobów DSC, tworząc folder MyDscResource w systemie $pshome\Modules lub $env:SystemDrive\ProgramFiles\WindowsPowerShell\Modules.

Nie trzeba tworzyć podfolderu DSCResource. Skopiuj pliki manifestu modułu i modułu (MyDscResource.psm1 i MyDscResource.psd1) do folderu MyDscResource.

W tym momencie utworzysz i uruchomisz skrypt konfiguracji, tak jak w przypadku dowolnego zasobu DSC.

Tworzenie skryptu konfiguracji DSC

Po zapisaniu plików klasy i manifestu w strukturze folderów zgodnie z wcześniejszym opisem można utworzyć konfigurację, która używa nowego zasobu. Poniższa konfiguracja odwołuje się do modułu MyDSCResource. Zapisz konfigurację jako skrypt, MyResource.ps1.

Aby uzyskać informacje o sposobie uruchamiania konfiguracji DSC, zobacz Windows PowerShell Desired State Configuration Omówienie.

Przed uruchomieniem konfiguracji utwórz C:\test.txtplik . Konfiguracja sprawdza, czy plik istnieje w lokalizacji c:\test\test.txt. Jeśli plik nie istnieje, konfiguracja kopiuje plik z C:\test.txtpliku .

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

Uruchom ten skrypt, tak jak każdy skrypt konfiguracji DSC. Aby uruchomić konfigurację, w konsoli programu PowerShell z podwyższonym poziomem uprawnień uruchom następujące polecenie:

PS C:\test> .\MyResource.ps1

Dziedziczenie w klasach programu PowerShell

Deklarowanie klas bazowych dla klas programu PowerShell

Klasę programu PowerShell można zadeklarować jako typ podstawowy dla innej klasy programu PowerShell, jak pokazano w poniższym przykładzie, w którym owoce są typem podstawowym dla jabłek.

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

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

Deklarowanie wdrożonych interfejsów dla klas programu PowerShell

Zaimplementowane interfejsy można zadeklarować po typach podstawowych lub bezpośrednio po dwukropku (:), jeśli nie określono typu podstawowego. Rozdziel wszystkie nazwy typów przy użyciu przecinków. Jest to podobne do składni języka C#.

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

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

Wywoływanie konstruktorów klasy bazowej

Aby wywołać konstruktor klasy bazowej z podklasy, dodaj base słowo kluczowe, jak pokazano w poniższym przykładzie:

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

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

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

Jeśli klasa bazowa ma konstruktor domyślny (bez parametrów), można pominąć jawne wywołanie konstruktora, jak pokazano.

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

Wywoływanie metod klasy bazowej

Istniejące metody można zastąpić w podklasach. Aby przesłonić, zadeklaruj metody przy użyciu tej samej nazwy i podpisu.

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

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

Aby wywołać metody klasy bazowej z przesłonięć implementacje, rzutuj do klasy ([baseclass]$this) bazowej przy wywołaniu.

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

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

Wszystkie metody programu PowerShell są wirtualne. Metody platformy .NET niewirtualnej można ukryć w podklasie przy użyciu tej samej składni, co w przypadku przesłonięcia: deklaruj metody o tej samej nazwie i podpisie.

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

Bieżące ograniczenia dotyczące dziedziczenia klas

Ograniczenie dziedziczenia klasy polega na tym, że nie ma składni do deklarowania interfejsów w programie PowerShell.

Definiowanie typów niestandardowych w programie PowerShell

Windows PowerShell 5.0 wprowadzono kilka elementów języka.

Słowo kluczowe klasy

Definiuje nową klasę. Słowo class kluczowe jest prawdziwym typem .NET Framework. Elementy członkowskie klasy są publiczne.

class MyClass
{
}

Wyliczenia i słowa kluczowe wyliczenia

Dodano obsługę słowa kluczowego enum i jest to zmiana powodująca niezgodność. Ogranicznik enum jest obecnie nowym wierszem. Obejściem dla tych, którzy już używają enum , jest wstawienie znaku ampersand (&) przed wyrazem. Bieżące ograniczenia: nie można zdefiniować modułu wyliczającego pod względem siebie, ale można zainicjować enum pod względem innego enumelementu , jak pokazano w poniższym przykładzie:

Nie można obecnie określić typu podstawowego. Typ podstawowy to zawsze [int].

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

Wartość modułu wyliczającego musi być stałą czasu analizy. Nie można ustawić wartości modułu wyliczającego na wynik wywołanego polecenia.

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

Enum obsługuje operacje arytmetyczne, jak pokazano w poniższym przykładzie:

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

Ukryte słowo kluczowe

Słowo hidden kluczowe wprowadzone w Windows PowerShell 5.0 ukrywa elementy członkowskie klasy przed domyślnymi Get-Member wynikami. Określ właściwość ukrytą, jak pokazano w następującym wierszu:

hidden [type] $classmember = <value>

Ukryte elementy członkowskie nie są wyświetlane przy użyciu uzupełniania karty lub funkcji IntelliSense, chyba że ukończenie występuje w klasie definiującej ukryty element członkowski.

Dodano nowy atrybut System.Management.Automation.HiddenAttribute, aby kod języka C# mógł mieć te same semantyki w programie PowerShell.

Aby uzyskać więcej informacji, zobacz [about_Hidden[(/powershell/module/microsoft.powershell.core/about/about_hidden).

Import-DscResource

Import-DscResource jest teraz prawdziwym dynamicznym słowem kluczowym. Program PowerShell analizuje moduł główny określonego modułu, wyszukując klasy zawierające atrybut DscResource.

Właściwości

Nowe pole, ImplementingAssembly, zostało dodane do ModuleInfo. Jeśli skrypt definiuje klasy lub załadowany zestaw dla modułów ImplementingAssembly binarnych jest ustawiony na zestaw dynamiczny utworzony dla modułu skryptu. Nie jest ustawiana, gdy moduleType = Manifest.

Odbicie w ImplementingAssembly polu odnajduje zasoby w module. Oznacza to, że można odnajdywać zasoby napisane w programie PowerShell lub w innych językach zarządzanych.

Pola z inicjatorami.

[int] $i = 5

Statyczny jest obsługiwany i działa jak atrybut, podobny do ograniczeń typu, dzięki czemu można go określić w dowolnej kolejności.

static [int] $count = 0

Typ jest opcjonalny.

$s = "hello"

Wszyscy członkowie są publiczni. Właściwości wymagają nowego wiersza lub średnika. Jeśli nie określono typu obiektu, typ właściwości to Obiekt.

Konstruktory i wystąpienia

Klasy programu PowerShell mogą mieć konstruktory, które mają taką samą nazwę jak ich klasa. Konstruktory mogą być przeciążone. Konstruktory statyczne są obsługiwane. Właściwości z wyrażeniami inicjowania są inicjowane przed uruchomieniem dowolnego kodu w konstruktorze. Właściwości statyczne są inicjowane przed treścią konstruktora statycznego, a właściwości wystąpienia są inicjowane przed treścią konstruktora niestatycznego. Obecnie nie ma składni wywoływania konstruktora z innego konstruktora, takiego jak składnia języka C#: ": this()"). Obejście polega na zdefiniowaniu typowej metody Init.

Poniżej przedstawiono sposoby tworzenia wystąpień klas:

  • Utworzenie wystąpienia przy użyciu konstruktora domyślnego. Należy pamiętać, że New-Object ta wersja nie jest obsługiwana.

    $a = [MyClass]::new()

  • Wywoływanie konstruktora za pomocą parametru.

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

  • Przekazywanie tablicy do konstruktora z wieloma parametrami

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

W tej wersji nazwa typu jest widoczna tylko leksykalnie, co oznacza, że nie jest widoczna poza modułem ani skryptem definiującym klasę. Funkcje mogą zwracać wystąpienia klasy zdefiniowanej w programie PowerShell, a wystąpienia działają dobrze poza modułem lub skryptem.

Parametr Get-Memberstatyczny zawiera listę konstruktorów, dzięki czemu można wyświetlać przeciążenia, takie jak każda inna metoda. Wydajność tej składni jest również znacznie szybsza niż New-Object.

Metoda pseudostatyczna o nazwie new działa z typami platformy .NET, jak pokazano w poniższym przykładzie. [hashtable]::new()

Teraz można zobaczyć przeciążenia konstruktora z wartością Get-Memberlub, jak pokazano w tym przykładzie:

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

Metody

Metoda klasy programu PowerShell jest implementowana jako scriptBlock , która ma tylko blok końcowy. Wszystkie metody są publiczne. Poniżej przedstawiono przykład definiowania metody o nazwie DoSomething.

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

Wywołanie metody

Metody przeciążone są obsługiwane. Metody przeciążone mają taką samą nazwę jak istniejąca metoda, ale różnią się od ich określonych wartości.

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

Invocation

Zobacz Wywołanie metody.

Atrybuty

Dodano trzy nowe atrybuty: DscResource, DscResourceKeyi DscResourceMandatory.

Typy zwracane

Zwracany typ to kontrakt. Zwracana wartość jest konwertowana na oczekiwany typ. Jeśli nie określono typu zwracanego, zwracany typ jest void. Nie można zapisywać strumieniowo obiektów i obiektów do potoku celowo lub przypadkowo.

Określanie zakresu zmiennych leksykalnych

Poniżej przedstawiono przykład działania zakresu leksykalnego w tej wersji.

$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

Przykład: Tworzenie klas niestandardowych

Poniższy przykład tworzy kilka nowych, niestandardowych klas w celu zaimplementowania języka HTML Dynamic Stylesheet Language (DSL). W przykładzie dodano funkcje pomocnicze do tworzenia określonych typów elementów w ramach klasy elementów, takich jak style nagłówków i tabele, ponieważ typy nie mogą być używane poza zakresem modułu.

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

Zobacz też

about_Enum

about_Hidden

about_Language_Keywords

about_Methods

Tworzenie niestandardowych zasobów programu PowerShell Desired State Configuration