Consider the following call site:
$modifiedLocal = 'original local value'
'input object' | SomeScriptblockInvoker {
$modifiedLocal = 'modified local value'
[pscustomobject] @{
Local = $local
DollarBar = $_
}
}
$modifiedLocal
I would like to implement SomeScriptblockInvoker
such that
The output of the function at the call site would be the following:
Local DollarBar
----- ---------
local input object
modified local value
PowerShell seems to be capable of doing this. For example replacing SomeScriptblockInvoker
with ForEach-Object
yields exactly the desired output.
I have come close using the following definition:
New-Module m {
function SomeScriptblockInvoker {
param
(
[Parameter(Position = 1)]
[scriptblock]
$Scriptblock,
[Parameter(ValueFromPipeline)]
$InputObject
)
process
{
$InputObject | . $Scriptblock
}
}
} |
Import-Module
The output of the call site using that definition is the following:
Local DollarBar
----- ---------
local
modified local value
Note that DollarBar
is empty when it should be input object
.
In general, you can't. The caller of a scriptblock does not have control over the SessionState associated with that scriptblock and that SessionState determines (in part) the context in which a scriptblock is executed (see the Scope section for details). Depending on where the scriptblock is defined, its SessionState might match the caller's context, but it might not.
With respect to the context in which the scriptblock is executed, there are two related considerations:
Here is a good explanation of how this works.
$_
Automatic Variable$_
contains the current object in the pipeline. The scriptblock provided to %
is interpreted differently from the scriptblock provided .
and &
:
'input_object' | % {$_}
- The value of $_
is 'input_object'
because the scriptblock is bound to %
's -Process
parameter. That scriptblock is executed once for each object in the pipeline.'input_object' | . {process{$_}}
and 'input_object' | & {process{$_}}
- The value of $_
is 'input_object'
because the $_
in the scriptblock is inside a process{}
block which executes once for each object in the pipeline.Note that in the OP $_
was empty when the scriptblock was invoked using .
. This is because the scriptblock contained no process{}
block. Each of the statements in the scriptblock were implicitly part of the scriptblock's end{}
block. By the time an end{}
block is run, there is no longer any object in the pipeline and $_
is null.
.
vs &
vs %
.
, &
, and %
each invoke the scriptblock using the SessionState of the scriptblock with some differences according to the following table:
+---+-----------------+-----------+-------------------+----------------------+
| | Name | Kind | interprets {} as | adds scope to stack |
+---+-----------------+-----------+-------------------+----------------------+
| % | ForEach-Object | Command | Process block | No |
| . | dot-source | Operator | scriptblock | No |
| & | call | Operator | scriptblock | Yes |
+---+-----------------+-----------+-------------------+----------------------+
%
command has other parameters corresponding to Begin{}
and End{}
blocks.The two most viable options for passing the tests in OP are as follows. Note that neither invokes the scriptblock strictly in the caller's context but rather in a context using the SessionState associated with the scriptblock.
Change the call site so that the scriptblock includes process{}
:
$modifiedLocal = 'original local value'
'input object' | SomeScriptblockInvoker {
process {
$modifiedLocal = 'modified local value'
[pscustomobject] @{
Local = $local
DollarBar = $_
}
}
}
$modifiedLocal
And invoke the scriptblock using SomeScriptblockInvoker
in OP.
Invoke the scriptblock using %
as suggested by PetSerAl.