Variable Substitution in a PowerShell Script Block

Summary: Microsoft Scripting Guy, Ed Wilson, talks about performing variable substitution inside a Windows PowerShell script block.

Hey, Scripting Guy! Question Hey, Scripting Guy!  I am trying to create a command. The command contains variables that I would like to assign prior to creating the command. However, when I get to the script block portion of my code, it does not do the variable substitution for the value the way an expanding string normally works. Can you help me?


Hey, Scripting Guy! Answer Hello SW,

Microsoft Scripting Guy, Ed Wilson, is here. Last week at the first ever Northern Virginia PowerShell User Group, the Scripting Wife and I found a great little tea shop. I can’t help but thinking about the scone I got there—it was pretty good.

Expanding variable values

One of the really cool things about Windows PowerShell is the expanding strings—I absolutely love them. It surely beats having to do lots of string concatenation like I had to do back in the VBScript days. To illustrate this technique, I assign a string to the value of a variable—in this case, the $a variable. I look at the value contained in the variable. This code is shown here.

PS C:> $a = "This is a string"

PS C:> $a

This is a string

Now, by using the expanding string, I can see the value that is contained inside the $a variable. To suppress the variable expansion, I escape it with the grave accent character as shown here.

PS C:> "The value of `$a is $a"

The value of $a is This is a string

The problem with a script block

If, on the other hand, I want to expand the value of a variable inside a script block, it does not work. This is shown in the code that follows.

PS C:> $a = "This is a string"

PS C:> $a

This is a string

PS C:> $b = {"The value of `$a is $a"}

PS C:> $b

"The value of `$a is $a"

Solving the problem with variable expansion in a script block

The solution to expanding a variable inside a script block is to do two things. First create the script block as an expanding string. This is shown here:

PS C:> $a = "This is a string"

PS C:> $a

This is a string

PS C:> $b = "The value of `$a is $a"

PS C:> $b

The value of $a is This is a string

Now, I use the static Create method from the [scriptblock] class. This will create a script block. To do this, I use the [scriptblock] class and then call the Create method while passing the string contained in the $b variable. This is shown here.

PS C:> [scriptblock]::Create($b)

The value of $a is This is a string

I can confirm that it is in fact a script block by piping the results to the Get-Member cmdlet as shown here:

PS C:> [scriptblock]::Create($b) | gm


   TypeName: System.Management.Automation.ScriptBlock


Name                    MemberType Definition

----                    ---------- ----------

CheckRestrictedLanguage Method     void CheckRestrictedLanguage(System.Collection...

Equals                  Method     bool Equals(System.Object obj)

GetHashCode             Method     int GetHashCode()

GetNewClosure           Method     scriptblock GetNewClosure()

GetObjectData           Method     void GetObjectData(System.Runtime.Serializatio...

GetPowerShell           Method     powershell GetPowerShell(Params System.Object[...

GetSteppablePipeline    Method     System.Management.Automation.SteppablePipeline...

GetType                 Method     type GetType()

Invoke                  Method     System.Collections.ObjectModel.Collection[psob...

InvokeReturnAsIs        Method     System.Object InvokeReturnAsIs(Params System.O...

ToString                Method     string ToString()

Ast                     Property   System.Management.Automation.Language.Ast Ast ...

Attributes              Property   System.Collections.Generic.List[System.Attribu...

File                    Property   string File {get;}

IsFilter                Property   bool IsFilter {get;set;}

Module                  Property   psmoduleinfo Module {get;}

StartPosition           Property   System.Management.Automation.PSToken StartPosi...

Now, the cool thing about this is that I can also store the script block into another variable. This is shown here:

$sb = [scriptblock]::Create($b)

After I have stored the script block into the variable, I can also call any of the methods or properties of the script block. For example, here is the AST:

PS C:> $sb.Ast


ParamBlock         :

BeginBlock         :

ProcessBlock       :

EndBlock           : The value of $a is This is a string

DynamicParamBlock  :

ScriptRequirements :

Extent             : The value of $a is This is a string

Parent             :

It is not horribly exciting in this example, but for more complex code, it is definitely exciting stuff. We have a great Hey, Scripting Guy! Blog post written by Bartek Bielawski, which offers several good ideas for further exploration: Learn How IT Pros Can Use the PowerShell AST.

SW, that is all there is to using variable expansion and substitution in a Windows PowerShell script block.  Join me tomorrow when I will talk about way cool Windows PowerShell stuff.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

Comments (6)

  1. Maybe I’m missing the point but I think the example is missing the operator "&". Basically, if you use $b, it returns the script block itself, which gets converted to a string and returns the original code of the script block. But if you use &$b, that
    would execute the script block and return the string computed by it which will include the value of $a in the nearest enclosing block.

    Which of course is not the same as hardcoding the value from a variable at the time when the script block is created but also useful.

    But the code generation shown here is not right either, it creates a script block with the script in it being { The value of $a is This is a string }, which would fail if you try to execute it with &([scriptblock]::Create($b)). The right way would be to do

    $b = "`"The value of “`$a is $a`""

  2. x says:

    Is it not better to get a new closure?

  3. In general, we try to discourage the dynamic creation of code because of the problems that arise when doing variable substitution.  If you get the quoting wrong (so easy to do) then you end up injecting unexpected code into your script block.  In general, you're better off either adding parameters to the scriptblock that are resolved at execution time, as in $sb = {param($x, $y) $x+$y); & $sb 2 3 or, as the other posted suggested, use a closure for permanently associating values with a scriptblock. Of course there are situations where the goal is not simple value substitution and you really are intending to generate new code artifacts but these advanced scenarios are not common.

  4. Julius says:

    I’m missing something probably very simple:
    What about situations where you want to store a PoSH command in a variable that also includes a reference to another variable? Consider

    $arrSystems = @("sys1","sys2", … "sysN")
    foreach($System in $arrSystems) {
    $Command = GCI -Path "C:" -Include "something" -Recurse -EA SilentlyContinue | Select LastWriteTime,Fullname | Out-File "C:SomePath$System.log"
    $Sess – New-PSSession -ComputerName $System
    Invoke-Command -Session $Sess -ScriptBlock { $Command }

    In its current form PoSH will execute the command I want to run and store the output of the command in $Command versus storing the entire expanded command as a variable.

    My kneejerk reaction was to enclose the command in curly braces:
    $Command = { GCI -Path "C:" -Include "something" -Recurse -EA SilentlyContinue | Select LastWriteTime,Fullname | Out-File "C:SomePath$System.log" }

    And although that does the store command as a variable, $System is not expanded, so I have to do something like:
    $Command = ‘ GCI -Path "C:" -Include "something" -Recurse -EA SilentlyContinue | Select LastWriteTime,Fullname | Out-File "C:SomePath’ + $System + ‘.log"’

    Is that just the way in these circumstances or am I not thinking fourth-dimensionally Marty?

  5. Michael Simmons says:


    If I read this post right, then you would want

    $arrSystems = @("sys1","sys2", … "sysN")
    foreach($System in $arrSystems) {
    $Command = "GCI -Path ‘C:’ -Include ‘something’ -Recurse -EA SilentlyContinue | Select LastWriteTime,Fullname | Out-File ‘C:SomePath$System.log’ "
    $CommandScriptBlock = [Scriptblock]::Create($Command)
    $Sess – New-PSSession -ComputerName $System
    Invoke-Command -Session $Sess -ScriptBlock $CommandScriptBlock

Skip to main content