Функции PowerShell загружаются из функции

У меня есть модуль с несколькими файлами с функциями и загрузчиком модулей.

Пример функции:

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)?

Функция использует точечные источники файлов в другой области. Зачем вообще нужна эта функция-оболочка? Просто делайте то, что вы делаете в своем последнем фрагменте кода.

Ansgar Wiechers 30.07.2019 11:11

На самом деле, модуль загружает частные/общедоступные и общие файлы функций, и я переместил код в отдельную функцию, чтобы не дублировать код, потому что LoadModule содержит дополнительную логику, такую ​​​​как обработка ошибок и ведение журнала.

Stadub Dima 30.07.2019 11:23

Если вам нужно вызвать Init только один раз, вы можете удалить его после вызова.

user4003407 30.07.2019 15:56

@PetSerAl, не могли бы вы подробнее объяснить об удалении после звонка? Может быть, это было бы решением

Stadub Dima 30.07.2019 18:09
Remove-Item Function:\Init
user4003407 30.07.2019 20:46
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
5
1 012
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Примечание. Редактирование 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

Редактировать 1: дальнейшее разъяснение по точечному поиску

Точечный источник эквивалентен копированию и вставке содержимого указанного файла в файл, предшествующий точечному источнику. Файл, выполняющий операцию, «импортирует» содержимое целевого объекта дословно, не выполняя никаких дополнительных действий, прежде чем приступить к выполнению «импортированного» кода. например

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, модуль не сможет экспортировать их в среду.

Stadub Dima 30.07.2019 18:05

Кстати, большое спасибо за подробное объяснение "загрузка модулей" и "точечный синтаксис".

Stadub Dima 30.07.2019 18:07

Если вы используете его, как я описал, он должен работать для вашего варианта использования. Создайте файл .ps1, содержащий загрузчик модулей, а затем вызовите загрузчик модулей, чтобы импортировать оставшиеся функции в виде модулей. Импорт модуля, который затем импортирует другие модули, похоже, не работает. Поиск функции, которая затем импортирует модули, действительно работает.

Jekotia 30.07.2019 20:28

Я также должен отметить, что файлы .ps1 и .psm1 технически одинаковы. Вы ничего там не упускаете. Различие является семантическим, которое поощряет лучшие методы кодирования. Ваш повторно используемый код должен находиться в модулях, которые вы импортируете, а ваш код, специфичный для варианта использования, должен находиться в файлах .ps1, которые вы выполняете. Затем эти файлы .ps1 имеют доступ к вашим повторно используемым модулям.

Jekotia 30.07.2019 21:41

Обновлено: на самом деле вам не нужно использовать 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), вы можете найти:

  1. дополнительные сведения о профилях PowerShell здесь.
  2. сводка о том, какие файлы профилей используются и когда здесь.

Хотя это может показаться не таким удобным, как автозагрузчик, установка и импорт модулей являются предполагаемым и общепринятым подходом для этого. Если у вас нет особой причины не делать этого, вы должны стараться следовать установленным стандартам, чтобы потом не бороться с вредными привычками.

Вы также можете использовать изменить реестр для достижения этой цели.

Ответ принят как подходящий

После некоторых исследований я обнаружил: во время выполнения функции 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"
}

Другие вопросы по теме