Author Posts

June 24, 2015 at 10:36 am

function func1 {
$variable1 = 'somevariable'
$ScriptBlock = { echo $variable1 }
func2 -ScriptBlock $ScriptBlock
}

function func2 {
param($ScriptBlock)
## Open up $ScriptBlock and replace the text $variable1 with $using:variable1 here
Invoke-Command -Computer somecomputer -ScriptBlock $ScriptBlock
}

I would expect this to output 'somevariable' but instead I receive an error: "The value of the using variable '$using:variable1' cannot be retrieved because it has not been set in the local session."

When the scriptblock is passed from one function to another the value associated with $variable1 is stripped. Is there a way to pass that scriptblock from one function to the other and still be able to get it to work?

June 24, 2015 at 11:10 pm

Adam, in your example you try to use locally defined variable in remote session. It is a cause of $null i think

You can send variable value to remote thru -argumentlist or use splatting like
here https://mjolinor.wordpress.com/2014/01/24/splatting-parameters-pt-2-remote-possibilities/

btw, this variant show the variable value

function func1 {
$variable1 = 'somevariable'
$ScriptBlock = { echo $using:variable1 }
func2 -ScriptBlock $ScriptBlock
}

function func2 {
param($ScriptBlock)
Invoke-Command -computer somecomputer -ScriptBlock $ScriptBlock
}

June 25, 2015 at 5:35 am

Thanks for the reply, Max. I realize from that code that I gave it looks like I'm just passing the local variable and using the argument list is a method to do this but the script I'm having a problem with is much more complicated than that. I probably should have went into a little more detail on what I'm trying to do.

I'm using the AST to parse out and rename variables inside the scriptblock. I have an Invoke-ScriptBlock function that allows the user to pass a scriptblock with local session variables inside and a computername. If the computer name is local it will simply execute the scriptblock. If it's remote, it will find all [VariableExpressionAst] objects in the AST tree and attempt to prepend "$using:" to them as to make those variables expand prior to getting serialized over a WinRM session.

I had a small comment about "Open up $Scriptblock" in my example where I was trying to explain this.

June 25, 2015 at 6:57 am

[time passed... :)]

I compile all your code and it works for me if it in one scope (one module, one session)
But if I get func2 from module, but func1 from cmdline, I get your error

and THERE is the problem. different contexts 🙂
and because you construct new scriptblock you get it without original variable context
but unfortunately I cant find any way to get this context of a script block. Just description of it in .GetNewClosure() method

simple test

$m = new-module {
function func2 {
param($ScriptBlock)
#dir variable:
'vf2';
get-variable variable1;

& $ScriptBlock

}
export-modulemember -function func2
}

function func1 {
$variable1 = 'somevariable'
$ScriptBlock = { 'vf1'; get-variable variable1; echo "var1: $variable1" } #.GetNewClosure()
func2 -ScriptBlock $ScriptBlock
}

func1


vf2
get-variable : Cannot find a variable with the name 'variable1'.
At line:10 char:1
+ get-variable variable1;
+ ~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (variable1:String) [Get-Variable], ItemNotFoundException
+ FullyQualifiedErrorId : VariableNotFound,Microsoft.PowerShell.Commands.GetVariableCommand

vf1

Name Value
---- -----
variable1 somevariable
var1: somevariable

June 26, 2015 at 4:17 am

hmm, this is some hidden scope, because
if my scriptblock is get-variable variable1
'Global','Local','Script' | %{ get-variable variable1 -Scope $_}

I get variable1 on first try but not any other 3 tries

June 26, 2015 at 4:24 am

i find it in parent scope
get-variable variable1 -Scope 1`
but only inside func1

June 26, 2015 at 6:09 am

I Get it to work (pretty ugly variant, need some work, but not today 🙂 )

function func1 {
$variable1 = 'somevariable'
$ScriptBlock = { echo $variable1 }
func2 -ScriptBlock $ScriptBlock.GetNewClosure()
}

function func2 {
param($ScriptBlock)
## Open up $ScriptBlock and replace the text $variable1 with $using:variable1 here

$AstVariables = $ScriptBlock.Ast.FindAll({ $args[0] -is [System.Management.Automation.Language.VariableExpressionAst] }, $true)
$BlockText = $ScriptBlock.Ast.Endblock.Extent.Text
$selectProps = @(
	@{ n = 'Variable'; e = { $_.VariablePath.UserPath } }
	@{ n = 'Start'; e = { $_.Extent.StartOffset - $ScriptBlock.Ast.Extent.StartOffset -2 } }
	@{ n = 'End'; e = { $_.Extent.EndOffset - $ScriptBlock.Ast.Extent.StartOffset - 2} }
	'Parent'
)
$VariableLocations = $AstVariables | Select-Object -Property $selectProps | Sort-Object 'Start' -Descending
$VariableLocations | foreach {
	# If the variable is not inside double quotes and won't need to be expanded
	if ($_.Parent.Extent.Text -notmatch '^".*"$')
	{
		$NewName = '{0}:{1}' -f '$using', $_.Variable
	}
	else
	{
		$NewName = '$({0}:{1})' -f '$using', $_.Variable
	}
	$StartIndex = $_.Start
	$EndIndex = $_.End - $_.Start
	$BlockText = $BlockText.Remove($StartIndex, $EndIndex).Insert($StartIndex, $NewName)
}
# here is the magic
$SB = $ScriptBlock.Module.NewBoundScriptBlock([scriptblock]::Create($BlockText))
$SB1 = $ScriptBlock.Module.NewBoundScriptBlock({param($name) Get-Variable $name})

$AstVariables | Foreach-Object{
#TODO: here we need filtering, we do not want to import the same variables all the time
	$varname = $_.VariablePath.UserPath
	New-Variable -Name $varname -Value (& $SB1 $varname)
}

Invoke-Command -Computer somecomputer -ScriptBlock $SB
}

export-modulemember -function func2

pay attention that func1 call func2 with GetNewClosure() – Is's important

June 26, 2015 at 6:10 am

Thanks for your hard work on this, Max! I'm planning on giving it a shot today.

June 26, 2015 at 2:58 pm

Thanks, Max. That looks like that did it!

June 28, 2015 at 10:33 pm

And where is the final variant ? 🙂
btw, I make an error here. we need only value
$SB1 = $ScriptBlock.Module.NewBoundScriptBlock({param($name) Get-Variable $name -ValueOnly})

Besides variable filtering there is potential to optimize it removing (get-variable) but only if there is any way to use $variable:variablename syntax or any other way to get variable value by its name but do not use cmdlet.

And finally, in a function func2 we need to use highly unique internal variable names so we do not overwrite it with $Scriptblock variables when importing it in our own context

June 29, 2015 at 12:45 am

find a better way for variable setting

function func2 {
[CmdletBinding()]
....
$AstVariables | Foreach-Object{
#TODO: here we need filtering, we do not want to import the same variables all the time
$varname = $_.VariablePath.UserPath
New-Variable -Name $varname -Value ($PSCmdlet.SessionState.PSVariable.GetValue($varname))
}

$SB1 do not needed
thanks to http://get-powershell.com/ (link from your twitter 🙂 )