Question Comment créer un type Enum dynamiquement ?

Plus d'informations
il y a 3 ans 4 mois #30944 par Arnaud Petitjean
Bonjour les amis !

Je cherche désepérement à créer un type Enum à partir de données présentes dans un fichier de configuration.

Imaginons que le fichier contiennent une liste de villes, par exemple : 
Paris
Lyon
Bordeaux

Je souhaite lire ce fichier et créer dynamiquement un type enum pour m'en servir ensuite dans une classe en tant que type de l'un de mes membres.

Je pensais avoir trouvé une solution en utilisant les mécanismes de reflexion de .NET avec cette petite fonction :
Function New-Enum {
    Param (
        [String]$Name,
        [String[]]$Values
    )
    $Domain = [AppDomain]::CurrentDomain
    $DynAssembly = New-Object System.Reflection.AssemblyName('PrivilegeAssembly')
    $AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run) # Only run in memory
    $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('PrivilegeModule', $False)
    #endregion Module Builder
    
    $global:EnumBuilder = $ModuleBuilder.DefineEnum($name, 'Public', [uint32])
    $i = 0
    $Values | ForEach-Object {
        [void]$EnumBuilder.DefineLiteral($_, [uint32] $i++)
    }
    [void]$EnumBuilder.CreateType()
}

Ainsi si j'appelle la fonction ainsi, par exemple :
# Création du type Enum Location
PS > New-Enum -Name Location -Values 'paris','londres','milan'

# Je peux m'en servir de manière classique
PS > [location]::londres
londres

PS > [enum]::GetValues([Location])
paris
londres
milan

Mais si j'essaie de créer une classe en utilisant ce type, cela ne fonctionne pas :
Function New-Enum {
    Param (
        [String]$Name,
        [String[]]$Values
    )
    $Domain = [AppDomain]::CurrentDomain
    $DynAssembly = New-Object System.Reflection.AssemblyName('PrivilegeAssembly')
    $AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run) # Only run in memory
    $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('PrivilegeModule', $False)
    #endregion Module Builder
    
    $global:EnumBuilder = $ModuleBuilder.DefineEnum($name, 'Public', [uint32])
    $i = 0
    $Values | ForEach-Object {
        [void]$EnumBuilder.DefineLiteral($_, [uint32] $i++)
    }
    [void]$EnumBuilder.CreateType()
}
New-Enum -Name Location -Values 'paris','londres','milan'

class test {
     [Location]$Emplacement
     [Int]$Distance
}

Du moins, si j'exécute ce script, j'obtient l'erreur suivante : 
Au caractère .\testEnumOnTheFly.ps1:24 : 7
+      [Location]$Emplacement
+       ~~~~~~~~
Type [Location] introuvable.    
+ CategoryInfo          : ParserError: (:) [], ParseException    
+ FullyQualifiedErrorId : TypeNotFound

Le pire dans tout ça est que si je crée ma classe directement dans la console PS sans passer par un script, ça fonctionne.

Enfin, je ne suis pas sûr que ce soit une bonne idée de faire cela car la création du type Enum ne fonctionne carrément pas en PowerShell 7.

Merci d'avance pour votre éclairage !

MVP PowerShell et créateur de ce magnifique forum :-)
Auteur de 6 livres PowerShell aux éditions ENI
Fondateur de la société Start-Scripting
Besoin d'une formation PowerShell ?

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

Plus d'informations
il y a 3 ans 4 mois #30945 par Laurent Dardenne
Comme l'indique le bouton ceci sera une réponse rapide ;-)

Le problème que tu rencontres est dû au mécanisme de compilation ( exemple ), PS compile la classe AVANT tout le reste, ce qui implique que la classe Enum utilisée dans la déclaration n'existe pas encore.
En tout cas elle n'est pas créée en premier, ce qui est le cas avec la pluspart des langages compilé (qui schématiquement 'ordonne' les dépendances).

Dans ton cas, pour régler ce problème il me semble qu'il faille créer une dll dynamiquement sur disque (Add-Type le permet), ensuite s'appuyer sur le mécanisme de chargement d'un module ( une clé permet d'exécuter une script avant le chargement du module).
A tester.
Ce problème n'est pas aisée à résoudre il me semble, je regarde ça demain.

Tutoriels PowerShell
Les utilisateur(s) suivant ont remercié: Arnaud Petitjean

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

Plus d'informations
il y a 3 ans 4 mois #30948 par Thomas PEREZ
Hello,

Il existe deux possibilités, la première via des fonctions et la deuxième en passant par une class importé dynamiquement.
Les deux méthodes sont utilisables sur PowerShell 5, 6, 7.

Méthode fonction :
function Add-MyEnum
{
    <#
        .DESCRIPTION
        Créé un enum.
    #>
    param
    (
        [String]$Name,
        [Array]$values
    )
    Add-Type -TypeDefinition "
        public enum $Name
        {
            $values
        }
    "
}
function Get-MyEnum
{
    <#
        .DESCRIPTION
        Affiche une variable de type Enum.
    #>
    param (
        [Service]$Name
    )
    $Name
}

# Ajout d'un nouveau Enum.
Add-MyEnum -Name Service -values @('Wsus, winserv')

# Appel d'une fonction contenant un paramètre de type Enum.
Get-MyEnum -Name 'winserv'

Deuxième méthode avec une class :
function Add-MyEnum
{
    <#
        .DESCRIPTION
        Créé un enum.
    #>
    param
    (
        [String]$Name,
        [Array]$values
    )
    Add-Type -TypeDefinition "
        public enum $Name
        {
            $values
        }
    "
}

# Ajout d'un nouveau Enum.
Add-MyEnum -Name Service -values @('Wsus, winserv')

# Import d'une class dynamiquement.
$classModules = "using module monchemin\class.psm1"
$scriptBlock = [ScriptBlock]::Create($classModules)
. $scriptBlock

# Création d'une instance.
$test = [MyClass]@{MyEnum = 'Wsus' }

Fichier PowerShell contenant la class.
class MyClass
{
    [Service]$MyEnum
    MyClass ()
    {
        # $this.addEnum([String]$Name, [Array]$values)
    }
}

Voila en ésperant que cela vous aide.
Bonne soirée.

Thomas.
Les utilisateur(s) suivant ont remercié: Arnaud Petitjean

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

Plus d'informations
il y a 3 ans 4 mois #30951 par Arnaud Petitjean
Hello Thomas !

Ca a l'air de correspondre en tous points à ce que je voulais faire.

Je vais tester ça.

Merci !

MVP PowerShell et créateur de ce magnifique forum :-)
Auteur de 6 livres PowerShell aux éditions ENI
Fondateur de la société Start-Scripting
Besoin d'une formation PowerShell ?

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

Plus d'informations
il y a 3 ans 4 mois #30952 par Arnaud Petitjean
Salut Laurent,

Le problème que tu rencontres est dû au mécanisme de compilation (  exemple  ), PS compile la classe AVANT tout le reste, ce qui implique que la classe Enum utilisée dans la déclaration n'existe pas encore.

Oui, on dirait bien que c'est le cas.

Thomas vient de poster une solution intéressante, qui je pense est la bonne.

MVP PowerShell et créateur de ce magnifique forum :-)
Auteur de 6 livres PowerShell aux éditions ENI
Fondateur de la société Start-Scripting
Besoin d'une formation PowerShell ?

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

Plus d'informations
il y a 3 ans 4 mois #30953 par Laurent Dardenne
Salut,
>>qui je pense est la bonne.
Elle a qq contraintes en l'état ( portée de la classe et chemin codé en dur dans la clause using) mais donne une direction.

Il reste à la tester dans différent scénarios.

Tutoriels PowerShell

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

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