Как экспортировать класс в модуль powershell v5

У меня есть настройка модуля как библиотека для нескольких других скриптов. Я не могу понять, как получить объявление класса в области script, вызывающей Import-Module. Я попытался упорядочить Export-Module аргументом -class, например, -function, но не существует -class. Мне просто нужно объявить класс в каждом script?

Настройка:

  • holidays.psm1 в ~\documents\windows\powershell\modules\holidays\
  • активные script вызовы import-module holidays
  • в holiday.psm1 есть другая функция, которая возвращает объект класса правильно, но я не знаю, как создавать новые члены класса из активного script после импорта

Вот как выглядит класс:

Class data_block
{
    $array
    $rows
    $cols
    data_block($a,$r,$c)
    {
        $this.array = $a
        $this.rows = $r
        $this.cols = $c
    }
}

Ответы

Ответ 1

В соответствии с здесь и здесь вы можете использовать классы, определенные в вашем модуле, выполнив следующие действия в PowerShell 5:

using module holidays

Ответ 2

Я нашел способ загрузить классы без необходимости использования модуля. В файле MyModule.psd1 используйте строку:

ScriptsToProcess = @('Class.ps1')

И затем поместите свои классы в файл Class.ps1:

class MyClass {}

Обновление. Хотя вам не нужно использовать "использование модуля MyModule" с этим методом, вам все равно нужно:

  • Запустить "с помощью модуля MyModule"
  • Или запустите "Импорт-модуль MyModule"
  • Или вызовите любую функцию в вашем модуле (чтобы он автоматически импортировал ваш модуль в пути)

Ответ 3

PSA: существует известная проблема, которая хранит старые копии классов в памяти. Это делает работу с классами действительно запутанной, если вы не знаете об этом. Вы можете прочитать об этом здесь.


using подвержен ловушкам

Ключевое слово using подвержено различным подводным камням:

  • Оператор using не работает для модулей, не входящих в PSModulePath, если вы не укажете полный путь к модулю в операторе using. Это довольно удивительно, потому что хотя модуль доступен через Get-Module, оператор using может не работать в зависимости от того, как модуль был загружен.
  • Оператор using может использоваться только в самом начале "сценария". Никакая комбинация [scriptblock]::Create() или New-Module, кажется, не может преодолеть это. Строка, переданная в Invoke-Expression, похоже, действует как отдельный скрипт; оператор using в начале такой строковой сортировки. Таким образом, Invoke-Expression "using module $path" может быть успешным, но область, в которой доступно содержимое модуля, кажется довольно непостижимой. Например, если Invoke-Expression "using module $path" используется внутри блока сценариев Pester, классы внутри модуля недоступны из того же блока сценариев Pester.

Вышеприведенные утверждения основаны на этом наборе тестов.

ScriptsToProcess Запрещает доступ к функциям частного модуля

Определение класса в скрипте, на который ссылается манифест модуля ScriptsToProcess, на первый взгляд кажется экспортом класса из модуля. Однако вместо экспорта класса он "создает класс в глобальном SessionState вместо модуля, поэтому он... не может получить доступ к частным функциям". Насколько я могу судить, использование ScriptsToProcess похоже на определение класса вне модуля следующим образом:

#  this is like defining c in class.ps1 and referring to it in ScriptsToProcess
class c {
    [string] priv () { return priv }
    [string] pub  () { return pub  }
}

# this is like defining priv and pub in module.psm1 and referring to it in RootModule
New-Module {
    function priv { 'private function' }
    function pub  { 'public function' }
    Export-ModuleMember 'pub'
} | Import-Module

[c]::new().pub()  # succeeds
[c]::new().priv() # fails

Вызов этого результата в

public function
priv : The term 'priv' is not recognized ...
+         [string] priv () { return priv } ...

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

.NewBoundScriptBlock() Кажется, работает надежно

Вызов блока скрипта, привязанного к модулю, содержащему класс, похоже, надежно работает для экспорта экземпляров класса и не страдает от ловушек, которые делает using. Рассмотрим этот модуль, который содержит класс и был импортирован:

New-Module 'ModuleName' { class c {$p = 'some value'} } |
    Import-Module

Вызов [c]::new() внутри блока скриптов, привязанного к модулю, создает объект типа [c]:

PS C:\> $c = & (Get-Module 'ModuleName').NewBoundScriptBlock({[c]::new()})
PS C:\> $c.p
some value

Идиоматическая альтернатива .NewBoundScriptBlock()

Кажется, что существует более короткая идиоматическая альтернатива .NewBoundScriptBlock(). Каждая из следующих двух строк вызывает блок скрипта в состоянии сеанса вывода модуля с помощью Get-Module:

& (Get-Module 'ModuleName').NewBoundScriptBlock({[c]::new()})
& (Get-Module 'ModuleName') {[c]::new()}}

Последнее имеет преимущество в том, что при передаче объекта в конвейер будет передаваться поток управления в середине скриптового блока конвейера. .NewBoundScriptBlock(), с другой стороны, собирает все объекты, записанные в конвейер, и возвращает только после завершения выполнения всего блока скриптов.

Ответ 4

Это, конечно, не работает должным образом.
Идея в PS 5 заключается в том, что вы можете определить свой класс в отдельном файле с расширением .psm1.
Затем вы можете загрузить определение с помощью команды (например):

   using module C:\classes\whatever\path\to\file.psm1

Это должна быть первая строка в script (после комментариев).



Причиной такой боли является то, что даже если определения классов вызываются из script, модули загружаются для всего сеанса. Вы можете увидеть это, выполнив:

    get-module

Вы увидите имя загруженного вами файла. Независимо от того, снова ли вы запустите script, он будет НЕ перезагрузить определения классов! (Даже не прочитает файл psm1.) Это вызывает много скрежет зубов.


Иногда - иногда - вы можете запустить эту команду перед запуском script, которая перезагрузит модуль обновленными определениями классов:

    remove-module  file

где file - это имя без пути или расширения. Однако для сохранения вашего удобства я рекомендую перезапустить сеанс PS. Это, очевидно, громоздко; Microsoft должна как-то их очистить.

Ответ 5

Вы в значительной степени не можете. Согласно about_Classes help:

Ключевое слово класса

Определяет новый класс. Это настоящий тип .NET Framework. Члены класса являются общедоступными, но только публичными в пределах области модуля. Вы не можете ссылаться на имя типа как строку (например, New-Object не работает), и в этой версии вы не можете использовать литерал типа (например, [MyClass]) вне script/module, в котором определяется класс.

Это означает, что если вы хотите получить экземпляр data_block или использовать функции, которые работают с этими классами, сделайте функцию, скажем, New-DataBlock, и верните новый экземпляр data_block, который вы затем можете использовать для получения методов и свойств класса (вероятно, включая статические).

Ответ 6

Я столкнулся с несколькими проблемами, относящимися к классам PowerShell в v5.

Я решил использовать следующее обходное решение, так как это отлично совместимо с .net и PowerShell:

Add-Type -Language CSharp -TypeDefinition @"
namespace My.Custom.Namespace {
    public class Example
    {
        public string Name { get; set; }
        public System.Management.Automation.PSCredential Credential { get; set; }
        // ...
    }
}
"@

Преимущество состоит в том, что вам не нужна специальная сборка для добавления определения типа, вы можете добавить определение класса inline в свои сценарии или модули PowerShell.

Единственным недостатком является то, что вам нужно будет создать новую среду выполнения для повторной загрузки определения класса после того, как она была загружена в первый раз (подобно загрузке сборок в домене С#/. net).

Ответ 7

оператор using - это то, что вам нужно. в противном случае это тоже работает.

testclass.psm1

Используйте функцию для доставки класса

class abc{
    $testprop = 'It Worked!'
    [int]testMethod($num){return  $num * 5}
}

function new-abc(){
    return [abc]::new()
}

Export-ModuleMember -Function new-abc

someScript.ps1

Import-Module path\to\testclass.psm1
$testclass = new-abc
$testclass.testProp        # returns 'It Worked!'
$testclass.testMethod(500) # returns 2500


$testclass | gm

Name        MemberType Definition
----        ---------- ----------
Equals      Method     bool Equals(System.Object obj)
GetHashCode Method     int GetHashCode()
GetType     Method     type GetType()
testMethod  Method     int testMethod(System.Object num)
ToString    Method     string ToString()
testprop    Property   System.Object testprop {get;set;}

Ответ 8

То, как я работал над этой проблемой, - это перемещение вашего пользовательского определения класса в пустой .ps1 файл с тем же именем (например, в Java/С#), а затем загрузить его как в определение модуля, так и в зависимый код путем точечного поиска. Я знаю, что это не здорово, но для меня это лучше, чем иметь несколько определений одного и того же класса для нескольких файлов...

Ответ 9

Чтобы обновить определения классов во время разработки, выберите код для класса и нажмите F8, чтобы запустить выбранный код. Не так чисто, как опция -Force в команде Import-Module. Использование Module не имеет такой опции, и Remove-Module является спорадическим в лучшем случае, это лучший способ, который я нашел для разработки класса и увидеть результаты без необходимости закрывать ISE и снова запускать его.

Ответ 10

Я пробовал каждый подход на этой странице, и у меня есть YET, чтобы найти тот, который работает многократно. Я считаю, что кэширование модулей является самым неприятным аспектом этого... Я могу импортировать классы в качестве модулей и даже получать код для работы, но powershell только распознает новый класс с перерывами.. его почти как Powershell просто забывает, что вы когда-либо добавляли его...