Words: 700
Time to read: ~ 3.5 minutes
FizzBuzz
Let’s take the a moderation of FizzBuzz as an example. Let’s get the running totals of the fizz buzz numbers.
The initial way that I would do this would be a straight up switch
statement
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#region Normal way | |
[int]$FizzRunningTotal = 0 | |
[int]$FizzBuzzRunningTotal = 0 | |
[int]$BuzzRunningTotal = 0 | |
# Works | |
1..15 | ForEach-Object { | |
switch ($_) { | |
{ $_ % 15 -eq 0 } { | |
$FizzBuzzRunningTotal = $FizzBuzzRunningTotal + $_ | |
break | |
} | |
{ $_ % 5 -eq 0 } { | |
$BuzzRunningTotal = $BuzzRunningTotal + $_ | |
break | |
} | |
{ $_ % 3 -eq 0 } { | |
$FizzRunningTotal = $FizzRunningTotal + $_ | |
break | |
} | |
} | |
[PSCustomObject]@{ | |
Number = $_ | |
FizzRunningTotal = $FizzRunningTotal | |
BuzzRunningTotal = $BuzzRunningTotal | |
FizzBuzzRunningTotal = $FizzBuzzRunningTotal | |
} | |
} | Format-Table –AutoSize |

ScriptBlocks
Now what happens if we don’t want to use switch? What happens if we tried to do this with script blocks instead?
I’ve created 3 different script blocks here just for ease of use. If you want to create a single script block to do this then I encourage it! Let me know how you get on.
We’re also passing in the parameter into the script block by passing it in the brackets of the .InvokeReturnAsIs()
method.
Quick little tip, if you want to see what you can do with methods, run the method without specifying brackets
([Scriptblock]::Create('$i')).InvokeReturnAsIs

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[int]$FizzRunningTotal = 0 | |
[int]$FizzBuzzRunningTotal = 0 | |
[int]$BuzzRunningTotal = 0 | |
# Scriptblocks | |
$Mod15 = [Scriptblock]::Create('param([int]$Number) $FizzBuzzRunningTotal = $FizzBuzzRunningTotal + $_') | |
$Mod5 = [Scriptblock]::Create('param([int]$Number) $BuzzRunningTotal = $BuzzRunningTotal + $_') | |
$Mod3 = [Scriptblock]::Create('param([int]$Number) $FizzRunningTotal = $FizzRunningTotal + $_') | |
1..15 | ForEach-Object { | |
switch ($_) { | |
{ $_ % 15 -eq 0 } {$Mod15.InvokeReturnAsIs($_); break } | |
{ $_ % 5 -eq 0 } {$Mod5.InvokeReturnAsIs($_); break } | |
{ $_ % 3 -eq 0 } {$Mod3.InvokeReturnAsIs($_); break } | |
} | |
[PSCustomObject]@{ | |
Number = $_ | |
FizzRunningTotal = $FizzRunningTotal | |
BuzzRunningTotal = $BuzzRunningTotal | |
FizzBuzzRunningTotal = $FizzBuzzRunningTotal | |
} | |
} | Format-Table –AutoSize |
So we run the above and the results that we get are…

0 + 3 is 0 apparently…as well as 10 + 5 being 0 as well? Splendid!
Why? Let’s try troubleshooting it first.
Troubleshooting
When trying to figure out what’s wrong, I figured I’d move the Modulus operator to be inside the script block and just return the value instead of saving the value to the variable.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[int]$FizzRunningTotal = 0 | |
[int]$FizzBuzzRunningTotal = 0 | |
[int]$BuzzRunningTotal = 0 | |
$Mod15 = [Scriptblock]::Create('param([int]$Number) if ($Number % 15 -eq 0) { $FizzBuzzRunningTotal + $_ }') | |
$Mod5 = [Scriptblock]::Create('param([int]$Number) if ($Number % 5 -eq 0) { $BuzzRunningTotal + $_ }') | |
$Mod3 = [Scriptblock]::Create('param([int]$Number) if ($Number % 3 -eq 0) { $FizzRunningTotal + $_ }') | |
1..15 | ForEach-Object { | |
[PSCustomObject]@{ | |
Number = $_ | |
FizzRunningTotal = $Mod3.InvokeReturnAsIs($_) | |
BuzzRunningTotal = $Mod5.InvokeReturnAsIs($_) | |
FizzBuzzRunningTotal = $Mod15.InvokeReturnAsIs($_) | |
} | |
} | Format-Table –AutoSize |
Lo and behold it works!…ish

Great-ish!
There’s something subtle that I took from this and it’s probably best I show you. If you take a look at the screenshot above and you can see the numbers are aligned to the left. Yet I declared my variables to be int
s and that means that they should be right aligned like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[int]$number = 5 | |
[PSCustomObject]@{ Id = 1; Num = $number } | |
[string]$number2 = 5 | |
[PSCustomObject]@{ Id = 1; Num = $number } |

Also I’m sure you noticed the lack of 0’s in the screenshot? Another confirmation.
[int]$i; [string]$i

We can see that an int
defaults to 0 but a string
defaults to nothing.
My take away from this is that my script blocks are able to find my variables that I set at the start of the script. They just seem to have a problem with updating them for me…
Then let’s just update the variables from the value output by the script blocks, shall we? All we need for this is $var = $Scriptblock.Invoke()
.
Good but bloated
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[int]$FizzRunningTotal = 0 | |
[int]$FizzBuzzRunningTotal = 0 | |
[int]$BuzzRunningTotal = 0 | |
$Mod15 = [Scriptblock]::Create('param([int]$Number) $FizzBuzzRunningTotal + $_ ') | |
$Mod5 = [Scriptblock]::Create('param([int]$Number) $BuzzRunningTotal + $_ ') | |
$Mod3 = [Scriptblock]::Create('param([int]$Number) $FizzRunningTotal + $_ ') | |
1..15 | ForEach-Object { | |
switch ($_) { | |
{ $_ % 15 -eq 0 } { | |
$FizzBuzzRunningTotal = $Mod15.InvokeReturnAsIs($_) | |
break | |
} | |
{ $_ % 5 -eq 0 } { | |
$BuzzRunningTotal = $Mod5.InvokeReturnAsIs($_) | |
break | |
} | |
{ $_ % 3 -eq 0 } { | |
$FizzRunningTotal = $Mod3.InvokeReturnAsIs($_) | |
break | |
} | |
} | |
[PSCustomObject]@{ | |
Number = $_ | |
FizzRunningTotal = $FizzRunningTotal | |
BuzzRunningTotal = $BuzzRunningTotal | |
FizzBuzzRunningTotal = $FizzBuzzRunningTotal | |
} | |
} | Format-Table –AutoSize |

Leaner?
Can we make that leaner? Let’s try putting the modulus check back up into the script block again.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[int]$FizzRunningTotal = 0 | |
[int]$FizzBuzzRunningTotal = 0 | |
[int]$BuzzRunningTotal = 0 | |
$Mod15 = [Scriptblock]::Create('param([int]$Number) if ($Number % 15 -eq 0) { $FizzBuzzRunningTotal + $_ }') | |
$Mod5 = [Scriptblock]::Create('param([int]$Number) if ($Number % 5 -eq 0) { $BuzzRunningTotal + $_ }') | |
$Mod3 = [Scriptblock]::Create('param([int]$Number) if ($Number % 3 -eq 0) { $FizzRunningTotal + $_ }') | |
1..15 | ForEach-Object { | |
$FizzRunningTotal = $Mod3.InvokeReturnAsIs($_) | |
$BuzzRunningTotal = $Mod5.InvokeReturnAsIs($_) | |
$FizzBuzzRunningTotal = $Mod15.InvokeReturnAsIs($_) | |
[PSCustomObject]@{ | |
Number = $_ | |
FizzRunningTotal = $FizzRunningTotal | |
BuzzRunningTotal = $BuzzRunningTotal | |
FizzBuzzRunningTotal = $FizzBuzzRunningTotal | |
} | |
} | Format-Table –AutoSize |
Which gives us the results:

Dammit! We’re back to the default values again; we’re not outputting any values from the script blocks when they don’t match the modulus. So we’re getting 0s back.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[int]$Num = 2 | |
$sb = [ScriptBlock]::Create('if ($Num -eq 2) { $Num + $Num }') | |
$Num = $sb.InvokeReturnAsIs() | |
$Num | |
$Num = 1 | |
$Num = $sb.InvokeReturnAsIs() | |
$Num |
Because our script blocks doesn’t execute any code when $Num
is not equal to 2, it returns nothing and that nothing is getting converted to a 0!
A quick change to return the variable no matter what and we should be good to go!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[int]$FizzRunningTotal = 0 | |
[int]$FizzBuzzRunningTotal = 0 | |
[int]$BuzzRunningTotal = 0 | |
$Mod15 = [Scriptblock]::Create('param([int]$Number) if ($Number % 15 -eq 0) { $FizzBuzzRunningTotal + $_ } else { $FizzBuzzRunningTotal }') | |
$Mod5 = [Scriptblock]::Create('param([int]$Number) if ($Number % 5 -eq 0) { $BuzzRunningTotal + $_ } else { $BuzzRunningTotal }') | |
$Mod3 = [Scriptblock]::Create('param([int]$Number) if ($Number % 3 -eq 0) { $FizzRunningTotal + $_ } else { $FizzRunningTotal }') | |
1..15 | ForEach-Object { | |
$FizzRunningTotal = $Mod3.InvokeReturnAsIs($_) | |
$BuzzRunningTotal = $Mod5.InvokeReturnAsIs($_) | |
$FizzBuzzRunningTotal = $Mod15.InvokeReturnAsIs($_) | |
[PSCustomObject]@{ | |
Number = $_ | |
FizzRunningTotal = $FizzRunningTotal | |
BuzzRunningTotal = $BuzzRunningTotal | |
FizzBuzzRunningTotal = $FizzBuzzRunningTotal | |
} | |
} | Format-Table –AutoSize |
But does it work?

Yes! 0 + 3 is 3; 3 + 6 is 9; 9 + 9 is 18!
More Information:
Short version:
PowerShell will check up the scope for a variable but will not check downwards.
So our script block will check upwards for the variable we set and find it. BUT if you update the variable inside the script block, then PowerShell won’t check downwards and get the updated value.
I highly recommend you check out these resources by Richard Siddaway ( blog | twitter ) on script blocks: like this blog post on scope and this video on script blocks!
He can explain this to a better standard than I can. Think I’ll just learn by failing until I get to his level 🙂
2 thoughts on “Learning Module Scope the Hard Way”