Динамическое создание функций в модуле в PowerShell

Предположим, у меня есть следующий код в модуле (называемый MyModule.psm1, в правильном месте для модуля):

function new-function{

    $greeting='hello world'
    new-item -path function:\ -name write-greeting -value {write-output $greeting} -Options AllScope
    write-greeting
}

После импорта модуля и запуска новой функции я могу успешно вызвать функцию записи приветствия (созданную новой функцией).

Когда я пытаюсь вызвать функцию записи-приветствия вне области вызова новой функции, происходит сбой, потому что функция не существует.

Я пробовал новую функцию точечного поиска, но это не помогает. Я поставил -option Allscope, но, видимо, это только включает в детей.

Я также попытался явно следовать вызову new-item с помощью команды write-приветствия export-modulemember, которая не выдает ошибку, но также не создает функцию.

Я хочу иметь возможность динамически создавать функцию (т. Е. Через new-item, потому что содержимое и имя функции будет зависеть от ввода) из функции внутри модуля и иметь вновь созданную функцию, доступную для вызова вне модуля.

В частности, я хочу быть в состоянии сделать это:

Import-module MyModule
New-Function
write-greeting

и видеть "привет мир" как вывод

Есть идеи?

2 ответа

Решение

Сделать видимую функцию довольно просто: просто измените имя вашей функции в New-Item, чтобы global: модификатор объема:

new-item -path function:\ -name global:write-greeting -value {write-output $greeting} #-Options AllScope

У вас будет новая проблема с вашим примером, потому что $greeting будет существовать только в new-function объем, который не будет существовать, когда вы звоните write-greeting, Вы определяете модуль с несвязанным блоком сценария, что означает, что он будет искать $greeting в его области (это не собирается находить это), тогда это будет смотреть в любых родительских областях. Это не увидит тот из new-functionТаким образом, единственный способ получить какой-либо вывод - если модуль или глобальная область видимости содержат $greeting переменная.

Я не совсем уверен, как будут выглядеть ваши настоящие динамические функции, но самый простой способ обойти новую проблему - создать новое замыкание вокруг вашего блока скриптов следующим образом:

new-item -path function:\ -name global:write-greeting -value {write-output $greeting}.GetNewClosure()

Это создаст новый динамический модуль с копией состояния, доступной в то время. Конечно, это создает новую проблему в том, что функция не исчезнет, ​​если вы вызовете Remove-Module MyModule, Без дополнительной информации я не уверен, если это проблема для вас или нет...

Вы были близки к тому, чтобы указывать источник, но вам не хватало Export-ModuleMember. Вот полный пример:

function new-function
{
    $greeting='hello world'
    Invoke-Expression "function write-greeting { write-output '$greeting' }"
    write-greeting
}

. new-function

Export-ModuleMember -Function write-greeting

Вы также не нуждались или хотите -Scope AllScope.

Использование квалификатора global: scope работает, но не является идеальным решением. Во-первых, ваша функция может помешать другой функции в глобальной области видимости, чего обычно не должны делать модули. Во-вторых, ваша глобальная функция не будет удалена, если вы удалите модуль. Последнее - ваша глобальная функция не будет определена в области видимости модуля, поэтому, если ей нужен доступ к неэкспортированным функциям или переменным в вашем модуле, вы не сможете (легко) получить к ним доступ.

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

function AddModuleFileAsFunction {
    param (
        [string] $Name, 
        [switch] $Export
    )

    $content = Get-Content (Join-Path $PSScriptRoot "$Name.ps1") -Raw

    # Write-Host $content

    $expression = @"
function $Name {
$content
}
"@

    Invoke-Expression $expression

    if ($Export) {
        Export-ModuleMember -Function $Name
    }
}

это позволяет мне загружать скрипты как функции:

. AddModuleFileAsFunction "Get-WonderfulThings" -Export

(загружает тело Get-WonderfulThings.ps1 и экспортирует его как функцию:Get-WonderfulThings)

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