Skip to content
Menu
The Lonely Administrator
  • PowerShell Tips & Tricks
  • Books & Training
  • Essential PowerShell Learning Resources
  • Privacy Policy
  • About Me
The Lonely Administrator

The PowerShell Day Care: Building ScriptBlocks

Posted on September 22, 2011

Good morning kids and welcome to the PowerShell Day Care center. We offer a creative and nurturing environment for PowerShell professionals of all ages. Later there might even be juice and cookies. But first let's get out our blocks, our scriptblocks, and start building. I've written a number of posts on script blocks and today have a script to offer, that even if you don't need it as is, it might offer a few insights into PowerShell.

Manage and Report Active Directory, Exchange and Microsoft 365 with
ManageEngine ADManager Plus - Download Free Trial

Exclusive offer on ADManager Plus for US and UK regions. Claim now!

A scriptblock is a small (usually) chunk of PowerShell code enclosed in curly braces. You see them all the time in cmdlets like Where-Object and Start-Job. Think of scriptblocks as modular and re-usable commands that write objects to the pipeline. Often I think you can use a scriptblock for simple tasks instead of creating a function. For example, suppose I want an easy way to get the current day of the year.

[cc lang="DOS"]
PS C:\> (Get-Date).DayOfyear
265
[/cc]

Short and simple. But I'm lazy. I don't want to have to type that all the time. And while I could save it as a variable, if my PowerShell session is open for a few days, the variable will become stale. This is a simple example anyway. I could create a function but perhaps wrapping this in a scriptblock would be a better approach.

[cc lang="PowerShell"]
$doy={(Get-Date).DayOfyear}
[/cc]

The variable, $doy, is the command.

[cc lang="DOS"]
PS C:\> $doy
(Get-Date).DayOfyear
PS C:\>
[/cc]

To run the scriptblock I can use the invoke operator &.

[cc lang="DOS"]
PS C:\> &$doy
265
[/cc]

Or perhaps we want to quickly get running services. We might create a scriptblock like this:

[cc lang="PowerShell"]
$run={get-service | where {$_.status -eq 'running'}}
[/cc]

If I invoke $run, the command will execute, just as if I had typed it. Where things get tricky is when you want to build a scriptblock from other variables. Remember, scriptblocks run in their own scope. I run into this more often in scripting where I'm building a scriptblock from variables that will execute later. Here's an example of the problem.

[cc lang="PowerShell"]
$log=Read-Host "Enter a log name, ie system"
[int]$count=Read-Host "How many entries do you want?"
$sb={get-eventlog -log $log -newest $count}
start-job $sb
[/cc]

The job gets created but fails because it can't find values for $log and $count. The solution I came up with is to create a string first, taking advantage of variable expansion. Then explicitly define a scriptblock.

[cc lang="PowerShell"]
$log=Read-Host "Enter a log name, ie system"
[int]$count=Read-Host "How many entries do you want?"

$cmd="get-eventlog -log $log -newest $count"

$sb=$executioncontext.InvokeCommand.NewScriptBlock($cmd)
start-job $sb
[/cc]

This is slightly more complicated, but $cmd is defined with the expanded variables and then I create the scriptblock. For you overachievers, you can combine steps and do this:

[cc lang="PowerShell"]
$sb=$executioncontext.InvokeCommand.NewScriptBlock("get-eventlog -log $log -newest $count")
[/cc]

Now, let's take this to the next level. I have a number of scriptblocks that I want to define in my PowerShell profile. I could simply define them in the profile or a dot sourced script but that's too easy. So I wrote a script that parses a text file of scriptblock commands and creates them. First, here's the text file that gets parsed.

[cc lang="DOS"]
#script block commands
#name delimiter command
#default delimiter is the ~
#each command must be on one line

dc01 ~ mstsc -v jdhit-dc01 /console
run ~ gsv | where {$_.status -eq "running"}
bigp ~ ps | where {$_.ws -gt 100mb}
disk ~ Param ([string]$computername=$env:computername) get-wmiobject win32_logicaldisk -filter "drivetype=3" -computername $computername | Select "DeviceID","VolumeName",@{Name="SizeGB";Expression={"{0:N4}" -f ($_.size/1GB)}},@{Name="Freespace";Expression={"{0:N4}" -f ($_.FreeSpace/1GB)}},@{Name="Utilization";Expression={ "{0:P2}" -f (1-($_.freespace -as [double])/($_.size -as [double]))}}
doy ~ (get-date).DayOfYear
up ~ Param([string]$computername=$env:computername) Get-WmiObject win32_operatingsystem -computername $computername |Select @{name="Computer";Expression={$_.CSName}},@{Name="LastBoot";Expression={$_.ConvertToDateTime($_.LastBootUpTime) }},@{Name="Uptime";Expression={(Get-Date)-($_.ConvertToDateTime($_.LastBootUpTime))}}
dirt ~ Param([string]$Path=$env:temp) Get-ChildItem $path -Recurse -force -ea "silentlycontinue" | measure-object -Property Length -sum | Select @{Name="Path";Expression={$path}},Count,@{Name="SizeMB";Expression={"{0:N4}" -f ($_.sum/1mb)}}
[/cc]

The structure of the file is scriptblock name, a delimiter (I"m using ~) and the scriptblock command. The command must be one line. Notice scriptblocks can also take parameters. This file is parsed by my LoadCmds.ps1 script.

[cc lang="PowerShell"]
[cmdletbinding()]

param (
[Parameter(Position=0)]
[ValidateNotNullorEmpty()]
[string]$Path="cmds.txt",
[string]$Delimiter="~",
[switch]$Passthru
)

Write-Verbose "Starting $($myinvocation.mycommand)"

if (Test-Path $path) {
Write-Verbose "Processing $path"

#filter out blanks and lines that start with a comment
Get-Content $path | Where { ($_ -match $delimiter) -AND (!($_.Trim().StartsWith("#"))) } |
foreach -begin { $script:newvariables=@() } -process {
#split the line at the ~
$data=$_.Split("~")
$sb=$data[0].Trim()
$cmd=$data[1].Trim()
Write-Verbose "Creating $sb"
#add the scriptblock name to the array
$script:newvariables+=$sb
#create a script block object and assign it to the value variable
$value=$executioncontext.InvokeCommand.NewScriptBlock($cmd)
#define the new variable
Set-Variable -name $sb -Value $value -Scope Global
} -end {
if ($Passthru) {
#write the new variables to the pipeline if -Passthru
get-variable -Name $script:newvariables
}
} #close end
} #close if

else {
Write-Warning "Failed to find $path"
}

Write-Verbose "Ending $($myinvocation.mycommand)"
#end
[/cc]

The script takes parameters for the filepath and delimiter. It also uses -Passthru if you want to see the variables that get created. Assuming the file is found blanks and commented lines are filtered out.

[cc lang="PowerShell"]
Get-Content $path | Where { ($_ -match $delimiter) -AND (!($_.Trim().StartsWith("#"))) } |
[/cc]

Each line is piped to ForEach-Object where I take advantage of the Begin, Process and End parameters. In the Begin scriptblock I define a script variable for an array to hold each command I'm going to create. In the Process scriptblock each line is split into an array on the delimiter.

[cc lang="PowerShell"]
foreach -begin { $script:newvariables=@() } -process {
#split the line at the ~
$data=$_.Split("~")
$sb=$data[0].Trim()
$cmd=$data[1].Trim()
[/cc]

The variable name is added to the array and the scriptblock defined.

[cc lang="PowerShell"]
$script:newvariables+=$sb
#create a script block object and assign it to the value variable
$value=$executioncontext.InvokeCommand.NewScriptBlock($cmd)
[/cc]

All that is left is to define the scriptblock variable and set it to the global scope.

[cc lang="PowerShell"]
#define the new variable
Set-Variable -name $sb -Value $value -Scope Global
[/cc]

After each line in the text file has been processed the End script block runs and if -Passthru was used, the array with new scriptblock names is written to the pipeline.

[cc lang="PowerShell"]
-end {
if ($Passthru) {
#write the new variables to the pipeline if -Passthru
get-variable -Name $script:newvariables
}
} #close end
[/cc]

Whenever I want to add something, I update the text file. One advantage to the text file is that it is not a PowerShell executable so my execution policy doesn't apply.

If you want to try this out you can download a copy of LoadCmds. Otherwise, see what you can build with scriptblocks and be sure to share.


Behind the PowerShell Pipeline

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on Mastodon (Opens in new window) Mastodon
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to print (Opens in new window) Print
  • Click to email a link to a friend (Opens in new window) Email

Like this:

Like Loading...

Related

5 thoughts on “The PowerShell Day Care: Building ScriptBlocks”

  1. Rob Campbell says:
    September 22, 2011 at 8:47 pm

    Question:

    Is there any reason to prefer this:
    $sb=$executioncontext.InvokeCommand.NewScriptBlock($cmd)
    over
    $sb = [scriptblock]::create($cmd) ?

    Interesting bit:
    Given that same scriptblock that returns multiple objects, this will return [object[]]
    &$sb
    and this will return a generic collection
    $sb.invoke()

    1. Jeffery Hicks says:
      September 23, 2011 at 6:38 am

      Thanks for the feedback. There isn’t any specific reason for creating the scriptblock the way I did. I think it is merely old cold that I re-purposed without considering any newer alternatives. That’s a good lesson right there!

      1. Rob Campbell says:
        September 23, 2011 at 6:57 am

        Thanks! I use that method when I need to create script blocks dynamically. I’ve used the older method, but I find the create meathod much easier to read (and remember).

        Something else interesting I found with V2 was that, while they added the getnewclosure method for re-closing a scriptblock around a set of variables, it actually tested faster to create a brand new script block using an expandable string with those variables than to invoke that method on an existing scriptblock.

      2. Jeffery Hicks says:
        September 23, 2011 at 9:10 am

        Can you give a few examples that explain?

  2. Rob Campbell says:
    September 23, 2011 at 7:32 pm

    I did a blog article on that some time back:

    http://mjolinor.wordpress.com/2011/02/13/getnewclosure-vs-scriptblockcreate/

Comments are closed.

reports

Powered by Buttondown.

Join me on Mastodon

The PowerShell Practice Primer
Learn PowerShell in a Month of Lunches Fourth edition


Get More PowerShell Books

Other Online Content

github



PluralSightAuthor

Active Directory ADSI Automation Backup Books CIM CLI conferences console Friday Fun FridayFun Function functions Get-WMIObject GitHub hashtable HTML Hyper-V Iron Scripter ISE Measure-Object module modules MrRoboto new-object objects Out-Gridview Pipeline PowerShell PowerShell ISE Profile prompt Registry Regular Expressions remoting SAPIEN ScriptBlock Scripting Techmentor Training VBScript WMI WPF Write-Host xml

©2025 The Lonely Administrator | Powered by SuperbThemes!
%d