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

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

Настройка:

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

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

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

person jason    schedule 25.06.2015    source источник
comment
Import-Module holidays -verbose перечисляет ваши data_block?   -  person Vesper    schedule 25.06.2015
comment
Там есть еще одна функция, и все это появляется: PS ~> import-module holidays -verbose VERBOSE: Importing function 'get-holidays'.   -  person jason    schedule 25.06.2015
comment
Ты сможешь. Попробуйте изменить принятый ответ на: stackoverflow.com/a/38701492/2502814   -  person Vincent    schedule 19.08.2016
comment
Export-Module действительно существует (вопрос не риторический)? Это Export-ModuleMember?   -  person Peter Mortensen    schedule 18.06.2021


Ответы (9)


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

using module holidays
person Lars Truijens    schedule 01.08.2016
comment
using удивительно расстраивает. Я нашел его практически непригодным для использования, за исключением самых тривиальных обстоятельств. Однако оказывается, что .NewBoundScriptBlock() отлично работает для экспорта экземпляров класса. См. Мой ответ здесь. - person alx9r; 05.11.2016
comment
Опасность! Опасность! Я бесчисленное количество раз бил головой об стену по этому поводу. Видимо, не все 5.0 в этом отношении одинаковы. Это НЕ РАБОТАЕТ в 5.0.10514.6 (проверьте с помощью (Get-Host) .Version). Обновите свою копию WinRM 5.0, и она будет работать (5.0.10586.117 работает)! - person Dave Markle; 05.02.2017
comment
Вторая ссылка, в которой говорится, что вы можете создать функцию, которая создает экземпляры вашего класса и вызывает ее для получения объектов класса, - это обходной путь. - person Dave F; 24.02.2021

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 похоже на определение класса вне модуля следующим образом:

#  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(), с другой стороны, собирает все объекты, записанные в конвейер, и возвращает только после завершения выполнения всего блока сценария.

person alx9r    schedule 05.11.2016
comment
Полезно знать, хотя для using module требуется буквальный путь (переменные не допускаются), он также может быть относительным путем. Кроме того, имеет смысл не использовать по умолчанию какой-либо модуль с таким именем , который загружается. - person mklement0; 16.03.2017
comment
@ axl9r, тоже вне всяких сомнений, то, что вы объясняете, очень интересно! - person gsscoder; 16.07.2020

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

ScriptsToProcess = @('Class.ps1')

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

class MyClass {}

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

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

Update2: это загрузит класс в текущую область, поэтому, если вы импортируете модуль из функции, например, класс не будет доступен за пределами функции. К сожалению, единственный надежный метод, который я вижу, - это написать свой класс на C # и загрузить его с помощью Add-Type -Language CSharp -TypeDefinition 'MyClass...'.

person ili    schedule 05.10.2017
comment
Это отличный способ предоставить доступ к общедоступным классам, которые хранятся в отдельных файлах ps1 или в подпапке вашего модуля. Может использоваться как символ подстановки ScriptsToProcess = @('publicClasses\*.ps1') - person Austin S.; 28.01.2018
comment
Не работает: Import-Module : The member 'ScriptsToProcess' in the module manifest is not valid: The path cannot be processed because it resolved to more than one file; only one file at a time can be processed.. Verify that a valid value is specified for this field in the 'C:\Stash\MyModule\MyModule.psd1' file. - person Mohamed Nuur; 22.11.2018

с использованием - это правильный путь, если он работает для вас. В противном случае это тоже работает.

Файл 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;}
person Jakobii    schedule 07.02.2018
comment
Что тебе в этом не нравится? - person Espen; 07.02.2018
comment
По моему скромному мнению, это лучший способ, который я когда-либо видел, для модуляции вещей в границах объекта и доступа к нему из модуля. Возможно, это не изолирует возможности для приватизации вещей в классе, но это лучшее, что мы можем сделать с учетом некоторых ограничений PS. - person Mark S; 28.08.2019
comment
Мне нравится этот путь, он PS идиоматический, и позвольте мне избежать использования using module. - person gsscoder; 16.07.2020

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

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

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

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

Get-Module

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

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

Remove-Module  file

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

person 0xG    schedule 23.02.2017
comment
Я создал эту проблему для решения проблемы, связанной с тем, что оператор using module не перезагружает модуль после того, как в него были внесены изменения. Пожалуйста, поднимите палец вверх, чтобы проголосовать за него. github.com/PowerShell/PowerShell/issues/7654 - person deadlydog; 29.08.2018
comment
windowsserver .uservoice.com / forum / 301869-powershell / @deadlydog, я думаю, это вы также добавили этот голос пользователя. - person JoePC; 03.04.2019
comment
Вы должны иметь возможность перезагрузить модуль с помощью Import-Module MyModule -Force. Не идеальный, но все же жизнеспособный обходной путь. - person curropar; 31.12.2019

Вы почти не можете. Согласно about_Classes справке:

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

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

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

person Vesper    schedule 25.06.2015
comment
Практически невозможно: раньше это было правдой, но теперь в Powershell 5 это возможно. Ответ Ларса Туйенса дает дополнительную команду для добавления после вашего модуля импорта. Оператор using включает в PowerShell возможность импортировать классы PowerShell из модулей PowerShell! - person Vincent; 19.08.2016
comment
Похоже, это никогда не было достаточно точным, поскольку похоже, что вы всегда могли & (Get-Module ModuleName).NewBoundScriptBlock({[data_block]::new()]} экспортировать экземпляр даже до того, как using был доступен. - person alx9r; 05.11.2016

Я также столкнулся с множеством проблем с классами PowerShell в версии 5.

На данный момент я решил использовать следующий обходной путь, поскольку он полностью совместим с .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; }
        // ...
    }
}
"@

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

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

person oɔɯǝɹ    schedule 05.01.2017

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

person Steve Rathbone    schedule 16.04.2016

Чтобы обновить определения классов во время разработки, выберите код для класса и нажмите F8, чтобы запустить выбранный код. Это не так чисто, как параметр -Force в команде Import-Module.

Видя, что using Module не имеет этой опции и Remove-Module в лучшем случае носит спорадический характер. Я нашел лучший способ разработать класс и увидеть результаты, не закрывая PowerShell ISE и снова запустите его.

person DavSum    schedule 12.10.2017