Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What exactly is a PowerShell ScriptBlock

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 free variables which are bound at run time in a lambda expression.

function Get-Block {
  $b = "PowerShell"
  $value = {"Hello $b"}
  return $value
}
$block = Get-Block
& $block
# Hello
# PowerShell is not written as it is not defined in the scope
# in which the block was executed.


function foo {
  $value = 5
  function bar {
    return $value
  }
  return bar
}
foo
# 5
# 5 is written $value existed during the evaluation of the bar function
# it is my understanding that a function is a named scriptblock
#  which is also registered to function:

Calling GetNewClosure() on a ScriptBlock returns a new ScriptBlock which closes over the variables referenced. But this is very limited in scope and ability.

What is a ScriptBlock's classification?

like image 345
Ian Davis Avatar asked Sep 24 '12 23:09

Ian Davis


People also ask

What does PowerShell IEX do?

Description. The Invoke-Expression cmdlet evaluates or runs a specified string as a command and returns the results of the expression or command. Without Invoke-Expression , a string submitted at the command line is returned (echoed) unchanged. Expressions are evaluated and run in the current scope.

What does @{} mean in PowerShell?

In PowerShell V2, @ is also the Splat operator. PS> # First use it to create a hashtable of parameters: PS> $params = @{path = "c:\temp"; Recurse= $true} PS> # Then use it to SPLAT the parameters - which is to say to expand a hash table PS> # into a set of command line parameters.

What does the ampersand mean in PowerShell?

The ampersand tells PowerShell to execute the scriptblock expression. In essence, it's telling PowerShell that the stuff between the curly braces is code and to run it as code. Passing Arguments to Scriptblocks. Like functions, you can pass "parameters" to scriptblocks also known as arguments.

How do I create a script block?

Creating a scriptblock is as simple as enclosing some code in curly braces. Once you have that code in a scriptblock, it can be executed a few different ways. Two ways are by using the call operator and the Invoke-Command cmdlet as shown below. Scriptblocks are also similar to functions.


2 Answers

Per the docs, a scriptblock is a "precompiled block of script text." So by default you just a pre-parsed block of script, no more, no less. Executing it creates a child scope, but beyond that it's as if you pasted the code inline. So the most appropriate term would simply be "readonly source code."

Calling GetNewClosure bolts on a dynamically generated Module which basically carries a snapshot of all the variables in the caller's scope at the time of calling GetNewClosure. It is not a real closure, simply a snapshot copy of variables. The scriptblock itself is still just source code, and variable binding does not occur until it is invoked. You can add/remove/edit variables in the attached Module as you wish.

function GetSB
{
   $funcVar = 'initial copy'

   {"FuncVar is $funcVar"}.GetNewClosure()

   $funcVar = 'updated value'  # no effect, snapshot is taken when GetNewClosure is called
}

$sb = GetSB

& $sb  # FuncVar is initial copy

$funcVar = 'outside'
& $sb  # FuncVar is initial copy

$sb.Module.SessionState.PSVariable.Remove('funcVar')
& $sb  # FuncVar is outside
like image 194
latkin Avatar answered Sep 19 '22 15:09

latkin


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.

  • PowerShell does support function closures, however the function keyword does not.

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.

like image 40
Sam Porch Avatar answered Sep 21 '22 15:09

Sam Porch