У меня есть модуль с несколькими файлами с функциями и загрузчиком модулей.
Пример функции:
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
Итак, я пытаюсь загрузить функции из файла Function1.ps1 по процедуре LoadModule.
Отладка LoadModule показывает загруженные внешние функции, но после завершения LoadModule процедуры функции становятся недоступными, поэтому скрипт не работает в Init строке.
Но переписанный загрузчик модулей без функции LoadModule работает нормально
Module1.psm1:
Get-ChildItem $PSScriptRoot\*.ps1 | %{
. $import.FullName
}
Init # In this case - all works fine
Итак, насколько я понимаю, файловые функции загружаются из функции, помещенной в некоторую изолированную область, и для доступа к ним мне нужно добавить некоторый флаг области.
Может быть, кто-нибудь знает, что мне нужно добавить, чтобы сделать функцию Init() доступной из тела скрипта module.pm1, но не сделать ее доступной извне (без использования Export-ModuleMember)?
На самом деле, модуль загружает частные/общедоступные и общие файлы функций, и я переместил код в отдельную функцию, чтобы не дублировать код, потому что LoadModule содержит дополнительную логику, такую как обработка ошибок и ведение журнала.
Если вам нужно вызвать Init только один раз, вы можете удалить его после вызова.
@PetSerAl, не могли бы вы подробнее объяснить об удалении после звонка? Может быть, это было бы решением
Remove-Item Function:\Init




Примечание. Редактирование 1, разъяснение того, что на самом деле делает точечный источник, включено в конец.
Во-первых, вы смешиваете терминологию и использование функций и модулей. Модули с расширением .psm1 следует импортировать в терминал с помощью командлета Import-Module. При точечном поиске, таком как то, что вы делаете здесь, вы должны ориентироваться только на файлы сценариев, которые содержат функции, то есть файлы с расширением .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. Это непрактичный загрузчик, но его достаточно для тестирования областей и доступности функций в загружаемых файлах. Эта точка функции получает файл ps1, содержащий функцию foo, и использует Import-Module для файла, содержащего функциональную панель.
Затем мы вызываем функции foo и bar, которые будут выдавать ошибки, чтобы установить, что ни одна из них не находится в текущей области. Хотя это и не является строго необходимым, это помогает проиллюстрировать их отсутствие.
Далее мы вызываем функцию loader. После точечного поиска foo.ps1 мы видим, что foo успешно выполнено, потому что foo находится в текущей области действия функции loader. После использования Import-Module вместо bar.psm1 мы видим, что bar также успешно выполнено. Теперь мы выходим из области действия функции loader и возвращаемся к основному сценарию.
Теперь мы видим, что выполнение foo завершается с ошибкой. Это потому, что мы не получаем foo.ps1 в рамках функции. Однако, поскольку мы импортировали bar.psm1, bar успешно выполняется. Это связано с тем, что модули по умолчанию импортируются в глобальную область.
Как мы можем использовать это, чтобы улучшить вашу функцию LoadModule? Главное для этого функционала то, что вам нужно перейти на использование модулей для ваших импортированных функций. Обратите внимание, что из моего тестирования вы не можете импортировать-модулировать функцию загрузчика; это работает только в том случае, если вы не используете источник загрузчика.
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
Точечный источник эквивалентен копированию и вставке содержимого указанного файла в файл, предшествующий точечному источнику. Файл, выполняющий операцию, «импортирует» содержимое целевого объекта дословно, не выполняя никаких дополнительных действий, прежде чем приступить к выполнению «импортированного» кода. например
foo.ps1Write-Output "I am foo"
. ".\bar.ps1"
bar.ps1
Write-Output "I am bar"
эффективно
Write-Output "I am foo"
Write-Output "I am bar"
Синтаксис Import-Module мне не подходит, потому что Module1.psm1 на самом деле это модуль, целью которого является загрузка в модуль других функций. И если модуль будет использовать другие функции, импортированные с помощью Import-Module, модуль не сможет экспортировать их в среду.
Кстати, большое спасибо за подробное объяснение "загрузка модулей" и "точечный синтаксис".
Если вы используете его, как я описал, он должен работать для вашего варианта использования. Создайте файл .ps1, содержащий загрузчик модулей, а затем вызовите загрузчик модулей, чтобы импортировать оставшиеся функции в виде модулей. Импорт модуля, который затем импортирует другие модули, похоже, не работает. Поиск функции, которая затем импортирует модули, действительно работает.
Я также должен отметить, что файлы .ps1 и .psm1 технически одинаковы. Вы ничего там не упускаете. Различие является семантическим, которое поощряет лучшие методы кодирования. Ваш повторно используемый код должен находиться в модулях, которые вы импортируете, а ваш код, специфичный для варианта использования, должен находиться в файлах .ps1, которые вы выполняете. Затем эти файлы .ps1 имеют доступ к вашим повторно используемым модулям.
Обновлено: на самом деле вам не нужно использовать Import-Module. Пока у вас есть модули в $env:PSModulePath, PowerShell будет автоматически загружать любые экспортированные функции при их первом вызове. Источник.
В зависимости от специфики вашего варианта использования вы можете использовать другой метод. Этот метод подходит, когда вы хотите массово импортировать модули в сеанс 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 (они почти такие же, как файл ~/.profile Unix), вы можете найти:
Хотя это может показаться не таким удобным, как автозагрузчик, установка и импорт модулей являются предполагаемым и общепринятым подходом для этого. Если у вас нет особой причины не делать этого, вы должны стараться следовать установленным стандартам, чтобы потом не бороться с вредными привычками.
Вы также можете использовать изменить реестр для достижения этой цели.
После некоторых исследований я обнаружил: во время выполнения функции LoadModule все зарегистрированные функции будут добавлены в Поставщик функций
Таким образом, из тела функции 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
}
на следующем шаге он просто назначает функции для списка Подробнее об этом
$script:functions = LoadModule $script:Private ##Function1.ps1
$script:functions += LoadModule $script:PublicFolder
После этого шага мы можем
$initScripts = $script:functions| #here{ $_.Name -eq 'Initalize'} #filter
$initScripts | ForEach-Object{ & $_ } ##execute
$script:functions| `
where { $_.Name -notlike '_*' } | ` # do not extport _Name functions
%{ Export-ModuleMember -Function $_.Name}
Полный код функции загрузки модуля я перенес в файл ModuleLoader.ps1. И его можно найти в репозитории GitHub PowershellScripts.
И полная версия файла Moudule.psm1 есть
if ($ModuleDevelopment){
. $PSScriptRoot\..\Shared-Functions\ModuleLoader.ps1 "$PSScriptRoot"
}
else {
. $PSScriptRoot\Shared\ModuleLoader.ps1 "$PSScriptRoot"
}
Функция использует точечные источники файлов в другой области. Зачем вообще нужна эта функция-оболочка? Просто делайте то, что вы делаете в своем последнем фрагменте кода.