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-Member
pará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
, DscResourceKey
y 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
crear recursos personalizados de configuración de estado deseado de PowerShell