Compartir a través de


about_Classes_and_DSC

Descripción breve

Describe cómo puede usar clases para desarrollar en PowerShell con Desired State Configuration (DSC).

Descripción larga

A partir de Windows PowerShell 5.0, el lenguaje se agregó para definir clases y otros tipos definidos por el usuario, mediante la sintaxis formal y la semántica similares a otros lenguajes de programación orientados a objetos. El objetivo es permitir a los desarrolladores y profesionales de TI adoptar PowerShell para una amplia gama de casos de uso, simplificar el desarrollo de artefactos de PowerShell, como recursos de DSC, y acelerar la cobertura de superficies de administración.

Escenarios admitidos

Se admiten los siguientes escenarios:

  • Defina los recursos de DSC y sus tipos asociados mediante el lenguaje de PowerShell.
  • Defina tipos personalizados en PowerShell mediante construcciones de programación conocidas orientadas a objetos, como clases, propiedades, métodos y herencia.
  • Depuración de tipos mediante el lenguaje de PowerShell.
  • Genere y controle excepciones mediante mecanismos formales y en el nivel correcto.

Definición de recursos de DSC con clases

Además de los cambios de sintaxis, las principales diferencias entre un recurso de DSC definido por la clase y un proveedor de recursos de DSC de cmdlet son los siguientes elementos:

  • No se requiere un archivo de formato de objeto de administración (MOF).
  • No se requiere una subcarpeta DSCResource en la carpeta del módulo.
  • Un archivo de módulo de PowerShell puede contener varias clases de recursos de DSC.

Creación de un proveedor de recursos de DSC definido por la clase

El ejemplo siguiente es un proveedor de recursos DSC definido por clase que se guarda como módulo, MyDSCResource.psm1. Siempre debe incluir una propiedad de clave en un proveedor de recursos de DSC definido por la clase.

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

Creación de un manifiesto de módulo

Después de crear el proveedor de recursos de DSC definido por la clase y guardarlo como módulo, cree un manifiesto de módulo para el módulo. Para que un recurso basado en clases esté disponible para el motor de DSC, debe incluir una instrucción DscResourcesToExport en el archivo de manifiesto que indique al módulo que exporte el recurso. En este ejemplo, el siguiente manifiesto de módulo se guarda como 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 = ''

}

Implementación de un proveedor de recursos de DSC

Implemente el nuevo proveedor de recursos de DSC mediante la creación de una carpeta MyDscResource en $pshome\Modules o $env:SystemDrive\ProgramFiles\WindowsPowerShell\Modules.

No es necesario crear una subcarpeta DSCResource. Copie los archivos de manifiesto de módulo y módulo (MyDscResource.psm1 y MyDscResource.psd1) en la carpeta MyDscResource.

Desde este punto, creará y ejecutará un script de configuración como lo haría con cualquier recurso de DSC.

Creación de un script de configuración de DSC

Después de guardar los archivos de clase y manifiesto en la estructura de carpetas como se ha descrito anteriormente, puede crear una configuración que use el nuevo recurso. La siguiente configuración hace referencia al módulo MyDSCResource. Guarde la configuración como un script MyResource.ps1.

Para obtener información sobre cómo ejecutar una configuración de DSC, consulte Introducción a la configuración de estado deseado de Windows PowerShell.

Antes de ejecutar la configuración, cree C:\test.txt. La configuración comprueba si el archivo existe en c:\test\test.txt. Si el archivo no existe, la configuración copia el archivo 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

Ejecute este script como lo haría con cualquier script de configuración de DSC. Para iniciar la configuración, en una consola de PowerShell con privilegios elevados, ejecute lo siguiente:

PS C:\test> .\MyResource.ps1

Herencia en clases de PowerShell

Declarar clases base para clases de PowerShell

Puede declarar una clase de PowerShell como un tipo base para otra clase de PowerShell, como se muestra en el ejemplo siguiente, en el que fruta es un tipo base para manzana.

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

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

Declarar interfaces implementadas para clases de PowerShell

Puede declarar interfaces implementadas después de los tipos base o inmediatamente después de dos puntos (:) si no hay ningún tipo base especificado. Separe todos los nombres de tipo mediante comas. Esto es similar a la sintaxis de C#.

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

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

Llamar a constructores de clase base

Para llamar a un constructor de clase base desde una subclase, agregue la palabra clave base, como se muestra en el ejemplo siguiente:

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

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

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

Si una clase base tiene un constructor predeterminado (sin parámetros), puede omitir una llamada de constructor explícita, como se muestra.

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

Llamar a métodos de clase base

Puede invalidar los métodos existentes en subclases. Para realizar la invalidación, declare métodos mediante el mismo nombre y la misma firma.

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

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

Para llamar a métodos de clase base desde implementaciones invalidados, convierta a la clase base ([baseclass]$this) en la invocación.

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

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

Todos los métodos de PowerShell son virtuales. Puede ocultar métodos .NET que no son virtuales en una subclase mediante la misma sintaxis que para una invalidación: declare métodos con el mismo nombre y 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

Limitaciones actuales con herencia de clases

Una limitación con la herencia de clases es que no hay sintaxis para declarar interfaces en PowerShell.

Definición de tipos personalizados en PowerShell

Windows PowerShell 5.0 introdujo varios elementos de lenguaje.

Palabra clave Class

Define una nueva clase. La palabra clave class es un tipo true de .NET Framework. Los miembros de clase son públicos.

class MyClass
{
}

Enumeración y palabra clave enum

Se ha agregado compatibilidad con la palabra clave enum y es un cambio importante. El delimitador de enum es actualmente una nueva línea. Una solución alternativa para aquellos que ya usan enum es insertar una y comercial (&) antes de la palabra. Limitaciones actuales: no puede definir un enumerador en términos propios, pero puede inicializar enum en términos de otra enum, como se muestra en el ejemplo siguiente:

El tipo base no se puede especificar actualmente. El tipo base siempre es [int].

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

Un valor del enumerador debe ser una constante de tiempo de análisis. El valor del enumerador no se puede establecer en el resultado de un comando invocado.

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

Enum admite operaciones aritméticas, como se muestra en el ejemplo siguiente:

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

Palabra clave Hidden

La palabra clave hidden, introducida en Windows PowerShell 5.0, oculta los miembros de clase de los resultados de Get-Member predeterminados. Especifique la propiedad oculta como se muestra en la siguiente línea:

hidden [type] $classmember = <value>

Los miembros ocultos no se muestran mediante la finalización de tabulación o IntelliSense, a menos que se produzca la finalización en la clase que define el miembro oculto.

Se agregó un nuevo atributo, System.Management.Automation.HiddenAttribute, para que el código de C# pueda tener la misma semántica dentro de PowerShell.

Para obtener más información, consulte [about_Hidden[(/powershell/module/microsoft.powershell.core/about/about_hidden).

Import-DscResource

Import-DscResource ahora es una palabra clave dinámica verdadera. PowerShell analiza el módulo raíz del módulo especificado y busca clases que contienen el atributo DscResource.

Propiedades

Se agregó un nuevo campo, ImplementingAssembly, a ModuleInfo. Si el script define clases o el ensamblado cargado para los módulos binarios ImplementingAssembly se establece en el ensamblado dinámico creado para un módulo de script. No se establece cuando ModuleType = Manifest.

La reflexión en el campo ImplementingAssembly detecta recursos en un módulo. Esto significa que puede detectar recursos escritos en PowerShell u otros lenguajes administrados.

Campos con inicializadores.

[int] $i = 5

Static se admite y funciona como un atributo, similar a las restricciones de tipo, por lo que se puede especificar en cualquier orden.

static [int] $count = 0

Un tipo es opcional.

$s = "hello"

Todos los miembros son públicos. Las propiedades requieren una nueva línea o punto y coma. Si no se especifica ningún tipo de objeto, el tipo de propiedad es Object.

Constructores y creación de instancias

Las clases de PowerShell pueden tener constructores que tengan el mismo nombre que su clase. Los constructores se pueden sobrecargar. Se admiten constructores estáticos. Las propiedades con expresiones de inicialización se inicializan antes de ejecutar cualquier código en un constructor. Las propiedades estáticas se inicializan antes del cuerpo de un constructor estático y las propiedades de instancia se inicializan antes del cuerpo del constructor no estático. Actualmente, no hay ninguna sintaxis para llamar a un constructor desde otro constructor, como la sintaxis de C#: ": this()"). La solución consiste en definir un método Init común.

Las siguientes son formas de crear instancias de clases:

  • Creación de instancias mediante el constructor predeterminado. Tenga en cuenta que New-Object no se admite en esta versión.

    $a = [MyClass]::new()

  • Llamada a un constructor con un parámetro .

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

  • Pasar una matriz a un constructor con varios parámetros

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

Para esta versión, el nombre de tipo solo es visible léxicamente, lo que significa que no está visible fuera del módulo o script que define la clase. Las funciones pueden devolver instancias de una clase definida en PowerShell y las instancias funcionan bien fuera del módulo o script.

El Get-Memberparámetro Static enumera constructores, por lo que puede ver sobrecargas como cualquier otro método. El rendimiento de esta sintaxis también es considerablemente más rápido que New-Object.

El método pseudo-estático denominado nuevo funciona con tipos de .NET, como se muestra en el ejemplo siguiente. [hashtable]::new()

Ahora puede ver sobrecargas de constructor con Get-Member, o como se muestra en este ejemplo:

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

Métodos

Un método de clase de PowerShell se implementa como un scriptBlock que solo tiene un bloque final. Todos los métodos son públicos. A continuación se muestra un ejemplo de definición de un método denominado DoSomething.

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

Invocación de método

Se admiten métodos sobrecargados. Los métodos sobrecargados se denominan igual que un método existente, pero se diferencian por sus valores especificados.

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

Invocación

Consulte invocación de método.

Atributos

Se agregaron tres atributos nuevos: DscResource, DscResourceKeyy DscResourceMandatory.

Tipos devueltos

El tipo de valor devuelto es un contrato. El valor devuelto se convierte en el tipo esperado. Si no se especifica ningún tipo de valor devuelto, el tipo de valor devuelto es void. No se puede escribir ningún streaming de objetos y objetos en la canalización intencionadamente o por accidente.

Ámbito léxico de variables

A continuación se muestra un ejemplo de cómo funciona el ámbito léxico en esta versión.

$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

Ejemplo: Creación de clases personalizadas

En el ejemplo siguiente se crean varias clases personalizadas nuevas para implementar un lenguaje de hoja de estilos dinámicos (DSL) HTML. En el ejemplo se agregan funciones auxiliares para crear tipos de elementos específicos como parte de la clase de elemento, como estilos de encabezado y tablas, ya que los tipos no se pueden usar fuera del ámbito de un módulo.

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

Consulte también

about_Enum

about_Hidden

about_Language_Keywords

about_Methods

crear recursos personalizados de configuración de estado deseado de PowerShell