about_Scopes

简短说明

介绍 PowerShell 中的范围概念,并演示如何设置和更改元素的范围。

长说明

PowerShell 通过限制可读取和更改变量、别名、函数和 PowerShell 驱动器 (PSDrives) 访问。 PowerShell 使用范围规则来确保不会无意中更改不应更改的项。

以下是范围的基本规则:

  • 范围可以嵌套。 外部范围称为父范围。 任何嵌套作用域都是该父级的子范围。

  • 项在创建项的范围和任何子作用域中可见,除非显式将其设为私有。

  • 可以在当前范围之外的作用域中声明变量、别名、函数和 PowerShell 驱动器。

  • 除非显式指定其他范围,否则在范围内创建的项只能在创建范围中更改。

如果在某个范围内创建项,并且该项与不同范围内的项共享其名称,则原始项可能隐藏在新项下,但不会被重写或更改。

PowerShell 范围

PowerShell 支持以下范围:

  • 全局:在 PowerShell 启动时或创建新会话或运行空间时生效的范围。 PowerShell 启动时存在的变量和函数已在全局范围内创建,例如自动变量和首选项变量。 PowerShell 配置文件中的变量、别名和函数也是在全局范围内创建的。 全局范围是会话中的根父范围。

  • 本地:当前范围。 本地范围可以是全局范围或任何其他作用域。

  • 脚本:脚本文件运行时创建的范围。 只有脚本中的命令在脚本范围内运行。 对于脚本中的命令,脚本范围是本地范围。

注意

Private 不是范围。 这是 一个选项 ,用于更改定义项的范围之外的项的可见性。

父级和子范围

可以通过调用脚本或函数创建新的子范围。 调用范围是父范围。 被调用的脚本或函数是子范围。 调用的函数或脚本可以调用其他函数,从而创建其根作用域为全局范围的子范围的层次结构。

除非显式将项设为私有项,否则父范围中的项可用于子范围。 但是,在子作用域中创建和更改的项不会影响父范围,除非在创建项时显式指定范围。

注意

模块中的函数不在调用范围的子范围内运行。 模块具有链接到全局范围的自己的会话状态。 所有模块代码都在具有其自己的根范围的特定于模块的范围层次结构中运行。

继承

子范围不会从父范围继承变量、别名和函数。 除非项是私有项,否则子范围可以查看父范围中的项。 而且,它可以通过显式指定父范围来更改项,但这些项不是子作用域的一部分。

但是,使用一组项创建子范围。 通常,它包括具有 AllScope 选项的所有别名。 本文稍后将讨论此选项。 它包括具有 AllScope 选项的所有变量,以及一些自动变量。

若要查找特定范围内的项,请使用 或 Get-AliasGet-Variable Scope 参数。

例如,若要获取本地范围中的所有变量,请键入:

Get-Variable -Scope local

若要获取全局范围中的所有变量,请键入:

Get-Variable -Scope global

范围修饰符

变量、别名或函数名称可以包含以下任一可选范围修饰符:

  • global: - 指定名称存在于 全局 范围中。

  • local: - 指定 本地范围中 存在该名称。 当前范围始终为 本地 范围。

  • private: - 指定名称为 Private 且仅对当前范围可见。

  • script: - 指定名称存在于 脚本 范围中。 脚本 范围是最近的上级脚本文件的范围,如果没有最近的上级脚本文件,则为 全局

  • using:- 用于在通过 和 Invoke-CommandStart-Job cmdlet 运行脚本时访问在另一个作用域中定义的变量。

  • workflow: - 指定名称存在于工作流中。 注意:PowerShell v6 及更高版本不支持工作流。

  • <variable-namespace> - 由 PowerShell PSDrive 提供程序创建的修饰符。 例如:

    命名空间 说明
    Alias: 在当前作用域中定义的别名
    Env: 在当前范围内定义的环境变量
    Function: 当前作用域中定义的函数
    Variable: 在当前作用域中定义的变量

脚本的默认范围是脚本范围。 函数和别名的默认范围是本地范围,即使它们是在脚本中定义的。

使用范围修饰符

若要指定新变量、别名或函数的范围,请使用范围修饰符。

变量中范围修饰符的语法为:

$[<scope-modifier>:]<name> = <value>

函数中范围修饰符的语法为:

function [<scope-modifier>:]<name> {<function-body>}

以下命令不使用范围修饰符,在当前或 本地 范围内创建变量:

$a = "one"

若要在 全局 范围内创建同一变量,请使用范围 global: 修饰符:

$global:a = "one"

若要在 脚本 范围内创建同一变量,请使用 script: 范围修饰符:

$script:a = "one"

还可以将范围修饰符与函数一起使用。 以下函数定义在 全局 范围内创建函数:

function global:Hello {
  Write-Host "Hello, World"
}

还可以使用范围修饰符来引用不同作用域中的变量。 以下命令引用 $test 变量,首先在本地作用域中,然后在全局范围内:

$test
$global:test

Using: 作用域修饰符

使用 是一种特殊的范围修饰符,用于标识远程命令中的局部变量。 如果没有修饰符,PowerShell 需要在远程会话中定义远程命令中的变量。

范围 Using 修饰符在 PowerShell 3.0 中引入。

对于在会话外执行的任何脚本或命令,需要 Using 作用域修饰符来嵌入调用会话范围内的变量值,以便会话外代码可以访问它们。 以下 Using 上下文支持范围修饰符:

  • 远程执行的命令,以 Invoke-Command 使用 ComputerNameHostNameSSHConnectionSession 参数 (远程会话)
  • 后台作业,从 Start-Job (进程外会话)
  • 线程作业,通过 Start-ThreadJobForEach-Object -Parallel (单独的线程会话) 启动

根据上下文,嵌入的变量值可以是调用方范围内数据的独立副本或对其的引用。 在远程会话和进程外会话中,它们始终是独立的副本。

有关详细信息,请参阅 about_Remote_Variables

在线程会话中,它们通过引用传递。 这意味着可以修改不同线程中的调用范围变量。 若要安全地修改变量,需要线程同步。

有关详细信息,请参阅:

变量值的序列化

远程执行的命令和后台作业在进程外运行。 进程外会话使用基于 XML 的序列化和反序列化使变量的值跨进程边界可用。 序列化过程将对象转换为 PSObject,该 PSObject 包含原始对象属性,但不包含其方法。

对于一组有限的类型,反序列化会将对象解冻回原始类型。 解除冻结的对象是原始对象实例的副本。 它具有类型属性和方法。 对于简单类型(如 System.Version),复制是精确的。 对于复杂类型,副本是不完美的。 例如,解除冻结的证书对象不包括私钥。

所有其他类型的实例都是 PSObject 实例。 PSTypeNames 属性包含带反序列化前缀的原始类型名称,例如 Deserialized.System.Data.DataTable

AllScope 选项

变量和别名具有一个 Option 属性,该属性可以采用 AllScope 的值。 具有 AllScope 属性的项将成为所创建的任何子范围的一部分,尽管父范围不会追溯继承这些项。

具有 AllScope 属性的项在子范围中可见,并且它是该范围的一部分。 对任何作用域中的项的更改都会影响定义变量的所有范围。

管理范围

多个 cmdlet 具有 Scope 参数,可用于获取或设置 (在特定范围内创建和更改) 项。 使用以下命令查找会话中具有 Scope 参数的所有 cmdlet:

Get-Help * -Parameter scope

若要查找在特定范围内可见的变量,请使用 ScopeGet-Variable参数。 可见变量包括全局变量、父作用域中的变量和当前作用域中的变量。

例如,以下命令获取在本地范围内可见的变量:

Get-Variable -Scope local

若要在特定范围内创建变量,请使用 的范围修饰符或 Set-VariableScope 参数。 以下命令在全局范围内创建变量:

New-Variable -Scope global -Name a -Value "One"

还可以使用 、 Set-AliasGet-Alias cmdlet 的 New-AliasScope 参数来指定范围。 以下命令在全局范围内创建别名:

New-Alias -Scope global -Name np -Value Notepad.exe

若要获取特定范围内的函数, Get-Item 请在作用域中使用 cmdlet。 cmdlet Get-Item 没有 Scope 参数。

注意

对于使用 Scope 参数的 cmdlet,还可以按编号引用范围。 数字描述一个范围到另一个范围的相对位置。 范围 0 表示当前或本地范围。 范围 1 指示直接父范围。 范围 2 指示父范围的父级,依此。 如果已创建多个递归作用域,则编号范围非常有用。

将点源表示法与范围配合使用

脚本和函数遵循范围的所有规则。 在特定范围内创建它们,它们仅影响该范围,除非使用 cmdlet 参数或范围修饰符来更改该范围。

但是,可以使用点源表示法将脚本或函数添加到当前范围。 然后,当脚本在当前范围内运行时,该脚本创建的任何函数、别名和变量都可以在当前范围内使用。

若要将函数添加到当前范围,请在函数调用中的函数路径和名称前键入点 (.) 和空格。

例如,若要从脚本范围中的 C:\Scripts 目录 (脚本) 的默认值运行 Sample.ps1 脚本,请使用以下命令:

c:\scripts\sample.ps1

若要在本地范围内运行 Sample.ps1 脚本,请使用以下命令:

. c:\scripts.sample.ps1

使用调用运算符 (&) 运行函数或脚本时,不会将其添加到当前范围。 以下示例使用 调用运算符:

& c:\scripts.sample.ps1

可以在 about_operators 中详细了解呼叫运算符。

Sample.ps1 脚本创建的任何别名、函数或变量在当前范围内不可用。

不带范围限制

一些 PowerShell 概念类似于作用域或与作用域交互。 这些概念可能与范围或范围的行为相混淆。

会话、模块和嵌套提示是自包含环境,但它们不是会话中全局范围的子范围。

会话

会话是运行 PowerShell 的环境。 在远程计算机上创建会话时,PowerShell 会与远程计算机建立持久连接。 通过持久连接,可以将会话用于多个相关命令。

由于会话是包含的环境,因此它有自己的范围,但会话不是在其中创建会话的子范围。 会话从其自己的全局范围开始。 此范围独立于会话的全局范围。 可以在会话中创建子范围。 例如,可以运行脚本以在会话中创建子范围。

模块

可以使用 PowerShell 模块来共享和交付 PowerShell 工具。 模块是可以包含 cmdlet、脚本、函数、变量、别名和其他有用项的单元。 除非显式定义,否则模块中的项在模块外部不可访问。 因此,可以将模块添加到会话并使用公共项,而不必担心其他项可能会覆盖会话中的 cmdlet、脚本、函数和其他项。

默认情况下,模块将加载到当前 会话状态 的顶层,而不是当前 范围。 当前会话状态可以是模块会话状态或全局会话状态。 将模块添加到会话不会更改范围。 如果位于全局范围内,则会将模块加载到全局会话状态。 任何导出都放入全局表中。 如果从 module1 内部 加载 module2,则 module2 将加载到 module1 的会话状态中,而不是全局会话状态。 模块 2 的任何导出都放置在 module1 会话状态的顶部。 如果使用 Import-Module -Scope local,则导出将置于当前范围对象中,而不是放在顶层。 如果在 某个模块Import-Module -Scope global 中使用 (或 Import-Module -Global) 加载另一个模块,则会将该模块及其导出加载到全局会话状态,而不是模块的本地会话状态。 此功能旨在编写操作模块的模块。 Windows 兼容性模块执行此操作以将代理模块导入全局会话状态。

在会话状态中,模块具有自己的范围。 请考虑以下模块 C:\temp\mod1.psm1

$a = "Hello"

function foo {
    "`$a = $a"
    "`$global:a = $global:a"
}

现在,我们创建一个全局变量 $a,为其指定一个值并调用 函数 foo

$a = "Goodbye"
foo

模块在模块范围中声明变量 $a ,然后函数 foo 输出两个作用域中变量的值。

$a = Hello
$global:a = Goodbye

嵌套提示

嵌套提示没有自己的范围。 输入嵌套提示时,嵌套提示是环境的子集。 但是,你仍在本地范围内。

脚本确实有其自己的范围。 如果要调试脚本,并且到达脚本中的断点,则输入脚本范围。

专用选项

别名和变量具有一个 Option 属性,该值可以为 Private。 可以在创建项的作用域中查看和更改具有 “专用” 选项的项,但不能在该范围之外查看或更改这些项。

例如,如果创建一个在全局范围内具有私有选项的变量,然后运行脚本, Get-Variable 则脚本中的命令不会显示私有变量。 在此实例中使用全局范围修饰符不显示私有变量。

可以使用 、、 New-AliasSet-Alias cmdlet 的 New-VariableOption 参数将 Option 属性Set-Variable的值设置为 Private。

可见性

变量或别名的 Visibility 属性确定是否可以看到创建它的容器外部的项。 容器可以是模块、脚本或管理单元。 可见性是为容器设计的,其方式与 Option 属性的 Private 值针对范围设计的相同。

Visibility 属性采用 PublicPrivate 值。 只能在创建项的容器中查看和更改具有专用可见性的项。 如果添加或导入容器,则无法查看或更改具有专用可见性的项。

因为可见性是为容器设计的,所以它在一个范围内的工作方式不同。

  • 如果创建的项在全局范围内具有专用可见性,则不能在任何范围内查看或更改该项。
  • 如果尝试查看或更改具有专用可见性的变量的值,PowerShell 将返回错误消息。

可以使用 New-VariableSet-Variable cmdlet 创建具有专用可见性的变量。

示例

示例 1:仅在脚本中更改变量值

以下命令更改脚本中变量的值 $ConfirmPreference 。 更改不会影响全局范围。

首先,若要在本地范围内显示变量的值 $ConfirmPreference ,请使用以下命令:

PS>  $ConfirmPreference
High

创建包含以下命令的 Scope.ps1 脚本:

$ConfirmPreference = "Low"
"The value of `$ConfirmPreference is $ConfirmPreference."

运行该脚本。 该脚本更改变量的值, $ConfirmPreference 然后在脚本范围内报告其值。 输出应类似于以下输出:

The value of $ConfirmPreference is Low.

接下来,测试当前范围内变量的 $ConfirmPreference 当前值。

PS>  $ConfirmPreference
High

此示例表明,对脚本作用域中变量的值的更改不会影响该变量在父范围中的值。

示例 2:查看不同范围内的变量值

可以使用范围修饰符查看本地作用域和父作用域中的变量的值。

首先,在全局范围内定义变量 $test

$test = "Global"

接下来,创建定义 $test 变量的 Sample.ps1 脚本。 在脚本中,使用范围修饰符引用变量的 $test 全局或本地版本。

在 Sample.ps1:

$test = "Local"
"The local value of `$test is $test."
"The global value of `$test is $global:test."

运行 Sample.ps1 时,输出应类似于以下输出:

The local value of $test is Local.
The global value of $test is Global.

脚本完成后,会话中仅定义 的 $test 全局值。

PS>  $test
Global

示例 3:更改父范围中变量的值

除非使用 Private 选项或其他方法保护项,否则可以在父范围中查看和更改变量的值。

首先,在全局范围内定义变量 $test

$test = "Global"

接下来,创建定义 $test 变量的 Sample.ps1 脚本。 在脚本中,使用范围修饰符引用变量的 $test 全局或本地版本。

在 Sample.ps1:

$global:test = "Local"
"The global value of `$test is $global:test."

脚本完成后,将更改 的 $test 全局值。

PS>  $test
Local

示例 4:创建私有变量

私有变量是具有 Option 属性的变量,其值为 Private私有 变量由子范围继承,但只能在创建它们的作用域中查看或更改它们。

以下命令在本地范围内创建名为 的 $ptest 私有变量。

New-Variable -Name ptest -Value 1 -Option private

可以在本地范围内显示和更改 的值 $ptest

PS>  $ptest
1

PS>  $ptest = 2
PS>  $ptest
2

接下来,创建包含以下命令的 Sample.ps1 脚本。 命令尝试显示和更改 的值 $ptest

在 Sample.ps1:

"The value of `$Ptest is $Ptest."
"The value of `$Ptest is $global:Ptest."

变量 $ptest 在脚本范围中不可见,输出为空。

"The value of $Ptest is ."
"The value of $Ptest is ."

示例 5:在远程命令中使用局部变量

对于在本地会话中创建的远程命令中的变量,请使用 Using 范围修饰符。 PowerShell 假定远程命令中的变量是在远程会话中创建的。

语法为:

$Using:<VariableName>

例如,以下命令在本地会话中创建变量 $Cred ,然后在远程命令中使用 $Cred 变量:

$Cred = Get-Credential
Invoke-Command $s {Remove-Item .\Test*.ps1 -Credential $Using:Cred}

PowerShell 3.0 中引入了 Using 范围。 在 PowerShell 2.0 中,若要指示已在本地会话中创建变量,请使用以下命令格式。

$Cred = Get-Credential
Invoke-Command $s {
  param($c)
  Remove-Item .\Test*.ps1 -Credential $c
} -ArgumentList $Cred

另请参阅