PowerShell 函数从函数加载

PowerShell functions load from function

我有一个模块,其中包含多个具有函数和模块加载器的文件。

示例函数:

Function1.ps1

function Init() {
    echo "I am the module initialization logic"
}

function DoStuff() {
    echo "Me performing important stuff"
}

模块加载器文件:

Module1.psm1:

$script:Functions = Get-ChildItem $PSScriptRoot\*.ps1 

function LoadModule {
    Param($path)
    foreach ($import in @($path)) {
        . $import.FullName
    }
}

LoadModule script:Functions

Init # function doesn't found 

所以我试图通过过程 LoadModule 从文件 Function1.ps1 加载函数。 调试 LoadModule 显示已加载外部函数,但在完成 LoadModule 过程后,函数变得不可访问,因此脚本在 Init 行失败。

但是没有 LoadModule 功能的重写模块加载器工作正常

Module1.psm1:

Get-ChildItem $PSScriptRoot\*.ps1 | %{
    . $import.FullName
}
Init # In this case - all works fine

据我所知,文件函数是从位于某个独立作用域中的函数加载的,并且为了能够访问它们,我需要添加一些作用域标志。

也许有人知道我应该添加什么才能使函数 Init() 可从 module.pm1 脚本主体访问但不能使其从外部访问(不使用 Export-ModuleMember)?

注意:编辑 1,最后包含对点源实际作用的澄清。

首先,您混淆了函数和模块的术语和用法。应使用 Import-Module cmdlet 将具有 .psm1 扩展名的模块导入终端。当点采购时,例如您在此处所做的,您应该只针对包含函数的脚本文件,这些文件是扩展名为 .ps1 的文件。

我也是 PowerShell 的新手,我 运行 遇到了同样的问题。在花了大约一个小时阅读这个问题后,我找不到解决方案,但我发现的很多信息都表明这是一个范围问题。所以我创建了一个测试,使用了三个文件。

foo.ps1

function foo {
  Write-Output "foo"
}

bar.psm1

function bar {
  Write-Output "bar"
}

scoping.ps1

function loader {
    echo "dot sourcing file"
    . ".\foo.ps1"
    foo
    echo "Importing module"
    Import-Module -Name ".\bar.psm1"
    bar
}

foo
bar

loader

foo
bar

pause

让我们来看看这个脚本的作用。

首先我们定义一个虚拟 loader 函数。这不是一个实用的加载程序,但足以测试范围和加载文件中函数的可用性。此函数点源包含函数 foo 的 ps1 文件,并使用 Import-Module 作为包含函数 bar 的文件。

接下来,我们调用函数 foobar,这将产生错误,以确定两者都不在当前范围内。虽然不是绝对必要的,但这有助于说明它们的缺席。

接下来,我们调用loader函数。在 dot sourcing foo.ps1 之后,我们看到 foo 成功执行,因为 fooloader 函数的当前范围内。在对bar.psm1使用Import-Module之后,我们看到bar也成功执行了。现在我们退出 loader 函数的范围并 return 到主脚本。

现在我们看到 foo 的执行失败并出现错误。这是因为我们在函数范围内点源 foo.ps1 。但是,因为我们导入了 bar.psm1,bar 成功执行了。这是因为模块默认导入到全局范围。


我们如何使用它来改进您的 LoadModule 功能?此功能的主要内容是您需要切换为对导入的函数使用模块。请注意,根据我的测试,您不能 Import-Module 加载程序功能;这仅在您 dot source 加载程序时有效。

LoadModule.ps1

function LoadModule($Path) {
    Get-ChildItem -Path "$Path" -Filter "*.psm1" -Recurse -File -Name| ForEach-Object {
        $File = "$Path$_"
        echo "Import-Module -Name $File"
        Import-Module -Name "$File" -Force
    }
}

现在在终端中:

. ".\LoadModule.ps1"
LoadModule ".\"
foo
bar

编辑 1:进一步澄清网点来源

点源相当于将指定文件的内容复制粘贴到执行点源的文件中。该文件逐字执行 "imports" 目标内容,在继续执行 "imported" 代码之前不执行任何其他操作。例如

foo.ps1 写输出"I am foo"<br> . ".\bar.ps1"

bar.ps1

Write-Output "I am bar"

实际上是

Write-Output "I am foo"
Write-Output "I am bar"

编辑:您实际上不需要使用 Import-Module。只要您的 $env:PSModulePath 中有模块,PowerShell 就会在首次调用时自动加载任何导出的函数。 Source.

根据您的用例的具体情况,您可以使用另一种方法。此方法解决了何时要将模块大量导入 PowerShell 会话的问题。

当您启动 PowerShell 时,它会查看环境变量 $PSModulePath 的值以确定应在何处查找模块。然后它会在此目录下查找包含 psm1 和 psd1 文件的目录。您可以在会话期间修改此变量,然后按名称导入模块。这是一个示例,使用我添加到我的 PowerShell profile.ps1 文件中的内容:

$MyPSPath = [Environment]::GetFolderPath("MyDocuments") + "\WindowsPowerShell"

$env:PSModulePath = $env:PSModulePath + ";$MyPSPath\Custom\Modules"

Import-Module `
    -Name Confirm-Directory, `
    Confirm-File, `
    Get-FileFromURL, `
    Get-RedirectedURL, `
    Get-RemoteFileName, `
    Get-ReparseTarget, `
    Get-ReparseType, `
    Get-SpecialPath, `
    Test-ReparsePoint

如果您是 PowerShell 配置文件的新手(它们与 Unix 的 ~/.profile 文件几乎相同),您可以找到:

  1. 有关 PowerShell 配置文件的更多信息 here
  2. 关于使用哪些配置文件以及何时使用的摘要here

虽然这看起来不像自动加载器那么方便,但安装和导入模块是为此目的和接受的方法。除非你有特殊原因不这样做,否则你应该尝试遵循既定标准,这样你以后就不会为了改掉坏习惯而苦苦挣扎。

你也可以modify the registry实现这个。

经过一番研究发现:LoadModule函数在执行过程中,所有注册的函数都会被添加到Functions Provider

所以从 LoadModule 函数体可以通过 Get-ChildItem -Path Function:

枚举它们
[DBG]: PS > Get-ChildItem -Path Function:
CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Function        C:
Function        Close-VSCodeHtmlContentView                        0.2.0      PowerShellEditorServices.VSCode
Function        Init                                               0.0        Module1
Function        ConvertFrom-ScriptExtent                           0.2.0    
Function        Module1                                            0.0        Module1

因此我们可以在 LoadModule

调用开始时将函数列表存储到变量中
$loadedFunctions =  Get-ChildItem -Path Function:

点加载符号后检索添加的函数列表

Get-ChildItem -Path Function: |  where { $loadedFunctions -notcontains $_ } 

因此修改后的 LoadModule 函数将如下所示:

function LoadModule {
    param ($path)
    $loadRef = Get-PSCallStack

    $loadedFunctions =  Get-ChildItem -Path Function:
    foreach ($import in @($path)) {
        . $import.FullName
    }
    $functions= Get-ChildItem -Path Function: | `
        Where-Object { $loadedFunctions -notcontains $_ } | `
        ForEach-Object{ Get-Item function:$_ }

    return $functions
}

下一步它只是将函数分配给列表 More about this

$script:functions = LoadModule $script:Private ##Function1.ps1
$script:functions += LoadModule $script:PublicFolder

经过这一步,我们可以

  • 调用初始化程序:
    $initScripts = $script:functions| #here{ $_.Name -eq 'Initalize'} #filter
    $initScripts | ForEach-Object{ & $_ } ##execute
  • 并导出 Public 函数:
    $script:functions| `
    where { $_.Name -notlike '_*'  } |  ` # do not extport _Name functions
    %{ Export-ModuleMember -Function $_.Name}

我移动到 ModuleLoader.ps1 文件的模块加载函数的完整代码。它可以在 GitHub repo PowershellScripts

中找到

Moudule.psm1文件的完整版本是

if($ModuleDevelopment){
    . $PSScriptRoot\..\Shared-Functions\ModuleLoader.ps1 "$PSScriptRoot"
}
else {
    . $PSScriptRoot\Shared\ModuleLoader.ps1 "$PSScriptRoot"
}