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 的文件。
接下来,我们调用函数 foo
和 bar
,这将产生错误,以确定两者都不在当前范围内。虽然不是绝对必要的,但这有助于说明它们的缺席。
接下来,我们调用loader
函数。在 dot sourcing foo.ps1
之后,我们看到 foo
成功执行,因为 foo
在 loader
函数的当前范围内。在对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
文件几乎相同),您可以找到:
虽然这看起来不像自动加载器那么方便,但安装和导入模块是为此目的和接受的方法。除非你有特殊原因不这样做,否则你应该尝试遵循既定标准,这样你以后就不会为了改掉坏习惯而苦苦挣扎。
你也可以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"
}
我有一个模块,其中包含多个具有函数和模块加载器的文件。
示例函数:
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 的文件。
接下来,我们调用函数 foo
和 bar
,这将产生错误,以确定两者都不在当前范围内。虽然不是绝对必要的,但这有助于说明它们的缺席。
接下来,我们调用loader
函数。在 dot sourcing foo.ps1
之后,我们看到 foo
成功执行,因为 foo
在 loader
函数的当前范围内。在对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
文件几乎相同),您可以找到:
虽然这看起来不像自动加载器那么方便,但安装和导入模块是为此目的和接受的方法。除非你有特殊原因不这样做,否则你应该尝试遵循既定标准,这样你以后就不会为了改掉坏习惯而苦苦挣扎。
你也可以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"
}