A PowerShell ScriptBlock is not a lexical closure as it does not close over the variables referenced in its declaring environment. Instead it seems to leverage dynamic scope and
A PowerShell ScriptBlock is equivalent to a first-class, anonymous function. Most of the confusion I've seen is not with ScriptBlocks, but with the function keyword.
Examples
Function:
PS> function Hello {
>> param ([string] $thing)
>>
>> return ("Hello " + $thing)
>> }
PS> Hello "World"
"Hello World"
ScriptBlock:
PS> $HelloSB = {
>> param ([string] $thing)
>>
>> return ("Hello " + $thing)
>> }
PS> & $HelloSB "World"
"Hello World"
PS> $HelloRef = $HelloSB
PS> & $HelloRef "Universe"
"Hello Universe"
Closure:
PS> $Greeter = {
>> param ([string] $Greeting)
>>
>> return ( {
>> param ([string] $thing)
>>
>> return ($Greeting + " " + $thing)
>> }.GetNewClosure() )
>> }
PS> $Ahoy = (& $Greeter "Ahoy")
PS> & $Ahoy "World"
"Ahoy World"
PS> $Hola = (& $Greeter "Hola")
PS> & $Hola "Mundo"
"Hola Mundo"
Although you can get around the limitation of the function keyword with the "Set-Item" cmdlet:
PS> function Greeter = { ... } # ✕ Error
PS> function Greeter { ... }.GetNewClosure() # ✕ Error
PS> Set-Item -Path "Function:Greeter" -Value $Greeter # (defined above) ✓ OK
PS> $Hola = Greeter "Hola"
PS> & $Hola "Mundo"
"Hola Mundo"
The Value parameter of the "Set-Item" cmdlet can be any ScriptBlock, even one returned by another function. (The "Greeter" function, for example, returns a closure, as shown above.)
PS> Set-Item -Path "Function:Aloha" -Value (Greeter "Aloha")
PS> Aloha "World"
"Aloha World"
Two other important points:
PowerShell uses dynamic scoping, not lexical scoping.
A lexical closure is closed on its source-code environment, whereas a dynamic closure is closed based on the active/dynamic environment that exists when GetNewClosure() is called. (Which is more appropriate for a scripting language.)
PowerShell may have "functions" and "return" statements, but actually its input/output is based on streams and piping. Anything written out of a ScriptBlock with the "Write-Output" or "write" cmdlet will be returned.