11. 模块

编辑说明

重要

Windows PowerShell 语言规范 3.0 于 2012 年 12 月发布,基于 Windows PowerShell 3.0。 此规范不反映 PowerShell 的当前状态。 没有计划更新本文档以反映当前状态。 此处提供了本文档供历史参考。

规范文档可从 Microsoft 下载中心以 Microsoft Word 文档的形式获取,网址为:https://www.microsoft.com/download/details.aspx?id=36389 该 Word 文档已转换为 Microsoft Learn 上的演示文稿。 转换期间,进行了一些编辑更改,以适应 Docs 平台的格式设置。 已更正某些拼写错误和次要错误。

11.1 简介

§3.14中所述,模块是一个自包含的可重用单元,允许对 PowerShell 代码进行分区、组织和抽象。 模块可以包含一个或多个 模块成员,这些成员是命令(如 cmdlet 和函数)和项(如变量和别名)。 这些成员的名称可以对模块保密,也可以将它们导出到模块导入的会话中。

有三种不同的 模块类型:,清单、脚本和二进制。 清单模块 是一个文件,其中包含有关模块的信息,并控制该模块使用的某些方面。 脚本模块 是一个 PowerShell 脚本文件,文件扩展名为 .psm1,而不是 .ps1二进制模块 包含定义 cmdlet 和提供程序的类类型。 与脚本模块不同,二进制模块是用编译语言编写的。 此规范未涵盖二进制模块。

二进制模块是针对 PowerShell 库编译的 .NET 程序集(即 DLL)。

模块可以 嵌套;也就是说,一个模块可以导入另一个模块。 具有关联嵌套模块的模块是 根模块

创建 PowerShell 会话时,默认情况下不会导入任何模块。

导入模块时,用于查找它们的搜索路径由环境变量 PSModulePath定义。

以下命令行脚本与模块相关:

11.2 编写脚本模块

脚本模块是脚本文件。 请考虑以下脚本模块:

function Convert-CentigradeToFahrenheit ([double]$tempC) {
    return ($tempC * (9.0 / 5.0)) + 32.0
}
New-Alias c2f Convert-CentigradeToFahrenheit

function Convert-FahrenheitToCentigrade ([double]$tempF) {
    return ($tempF - 32.0) * (5.0 / 9.0)
}
New-Alias f2c Convert-FahrenheitToCentigrade

Export-ModuleMember -Function Convert-CentigradeToFahrenheit
Export-ModuleMember -Function Convert-FahrenheitToCentigrade
Export-ModuleMember -Alias c2f, f2c

此模块包含两个函数,每个函数都有一个别名。 默认情况下,导出所有函数名,且只导出函数名。 但是,一旦使用 cmdlet Export-ModuleMember 导出任何内容,则只会导出显式导出的内容。 可以通过一个调用或多次调用此 cmdlet 来导出一系列命令和项;这些调用在当前会话中是累积的。

11.3 安装脚本模块

脚本模块在脚本文件中定义,模块可以存储在任何目录中。 当与模块相关的 cmdlet 查找名称不包含完全限定路径的模块时,环境变量 PSModulePath 指向一组要搜索的目录。 可以提供其他查找路径;例如

$Env:PSModulepath = $Env:PSModulepath + ";<additional-path>"

添加的任何其他路径仅影响当前会话。

或者,在导入模块时可以指定完全限定的路径。

11.4 导入脚本模块

在使用模块中的资源之前,必须使用 cmdlet Import-Module将该模块导入到当前会话中。 Import-Module 可以限制实际导入的资源。

导入模块后,将执行其脚本文件。 可以通过在脚本文件中定义一个或多个参数,并通过 Import-ModuleArgumentList 参数传入相应的参数来配置该过程。

请考虑以下脚本,该脚本使用 §11.2中定义的这些函数和别名:

Import-Module“E:\Scripts\Modules\PSTest_Temperature”-Verbose

"0 degrees C is " + (Convert-CentigradeToFahrenheit 0) + " degrees F"
"100 degrees C is " + (c2f 100) + " degrees F"
"32 degrees F is " + (Convert-FahrenheitToCentigrade 32) + " degrees C"
"212 degrees F is " + (f2c 212) + " degrees C"

当模块中的命令或项与会话中的命令或项具有相同的名称时,导入模块会导致名称冲突。 名称冲突导致名称被隐藏或替换。 Import-Module 的 Prefix 参数可用于避免命名冲突。 此外,别名Cmdlet函数,以及 变量 参数可以限制要导入的命令的选择,从而减少名称冲突的可能性。

即使命令被隐藏,也可以通过使用它所在模块的名称来限定它的名称来运行它。 例如,& M\F 100 在模块 M中调用函数 F,并传递参数 100。

当会话包含具有相同名称的相同类型的命令(例如具有相同名称的两个 cmdlet)时,默认情况下,它运行最近添加的命令。

请参阅 §3.5.6,了解与模块相关的范围。

11.5 删除脚本模块

可以通过 cmdlet Remove-Module从会话中删除一个或多个模块。

删除模块不会卸载该模块。

在脚本模块中,可以指定在删除该模块之前要执行的代码,如下所示:

$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = { *on-removal-code* }

11.6 模块清单

§11.1中所述,清单模块是包含有关模块的信息的文件,并控制该模块使用的某些方面。

模块不需要相应的清单,但如果具有相应的清单,该清单的名称与它描述的模块相同,但具有 .psd1 文件扩展名。

清单包含 PowerShell 脚本的有限子集,该脚本返回包含一组键的哈希表。 这些键及其值指定了该模块的清单元素。 也就是说,它们描述模块的内容和属性,定义任何先决条件,并确定组件的处理方式。

实质上,清单是数据文件;但是,它可以包含对数据类型、if 语句和算术和比较运算符的引用。 (不允许赋值、函数定义和循环。清单还具有对环境变量的读取访问权限,并且它可以包含对 cmdlet Join-Path的调用,因此可以构造路径。

注意

编辑器备注:原始文档包含模块清单文件中允许的键列表。 该列表已过时且不完整。 有关模块清单中密钥的完整列表,请参阅 New-ModuleManifest

所需的唯一密钥是 ModuleVersion

下面是简单清单的示例:

@{
ModuleVersion = '1.0'
Author = 'John Doe'
RequiredModules = @()
FunctionsToExport = 'Set*','Get*','Process*'
}

密钥 GUID 具有 string 值。 这为模块指定了一个全局唯一标识符(GUID)。 GUID 可用于区分具有相同名称的模块。 若要创建新的 GUID,请调用方法 [guid]::NewGuid()

11.7 动态模块

动态模块 是在运行时由 cmdlet New-Module在内存中创建的模块,不会从磁盘加载。 请考虑以下示例:

$sb = {
    function Convert-CentigradeToFahrenheit ([double]$tempC) {
        return ($tempC * (9.0 / 5.0)) + 32.0
    }

    New-Alias c2f Convert-CentigradeToFahrenheit

    function Convert-FahrenheitToCentigrade ([double]$tempF) {
        return ($tempF - 32.0) * (5.0 / 9.0)
    }

    New-Alias f2c Convert-FahrenheitToCentigrade

    Export-ModuleMember -Function Convert-CentigradeToFahrenheit
    Export-ModuleMember -Function Convert-FahrenheitToCentigrade
    Export-ModuleMember -Alias c2f, f2c
}

New-Module -Name MyDynMod -ScriptBlock $sb
Convert-CentigradeToFahrenheit 100
c2f 100

脚本块 $sb 定义模块的内容,在本例中为这些函数定义两个函数和两个别名。 与磁盘上的模块一样,默认情况下仅导出函数,因此存在 Export-ModuleMember cmdlet 调用来导出函数和别名。

运行 New-Module 后,导出的四个名称可用于会话,如对 Convert-CentigradeToFahrenheit 和 c2f 的调用所示。

与所有模块一样,动态模块中的成员在全局作用域的子级私有模块作用域内运行。 Get-Module 无法获取动态模块,但 Get-Command 可以获取导出的成员。

若要使动态模块可用于 Get-Module,请通过管道将 New-Module 命令传递给 Import-Module,或者通过管道将 New-Module 返回的模块对象传递给 Import-Module。 此操作将动态模块添加到 Get-Module 列表中,但它不会将模块保存到磁盘或使其持久化。

11.8 闭包

动态模块可用于创建闭包,即一个具有附加数据的函数。 请考虑以下示例:

function Get-NextID ([int]$startValue = 1) {
    $nextID = $startValue
    {
        ($script:nextID++)
    }.GetNewClosure()
}

$v1 = Get-NextID      # get a scriptblock with $startValue of 0
& $v1                 # invoke Get-NextID getting back 1
& $v1                 # invoke Get-NextID getting back 2

$v2 = Get-NextID 100  # get a scriptblock with $startValue of 100
& $v2                 # invoke Get-NextID getting back 100
& $v2                 # invoke Get-NextID getting back 101

此处的意向是,Get-NextID 返回可指定起始值的序列中的下一个 ID。 但是,必须支持多个序列,每个序列都有自己的 $startValue$nextID 上下文。 这是通过调用方法 [scriptblock]::GetNewClosure§4.3.7)来实现的。

每次通过 GetNewClosure创建新的闭包时,都会生成一个新的动态模块,调用者的作用域中的变量(在本例中,是包含增量的脚本块)将被复制到这个新的模块中。 为了确保在父函数内(但在脚本块外)定义的 nextId 递增,需要显式的 script: scope 前缀。

当然,脚本代码块不必是一个命名函数;例如:

$v3 = & {      # get a scriptblock with $startValue of 200
    param ([int]$startValue = 1)
    $nextID = $startValue
    {
        ($script:nextID++)
    }.GetNewClosure()
} 200

& $v3          # invoke script getting back 200
& $v3          # invoke script getting back 201