Clases
Introducción
Las clases han estado disponibles desde la versión 5.0 de PowerShell. Se han integrado en gran medida para DSC. Su uso en DSC reduce la interacción con archivos de tipo MOF (véase el capítulo Desired State Configuration (DSC)).
La noción de clase puede parecer totalmente abstracta. En realidad, con PowerShell, usted trabaja con clases sin darse cuenta. O al menos, interactúa con objetos instanciados a partir de clases del framework .NET o proporcionados por terceros. Es muy importante comprender el concepto de objeto antes de continuar.
Tenga en cuenta que este capítulo no pretende ser un curso de programación orientada a objetos (POO).
Creación de una clase
Una clase sirve para definir la estructura de un objeto. En cierto modo, es su esqueleto. Define los miembros de un objeto, incluidas las propiedades y los métodos.
1. Palabra clave Class
Al igual que la declaración de una función o workflow, la declaración de una clase se realiza mediante una palabra clave: Class. El nombre de una clase debe ser lo más representativo posible de su funcionamiento. Es recomendable prestar atención al nombre que se utilice para evitar confusiones con otras clases.
Para evitar este tipo de problemas, las clases se agrupan bajo el mismo espacio de nombres. Ejemplo: <Nombre de agrupación de clases>.<Nombre de clase>.
Sintaxis
Class <NombreDeMiClase> {
[Type]<Miembro1>
[Type]<Miembro2>
[Type]<Miembro3>
...
}
Es probable que el concepto de miembro ya le resulte familiar, ya que es la información que muestra el comando Get-Member. A diferencia de C#, los únicos tipos de miembros que se pueden definir son las propiedades y los métodos. Además, todos los miembros son públicos. La definición de miembros privadso aún no está implementada en PowerShell (versión 5.1). También puede enumerar los miembros de una clase mediante la siguiente sintaxis: [Miclase].GetMembers().
Ejemplo de clase
PS > Class Autor {
$Name
$Email
$Age
$PostalCode
}
Se ha creado la clase [Autor]. Todos sus miembros son propiedades.
2. Propiedad
Una propiedad se compone en realidad de tres elementos: un campo y dos métodos. El campo contiene el valor. El campo que almacena el valor no puede definirse de forma independiente, como sí ocurre en C#. Los métodos de acceso (getter) y de modificación (setter) se utilizan respectivamente para leer y escribir el valor contenido en el campo.
En PowerShell, aún no es posible personalizar el comportamiento de estos dos métodos. Tampoco es posible eliminar el método modificador y, por tanto, declarar una propiedad de solo lectura. Sin embargo, se puede encontrar una solución alternativa utilizando...
Sobrecarga de métodos (overload)
La sobrecarga de métodos (overload en inglés) permite definir un conjunto de métodos con el mismo nombre. Esto puede parecer un poco confuso, pero hay una explicación bastante sencilla para este proceso. Cada método tiene una firma. Esta firma se define en función de los parámetros del método. El número, el orden y, sobre todo, el tipo de parámetros determinan la firma.
Ejemplo de sobrecarga de un nuevo método GetPostalCode()
PS > Class Autor {
...
[String]GetPostalCode([int]$PostalCode){
if ($PostalCode -eq 44000){
Return 'Nantes'
}else{
Return 'Desconocido'
}
}
[int]GetPostalCode([string]$PostalCode){
if ($PostalCode -eq 'Nantes'){
Return 44000 ...
Estática
Las clases tienen una opción estática sobre sus miembros, identificada por la palabra clave static, situada antes del miembro.
Sintaxis
Clase <NombredeMiClase> {
static [Type]<Miembro1>
static [Type]<Miembro2>
...
}
Esta característica garantiza que el miembro esté disponible sin necesidad de instanciar el objeto y, por tanto, de llamar a un constructor.
Las sintaxis para utilizar miembros estáticos son las siguientes:
[MiClase]::<MiembroEstático>
$MiObjeto::<MiembroEstático>
Los miembros estáticos se encuentran en un gran número de clases del sistema.
Ejemplo de clase conocida
PS > [DateTime]::Now
sábado 15 octubre 2016 19:47:04
Ejemplo de integración de un miembro estático
PS > Class Autor {
...
Static $Edition = 'ExpertIT'
}
PS > [Autor]::Edition
ExpertIT
PS > $Me = [Autor]::New(25)
PS > $Me::Edition
ExpertIT
Preste especial atención a la sintaxis con ::, y no al punto simple (.). Si ha comprendido bien los principios anteriores comprenderá fácilmente la lógica detrás del uso de la variable automática...
Ámbito de las variables
Antes de seguir adelante, es importante conocer bien los conceptos básicos sobre los ámbitos de PowerShell.
Un recordatorio rápido:
-
Ámbito global: este ámbito se crea al iniciar una sesión de PowerShell. Las variables definidas en él pueden ser leídas por todos los ámbitos hijos. Para modificar una variable global en un ámbito hijo, se debe utilizar la siguiente sintaxis: $global:<mi_variable>.
-
Ámbito de script: el ámbito de script se refiere al ámbito que se crea al ejecutar un script. Una vez finalizado el script, las variables definidas en él dejan de existir. Cuando se trabaja desde la consola, el ámbito del script equivale al ámbito global. Para modificar una variable de script en un ámbito hijo, utilice la siguiente sintaxis: $script:<mi_variable>.
-
Ámbito local: corresponde al contexto actual, como una función, un bloque de script o un script en ejecución. Se pueden utilizar dos sintaxis diferentes: $local:<mi_variable>, $<mi_variable>. La segunda sintaxis es más habitual.
En nuestra situación con las clases, solo es necesario tener en cuenta estos tres ámbitos. No se recomienda utilizar estas sintaxis en un script, a menos que sepa exactamente lo que está haciendo. Si necesita manipular un objeto dentro de una propiedad o método, lo más adecuado es utilizar parámetros en métodos o constructores.
1. Propiedad
Dentro de una propiedad, es posible acceder a una variable definida en el mismo ámbito en el que se declara la clase, lo cual no ocurre con los métodos.
Ejemplo
PS > $Var = 0
PS > Class Scoping {
$Prop = $Var
Static $StaticProp = $Var
}
PS > $Sco = [Scoping]::New()
PS > [Scoping]::StaticProp
0
PS > $Sco.Prop
0
¿Qué ocurre si se modifica la variable $Var? La siguiente llamada al constructor utilizará el nuevo valor de la variable al establecer la propiedad Prop. Sin embargo, la propiedad estática conservará su valor original. Esto se debe a que el valor de una propiedad estática se establece durante...
Herencia
La herencia se utiliza para declarar una clase base que luego utilizan las clases derivadas. Esta mecanismo permite que la clase derivada herede todos los miembros de la clase base, especialmente los métodos.
Sintaxis
PS > Class <BaseClass>{
<Property1>
<Property2>
<Methode1>
<BaseClass>($Parameter1,$Parameter2){}
}
PS > Class <DeriClass> : <BaseClass> {
<otherProperty3>
<DeriClass>($Parameter1,$Parameter2):base($Parameter1,$Parameter2){}
}
La declaración de la clase derivada incluye su nombre y el de la clase base, separados por el carácter :. No es posible declarar varias clases base para una clase derivada:
PS > Class <DeriClass> : <BaseClass>,<BaseClase2>
A continuación, hay que declarar el constructor de la clase derivada. Para ello, entre la declaración de parámetros e instrucciones, es necesario añadir la palabra clave base, también precedida del carácter :. Le siguen los parámetros del constructor de la clase base.
Si la clase base utiliza el constructor predeterminado de PowerShell, o si se declara un constructor sin parámetros, entonces...
Creación Enum
PowerShell V5 implementa la noción de enumeración, palabra clave enum. Antes de PowerShell 5, era necesario utilizar el cmdlet Add-Type junto con código en C# para declarar enumeraciones.
Las enumeraciones pueden utilizarse para restringir los valores que puede contener una variable. De forma similar a lo que permite el atributo de validación ValidateSet() en un parámetro.
1. Palabra clave enum
Sintaxis de la palabra clave enum:
PS > enum <NombreDeLaEnumeración> {
<NombreValor1>
<NombreValor2>
<NombreValor3>
...
}
Ejemplo
PS > enum CódigoLibro {
Recurso Informático
ExpertIT
Epsilon
}
PS > [CódigoLibro]$Estelibro = 'ExpertIT'
Si especifica una cadena de caracteres distinta de los nombres de los valores contenidos en la enumeración [CódigoLibro], aparecerá un error.
Los nombres de los valores no pueden empezar por un número.
Las enumeraciones son conjuntos de constantes que empiezan por 0. Permiten definir una variable en función de un valor y no de un nombre:
PS > [CódigoLibro]$Estelibro = 1
PS > $Estelibro
ExpertIT...
Formato
Las clases en PowerShell reaccionan de la misma manera que las clases en el framework .NET para dar formato. El proceso se describió en la sección anterior de este capítulo. Solo es necesario aplicar el mismo principio, adaptado a las clases definidas en PowerShell. En primer lugar, cree un archivo format.ps1xml. En el nombre del tipo, especificamos la clase que queremos asignarle. Por último, aplicamos el cambio mediante el comando Update-FormatData.
Integración en un módulo
Es probable que las clases tengan un destino más amplio que un simple script PowerShell. Por ello, resulta totalmente adecuado integrarlas en un módulo para facilitar su reutilización y distribución.
Los módulos constan de un manifiesto en formato PSD1 y un archivo de módulo en formato PSM1. En este caso, solo se necesita el archivo de manifiesto. El campo ScriptToProcess se utiliza para cargar la clase contenida en un archivo PS1.
Organización del módulo TestClass:
└─TestClass
├─ Classes
└─ myclass.ps1
└─ TestClass.psd1
El archivo myclass.ps1 contiene la clase [Computer] del ejemplo anterior. Para el archivo de manifiesto, rellenamos el campo ScriptToProcess de la siguiente manera:
ScriptsToProcess = @('Class/myclass.ps1')
Por último, el módulo se coloca en un directorio donde se descubre e importa automáticamente mediante el comando Import-Module:
PS > Import-Module TestClass
Por último, probamos la instanciación de la clase [Computer]:
PS > [Computer]::New('PC-Generic','Windows')
Name OS
---- --
PC-Generic Windows
La clase puede ahora...
Palabra clave Using
La instrucción using ya se trató anteriormente, cuando se explicó cómo colocar clases en un módulo. También se utiliza para facilitar la llamada a clases presentes en los namespaces. Pero, ¿qué es un namespace?
Es una estructura jerárquica que organiza las clases de acuerdo con una utilidad/temática en particular.
Por ejemplo, la clase Directory designa una carpeta, y esta clase está contenida en el espacio de nombres system.IO. Este último también contiene la clase File, que designa archivos.
Cuando se llama a una clase del framework .NET en PowerShell, el namespace y el nombre de la clase aparecen entre corchetes.
Por ejemplo, crear un directorio utilizando la clase Directory:
PS > [System.IO.Directory]::CreateDirectory('C:\Temps')
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 5/17/2023 6:25 PM Temps...
Uso de clases en la validación de parámetros
En el capítulo Funciones Avanzadas, vimos en detalle cómo los parámetros pueden ser validados de diferentes maneras. Ahora que hemos cubierto la noción de clases, podemos añadir a estos elementos mediante el uso de clases.
1. ValidateSet Dinámico
El atributo de validación [ValidateSet()] limita los valores aceptables para el parámetro. En el capítulo Funciones avanzadas, los valores pasados al atributo ValidateSet estaban contenidos en una colección fija definida en la función. Para cambiar estos valores será necesario modificar la definición de la función.
¿Qué debemos hacer si necesitamos valores dinámicos en lugar de fijos? ¿Usar el atributo ArgumentCompleter?
Esto son solo sugerencias, el usuario puede proporcionar valores distintos a los sugeridos. ¿Utilizar parámetros dinámicos? Sí, es una posibilidad, pero también tiene importantes desventajas.
La otra solución propuesta aquí es utilizar clases para asignar dinámicamente valores a [ValidateSet()]. Esta funcionalidad está disponible únicamente a partir de PowerShell 7.
Para el ejemplo, se utilizará la función Restart-ServiceRunning. Esta función reinicia solo los servicios en estado de Running. Esta es la función:
function Restart-ServiceRunning {
[CmdletBinding()]
Param (
[string[]]$Name
)
Restart-Service @PSBoundParameters
}
En este ejemplo, la variable $PSBoundParameters y la técnica de splatting se utilizan para pasar valores de parámetros directamente al comando Restart-Service. Para obtener más información sobre splatting, consulte el capítulo Funciones avanzadas.
En el caso de esta función, no se aplica ninguna lógica de verificación o filtrado para validar que el nombre del servicio pasado como parámetro corresponde a un servicio iniciado. El objetivo es transferir esta lógica a un [ValidateSet()] dinámico.
Para ello, creamos una clase ServiceStarted heredada de la clase IValidateSetValuesGenerator contenida en el namespace...