Variables losing value in a scriptblock when passed to a function

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?

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
}

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.

[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 :slight_smile:
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

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

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

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

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

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

Thanks, Max. That looks like that did it!

And where is the final variant ? :slight_smile:
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

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 :slight_smile: )