IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Scripts/Batch Discussion :

Astuce: traduire des directives en des paramètres dynamiques


Sujet :

Scripts/Batch

  1. #1
    Membre chevronné
    Avatar de I'm_HERE
    Homme Profil pro
    Inscrit en
    Juillet 2008
    Messages
    1 013
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Tunisie

    Informations forums :
    Inscription : Juillet 2008
    Messages : 1 013
    Points : 1 991
    Points
    1 991
    Par défaut Astuce: traduire des directives en des paramètres dynamiques
    salut,

    Ce matin en regardant le site powershell-scripting.com je me suis aperçu que Laurent a des contributions, l'une d'elles est la création de "directives conditionnelles", alors je mes suis dis: "tiens c'est une bonne idée ça !" alors je me suis inspiré de son dernier topic pour créer une fonction se basant sur les directives...et tant qu'on y est... je vais copiez son intro

    <DEBUT_COPIE>
    le principe est de délimiter une ou plusieurs lignes de code par un mot clé de début de directive et

    un mot clé de fin de directive...

    <FIN_COPIE>

    le mot clé de debut étant <#DynParam et le mot clé de fin est Dynparam#>

    malheureusement on ne trouve pas beaucoup de documentation au sujet des paramètres dynamiques, alors on va commencer par différencier les paramètres dynamiques des paramètres statiques:

    les paramètres statiques:
    ils sont parser avant l'execution du code 'parse time'
    ils ne permettent pas de voir les autres paramètres dans le script ou dans la fonction
    ils sont visible dans le systeme d'aide (get-help)

    les paramètres dynamiques:
    ils sont parser dynamiquement à la volée 'runtime'
    ils peuvent voir les autres paramètres
    ils ne sont pas visibles dans le systeme d'aide (get-help)

    pour voir les cmdlets qui ont des paramètres dynamiques:


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    :exitfor foreach($cmd in Get-Command -CommandType cmdlet) {
      foreach($param in $cmd.ParameterSets | select -Expand parameters) {
          if($param.IsDynamic) {
             New-Object psobject -Property @{
                Command = $cmd
                Provider = $PWD.Provider.Name
             }
             continue exitfor
          }
      }
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    Provider                                Command
    --------                                -------
    FileSystem                              Add-Content
    FileSystem                              Get-Content
    FileSystem                              Set-Content
    pour voir ces paramètres dans chaque psprovider vous pouvez utiliser ce bout de code dans chaque psprovider:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    foreach($cmd in Get-Command -CommandType cmdlet) {
      $array=@()
      foreach($param in $cmd.ParameterSets | select -ExpandProperty parameters) {
          if($param.IsDynamic) {
               $array+=$param.name
          }
      }
         if($array) 
         {
              new-object psobject -Property @{
                Command =  $cmd.name
                DynamicParameter = $array | select -Unique
                Provider = $PWD.Provider.Name
              }
         }
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    DynamicParameter           Provider                   Command
    ----------------           --------                   -------
    Encoding                   FileSystem                 Add-Content
    {Delimiter, Wait, Encod... FileSystem                 Get-Content
    Encoding                   FileSystem                 Set-Content
    revenons maintenant à notre fonction:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    #################################################################################################
    # 
    #  Description:
    #     Converti une directive de création de paramètres dynamique en 
    #     code .NET
    #
    #  Version:
    #     1.0
    #
    #  Auteur:
    #     Walid Toumi
    #
    #  Blog:
    #     http://walid-toumi.blogspot.com/
    #
    #  NOTE:
    #    1) pour le moment cette version ne s'utilise qu'avec ISE, vous pouvez copier votre fonction à convertir 
    #       dans le volet de script courant puis appelez la fonction 'ConvertTo-DynamicParam'
    #
    #    2) cette fonction n'est pas complète donc à ne pas utiliser dans des environements de production
    #
    ###################################################{#############################################
    function ConvertTo-DynamicParam {
    
    if([string]::IsNullOrEmpty($psise)) {
        throw 'cette fonction requiert "ISE"'
     }
    $dp=''
    $content = $psISE.CurrentFile.Editor.Text
    # $content =  [IO.FILE]::ReadAllText((join-path $pwd .\testdyn.ps1))
    $dyncomm = @([management.automation.psparser]::Tokenize($content,[ref]$null) | 
        Where { 
            $_.Type -eq 'comment' -and $_.Content -match '<#Dynparam'
              })[0].Content -replace '<#Dynparam.*|Dynparam#>'
    $tmpfunc = Set-Item -Path function:dummyfunc -Value $dyncomm -PassThru
    Remove-Item function:dummyfunc -Force
    $commun=[Management.Automation.Internal.CommonParameters].GetProperties() | foreach { $_.Name }
    $params=@($tmpfunc.Parameters.GetEnumerator() | ? { $commun -notcontains $_.key })
    $ParameterName = $params[0].Value.Name
    $aliases = @($params[0].value.Aliases)
    $attr = $params[0].Value.attributes | Where { $_.TypeId -like '*ParameterAttribute' }
    $vals = $params[0].Value.Attributes | Where { $_.TypeId -notmatch 'ParameterAttribute|ArgumentTypeConverterAttribute' }
    $Mandatory = $attr.Mandatory
    $ParameterSetName=$attr.ParameterSetName
    $Position=$attr.Position
    $ByValue=$attr.ValueFromPipeline
    $ByPropertyName=$attr.ValueFromPipelineByPropertyName
    $RemainingArgs=$attr.ValueFromRemainingArguments
    $Type=$params[0].value.ParameterType.fullname
        $dp+=@"
     DynamicParam { `n
     #if(une condition) {
    `$Attributes = New-Object 'Management.Automation.ParameterAttribute' 
    `$Attributes.ParameterSetName = '$ParameterSetName'            
    `$Attributes.Mandatory = `$$Mandatory
    `$Attributes.Position = $Position
    `$Attributes.ValueFromPipeline = `$$ByValue
    `$Attributes.ValueFromPipelineByPropertyName = `$$ByPropertyName
    `$Attributes.ValueFromRemainingArguments = `$$RemainingArgs
    `$Attributes.HelpMessage = '$helpMessage '
    `$AttributeCollection = New-Object 'Collections.ObjectModel.Collection[Attribute]' 
    `$AttributeCollection.Add(`$Attributes) `n
    "@
    if($aliases) {
       $dp+=@"
    `$Attribute=new-object 'System.Management.Automation.AliasAttribute' $($aliases -join ',')
    `$attributeCollection.Add(`$Attribute) `n
    "@
    } 
    # attributs de validation
       if($Vals) {
          foreach($Val in $Vals) {
             Switch ($val.TypeId.Name) {
               'ValidateScriptAttribute' 
                 { 
         $dp+=@"          
    `$Attribute=new-object 'Management.Automation.ValidateScriptAttribute' {`$$($Val.ScriptBlock)} 
    `$attributeCollection.Add(`$Attribute) `n
    "@
                 }
               'ValidatePatternAttribute'
                 { 
        $dp+=@"            
    `$Attribute=new-object 'Management.Automation.ValidatePatternAttribute' '$($Val.RegexPattern)' 
    `$attributeCollection.Add(`$Attribute) `n
    "@
                 }
               'ValidateSetAttribute'
                 {
        $dp+=@"           
    `$Attribute = new-object System.Management.Automation.ValidateSetAttribute $($Val.ValidValues -join ',')
    `$attributeCollection.Add(`$Attribute) `n
    "@
                 }
               'ValidateLengthAttribute'
                 {
        $dp+=@"           
    `$Attribute = new-object System.Management.Automation.ValidateLengthAttribute $($Val.MinLength,$Val.MaxLength -join ',')
    `$attributeCollection.Add(`$Attribute) `n
    "@
                 } 
               'ValidateCountAttribute'
                 {
        $dp+=@"           
    `$Attribute = new-object System.Management.Automation.ValidateCountAttribute $($Val.MinLength,$Val.MaxLength -join ',')
    `$attributeCollection.Add(`$Attribute) `n
    "@
                 }
               'ValidateRangeAttribute'
                 {
        $dp+=@"           
    `$Attribute = new-object System.Management.Automation.ValidateRangeAttribute $($Val.MinRange,$Val.MaxRange -join ',')
    `$attributeCollection.Add(`$Attribute) `n
    "@
                 }
               'ValidateNotNullOrEmptyAttribute'
                 {
        $dp+=@"           
    `$Attribute = new-object System.Management.Automation.ValidateNotNullOrEmptyAttribute
    `$attributeCollection.Add(`$Attribute) `n
    "@
                 }
              'AllowEmptyCollectionAttribute'
                 {
        $dp+=@"           
    `$Attribute = new-object System.Management.Automation.AllowEmptyCollectionAttribute
    `$attributeCollection.Add(`$Attribute) `n
    "@
                 }
             'AllowEmptyStringAttribute'
                 {
        $dp+=@"           
    `$Attribute = new-object System.Management.Automation.AllowEmptyStringAttribute
    `$attributeCollection.Add(`$Attribute) `n
    "@ 
                }
             } #switch
          }#foreach vals
       }#if 
     $dp+=@"
    `$MyParam = @{            
                Typ = 'Management.Automation.RuntimeDefinedParameter'            
                Arg = @(            
                    '$ParameterName'          
                    '$Type'          
                    ,`$AttributeCollection         
                )            
            }  
    `$DynamicParam = New-Object @MyParam
    `$ParamDict = New-Object 'Management.Automation.RuntimeDefinedParameterDictionary'          
    `$ParamDict.Add("$ParameterName", `$DynamicParam)
    `$ParamDict 
     # endif condition }
     
     # vous pouvez testez la présence du paramètre dans le corps de la fonction dynamque par: 
     #
     # if(`$PSBoundParameters.ContainsKey('$ParameterName')) {
     #    // votre code ici 
     #    // vous pouvez utiliser votre variable dynamique comme ceci: `$PSBoundParameters.$ParameterName
     # } 
    }`n
    "@
    $tmpfile =  ([IO.Path]::GetTempFileName()) + '.ps1'
    [regex]::Replace($content,'(?si)<#DynParam.+?DynParam#>',$dp) | Out-File $tmpfile -Force
    ise $tmpfile
    write-host "la fonction converti est dans le fichier: $tmpfile" -ForegroundColor green
    }
    vous verrez que le principe est simple, mais comment ça marche ?

    on met notre fonction dans le volet de script de 'ISE' et on appel notre convertTo-DynamicParam,

    celle-ci va traduire notre 'code Powershell' en du code '.NET' et va créer un fichier dans le 'TEMP'

    contenant notre fonction transformé..

    voici un exemple d'utilisation:

    on créer une simple fonction, ou disant le corps de notre fonction:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function get-simple {
     [cmdletBinding()]
     param(
       [parameter(mandatory=$true)]
       [string]$String
     )
     End {
       
     }
    }

    maintenant on créer nos directives et une clause PARAM qui contiendra notre paramètre dynamique:


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function get-simple {
     [cmdletBinding()]
     param(
       [parameter(mandatory=$true)]
       [string]$String
     )
    <#DynParam
     param(
      [parameter()]
      [Switch]$Reverse
     )
    DynParam#>
     End {
       
     }
    }

    maintenant on copie notre fonction dans ISE, on appel la fonction 'convertTo-DynamicParam' un autre

    volet va s'afficher contenant ce code:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    function get-simple {
     [cmdletBinding()]
     param(
       [parameter(mandatory=$true)]
       [string]$String
     )
     DynamicParam { 
    
     #if(une condition) {
    $Attributes = New-Object 'Management.Automation.ParameterAttribute' 
    $Attributes.ParameterSetName = '__AllParameterSets'            
    $Attributes.Mandatory = $False
    $Attributes.Position = -2147483648
    $Attributes.ValueFromPipeline = $False
    $Attributes.ValueFromPipelineByPropertyName = $False
    $Attributes.ValueFromRemainingArguments = $False
    $Attributes.HelpMessage = ' '
    $AttributeCollection = New-Object 'Collections.ObjectModel.Collection[Attribute]' 
    $AttributeCollection.Add($Attributes) 
    $MyParam = @{            
                Typ = 'Management.Automation.RuntimeDefinedParameter'            
                Arg = @(            
                    'Reverse'          
                    'System.Management.Automation.SwitchParameter'          
                    ,$AttributeCollection         
                )            
            }  
    $DynamicParam = New-Object @MyParam
    $ParamDict = New-Object 'Management.Automation.RuntimeDefinedParameterDictionary'          
    $ParamDict.Add("Reverse", $DynamicParam)
    $ParamDict 
     # endif condition }
     
     # vous pouvez testez la présence du paramètre dans le corps de la fonction dynamque par: 
     #
     # if($PSBoundParameters.ContainsKey('Reverse')) {
     #    // votre code ici 
     #    // vous pouvez utiliser votre variable dynamique comme ceci: $PSBoundParameters.Reverse
     # } 
    }
    
     End {
       
     }
    }

    on décommente if(condition) { et endif condition } et met l'instruction qu'on veux, par exemple:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    if($String -notmatch '\d') {
    ensuite, dans le corps de notre fonction (dans notre cas la partie "END") on teste la présence de notre switch $Reverse:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    End {
      if($PSBoundParameters.ContainsKey('Reverse')) {
         
      } 
    }
    puis on écris notre code, par exemple:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    End {
      if($PSBoundParameters.ContainsKey('Reverse')) {
          return  -join [regex]::Matches($String,".",'RightToLeft')  
      } 
      $String
    }
    et c'est tout:


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    function get-simple {
     [cmdletBinding()]
     param(
       [parameter(mandatory=$true)]
       [string]$String
     )
     DynamicParam { 
    
     if($String -notmatch '\d') {
    $Attributes = New-Object 'Management.Automation.ParameterAttribute' 
    $Attributes.ParameterSetName = '__AllParameterSets'            
    $Attributes.Mandatory = $False
    $Attributes.Position = -2147483648
    $Attributes.ValueFromPipeline = $False
    $Attributes.ValueFromPipelineByPropertyName = $False
    $Attributes.ValueFromRemainingArguments = $False
    $Attributes.HelpMessage = ' '
    $AttributeCollection = New-Object 'Collections.ObjectModel.Collection[Attribute]' 
    $AttributeCollection.Add($Attributes) 
    $MyParameter = @{            
                TypeName = 'Management.Automation.RuntimeDefinedParameter'            
                ArgumentList = @(            
                    'Reverse'          
                    'System.Management.Automation.SwitchParameter'          
                    ,$AttributeCollection         
                )            
            }  
    $Dynamic = New-Object @MyParameter 
    $ParamDictionary = New-Object 'Management.Automation.RuntimeDefinedParameterDictionary'          
    $ParamDictionary.Add("Reverse", $Dynamic)
    $ParamDictionary 
    }
     
     # dans le corps de la fonction vous pouvez testez la présence du paramètres
     # dynamque par: 
     #
     # if($PSBoundParameters.ContainsKey('Reverse')) {
     #      // votre code 
     #      // vous pouvez utiliser votre variable dynamique ainsi: $PSBoundParameters.Reverse
     # } 
    }
    
     End {
         if($PSBoundParameters.ContainsKey('Reverse')) {
          return  -join [regex]::Matches($String,".",'RightToLeft')  
      } 
      $String
     }
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    PS II> get-simple 'string 23' -Reverse
    # ERREUR
    PS II> get-simple "developpez.com" -Reverse
    moc.zeppoleved

  2. #2
    Rédacteur


    Profil pro
    Inscrit en
    Janvier 2003
    Messages
    7 171
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2003
    Messages : 7 171
    Points : 15 060
    Points
    15 060
    Billets dans le blog
    1
    Par défaut
    Salut Walid,
    tu nous proposes un complément intéressant. Je comptais à l'origine étendre la fonction en intégrant de la substitution, mais soit on crée autant de mots clé que de traitement dédié soit on implémente des templates.

    Je ne l'ai pas implémenté car je m'éloignais du besoin de base, quant au template il existe sous Visual Studio avec T4 (Text Template Transformation Toolkit, cf. http://msdn.microsoft.com/fr-fr/libr...v=vs.100).aspx ), et là on est plus côté développement qu'administration.

    VS proposant un outil en ligne de commande (TextTransform.exe, http://msdn.microsoft.com/en-us/library/bb126245.aspx), autant l'utiliser, c'était la prochaine étape

    La fonction Remove-Conditonnal, ne fait que filtrer du code et n'a pas pour objectif d'être un préprocessor ou un DSL, mais si on m'en proposait un natif je serais ravi !

  3. #3
    Membre chevronné
    Avatar de I'm_HERE
    Homme Profil pro
    Inscrit en
    Juillet 2008
    Messages
    1 013
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Tunisie

    Informations forums :
    Inscription : Juillet 2008
    Messages : 1 013
    Points : 1 991
    Points
    1 991
    Par défaut
    salut Laurent et merci pour ta contribution,

    voici une suite du premier post,


    prenons cette petite fonction:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    function Test-Scriptblocks {   
       [cmdletbinding()]
        Param (
          [parameter(ValueFromPipeLine=$true)]
           $Bypipeline
          ,$ByArg = $env:USERNAME)
       End {
        "EndBlock: $ByArg" | Write-Host 
       }
       Begin{
        "BeginBlock"
       }
       DynamicParam { 
        "DynamicParam:ByArgs: $($PSBoundParameters['ByArg'])" |write-host 
        "DynamicParam:ByPipeLine: $($PSBoundParameters['ByPipeline'])" |write-host 
       }
       process {
        "ProcessBlock $ByPipeline" | Write-Host
       }
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    PS II> 1..5 | Test-Scriptblocks
    DynamicParam:ByArgs: 
    DynamicParam:ByPipeLine: 
    BeginBlock
    ProcessBlock 1
    ProcessBlock 2
    ProcessBlock 3
    ProcessBlock 4
    ProcessBlock 5
    EndBlock: Walid

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    PS II> 1..5 | Test-Scriptblocks -ByArg SALAM
    DynamicParam:ByArgs: SALAM
    DynamicParam:ByPipeLine: 
    BeginBlock
    ProcessBlock 1
    ProcessBlock 2
    ProcessBlock 3
    ProcessBlock 4
    ProcessBlock 5
    EndBlock: SALAM
    comme vous voyez cette petite fonction nous montre plusieurs aspects sur le comportement du bloc DynamicParam:

    1- on peux déplacer notre scriptblock DynamicParam ou on veux dans la fonction

    2- si le paramètre statique est initialisé alors il sera évalué avant son evaluation dans le bloc DynamicParam

    3- il faut mettre explicitement au moins un bloc (begin ou End ou Process) avec le bloc DynamicParam

    4- le paramètre $Bypipeline n'est pas visible dans le bloc DynamicParam, il faut utiliser une fonction proxy couplé à un bloc DynamicParam pour manipuler le pipeline avec la technique du SteppablePipeline

Discussions similaires

  1. Réponses: 2
    Dernier message: 27/10/2009, 10h36
  2. Trigger pour mettre des droits sur des procedures et des vues
    Par briino dans le forum Développement
    Réponses: 3
    Dernier message: 23/09/2009, 09h44
  3. Réponses: 4
    Dernier message: 02/04/2008, 17h51
  4. Réponses: 3
    Dernier message: 13/09/2007, 18h11
  5. Réponses: 3
    Dernier message: 23/01/2007, 08h14

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo