salut,
invoke-expression, ou comment générer du code dynamiquement depuis une chaine de caractères, on va tout au long de ce petit how-to comprendre des techniques diverses pour travailler avec cette cmdlet, j'ai choisi de decouper ce howto en 3 sections qui sortent de l'ordinaire:
LES BONS:
- ajouter des fonctionnalités au code:
parfois on veux créer rapidement une fonctionnalité qui n'existe pas par defaut, prenons le cas de l'operateur "-replace" cet opérateur nous permet d'utiliser les RegExp's mais ne permet pas d'utiliser de delegués 'expression-lambda', pour palier a ce problème on peux utiliser une fonction externe et 'essayer de simuler les délégués'
c'est parfait 'dans certains cas', mais ça reste limité:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 PS > function foo($m) {"$m" * 2} PS > $str="Aa1b2c3B" PS > $str -creplace '([a-z])',(foo '$1') Aaa1bb2cc3B
une autre méthode plus puissante mais aussi plus 'artisanale' est de générer du code avec 'invoke-expression', Laurent nous a fait un bel exemple avec ceci:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 PS > function foo($m) {"$m".ToUpper()} PS > $str="Aa1b2c3B" PS > $str -creplace '([a-z])',(foo '$1') Aa1b2c3B
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 $S='www.developpez.com' #http://stormimon.developpez.com/dotnet/expressions-regulieres/#L3.7.2 $Pattern='(?<=\.)(.)' #En plus compliqué : génération de code #Ne nécessite pas de délégué, mais de l'aspirine Invoke-Expression "`"$($S -replace $Pattern,'$(''$1''.ToUpper())')`""
LES BRUTES
- executer du code externe
je voulais mettre ceci danbs la section des 'MECHANTS' mais puisque ça m'arrive souvent de le faire, alors je le mets dans cette section.
parfois, on téléchage du NET, (de source fiable bien sur) des fichiers 'txt' contenant du code PS issu de conférence sur techdays ou autres,j'essaye d'executer ces codes dans un environment restreint, et puisque je suis un 'Lazy Man' j'utilise 'invoke-expression' pour executer tout le code en une seul fois, surtout si c'est une fonction ayant une centaine de lignes:
REMARQUE: je sais que ceci n'est pas une bonne habitude , surtout si les codes à executer ne sont pas de sources fiables, mais le but est de montrer que cette technique est possible.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14 PS > cat source_fiable.txt function test-fonctionde10000ligne { write-host 'Hello' } # PS est intelligent il nous retourne une erreur parlante PS > powershell -file source_fiable.txt Échec du traitement de -File «*source_fiable.txt*», car le fichier ne comporte pas d'extension «*.ps1*». Spécifiez un nom de fichier de script PowerShell valide, puis réessayez. # on execute le code contenu dans le fichier et non pas le fichier en lui même. # 'iex' est l'alias de 'invoke-expression' PS > cat source_fiable.txt | Out-String | iex PS > test-fonctionde10000ligne Hello
LES MECHANTS
securité
- injection de code:
l'injection de code est le pire ennemi des commandes de saisi d'utilisateur, 'Read-Host' n'echappe pas de cette "règle".
une solution, pour remedier à ce problème est de tester la saisi utilisateur avant de la passé à 'invoke-expression', pour ceci on a créer une petite fonction 'pouvant être extensible' pour verifier nos chaines de caractères:
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 PS > $prompt = read-host "Password" Password: developpez.com ; rm $env:temp -whatif PS > Invoke-Expression "echo $prompt" developpez.com WhatIf*: Opération «*Supprimer le répertoire*» en cours sur la cible «*x:\Documents and Settings\xxxx\Local Settings\Temp*». PS > $prompt = read-host "Password" Password: developpez.com ; &{echo "attaque reussi ;)"} PS > Invoke-Expression "echo $prompt" developpez.com attaque reussi ;) PS > $prompt = read-host "Password" Password: developpez.com ; .{ stop-process -n *ss -whatif} PS > Invoke-Expression "echo $prompt" developpez.com WhatIf*: Opération «*Stop-Process*» en cours sur la cible «*csrss (580)*». WhatIf*: Opération «*Stop-Process*» en cours sur la cible «*lsass (668)*». WhatIf*: Opération «*Stop-Process*» en cours sur la cible «*smss (512)*».
- passer au delà de la restriction de la session
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 PS > function Protect-Code { >> param([Parameter(ValueFromPipeLine=$true)][string]$InputObject) >> $test = $false >> $test = $inputObject -match "(?<!'|"")[\.&]{.+}(?!""|')" >> if($test) >> { write-Error $matches[0] >> return $false >> } >> $Parts = $InputObject.Split(';') >> if($Parts) >> { >> foreach($Part in $Parts) { >> $token,$null = $Part.Trim().Split(' ') >> if($token | get-command 2>$null) >> { write-error "$token" >> return $false >> } >> } >> } >> $InputObject >> } PS > $prompt = Read-Host -Prompt "Password" | Protect-Code Password: pa@sw@rd; .{rm . -whatif} Protect-Code : .{rm . -whatif} PS > $prompt = Read-Host -Prompt "Password" | Protect-Code Password: pa@sw@rd; ls Protect-Code : ls
en générant du code et en l'appelant dynamiquement, invoke-expression va creer son propre environement d'execution et va executer notre code:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 PS > function TopSecret {'Hello'} PS > TopSecret Hello PS > (Get-Command TopSecret).Visibility = 'Private' PS > TopSecret Le terme «*TopSecret*» n'est pas reconnu comme nom d'applet de commande, fonction, fichier de script ou proamme exécutable.Remarque: on pouvez aussi appelez notre code avec le nom de la fonction directement:
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 PS > $tokens = 't' + 'o' + 'p' + 's' + 'e' + 'c' + 'r' + 'e' + 't' PS > Invoke-Expression "$tokens" Hello PS > Invoke-Expression "get-command $tokens" CommandType Name Definition ----------- ---- ---------- Function TopSecret 'Hello' PS > Invoke-Expression "(get-command $tokens).Visibility = 'Public'" PS > TopSecret Hello
mais j'ai voulu montré la flexibilité et le dynamisme de cette cmdlet.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2 PS II> Invoke-Expression "topsecret"
Revenons maintenant à notre fonction 'TopSecret', Powershell a plusieurs couche de sécurités dans son runspace, la première couche, comme nous l'avons vu est le choix entre rendre notre 'code' privée ou public, la deuxième couche est de mettre une restriction sur le langage, les restrictions possibles sont:
par défaut c'est 'FullLanguage', essayons maintenant avec 'RestrictedLanguage' pour voir si cette 'barrière' peux être vraiment une 'barrière' devant du code générés:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 PS > [enum]::getvalues('Management.Automation.PSLanguageMode') FullLanguage RestrictedLanguage NoLanguage
il faut une couche de securite de plus pour securiser cette faille:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10 PS > function TopSecret {'Hello'} PS > (gcm TopSecret).visibility="private" PS > &{TopSecret} Hello PS > $ExecutionContext.SessionState.LanguageMode = 'restrictedLanguage' PS > &{TopSecret} Les littéraux de blocs de script ne sont pas autorisés en mode de langage restreint ou dans une section Data. PS > iex "TopSecret" Hello
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 PS > powershell PS > function TopSecret {'Hello'} PS > (gcm TopSecret).visibility="private" PS > $ExecutionContext.SessionState.LanguageMode = 'nolanguage' PS > iex "Hello" La syntaxe n'est pas prise en charge par cette instance d'exécution. Cela peut être dû au fait qu'elle est en mode sans langue.
performence
l'execution du code avec 'invoke-expression' souffre de problèmes de performance, c'est plus gourmand en mémoire et plus long en temps d'execution, parceque le parseur parse essaye de parser en deux étapes, la première 'invoke-expression' en elle-même puis le code à générer
prenons l'exemple des boucles, l'execution de 'invoke-expression' dans une boucle est extrement longue , il vaux mieux générer le code de la boucle.
etapes d'interpretation des variables:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 PS > (measure-command {for($i=0; $i -lt 1000; $i++) {iex '"test"'}}).TotalMilliseconds 376,3031 PS > (measure-command {iex 'for($i=0; $i -lt 1000; $i++) {"test"}'}).TotalMilliseconds 4,869
ces étapes peuvent rendre nos codes plus compliqué à comprendre mais comme à dit Laurent necessite de l'aspirine
voici un petit exemple montrant différentes étapes d'execution de variables:
en raison, de "ces levels d'executions" et "les règles d'utilisation des guillemets simples et doubles" le debogage du code généré avec "invoke-expression" peux devenir extrement compliqué.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12 PS > $cmd='write-host -fore red -object ' PS > $inp='$Mystr' PS > $Mystr = '$Another' PS > PS > invoke-expression "$cmd ```$inp" $inp PS > invoke-expression "$cmd ``````$inp" `$Mystr PS > invoke-expression "$cmd ````````````$inp" ```$Another PS >
Partager