4 min read
Bulk Testing PowerShell Scripts with the Tokenizer
In Part 1, we explored the internal PowerShell parser to see how it analyzes code and breaks it down into individual...
Unlocking the Power of PowerShell: Tips for Success
Want to make your PowerShell scripts faster and more flexible? Learn how to optimize the pipeline with script blocks and reusable functions!
Did you know that you can assign PowerShell control structures to variables, but you cannot pipe them through the PowerShell pipeline?
This works fine:
# this works
$result = for ($x=0; $x-lt100; $x+=3)
{
$x
}
$result
However, piping the results from a control structure to another command just fails:
# this fails
for ($x=0; $x-lt100; $x+=3)
{
$x
} | Out-GridView
That’s because PowerShell’s pipeline operator (|) is closely tied to script blocks ({}).
Here’s how you can stream-enable any control structure and add real-time processing to it:
# this works
. {
for ($x=0; $x-lt100; $x+=3)
{
$x
}
} | Out-GridView
All it takes is enclosing the control structure in a script block and calling it using the . operator (no new variable scope is created). With this knowledge, you can already use the superfast classic foreach or do loops inside a pipeline.
The entire PowerShell pipeline concept is driven by script blocks. You can replace ForEach-Object or Where-Object with simple script blocks.
All three lines produce the same result:
Get-Service | ForEach-Object { "Working on $($_.DisplayName)..." }
Get-Service | ForEach-Object -Process { "Working on $($_.DisplayName)..." }
Get-Service | . { process { "Working on $($_.DisplayName)..." } }
Likewise, all three lines produce the same result:
Get-Service | Where-Object Status -eq Running
Get-Service | Where-Object { $_.Status -like 'Running' }
Get-Service | . { process { if ($_.Status -like 'Running') { $_ } } }
Why is that useful? Because you can now run ForEach-Object in its own variable scope if needed. Simply replace the . operator with &.
More importantly, you can easily turn ForEach-Object and Where-Object into standalone functions, allowing you to create reusable code. Let’s illustrate this with an example.
Here’s a "normal" pipeline approach that lists all files in the Windows folder that have been changed within the past 24 hours:
$now = Get-Date
$cutoff = $now.AddHours(-24)
Get-ChildItem -Path c:\Windows -File | Where-Object { $_.LastWriteTime -gt $cutoff }
It works well in this particular script; however, filtering files by 'age' is something that could be useful in many scripts. You may not always want to start from scratch. Let’s see how the new script block knowledge can help turn ad-hoc commands like ForEach-Object and Where-Object into new and useful specialized commands.
When translated to script blocks, the previous code looks like this:
$now = Get-Date
$cutoff = $now.AddHours(-24)
Get-ChildItem -Path c:\Windows -File | & { process { if ($_.LastWriteTime -gt $cutoff) { $_ } } }
Any script block can easily be turned into a function by using the "function" keyword and giving it a name:
function Filter-NewFile
{ process { if ($_.LastWriteTime -gt $cutoff) { $_ } } }
$now = Get-Date
$cutoff = $now.AddHours(-24)
Get-ChildItem -Path c:\Windows -File | Filter-NewFile
At this point, the code is much more structured and easier to read. What’s better, the function now runs about 10 times faster than ForEach-Object or Where-Object.
To make the function truly reusable, you’d only need to internalize the helper variables it requires. With just a few changes, the initial code has become a versatile, reusable command that can be useful in many future projects:
function Filter-NewFile
{
param
(
[int]$Hours
)
begin
{
$now = Get-Date
$cutoff = $now.AddHours(-$Hours)
}
process
{
if ($_.LastWriteTime -gt $cutoff)
{ $_ }
}
}
Get-ChildItem -Path c:\Windows -File | Filter-NewFile -Hours 480
Unleash the full potential of PowerShell with our handy poster. Whether you're a beginner or a seasoned pro, this cheat sheet is designed to be your go-to resource for the most important and commonly used cmdlets.
The poster is available for download and in paper form.
Apr 16, 2025 by Aleksandar Nikolić and Dr. Tobias Weltner
In Part 1, we explored the internal PowerShell parser to see how it analyzes code and breaks it down into individual...
Apr 11, 2025 by Aleksandar Nikolić and Dr. Tobias Weltner
The internal PowerShell parser processes any code before execution. With full access to this parser, you can analyze...
Apr 3, 2025 by Aleksandar Nikolić and Dr. Tobias Weltner
In part 3, we identified a useful .NET method to display system dialogs and then wrapped it inside a new PowerShell...
Tobias Weltner and Aleksandar Nikolić joinly wrote the blog post series 'Tobias&Aleksandar's PowerShell tips'. So we introduce both of them here:
----------------------------
Aleksandar Nikolić is a Microsoft Azure MVP and co-founder of PowerShellMagazine.com, the ultimate online source for PowerShell enthusiasts. With over 18 years of experience in system administration, he is a respected trainer and speaker who travels the globe to share his knowledge and skills on Azure, Entra, and PowerShell. He has spoken at IT events such as Microsoft Ignite, ESPC, NIC, CloudBrew, NTK, and PowerShell Conference Europe.
----------------------------
Tobias is a long-time Microsoft MVP and has been involved with the development of PowerShell since its early days. He invented the PowerShell IDE "ISESteroids", has written numerous books on PowerShell for Microsoft Press and O'Reilly, founded the PowerShell Conference EU (psconf.eu), and is currently contributing to the advancement of PowerShell as member in the "Microsoft Cmdlet Working Group". Tobias shares his expertise as a consultant in projects and as a trainer in in-house trainings for numerous companies and agencies across Europe.