8

Given I have:

$a = "world"
$b = { write-host "hello $a" }

How do I get the resolved text of the script block, which should be the entre string including write-host:

write-host "hello world"

UPDATE: additional clarifications

If you just print $b you get the variable and not the resolved value

write-host "hello $a"

If you execute the script block with & $b you get the printed value, not the contents of the script block:

hello world

This question is seeking a string containing the contents of the script block with the evaluated variables, which is:

write-host "hello world"
5
  • Possible duplicate of Pass arguments to a scriptblock in powershell Commented Mar 8, 2019 at 13:14
  • you have a scriptblock and you need to invoke/run the scriptblock. [grin] simply calling the $Var that holds the scriptblock will give the literal content, not run it. you can run it thus ... Invoke-Command -ScriptBlock $b output = hello world Commented Mar 8, 2019 at 14:43
  • 1
    I dont believe this is a duplicate because i am not executing the script block - i want a string of its syntax with evaluated variables Commented Mar 8, 2019 at 14:57
  • @Lee_Dailey - as you can see from the answer, you can do that using $ExecutionContext.InvokeCommand.ExpandString($b) Commented Mar 8, 2019 at 16:52
  • @alastairtree - ha! i learned something new! [grin] i will delete this comment soon - and immediately delete my wrong comment to avoid confusing folks. Commented Mar 8, 2019 at 17:00

2 Answers 2

15

As in the original question, if your entire scriptblock contents is not a string (but you want it to be) and you need variable substitution within the scriptblock, you can use the following:

$ExecutionContext.InvokeCommand.ExpandString($b)

Calling .InvokeCommand.ExpandString($b) on the current execution context will use the variables in the current scope for substitution.

The following is one way to create a scripblock and retrieve its contents:

$a = "world"
$b = [ScriptBlock]::create("write-host hello $a")
$b

write-host hello world

You can use your scriptblock notation {} as well to accomplish the same thing, but you need to use the & call operator:

$a = "world"
$b = {"write-host hello $a"}
& $b

write-host hello world

A feature to using the method above is that if you change the value of $a at any time and then call the scriptblock again, the output will be updated like so:

$a = "world"
$b = {"write-host hello $a"}
& $b
write-host hello world
$a = "hi"
& $b
write-host hello hi

The GetNewClosure() method can be used to create a clone of the scriptblock above to take a theoretical snapshot of the scriptblock's current evaluation. It will be immune to the changing of the $a value later the code:

$b = {"write-host hello $a"}.GetNewClosure()
& $b
write-host hello world
$a = "new world"
& $b
write-host hello world

The {} notation denotes a scriptblock object as you probably already know. That can be passed into Invoke-Command, which opens up other options. You can also create parameters inside of the scriptblock that can be passed in later. See about_Script_Blocks for more information.

Sign up to request clarification or add additional context in comments.

8 Comments

Sorry, yeah meant $a not $b, although I think the question still stands. Updated the question
This is not correct and misses the crus of the question - printing $b just prints write-host "hello $a" and the question asks for write-host "hello world"
Found a short answer. Just run this $executioncontext.invokecommand.expandstring($b).
This approach doesn't work for script blocks that contain assignment or local variables, e.g.: $ExecutionContext.InvokeCommand.ExpandString({$x=1; $x}) yields =1;. I am looking for a way to truly serialize a closure to include all referenced variables so that I can execute it outside of the current execution context. Not sure if this is possible as PowerShell uses dynamic scoping for closures according to what-exactly-is-a-powershell-scriptblock.
A promising approach might be to extract variables from the SessionState Module and then use ScriptBlock.InvokeWithContext. Would probably need to walk the ScriptBlock AST to correctly substitute variables with strings.
|
-1

Another way to do it is:

$a = "world"
$b = "write-host ""hello $a"""
$b

The output is:

write-host "hello world"

1 Comment

Sorry this is a different question - I need a script block not a string

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.