Skip to content

Commit f1fa055

Browse files
committed
main
1 parent 1642883 commit f1fa055

File tree

2 files changed

+137
-2
lines changed

2 files changed

+137
-2
lines changed

docs/.vitepress/config.mts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ const vitepressConfig = defineConfig({
1818
markdown: {
1919
lineNumbers: true,
2020
theme: {
21-
light: await themeService.getTheme('Eva Light'),
21+
light: 'github-light',
22+
dark: 'github-dark',
23+
// light: await themeService.getTheme('Eva Light'),
2224
// dark: await themeService.getTheme('Eva Dark'),
2325
// light: await themeService.getTheme('JetBrains Rider New UI theme - Light'),
24-
dark: await themeService.getTheme('JetBrains Rider New UI theme - Dark'),
26+
// dark: await themeService.getTheme('JetBrains Rider New UI theme - Dark'),
2527
},
2628
codeTransformers: [transformerTwoslash()],
2729
config: md => {
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# Understanding Scoping & Context
2+
3+
Each powershell process can have multiple runspaces, each runspace has its own session state and scopes, session states and scopes can't be accessed across runspaces.
4+
5+
## Scope
6+
7+
- Scope nesting: each scope can have parent and child scopes, function and scriptblock creates its own scope in the hierarchy.
8+
```ps1
9+
function bar { echo $foo }
10+
function foo {
11+
$foo = 'foo' # defaults to $local: scope
12+
& bar # bar can read the context of foo because it's the child scope of foo's
13+
}
14+
```
15+
- Named scopes:
16+
- `$global:`: top level parent scope.
17+
- `$local:`: current scope, barely useful.
18+
- `$script:`: scope of the nearest script invoked, falls back to `$global:` if no script was found.
19+
- the default scope of a script file is `$script:`
20+
- `$script:` is the most magical scope
21+
- sourcing a script can treat scoped members within it as declared locally.
22+
- there's no way to prevent sourcing members from script as locals, even with `$private:`.
23+
- `$private:`: an accessor keyword to mark only accessible to current scope.
24+
- it looks like a scope name, but just an option to current scope.
25+
- use `New-Alias -Option Private` to create one private alias, variable and function can use direct `$private:` accessor.
26+
```ps1
27+
function foo {
28+
$foo = 'foo'
29+
$private:bar = 'bar' # [!code highlight]
30+
& bar
31+
}
32+
function bar {
33+
Write-Output "bar is null? $($null -eq $bar)" # [!code highlight]
34+
Write-Output $foo
35+
}
36+
& foo # bar is null? True foo
37+
```
38+
- `$using:`: represents a **copy** of that variable value, expanding it to remote command or background job which runs on a different process.
39+
- you can't re-assign or alter the original value within the process because it's a copy.
40+
- powershell transfer the value by xml-based serialization from process to process or remote to local mutually.
41+
```ps1
42+
$foo = 1
43+
$wrapped = Get-Variable foo
44+
# thread job can alter the value by [psvariable] instance
45+
# this is not possible for background job or remote command!
46+
Start-ThreadJob { ($using:wrapped).Value += 1 } | Receive-Job -Wait # [!code highlight]
47+
$foo # 2
48+
```
49+
- driver scopes: containers created from **PSDrive**, may also be accessed by path syntax.
50+
- `$env:` environment variables for **current scope**
51+
- `$function:`: functions declared for **current scope**
52+
- `$alias:`: alias declared for **current scope**
53+
- `$variable:`: variables declared for **current scope**
54+
55+
## Module Scope
56+
57+
Module has their own scope even after being imported to a runspace. That is, for example, the parent scope of a function/scriptblock imported from module is the module scope.
58+
59+
```ps1
60+
# inside module Foo
61+
$foo = 'foo from module'
62+
function foo {
63+
$foo # points to $foo from module scope
64+
}
65+
# variable $foo is not exported
66+
Export-ModuleMember -Function foo
67+
68+
# during a pwsh session
69+
Import-Module Foo
70+
$foo = 'foo from global'
71+
& foo # foo from module # [!code highlight]
72+
```
73+
74+
## ScriptBlock Context Injection
75+
76+
Extra members can be injected into the context of a scriptblock using `ScriptBlock.InvokeWithContext` method.
77+
So you can reference the functions or variable within the scriptblock.
78+
79+
- Parameters
80+
- `functions: IDictionary[string, scriptblock]`: functions to inject
81+
- `variables: List[psvariable]`: variables to inject
82+
- `params args: object[]`: arguments for the scriptblock
83+
84+
```ps1
85+
{ foofunc $foo; }.InvokeWithContext(@{ foofunc = { echo $args[0] } }, [psvariable]::new('foo', 123))
86+
# 123
87+
```
88+
89+
> [!NOTE]
90+
> See [ScriptBlock.InvokeWithContext Method](https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.scriptblock.invokewithcontext?view=powershellsdk-1.1.0)
91+
92+
### Delay-bind Parameter
93+
94+
Context injection is useful when implementing a cmdlet with delay-bind parameters such as `Foreach-Object -Process` and `Where-Object -Filter`, where you can access `$_` within the given scriptblock.
95+
But the given scriptblock doesn't share the same scope of `process` block of a pipeline cmdlet **when the cmdlet came from a module**, because the scriptblock was created from global scope while the `process` block was inside the module scope.
96+
The solution is obvious that to use `ScriptBlock.InvokeWithContext` to inject `$_` into the context of the given scriptblock.
97+
98+
```ps1
99+
function all {
100+
param (
101+
[Parameter(ValueFromPipeline)]
102+
[psobject]$InputObject,
103+
[Parameter(Position = 1, Mandatory)]
104+
[scriptblock]$Condition
105+
)
106+
107+
process {
108+
if (-not $Condition.InvokeWithContext($null, [psvariable]::new('_', $_))) { # [!code highlight]
109+
$false
110+
break
111+
}
112+
}
113+
114+
end { $true }
115+
}
116+
```
117+
118+
> [!IMPORTANT]
119+
> It's worth noting that `Where-Object` and `Foreach-Object` executes the given scriptblock in a global context, meaning that you can alter the members from global scope.
120+
> But it's not available for cmdlet using `ScriptBlock.InvokeWithContext`, the context is always local to the scriptblock itself, should use `$script:` scope to access members instead.
121+
>```ps1
122+
>$foo = 1
123+
>1..5 | foreach { $foo++ }
124+
>$foo # 6
125+
>
126+
>$foo = 1
127+
>1..5 | any { $foo++ }
128+
>$foo # 1
129+
>
130+
>$script:foo = 1
131+
>1..5 | any { $script:foo++ }
132+
>$foo # 6
133+
>```

0 commit comments

Comments
 (0)