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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
1 2 3 4 5
| End {
if($PSBoundParameters.ContainsKey('Reverse')) {
}
} |
puis on écris notre code, par exemple:
Code:
1 2 3 4 5 6 7
|
End {
if($PSBoundParameters.ContainsKey('Reverse')) {
return -join [regex]::Matches($String,".",'RightToLeft')
}
$String
} |
et c'est tout:
Code:
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:
1 2 3 4 5
|
PS II> get-simple 'string 23' -Reverse
# ERREUR
PS II> get-simple "developpez.com" -Reverse
moc.zeppoleved |