- Using AWS Lambda functions with Docker containers - Fri, May 12 2023
- A Go AWS SDK example - Fri, Nov 11 2022
- Getting started with Jenkins - Tue, Aug 16 2022
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
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
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
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.
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()
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:
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.
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 FindAll method 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
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:
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:
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:
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:
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:
This means we could shorten the line to compile the string by using the ScriptBlocktype:
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.
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.
Sorry, when I placed everything on one line, it worked! I suppose this is due to VS Code? Perhaps not. My question is: is there a way to format the code across several lines and not get such errors? Would be grateful for some guidance. Many thanks.
@rtan2010
Here is one of the most comprehensive post about the subject.
https://get-powershellblog.blogspot.com/2017/07/bye-bye-backtick-natural-line.html
But if you want to exchange more about this specific subject, the best way is to do as Leos suggested and open a forum topic.
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.
My sincerest thanks Mr, Graham Beer — this has been a very educational experience!
Best,
Hi Ramon,
you may join the 4sysops Powershell Group and open a forum topic, we will be happy to help you.
Leos
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!