Classes
Introduction
Les classes sont apparues depuis la version 5.0 de PowerShell. Elles ont été intégrées en très grande partie pour DSC. Leur utilisation dans DSC permet de réduire les interactions avec les fichiers de type MOF (voir le chapitre Desired State Configuration (DSC)).
La notion de classe peut paraître totalement abstraite. En vérité, avec PowerShell, on manipule des classes sans s’en rendre compte. Ou du moins, on manipule des objets instanciés par des classes du framework .NET ou mises à disposition par des éditeurs tiers. Il est très important d’avoir assimilé la notion d’objet avant d’aller plus loin.
Attention, le but de ce chapitre n’est pas de faire un cours sur la Programmation Orientée Objet (POO).
Création d’une classe
Une classe permet de définir la structure d’un objet. C’est en quelque sorte son squelette. Elle définit les membres d’un objet, propriétés et méthodes notamment.
1. Mot-clé Class
Tous comme la déclaration d’une fonction ou d’un workflow, la déclaration d’une classe se réalise avec un mot-clé : Class. Le nom d’une classe doit être le plus représentatif possible de son fonctionnement. Il est même recommandé de faire attention au nom que l’on utilise pour éviter toute confusion avec d’autres classes.
Pour éviter ce genre de problème, on regroupe les classes sous un même espace de noms. Exemple : <Nom de groupement de classes>.<Nom de la classe>.
Syntaxe
Class <NomDeMaClasse> {
[Type]<Membre1>
[Type]<Membre2>
[Type]<Membre3>
...
}
La notion de membre a sûrement fait écho. En effet, ce sont ces informations qu’affiche la commande Get-Member. Contrairement à C#, les seuls types de membres qu’il est possible de créer sont des propriétés et des méthodes. De plus, ils sont tous publics. La notion de membre privé n’a pas encore été implémentée sous PowerShell, actuellement en version 5.1. Autre syntaxe pour lister les membres d’une classe : [Maclasse].GetMembers().
Exemple de classe
PS > Class Autor {
$Name
$Email
$age
$PostalCode
}
La classe [Autor] vient d’être créée. Les membres qui la composent sont tous des propriétés.
2. Propriété
Une propriété est en réalité composée de trois éléments : un champ et deux méthodes. Le champ contient la valeur. Il n’est pas définissable directement dans la classe, contrairement à C#. Les méthodes accesseur (ou getter) et mutateur (setter) permettent respectivement de lire et d’écrire la valeur contenue par le champ.
Dans PowerShell, il n’est...
Surcharge de méthode (overload)
La surcharge de méthode (overload en anglais) permet de définir un ensemble de méthodes portant le même nom. Cela peut paraître un peu déroutant. Mais il y a une explication assez simple à ce procédé. Chaque méthode dispose d’une signature. Cette signature est définie en fonction des paramètres de la méthode. Le nombre, l’emplacement, mais surtout le type des paramètres joue sur la signature.
Exemple d’overload sur une nouvelle méthode GetPostalCode()
PS > Class Autor {
...
[String]GetPostalCode([int]$PostalCode){
if ($PostalCode -eq 44000){
Return 'Nantes'
}else{
Return 'Inconnu'
}
}
[int]GetPostalCode([string]$PostalCode){
if ($PostalCode -eq 'Nantes'){
Return 44000 ...
Statique
Les classes disposent de l’option statique sur ses membres, identifiée grâce au mot-clé static, placé avant le membre.
Syntaxe
Class <NomDeMaClasse> {
static [Type]<Membre1>
static [Type]<Membre2>
...
}
Cette fonctionnalité fait en sorte que le membre soit disponible sans nécessiter l’instanciation de l’objet, et donc l’appel d’un constructeur.
Les syntaxes qui permettent d’utiliser les membres statiques sont les suivantes :
[MaClass]::<MembreStatic>
$MonObjet::<MembreStatic>
On retrouve les membres statiques dans un grand nombre de classes système.
Exemple avec une classe très connue
PS > [DateTime]::Now
samedi 15 octobre 2016 19:47:04
Exemple d’intégration d’un membre statique
PS > Class Autor {
...
Static $Edition = 'ExpertIT'
}
PS > [Autor]::Edition
ExpertIT
PS > $Me = [Autor]::New(25)
PS > $Me::Edition
ExpertIT
Notez très attentivement la syntaxe des ::, et non du simple point. Si vous avez parfaitement assimilé les principes précédents, vous arriverez rapidement à la logique suivante concernant la variable automatique...
Portée des variables
Avant d’aller plus loin, il est important d’avoir parfaitement assimilé la base des portées sous PowerShell.
Un petit rappel s’impose :
-
Portée globale : cette portée est créée lors de l’initialisation d’un environnement PowerShell. Les variables sont accessibles en lecture à toutes les portées enfants. Pour modifier une variable globale dans une portée enfant, il est nécessaire d’utiliser la syntaxe suivante : $global:<ma_variable>.
-
Portée script : la portée script fait référence à celle créée lors de l’exécution d’un script. Une fois le script terminé, les variables créées dans la portée disparaissent. Lorsqu’elle est utilisée dans une console, la portée est équivalente à la portée globale. Pour modifier une variable script dans une portée enfant, il est nécessaire d’utiliser la syntaxe suivante : $script:<ma_variable>.
-
Portée locale : elle fait référence à la portée où l’on se trouve, que ce soit une fonction, un bloc de script, un script. Il est possible d’utiliser deux syntaxes différentes : $local:<ma_variable>, $<ma_variable>. La seconde syntaxe est plus courante.
Dans notre situation avec les classes, seules ces trois portées sont à prendre en compte. Il est déconseillé d’utiliser ce type de syntaxe dans un script, à moins de savoir exactement ce que l’on fait. Si jamais on doit traiter un objet dans une propriété ou méthode, on privilégie l’utilisation de paramètres dans des méthodes ou des constructeurs.
1. Propriété
Dans une propriété, on peut faire appel à une variable dans la portée où la classe est définie. Ce qui n’est pas le cas pour les méthodes.
Exemple
PS > $Var = 0
PS > Class Scoping {
$Prop = $Var
Static $StaticProp = $Var
}
PS > $Sco = [Scopring]::New()
PS > [Scoping]::StaticProp
0 ...
Héritage
L’héritage permet de déclarer une classe de base qui sera ensuite exploitée à travers des classes dérivées. Cette exploitation se caractérise par un héritage de l’ensemble des membres de la classe dérivée depuis la classe de base, notamment les méthodes.
Syntaxe
PS > Class <BaseClass>{
<Property1>
<Property2>
<Methode1>
<BaseClass>($Parameter1,$Parameter2){}
}
PS > Class <DeriClass> : <BaseClass> {
<otherProprerty3>
<DeriClass>($Parameter1,$Parameter2):base($Parameter1,$Parameter2){}
}
La déclaration de la classe dérivée comporte son nom ainsi que celui de la classe de base, séparés par le caractère :. Il n’est pas possible de déclarer plusieurs classes de base pour une classe dérivée :
PS > Class <DeriClass> : <BaseClass>,<BaseClase2>
Ensuite, il faut déclarer le constructeur de la classe dérivée. Pour cela, il faut ajouter, entre la déclaration des paramètres et des instructions, le mot-clé base, lui aussi précédé...
Création enum
PowerShell V5 implémente la notion d’énumération, mot-clé enum. Avant cela, il fallait utiliser la cmdlet Add-Type suivie d’un code en C#.
Les énumérations permettent de restreindre les valeurs que peut contenir une variable. Un peu comme le paramètre de validation ValidateSetAttribute() pourrait le faire sur un paramètre.
1. Mot-clé enum
Syntaxe du mot-clé enum :
PS > enum <NomDeLEnumeration> {
<NomDeValeur1>
<NomDeValeur2>
<NomDeValeur3>
...
}
Exemple
PS > enum CodeLivre {
RessourceInformatique
ExpertIT
Epsilon
}
PS > [CodeLivre]$Celivre = 'ExperIT'
Dans le cas où l’on stipule une autre chaîne de caractères que les noms de valeurs contenues dans l’énumération [CodeLivre], une erreur apparaît.
Il n’est pas possible de faire commencer les noms de valeurs par un chiffre.
Les énumérations sont des ensembles de constantes qui débutent à 0. Elles permettent de définir une variable sur une valeur et non plus sur un nom :
PS > [CodeLivre]$Celivre = 1
PS > $Celivre ...
Formatage
Les classes sous PowerShell réagissent de la même manière que les classes du framework .NET pour le formatage. Le procédé a été décrit dans la section précédente de ce chapitre. Il suffit de reprendre le même principe tout en l’adaptant aux classes PowerShell. On crée d’abord un fichier format.ps1xml. En nom de type, on spécifie la classe que l’on veut lui affecter. Enfin, on applique le changement grâce à la commande Update-FormatData.
Intégration dans un module
Les classes sont susceptibles d’avoir une destination plus vaste qu’un simple script PowerShell. Il est donc tout à fait justifié de vouloir les intégrer à un module pour faciliter leur partage.
Les modules, sont constitués d’un manifeste au format PSD1 et d’un fichier de module au format PSM1. Dans le cas présent, seul le fichier manifeste est nécessaire. On renseigne le champ ScriptToProcess pour charger la classe contenue dans un fichier PS1.
Organisation du module TestClass :
└─TestClass
├─ Classes
└─ myclass.ps1
└─ TestClass.psd1
Le fichier myclass.ps1 contient la classe [Computer] de l’exemple précédent. Pour le fichier manifeste, on renseigne le champ ScriptToProcess de la manière suivante :
ScriptsToProcess = @('Class/myclass.ps1')
Enfin, le module est placé dans un répertoire où il est découvert automatiquement pour être importé via la commande Import-Module :
PS > Import-Module TestClass
On teste enfin l’instanciation de la classe [Computer] :
PS > [Computer]::New('PC-Generic','Windows')
Name OS
---- -- ...
Mot-clé Using
L’instruction using a déjà été abordée juste avant, quand il a été question de placer des classes dans un module. Elle sert également à faciliter l’appel de classe présent dans des namespaces. Mais qu’est-ce qu’un namespace ? Il s’agit d’une structure hiérarchique qui va organiser les classes en fonction d’une utilité/thématique particulière.
Par exemple, la classe Directory désigne un dossier, et cette classe est contenue dans le namespace system.IO. Ce dernier contient également la classe File qui désigne les fichiers.
Quand on appelle une classe du framework .NET en PowerShell, on renseigne le namespace et le nom de la classe entre crochets.
Par exemple, création d'un dossier avec la classe Directory :
PS > [System.IO.Directory]::CreateDirectory('C:\Temps')
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 5/17/2023 6:25...
Utilisation des classes sur la validation de paramètre
Dans le chapitre Fonctions avancées, il a été abordé en détail la validation des paramètres de différentes manières. Maintenant que la notion de classe a été vue, il est possible de compléter ces éléments par l’utilisation des classes.
1. ValidateSet Dynamique
L’attribut de validation [ValidateSet()] réalise une limitation des valeurs acceptable par le paramètre. Dans le chapitre Fonctions avancées, les valeurs passées à l’attribut ValidateSet étaient contenues dans une collection fixe définie dans la fonction. Le changement de ces valeurs devra passer par une modification de la définition de la fonction.
Comment procéder dans le cas où il est nécessaire d’avoir des valeurs dynamiques à la place de valeurs fixes ? Utiliser l’attribut ArgumentCompleter ? Ce ne sont que des propositions, l’utilisateur peut passer d’autres valeurs que celles proposées. Utiliser les paramètres dynamiques ? Oui c’est une possibilité, mais cela apporte aussi des inconvénients non négligeables.
L’autre solution proposée ici est l’utilisation des classes pour assigner dynamiquement des valeurs à [ValidateSet()]. Cette fonctionnalité n’est disponible que depuis PowerShell 7.
Pour l’exemple, la fonction Restart-ServiceRunning va être utilisée. Celle-ci redémarre seulement les services se trouvant dans un état Running. Voici la fonction :
function Restart-ServiceRunning {
[CmdletBinding()]
Param (
[string[]]$Name
)
Restart-Service @PSBoundParameters
}
Dans cet exemple, la variable $PSBoundParameters et la technique du splatting sont utilisées pour passer les valeurs des paramètres directement sur la commande Restart-Service. Si vous voulez en savoir plus sur le splatting, référez-vous au chapitre Fonctions avancées.
Dans le cas de cette fonction, aucune logique de vérification ou de filtrage n’est mise en place pour valider que le nom du service...