Question (Doc] Bloc de script à liaison retardée

Plus d'informations
il y a 13 ans 8 mois #7394 par Laurent Dardenne
Je vous propose de documenter un point sur PowerShell qui est peu connu il me semble.
Il s'agit du comportement d'un argument en tant que bloc de script à liaison retardée (delay-bind ScriptBlock).

Quand l'argument d'un paramètre est de type [ScriptBlock], que le paramètre n'est pas déclaré avec le type [System.Objet] ou [ScriptBlock] et que le paramètre récupére l'objet dans le pipeline (ValueFromPipeline=$true), l'exécution de ce ScriptBlock est retardé jusqu'à ce que des données soient fournies dans le pipeline.

L'objet issu du pipeline est passé en argument au ScriptBlock en tant que $_, le résultat de l'exécution du ScriptBlock est ensuite
lié comme argument du paramètre.

Quelques exemples :
[code:1]
Function Test{
param (
[Parameter(Mandatory=$true,ValueFromPipeline = $true)]
[System.Management.Automation.PSObject] $InputObject)

Write-Host $inputObject.GetType().Fullname
$inputObject
}
Get-Date| Test
#Reçoit un objet de type DateTime et affiche la date
Get-Date| Test -inputobject {$_.DayOfWeek}
#Reçoit un objet de type String et affiche le contenu de la propriété DayOfWeek

Function Test1{
param (
[Parameter(Mandatory=$true,ValueFromPipeline = $true)]
[System.Object] $InputObject)

Write-Host $inputObject.GetType().Fullname
$inputObject
}

$O=new-object System.Object
$O|Add-Member NoteProperty DayOfWeek \"Dimanche\"
$O|Add-Member NoteProperty Heure 12

$O| Test1
#Reçoit un objet de type Object et affiche toutes ses propriétés
$O| Test1 -inputobject {$_.DayOfWeek}
#Provoque une erreur de liaison (binding)
#Reçoit un objet de type System.Management.Automation.ScriptBlock et affiche son contenu

Function Test2{
param (
[Parameter(Mandatory=$true,ValueFromPipeline = $true)]
[ScriptBlock] $InputObject)

Write-Host $inputObject.GetType().Fullname
$inputObject
}

$sb={Write-host \"Test\" -fore green}
$sb| Test2
#Reçoit un objet de type System.Management.Automation.ScriptBlock et affiche
# le contenu de l'objet émis dans le pipeline
$sb| Test2 -inputobject {$_.IsFilter}
#Provoque une erreur de liaison (binding)
#Reçoit un objet de type System.Management.Automation.ScriptBlock, mais affiche
# cette fois le contenu de l'argument.
[/code:1]
On peut retrouver ce comportement à l'aide du cmdlet Trace-Command :
[code:1]
Trace-Command parameterbinding {$input| Test -inputobject {$_.DayOfWeek}} -pshost -inputobject (Get-Date)
[/code:1]
Ce qui nous affiche :

DÉBOGUER : ParameterBinding Information: 0 : BIND NAMED cmd line args [Test]
DÉBOGUER : ParameterBinding Information: 0 : Adding ScriptBlock to delay-bind list for parameter 'InputObject'
DÉBOGUER : ParameterBinding Information: 0 : BIND POSITIONAL cmd line args [Test]
DÉBOGUER : ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Test]
DÉBOGUER : ParameterBinding Information: 0 : CALLING BeginProcessing
DÉBOGUER : ParameterBinding Information: 0 : BIND PIPELINE object to parameters: [Test]
DÉBOGUER : ParameterBinding Information: 0 : Invoking delay-bind ScriptBlock
DÉBOGUER : ParameterBinding Information: 0 : BIND arg [Thursday] to parameter [InputObject]
DÉBOGUER : ParameterBinding Information: 0 : Executing DATA GENERATION metadata:
[System.Management.Automation.ArgumentTypeConverterAttribute]
DÉBOGUER : ParameterBinding Information: 0 : result returned from DATA GENERATION: Thursday
DÉBOGUER : ParameterBinding Information: 0 : COERCE arg to [System.Management.Automation.PSObject]
DÉBOGUER : ParameterBinding Information: 0 : Parameter and arg types the same, no coercion is needed.
DÉBOGUER : ParameterBinding Information: 0 : BIND arg [Thursday] to param [InputObject] SUCCESSFUL
DÉBOGUER : ParameterBinding Information: 0 : PIPELINE object TYPE = [System.DateTime]
DÉBOGUER : ParameterBinding Information: 0 : RESTORING pipeline parameter's original values
DÉBOGUER : ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Test]
DÉBOGUER : ParameterBinding Information: 0 : CALLING EndProcessing
...

Pour ce qui est des fonctions de tests provoquant une erreur, le binding n'est pas retardé :
[code:1]
Trace-Command parameterbinding {$input| Test1 -inputobject {$_.DayOfWeek}} -pshost -inputobject $O
[/code:1]

DÉBOGUER : ParameterBinding Information: 0 : BIND NAMED cmd line args [Test1]
DÉBOGUER : ParameterBinding Information: 0 : BIND arg [$_.DayOfWeek] to parameter [InputObject]
DÉBOGUER : ParameterBinding Information: 0 : Executing DATA GENERATION metadata:
[System.Management.Automation.ArgumentTypeConverterAttribute]
DÉBOGUER : ParameterBinding Information: 0 : result returned from DATA GENERATION: $_.DayOfWeek
DÉBOGUER : ParameterBinding Information: 0 : COERCE arg to [System.Object]
DÉBOGUER : ParameterBinding Information: 0 : Parameter and arg types the same, no coercion is needed.
DÉBOGUER : ParameterBinding Information: 0 : BIND arg [$_.DayOfWeek] to param [InputObject] SUCCESSFUL
DÉBOGUER : ParameterBinding Information: 0 : BIND POSITIONAL cmd line args [Test1]
DÉBOGUER : ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Test1]
DÉBOGUER : ParameterBinding Information: 0 : CALLING BeginProcessing
...

Vous trouverez un exemple dans l'aide en ligne de du cmdlet Select-String.

Pour plus de détails, voir les classes privées suivantes (cf. Reflector):
System.Management.Automation.CmdletParameterBinderController
System.Management.Automation.CmdletParameterBinderController+DelayedScriptBlockArgument

[edit]
Les spécifications de la V2 :
If the parameter type is not object or scriptblock, an argument having type scriptblock is evaluated and its result is passed as the argument's value. (This is known as delayed script block binding.) If the parameter type is object or scriptblock, an argument having type scriptblock is passed as is.
Message édité par: Laurent Dardenne, à: 15/07/10 19:37<br><br>Message édité par: Laurent Dardenne, à: 5/03/14 18:12

Tutoriels PowerShell

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

Plus d'informations
il y a 13 ans 8 mois #7413 par Laurent Dardenne
Voici un exemple de niveau avancé qui creuse le sujet.
L'objectif est de créer un objet personnalisé puis de l'utiliser avec certains cmdlets.
On paramètre le cmdlet à partir de l'objet émit dans le pipeline.

Un usage classique de new-item :
[code:1]
del C:\Temp\Test_ToDelete.tmp
new-item -path \&quot;C:\Temp\&quot; -name Test_ToDelete.tmp -type \&quot;File\&quot; -value \&quot;Fichier de test.\&quot; -force
Type C:\Temp\Test_ToDelete.tmp
[/code:1]
On peut très bien paramètrer cet appel :
[code:1]
$path=\&quot;C:\Temp\&quot;
$name=\&quot;Test_ToDelete.tmp\&quot;
$Content=\&quot;Fichier de test.\&quot;
new-item -path $Path -name $FileName -type \&quot;File\&quot; -value $Content -Force
Type C:\Temp\Test_ToDelete.tmp
[/code:1]
Si cela vous suffit, pas besoin de lire la suite ;-)

De mon coté comme j'utilise le plus souvent des objets personnalisés et le pipeline, ces derniers temps je me suis dit que cela serait bien de pouvoir coupler les deux.
Pour détailler les paramètres d'une commande on utilisera cette fonction . Elle renvoi le résultat suivant :
[code:1]
Get-Parameter New-Item
# ParameterSet: pathSet
#
# Name Type Pos BV BP Aliases Mandatory Dynamic
# ---- ---- --- -- --


# Confirm SwitchParameter Named False False {cf, co} False False
# Credential PSCredential Named False True {cr} False False
# Force SwitchParameter Named False False {f} False False
# ItemType String Named False True {Type, i} False False
# *Path String[] 1 False True {p} True False
# UseTransaction SwitchParameter Named False False {usetx, u} False False
# Value Object Named True True {v} False False
# WhatIf SwitchParameter Named False False {wi, w} False False
#
#
# ParameterSet: nameSet
#
# Name Type Pos BV BP Aliases Mandatory Dynamic
# ---- ---- --- -- --


# Confirm SwitchParameter Named False False {cf, co} False False
# Credential PSCredential Named False True {cr} False False
# Force SwitchParameter Named False False {f} False False
# ItemType String Named False True {Type, i} False False
# *Name String Named False True {n} True False
# *Path String[] 1 False True {p} False False
# UseTransaction SwitchParameter Named False False {usetx, u} False False
# Value Object Named True True {v} False False
# WhatIf SwitchParameter Named False False {wi, w} False False
[/code:1]
Le cmdlet New-Item propose 2 jeux de paramètres, chaque jeux a un paramètre obligatoire et seul le paramètre Value est émit par valeur dans le pipeline .
Tous les paramètres qui nous intéressent (path, name, type et value) sont passés dans le pipeline par nom de propriétés.

A partir de ces informations on peut créer notre objet personnalisé, chaque nom de ses membres correspond à un nom de paramètre du cmdlet ciblé :
[code:1]
$F=New-Object PSObject -Property @{Name=\&quot;Test_ToDelete.tmp\&quot;;Path=\&quot;C:\Temp\&quot;;Value=\&quot;Fichier de test.\&quot;;Type=\&quot;File\&quot;}
[/code:1]
Pour la suite des explications on utilisera le compte-rendu du cmdlet Trace-Command, qui trace l'exécution d'une suite d'instructions.

Les lignes suivantes sont communes, en début de fichier, aux trois exemples à venir :

#...
#MANDATORY PARAMETER CHECK on cmdlet [New-Item]
#CALLING BeginProcessing
#BIND PIPELINE object to parameters: [New-Item]

et les suivantes le sont en fin de fichier :

# Parameter [Credential] PIPELINE INPUT ValueFromPipelineByPropertyName WITH COERCION
#MANDATORY PARAMETER CHECK on cmdlet [New-Item]
#CALLING BeginProcessing
#...

Dans notre exemple on ne gére pas tous les paramètres du cmdlet, Credential par exemple.
Premier essai, le premier qui vient à l'esprit :
[code:1]
del C:\Temp\Test_ToDelete.tmp
Trace-Command parameterbinding {$F|new-item } -filepath C:\Temp\newitem1.Log #-pshost
[/code:1]
Ici on obtient une exception, car le paramètre Name n'est pas lié :

# Parameter [Value] PIPELINE INPUT ValueFromPipeline NO COERCION
# BIND arg [@{Value=Fichier de test.; Name=Test_ToDelete.tmp; Path=C:\Temp; Type=File}] to param [Value] SUCCESSFUL
# Parameter [Path] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
# BIND arg [System.String[]] to param [Path] SUCCESSFUL
# Parameter [ItemType] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
# BIND arg [File] to param [ItemType] SUCCESSFUL

Ainsi, la liaison utilise le jeux de paramètre nommé pathSet, le moteur ne recherche donc pas à lier le paramètre Name.
On peut le savoir en utilisant la trace (cf. Get-Trace) suivante :
[code:1]
Trace-Command ParameterBinderController {$F|new-item -name {$_.name}} -filepath C:\Temp\newitem1-1.Log
[/code:1]
C'est ici que la liaison retardée (delay-bind ScriptBlock) va nous être utile.
En précisant le paramètre Name on force le moteur de PS à utiliser le jeux de paramètre nommé nameSet :
[code:1]
Trace-Command parameterbinding {$F|new-item -name {$_.name} -Force } -filepath C:\Temp\newitem2.Log #-pshost
Type C:\Temp\Test_ToDelete.tmp
[/code:1]

# Invoking delay-bind ScriptBlock
# BIND arg [Test_ToDelete.tmp] to parameter [Name]
# BIND arg [Test_ToDelete.tmp] to param [Name] SUCCESSFUL
# Parameter [Value] PIPELINE INPUT ValueFromPipeline NO COERCION
# BIND arg [@{Value=Fichier de test.; Name=Test_ToDelete.tmp; Path=C:\Temp; Type=File}] to param [Value] SUCCESSFUL
# Parameter [Path] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
# BIND arg [System.String[]] to param [Path] SUCCESSFUL
# Parameter [ItemType] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
# BIND arg [File] to param [ItemType] SUCCESSFUL

Par contre, on constate que le contenu du fichier n'est pas celui attendu :

@{Value=Fichier de test.; Name=Test_ToDelete.tmp; Path=C:\Temp; Type=File}

Le paramètre Value étant de type Object, il n'y a pas de conversion de type.
On doit redéfinir la méthode ToString() de notre objet personnalisé :
[code:1]
$F|Add-member ScriptMethod ToString {$this.Value} -Force
Trace-Command parameterbinding {$F|new-item -name {$_.name} -Force} -filepath C:\Temp\newitem3.Log #-pshost
Type C:\Temp\Test_ToDelete.tmp
[/code:1]
La lecture du fichier de trace nous confirme que cette fois le résultat est bien celui attendu :

# Invoking delay-bind ScriptBlock
# BIND arg [Test_ToDelete.tmp] to param [Name] SUCCESSFUL
# Parameter [Value] PIPELINE INPUT ValueFromPipeline NO COERCION
# BIND arg [Fichier de test.] to param [Value] SUCCESSFUL
# Parameter [Path] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
# BIND arg [System.String[]] to param [Path] SUCCESSFUL
# Parameter [ItemType] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
# BIND arg [File] to param [ItemType] SUCCESSFUL

Essayons un autre cmdlet :
[code:1]
Get-Parameter New-Variable
# ParameterSet: __AllParameterSets
#
# Name Type Pos BV BP Aliases Mandatory Dynamic CommandName
# ---- ---- --- -- --



# PassThru SwitchParameter Named False False {p} False False
# Force SwitchParameter Named False False {f} False False
# Scope String Named False False {s} False False
# Confirm SwitchParameter Named False False {cf, c} False False
# WhatIf SwitchParameter Named False False {wi, w} False False
# Value Object 2 True True {va} False False
# Name String 1 False True {n} True False
# Description String Named False False {d} False False
# Visibility SessionStateEntryVisibility Named False False {vi} False False
# Option ScopedItemOptions Named False False {o} False False


gv T*
Trace-Command parameterbinding {$F|new-Variable } -pshost
gv T*
rv Test_ToDelete.tmp
[/code:1]
Pour ce cmdlet, le binding se fait simplement sans utiliser la liaison retardée.
PowerShell ? Ouai c'est pas mal :lol:

[edit]
Un autre exemple :
[code:1]Get-ChildItem -Path *.txt |Rename-Item -NewName {$_.name -replace \&quot;.txt$\&quot;,\&quot;.bat\&quot;} -whatif[/code:1]<br><br>Message édité par: Laurent Dardenne, à: 24/07/10 12:41

Tutoriels PowerShell

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

Plus d'informations
il y a 11 ans 6 mois #12794 par jojo
Laurent Dardenne écrit:

Quand l'argument d'un paramètre est de type [ScriptBlock], que le paramètre n'est pas déclaré avec le type [System.Objet] ou [ScriptBlock] et que le paramètre récupére l'objet dans le pipeline (ValueFromPipeline=$true), l'exécution de ce ScriptBlock est retardé jusqu'à ce que des données soient fournies dans le pipeline.


hello Laurent :)

merci pour tes explication, mais j'ai pas compris une chose, vous dites que le le paramètre doit récupérer l'objet dans le pipeline (ValueFromPipeline=$true) pour que le delay-bind fonctionne alors que dans mes tests la propriété -property du cmdlet sort-object permet ce genre de binding sans qu'elle accepte une entrée par le pipeline:
[code:1]
PS D:\&gt; Get-Parameter Sort-Object


ParameterSet: __AllParameterSets

Name Type Pos BV BP Aliases Mandatory Dynamic
---- ---- --- -- --


CaseSensitive SwitchParameter Named False False {ca} False False
Culture String Named False False {cu} False False
Descending SwitchParameter Named False False {d} False False
InputObject PSObject Named True False {i} False False
* Property Object[] 1 False False {p} False False
Unique SwitchParameter Named False False {u} False False



PS D:\&gt; Get-Process | Sort-Object -Property {$_.Name[-1]}
# ça fonctionne[/code:1]

pouvez m'éclaircir sur ce point, merci :)

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

Plus d'informations
il y a 11 ans 6 mois #12796 par Laurent Dardenne
Bonjour Jojo,
jojo écrit:

pouvez m'éclaircir sur ce point, merci :)

Ici, je pense que c'est le code du cmdlet qui gère le scriptblock et pas le parseur. Le delay-bind n'est donc pas déclenché, la régle n'étant pas respectée:

[STA] C:\temp&gt; get-help Sort-Object -parameter property|more

-Property &lt;Object[]&gt;
Specifies the properties to use when sorting. Objects are sorted based on the values of these properties. Enter
the names of the properties. Wildcards are permitted.

If you specify multiple properties, the objects are first sorted by the first property. If more than one object
has the same value for the first property, those objects are sorted by the second property. This process continues
until there are no more specified properties or no groups of objects.

If you do not specify properties, the cmdlet sorts based on default properties for the object type.

The value of the Property parameter can be a calculated property. To create a calculated, property, use a hash
table. Valid keys are:

-- Expression &lt;string&gt; or &lt;script block&gt;

-- Ascending &lt;Boolean&gt;

-- Descending &lt;Boolean&gt;

Obligatoire ? false
Position ? 1
Valeur par défaut Default properties
Accepter l'entrée de pipeline ? false
Accepter les caractères génériques ? true

Ceci je n'ai pas trouvé trace de ce comportement dans les specs publiées de PS.<br><br>Message édité par: Laurent Dardenne, à: 28/09/12 11:49

Tutoriels PowerShell

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

Plus d'informations
il y a 11 ans 6 mois #12797 par jojo
bonjour Laurent et merci,

donc il ne s'agit pas de delay-bind ScriptBlock :blink:

j'ai trouvé un autre code semblable dans le NET, mais j'ai pas trouvé les règles de ce compotement bizzaroide :silly:

[code:1]
PS D:\&gt; Get-Parameter Copy-Item


ParameterSet: Path

Name Type Pos BV BP Aliases Mandatory Dynamic
---- ---- --- -- --


Confirm SwitchParameter Named False False {cf, conf} False False
Container SwitchParameter Named False False {cont} False False
Credential PSCredential Named False True {cr} False False
Destination String 2 False True {d} False False
Exclude String[] Named False False {e} False False
Filter String Named False False {fi} False False
Force SwitchParameter Named False False {fo} False False
Include String[] Named False False {i} False False
PassThru SwitchParameter Named False False {pas} False False
*Path String[] 1 True True {pat} True False
Recurse SwitchParameter Named False False {r} False False
UseTransaction SwitchParameter Named False False {usetx, u} False False
WhatIf SwitchParameter Named False False {wi, w} False False





ParameterSet: LiteralPath

Name Type Pos BV BP Aliases Mandatory Dynamic
---- ---- --- -- --


Confirm SwitchParameter Named False False {cf, conf} False False
Container SwitchParameter Named False False {cont} False False
Credential PSCredential Named False True {cr} False False
Destination String 2 False True {d} False False
Exclude String[] Named False False {e} False False
Filter String Named False False {fi} False False
Force SwitchParameter Named False False {fo} False False
Include String[] Named False False {i} False False
*LiteralPath String[] 1 False True {PSPath, l} True False
PassThru SwitchParameter Named False False {p} False False
Recurse SwitchParameter Named False False {r} False False
UseTransaction SwitchParameter Named False False {usetx, u} False False
WhatIf SwitchParameter Named False False {wi, w} False False



PS D:\&gt; Copy-Item -Path $source -Destination $destination -Filter {$_.PSIsContainer} -Recurse -Force[/code:1]

stackoverflow.com/questions/12448513/cre...ll/12455327#12455327

es-ce qu'on peux appeler ce comportement delay-unbind ScriptBlock :P

es-ce qu'on peux \&quot;l'emuler\&quot; dans nos fonctions avancées ?

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

Plus d'informations
il y a 11 ans 6 mois #12801 par Laurent Dardenne
jojo écrit:

est-ce qu'on peux appeler ce comportement delay-unbind ScriptBlock

Je ne sais pas, faut le temps d'étudier ce cas.

jojo écrit:

est-ce qu'on peut \&quot;l'émuler\&quot; dans nos fonctions avancées ?

C'est le parseur qui s'en charge, enfin à vérifier dans les exemples précédents.

Tutoriels PowerShell

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

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