Question Gestion d'événement perso dans 1 objet synthétique

Plus d'informations
il y a 15 ans 9 mois #6954 par Laurent Dardenne
Quelques explications concernant la création d'un objet à partir d'un module.

La création de membres synthétiques ne permet pas de créer des champs privés accessibles uniquement par le code de l'instance, comme indiqué ici .
PowerShell v2 \"facilite\" cette création en utilisant un module dynamique à l'aide du cmdlet New-Module couplé au paramètre -AsCustomObject.

Par défaut dans un module toutes les fonctions sont publiques et toutes les variables sont privées.
Ce module peut modifier l'accès de ses membres en utilisant le cmdlet Export-ModuleMember.

Créons un objet possédant une propriété Name :
[code:1]
function New-CustomObject {

#On crée un objet à partir des membres d'un module dynamique,
#celui-ci sert à encapsuler des membres privés.
$CustomObject=New-Module -AsCustomObject -ScriptBlock {

#Crée une variable typée en lecture seule.
#Exportée, accessible directement par $Variable.Name
New-Variable Name -Option ReadOnly -Value ([String]\"Membre en lecture seule\"«»)

##Par défaut toutes les fonctions sont publiques, toutes les variables sont privées.
#Les membres exportée du module deviennent des membres publiques
#de l'objet créé par New-CustomObject.
Export-ModuleMember -Variable Name
}
#Emet l'objet dans le pipeline
$CustomObject
}
$O=New-CustomObject
$o|Gm
[/code:1]
Ici le module est transformé en un objet, de type PSCustomObject, le module bien qu'existant n'est plus accessible en tant que tel.
Comme la variable $Name est exportée, on la retrouve en tant que membre, propriété, de l'objet créé.
Celle-ci bien que de type NoteProperty est en lecture seule.
[code:1]
$o
$o.Name=\"test\"
#Impossible de remplacer la variable Name, car elle est constante ou en lecture seule.
[/code:1]
Puisque nous avons déclaré, à l'aide du cmdlet New-Variable, notre variable en ReadOnly la modification de son contenu est impossible.
Si vous utilisez ce type de déclaration dans un module statique, fichier .psm1, consultez cette entrée sur MSconnect .

Déclarons une autre variable dans notre module :
[code:1]
function New-CustomObject {

$CustomObject=New-Module -AsCustomObject -ScriptBlock {

#Cette variable n'est pas exportée.
#Elle reste accessible depuis la fonction GetModuleMember.
[Int] $Interne=48

New-Variable Name -Option ReadOnly -Value ([String]\"Membre en lecture seule\"«»)

Export-ModuleMember -Variable Name
}
$CustomObject
}
$O=New-CustomObject
$o|Gm
[/code:1]
La variable $Interne n'est pas ajoutée en tant que membre de notre objet. Par contre, on souhaite l'associer à un membre public, sous PS v1 cela nécessitait de nombreuses lignes de code.
Puisque la variable $CustomObject est de type PSCustomObject, on peut construire notre objet en 2 passes, la première à l'aide d'un module dynamique, la seconde avec add-member :
[code:1]
#Emet l'objet dans le pipeline
$CustomObject| add-member -membertype ScriptProperty -Name Nombre -value {$this.Interne} -Pass
[/code:1]
Testons cette nouvelle propriété :
[code:1]
function New-CustomObject {
#Première passe, on crée un objet à partir des membres d'un module dynamique,
$CustomObject=New-Module -AsCustomObject -ScriptBlock {

[Int] $Interne=48

New-Variable Name -Option ReadOnly -Value ([String]\"Membre en lecture seule\"«»)

Export-ModuleMember -Variable Name
}
#Seconde passe, on ajoute des membres à l'objet crée à partir d'un module dynamique.
$CustomObject| add-member -membertype ScriptProperty -Name Nombre -value {$this.Interne} -Pass
}
$O=New-CustomObject
$o.nombre
[/code:1]
Bien évidement cette approche ne fonctionne pas, car la variable $Interne est inaccessible à partir de la portée du scriptblock de notre propriété \"Nombre\".
D'une part, parce qu'elle n'est pas exportée et d'autre part, parce que le module utilise sa propre portée.
Il reste possible d'accéder à cette portée en déclarant DANS le module une fonction renvoyant la variable nommée $Interne :
[code:1]
function New-CustomObject {

$CustomObject=New-Module -AsCustomObject -ScriptBlock {

[Int] $Interne=48

New-Variable Name -Option ReadOnly -Value ([String]\"Membre en lecture seule\"«»)

#Accéde à la variable $Interne déclarée dans la portée du module.
function GetVariableInterne {
return $Interne
}

Export-ModuleMember -Variable Name `
-Function GetVariableInterne
}

$CustomObject| add-member -membertype ScriptProperty -Name Nombre -value {$this.GetVariableInterne()} -Pass
}
$O=New-CustomObject
$o.nombre
[/code:1]
Cette fois-ci cela fonctionne.
Le souci, il en faut sinon avec PowerShell la vie serait trop facile ;-), est que si on souhaite généraliser ce type de propriété on va devoir créer autant de méthodes que de variables 'masquées'.
Autant factoriser le code :
[code:1]
function New-CustomObject {

$CustomObject=New-Module -AsCustomObject -ScriptBlock {

[Int] $Interne=48

New-Variable Name -Option ReadOnly -Value ([String]\"Membre en lecture seule\"«»)

#Accéde à la variable $Interne déclarée dans la portée du module.
function GetModuleMember{
param([String]$Member)
invoke-expression \"Return `$$Member\"
}

Export-ModuleMember -Variable Name `
-Function GetModuleMember
}

$CustomObject| add-member -membertype ScriptProperty -Name Nombre -value {$this.GetModuleMember(\"Interne\"«»)} -Pass
}
$O=New-CustomObject
$o.nombre
[/code:1]
En utilisant le dynamisme de PowerShell on résoud aisément ce besoin.

Effectuons un dernier test en utilisant un objet et non plus une valeur :
[code:1]
function New-CustomObject {

$CustomObject=New-Module -AsCustomObject -ScriptBlock {

$Interne=new-object System.Diagnostics.EventLog(\"Application\"«»)

New-Variable Name -Option ReadOnly -Value ([String]\"Membre en lecture seule\"«»)

#Accéde à la variable $Interne déclarée dans la portée du module.
function GetModuleMember{
param([String]$Member)
invoke-expression \"Return `$$Member\"
}

Export-ModuleMember -Variable Name `
-Function GetModuleMember
}

$CustomObject| add-member -membertype ScriptProperty -Name Display -value {$this.GetModuleMember(\"Interne.LogDisplayName\"«»)} -Pass
}
$O=New-CustomObject
$o.Display
$E=$o.GetModuleMember(\"Interne\"«»)
$E.Log=\"System\" #Modification possible de l'objet interne.
$o.Display
($o.PSObject.Members[\"Display\"]).GetterScript
[/code:1]
Dans le cas où la variable $Interne pointe sur un objet, le problème suivant se présente:
comme le nom de la variable est connue de tous, son accès reste possible via la méthode GetModuleMember.

Pour éviter ce problème, on doit masquer le nom de la variable, on utilisera une hashtable pour filtrer les accès aux variables du module :
[code:1]
function New-CustomObject {

$CustomObject=New-Module -AsCustomObject -ScriptBlock {

New-Variable Name -Option ReadOnly -Value ([String]\"Membre en lecture seule\"«»)
$Interne=new-object System.Diagnostics.EventLog(\"Application\"«»)

#Hashtable définissant les variables du module accessibles de l'extérieur.
#Permet :
# - de masquer dans le corps des méthodes les noms de variables internes,
# - de limiter le type d'accès depuis l'extérieur du module.
$VariablesProxy=@{
\"Display\"=@{Name=\"Interne.LogDisplayName\"};
}

#Accesseurs privés.
#Accéde à toutes les variables déclarées dans la hashtable $VariablesProxy.
function _GetModuleMember{
param([String]$Member)
if (!$VariablesProxy.ContainsKey($Member))
{Throw \"La variable $Member n'existe pas.\"}
invoke-expression \"Return `$$($VariablesProxy.$Member.Name)\"
}
#Accesseurs publics permettent de se placer dans la portée du module.
function GetModuleMember{
param([String]$MemberName)
_GetModuleMember $MemberName
}
Export-ModuleMember -Variable Name `
-Function GetModuleMember
}

$CustomObject| add-member -membertype ScriptProperty -Name Display -value {$this.GetModuleMember(\"Display\"«»)} -Pass
}
$O=New-CustomObject
$o.Display
$E=$o.GetModuleMember(\"Interne\"«»)
($o.PSObject.Members[\"Display\"]).GetterScript
[/code:1]
Le nom de clé dans hashtable suit celui de la variable déclarée via Add-Member. Vous noterez qu'il est possible de pointer sur une propriété de l'objet : \"Interne.LogDisplayName\"
On utilise également une fonction privée pour masquer l'usage de la hashtable. Celle-ci (_GetModuleMember) reste accessible, car on exécute le code de la méthode GetModuleMember dans la portée du module.

Allons plus loin, si si c'est possible, maintenant nous aimerions accéder à une propriété de notre objet en lecture ET en écriture.
On reprend la même démarche en ajoutant une fonction SetModuleMember qui du coup doit effectuer quelques contrôles lors de l'affectation :
[code:1]
function New-CustomObject {

$CustomObject=New-Module -AsCustomObject -ScriptBlock {

New-Variable Name -Option ReadOnly -Value ([String]\"Membre en lecture seule\"«»)

[Int] $Interne=48
$Event=new-object System.Diagnostics.EventLog(\"Application\"«»)

$VariablesProxy=@{
\"MachineName\"=@{Name=\"Event.MachineName\"};
\"Nombre\"=@{Name=\"Interne\"}
}

#Accesseurs privés.
#Accéde à toutes les variables déclarées dans la hashtable $VariablesProxy.
function _GetModuleMember{
param([String]$Member)
if (!$VariablesProxy.ContainsKey($Member))
{Throw \"La variable $Member n'existe pas.\"}
invoke-expression \"Return `$$($VariablesProxy.$Member.Name)\"
}

function _SetModuleMember{
param([String]$Member,$Value)
if (!$VariablesProxy.ContainsKey($Member))
{Throw \"La variable $Member n'existe pas.\"}
if ($Value -eq $Null)
{ invoke-expression \"`$script:$($VariablesProxy.$Member.Name)=`$Null\" }
elseif ($Value -is [string])
#La portée 'Script' doit être précisée pour accéder en écriture
{ invoke-expression \"`$script:$($VariablesProxy.$Member.Name)=`\"$Value`\"\" }
else { invoke-expression \"`$script:$($VariablesProxy.$Member.Name)=$Value\" }
}

#Accesseurs publics, ils permettent de se placer dans la portée du module.
function GetModuleMember{
param([String]$MemberName)
_GetModuleMember $MemberName
}

function SetModuleMember{
param([String]$MemberName,$Value)
_SetModuleMember $MemberName $Value
}

Export-ModuleMember -Variable Name `
-Function GetModuleMember,SetModuleMember
}

$CustomObject|
add-member -membertype ScriptProperty -Name Nombre -value {$this.GetModuleMember(\"Nombre\"«»)} -pass|
add-member -membertype ScriptProperty -Name MachineName `
-value {$this.GetModuleMember(\"MachineName\"«»)} `
-second {$this.SetModuleMember(\"MachineName\",$Args[0])} -Pass
}
$O=New-CustomObject
$o.MachineName=\"Test\"
$o.SetModuleMember(\"Nombre\",77)
[/code:1]
On peut maintenant modifier une variable interne, mais pas de chance, l'ajout de la fonction SetModuleMember autorise à modifier d'autres variables qui ne devrait pas l'être.
Une histoire sans fin :-) ?

Pour résoudre cet énième problème, on doit ajouter un mode d'accès sur chaque entrée de la hashtable, mode d'accès que l'on contrôlera dans la fonction SetModuleMember :
[code:1]
\"Nombre\"=@{Name=\"Interne\";Access=\"RO\"}
..
if ($VariablesProxy.$Member.Access -eq \"RO\"«»)
{Throw \"La variable $MemberName est en lecture seule.\"}
[/code:1]
Le code modifié :
[code:1]
function New-CustomObject {

$CustomObject=New-Module -AsCustomObject -ScriptBlock {

New-Variable Name -Option ReadOnly -Value ([String]\"Membre en lecture seule\"«»)

[Int] $Interne=48
$Event=new-object System.Diagnostics.EventLog(\"Application\"«»)

$VariablesProxy=@{
\"MachineName\"=@{Name=\"Event.MachineName\";Access=\"RW\"};
\"Nombre\"=@{Name=\"Interne\";Access=\"RO\"}
}

function _GetModuleMember{
param([String]$Member)
if (!$VariablesProxy.ContainsKey($Member))
{Throw \"La variable $Member n'existe pas.\"}
invoke-expression \"Return `$$($VariablesProxy.$Member.Name)\"
}

function _SetModuleMember{
param([String]$Member,$Value)
if (!$VariablesProxy.ContainsKey($Member))
{Throw \"La variable $Member n'existe pas.\"}
if ($VariablesProxy.$Member.Access -eq \"RO\"«»)
{Throw \"La variable $MemberName est en lecture seule.\"}
if ($Value -eq $Null)
#La portée 'Script' doit être précisée pour accéder en écriture
{ invoke-expression \"`$script:$($VariablesProxy.$Member.Name)=`$Null\" }
elseif ($Value -is [string])
{ invoke-expression \"`$script:$($VariablesProxy.$Member.Name)=`\"$Value`\"\" }
else { invoke-expression \"`$script:$($VariablesProxy.$Member.Name)=$Value\" }
}

function GetModuleMember{
param([String]$MemberName)
_GetModuleMember $MemberName
}

function SetModuleMember{
param([String]$MemberName,$Value)
_SetModuleMember $MemberName $Value
}

Export-ModuleMember -Variable Name `
-Function GetModuleMember,SetModuleMember
}

$CustomObject|
add-member -membertype ScriptProperty -Name Nombre -value {$this.GetModuleMember(\"Nombre\"«»)} -pass|
add-member -membertype ScriptProperty -Name MachineName `
-value {$this.GetModuleMember(\"MachineName\"«»)} `
-second {$this.SetModuleMember(\"MachineName\",$Args[0])} -Pass
}
$O=New-CustomObject
$o.Name=\"Nouveau\"
$o.MachineName=\"Test\"
$o.SetModuleMember(\"Nombre\",77)
[/code:1]
Pour terminer il reste possible de paramètrer l'objet crée :
[code:1]
function New-CustomObject {
Param([string]$Texte,[int]$Value)


$CustomObject=New-Module -AsCustomObject -ScriptBlock {
[Int] $Interne=$Value
...
#ArgumentList propage les paramètres
} -ArgumentList $Texte,$Value

$CustomObject
}
$o=New-CustomObject \"Chaine\" 99
[/code:1]
Enfin sachez que les fonctions avancées déclarant plusieurs blocs ne sont pas autorisées ainsi que l'usage de certains attribut.
Vous pouvez bien sûr utiliser qu'une partie de ces différentes astuces, ces qq explications vous aurons permis j'espére de comprendre un peu mieux l'usage d'un module en tant qu'objet.

Pour creuser le sujet des modules, je vous recommande la lecture de ces articles issus du prochain ouvrage de Bruce Payette :
dotnetslackers.com/articles/net/Converti...t-into-a-Module.aspx
dotnetslackers.com/articles/net/converti...-a-module-part2.aspx

Comme quoi, PowerShell avec de la doc c'est quand même mieux :-)

Tutoriels PowerShell

Connexion ou Créer un compte pour participer à la conversation.

Temps de génération de la page : 0.047 secondes
Propulsé par Kunena