The scriptblock is a fundamental feature of the PowerShell language. Understanding the different ways to use scriptblocks is essential to for the advanced PowerShell developer.
Latest posts by Graham Beer (see all)

What is a scriptblock? ^

A scriptblock is a block of script code that exists as an object reference but does not require a name. To write a scriptblock we add a piece of code is encapsulated by curly braces {}. Scriptblocks are effectively containers that hold code and allow us to delay their execution. We use scriptblocks for a lot of tasks we do in PowerShell. Consider the following: Where, Foreach, While, Do, to name a few. But we can use scriptblocks for many other cases.

Using a scriptblock ^

For basic scriptblocks we add code between the braces {}. But doing this alone won't do very much. A lot of the commands we use do this for us, such as the Foreach statement:

1..5 | ForEach-Object { "This Number is: $_"  }

But if we want to execute a standalone scriptblock, we need to use the call invoker, &. This would look like:

$sb = { "Hello World" }
& $sb
Hello World scriptblock example

Hello World scriptblock example

Adding parameters to a scriptblock ^

Building on our "Hello World" example, you can use the param block as you would with a function. Scriptblocks are basically functions that don't have names. You would build the param block the same way you would the function. Let's look at an example:

{param($x, $y) $x + $y}

I've added a param block with two variables, $x and $y. Next, I’m simply going to add the two together. Again, I will need to use the call operator to invoke the scriptblock, but at the end of the scriptblock, I can add two values to fill my parameters in my param block. As always, an example is best:

& {param($x,  $y) $x + $y} 7  7
Scriptblock param first example

Scriptblock param first example

What we have written here is a scriptblock literal—a piece of legitimate PowerShell code surrounded by braces. Although not normally done, you could write out the above code like a function, which might help with clarity:

& {param($x,  $y) $x + $y} -x 12 -y 20
Scriptblock param second example

Scriptblock param second example

You won’t be able tab through the parameters, but you can hard define them like I've done in the example above.

Begin, Process, and End ^

You can add processing pipeline input methods—Begin, Process, and End—to a scriptblock the same way as a function. The Begin block runs at the start of the code and allows defining variables, paths, and the like. The Process block is the main processing part and contains the "doing" part of your code. Finally, the End block completes the code and serves as a post-clean-up method. Here is an example:

$ProcessingSB = {
    begin { '[Begin  ] Starting' }
    process { "[Process] $_" }
    end { Write-Output '[End    ] Finished' }
}
1, 2, 3 | & $ProcessingSB

We define the scriptblock and add each processing block. I’ve passed an array of three numbers and piped it to our scriptblock with the call operator.

Begin, Process, and End scriptblock example

Begin, Process, and End scriptblock example

Closures ^

Maybe when you create your scriptblock, you want it to use the variable defined at the time of creation and not pick up a newly assigned value to the same variable later. Sounds confusing, yes? Well here is an example that will make it clearer.

Let's create a variable I will assign a name to:

$Name = 'Graham'

Now let's create a scriptblock:

$TestName = { "My Name is: $Name" }

I will create a new variable to hold the $TestNamescriptblock but add the GetNewClosure() method to it:

$TestNameClose = $TestName.GetNewClosure()
GetNewClosure example 1

GetNewClosure example 1

I'll make a single change by giving a new name to our $Name variable:

$Name = '4sysops'

With no further changes, I'll run both scriptblocks again:

GetNewClosure example 2 with variable change

GetNewClosure example 2 with variable change

As you can see, the scriptblock without the GetNewClosure() method returns our new name of 4sysops. Our scriptblock defined with the GetNewClosure() method still returns the original value of the name variable of Graham.

Abstract syntax trees ^

An abstract syntax tree (AST) is a tree representation of the abstract syntactic structure of source code. This applies to any code written in a programming language. As the name suggests, the syntax is abstract. By this, it means it does not represent every detail appearing in the real syntax. Keeping with the theme, I'll just look at ASTs from the point of a scriptblock.

So how do I access the AST? Here is a basic example:

{$sum = $x+$y}.Ast

By adding the AST property to the end of the scriptblock, you can display the AST.

Access ASTs from a scriptblock

Access ASTs from a scriptblock

There can be a lot of information to delve into, so it's worth exploring. Let's look at another example:

$scriptblock = {    param ($int = 1)     "You have chosen the number [$int]"}

Say you want to find out if the parameter name is set in the param block and if so, to what. By using the FindAlmethod you can see if a param block was set and if it contains a value. Here is how:

$scriptblock.Ast.FindAll({$args[0] -is [System.Management.Automation.Language.ParamBlockAst]}, $true) |Select-Object -ExpandProperty parameters | Select-Object name
Finding the param block in a scriptblock and its value

Finding the param block in a scriptblock and its value

Using the AST gives you the ability to break down part your code and explore it in many ways.

Scriptblock parameters ^

Scriptblock parameters are an interesting technique rarely spoken about much. If a parameter accepts pipeline input by name or value, you can pass objects through the pipeline to execute. To show how this looks and works, I need to make sure a cmdlet accepts pipeline input. To do this I run the following code:

(Get-Command Get-Service).ParameterSets.Where{ $_.IsDefault }.Parameters.Where{ $_.ValueFromPipeline -eq $true } |
    Select-Object Name, ValueFromPipeline, ValueFromPipelineByPropertyName |
    Format-List

If it returns an object, it will look like this:

Checking a cmdlet for accepted pipeline input

Checking a cmdlet for accepted pipeline input

So I now know the Nameparameter for the Get-Servicecmdlet accepts pipeline input. I'm going to create a small CSV file with some service names to look at:

@'
Name,
BITS,
RemoteRegistry,
Spooler,
Winrm,
BITS
'@ > MyServicesCSV.csv

I've given my CSV file a header of Name. To show how this works and doesn't work, I will first try and pipe the CSV file without putting curly braces around the object I'm passing:

Trying to pipe a CSV file

Trying to pipe a CSV file

As you can see, it failed. I will now amend my code to add the curly braces. The working code looks like this:

Import-Csv -Path c:\temp\MyServices.csv |
    Get-Service -Name {$_.Name} |
    Select Name, Status

Notice the curly braces around the value for the Name parameter. Let’s try again:

Scriptblock parameter passing service names through the pipeline

Scriptblock parameter passing service names through the pipeline

It now works. This is very similar to using the Foreach loop technique but only works on values that accept pipeline input.

Compile a string to a scriptblock ^

Another useful technique is to convert a string to a scriptblock. This allows us to create dynamic commands we can wrap in a scriptblock for later execution. We do this by using a built-in PowerShell variable, $ExecutionContext. You can use this variable to find the execution objects available to cmdlets.

In this example I'm going to create a string to print a name. This is the string:

$string = '"My Name is $args"'

As we want to expand the string and use the $args variable, the inner part of the string contains double quotes whereas single quotes capture the outside of the string.

Next, we call $ExecutionContext using the InvokeCommand property. This lets us call the NewScriptBlock method and add our string:

$StringToSB = $ExecutionContext.InvokeCommand.NewScriptBlock($string)

I will then call $StringToSB and pass in a name:

Calling the NewScriptBlock method from ExecutionContext

Calling the NewScriptBlock method from ExecutionContext

We get the desired outcome by passing in a name to the scriptblock as we hoped.

We can actually do this with a little less typing. If we look at the typename of our $StringToSB variable, we can see it uses System.Management.Automation.ScriptBlock:

StringToSB typename

StringToSB typename

This means we could shorten the line to compile the string by using the ScriptBlocktype:

Scriptblock type to compile string

Scriptblock type to compile string

By using the $sb = [scriptblock]::Create($string) line of code, we have achieved the same results but with less typing.

Subscribe to 4sysops newsletter!

Wrap-up ^

I hope you've enjoyed this article. As you can see, there is a lot to scriptblocks. Hopefully this will give you some ideas on how you can expand on my examples and use them in your own code. There is certainly more you can do with scriptblocks, and it's an area I certainly enjoy exploring.

avataravataravataravatar
7 Comments
  1. Ramon Tan 3 years ago

    Hello Mr Graham Beer,

    Thank you for this excellent article.  I followed every example faithfully, and every one of them ran perfectly — I was using VS Code with shell=pwsh.exe Ver 6.1.1 on Windows 10 — until the example of Abstract Syntax Trees.  I enclose below a portion of the error message.  I underscore that I ran the code by using the “Run Selected Text” in the TERMINAL button on the menu of VS Code.  I could not figure out after 2 hours why it always fails on the First Pipe symbol (char 105 it says on the error message) .

    At line:1 char:105
    + ... -is [System.Management.Automation.Language.ParamBlockAst]}, $True) |
    +                                                                         ~
    An empty pipe element is not allowed.

    Would be grateful for some tips and guidance.

    Thanking you in advance.

     

  2. Author

    Hi Ramon

    Thank you for your kind words, i’m glad you enjoyed the article.

    The pipeline symbol, ‘|’, will expect code after to work. I’ve taken the code from above and used it in the latest version of PS core and it worked.

    Here it is in full, try copying and pasting it and rerun:

    $scriptblock = { param ($int = 1) “You have chosen the number [$int]” }
    $scriptblock.Ast.FindAll({$args[0] -is [System.Management.Automation.Language.ParamBlockAst]}, $true) |
    Select-Object -ExpandProperty parameters |
    Select-Object name

    Hope this helps.

     

    avatar
  3. Ramon Tan 3 years ago

    My sincerest thanks Mr, Graham Beer — this has been a very educational experience!

    Best,

  4. Ramon Tan (Rank 2) 3 years ago

    Many thanks to Mr Fullenwarth for the reference to the article in Get-PowerShell blog:

    “Bye Bye Backtick: Natural Line Continuations in PowerShell”

    It is a very well-written and very comprehensive article.  In reading it, I have discovered new and useful “technical secrets” of Powershell in respect of the way it works.

    Many thanks!

    avatar

Leave a reply to Leos Marek (Rank 4) Click here to cancel the reply

Please enclose code in pre tags

Your email address will not be published.

*

© 4sysops 2006 - 2022

CONTACT US

Please ask IT administration questions in the forums. Any other messages are welcome.

Sending

Log in with your credentials

or    

Forgot your details?

Create Account