Ответ 1
В соответствии с здесь и здесь вы можете использовать классы, определенные в вашем модуле, выполнив следующие действия в PowerShell 5:
using module holidays
У меня есть настройка модуля как библиотека для нескольких других скриптов. Я не могу понять, как получить объявление класса в области script, вызывающей Import-Module
. Я попытался упорядочить Export-Module
аргументом -class
, например, -function
, но не существует -class
. Мне просто нужно объявить класс в каждом script?
Настройка:
import-module holidays
Вот как выглядит класс:
Class data_block
{
$array
$rows
$cols
data_block($a,$r,$c)
{
$this.array = $a
$this.rows = $r
$this.cols = $c
}
}
В соответствии с здесь и здесь вы можете использовать классы, определенные в вашем модуле, выполнив следующие действия в PowerShell 5:
using module holidays
Я нашел способ загрузить классы без необходимости использования модуля. В файле MyModule.psd1 используйте строку:
ScriptsToProcess = @('Class.ps1')
И затем поместите свои классы в файл Class.ps1:
class MyClass {}
Обновление. Хотя вам не нужно использовать "использование модуля MyModule" с этим методом, вам все равно нужно:
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()
, с другой стороны, собирает все объекты, записанные в конвейер, и возвращает только после завершения выполнения всего блока скриптов.
Это, конечно, не работает должным образом.
Идея в 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 должна как-то их очистить.
Вы в значительной степени не можете. Согласно about_Classes
help:
Ключевое слово класса
Определяет новый класс. Это настоящий тип .NET Framework. Члены класса являются общедоступными, но только публичными в пределах области модуля. Вы не можете ссылаться на имя типа как строку (например, New-Object не работает), и в этой версии вы не можете использовать литерал типа (например, [MyClass]) вне script/module, в котором определяется класс.
Это означает, что если вы хотите получить экземпляр data_block
или использовать функции, которые работают с этими классами, сделайте функцию, скажем, New-DataBlock
, и верните новый экземпляр data_block
, который вы затем можете использовать для получения методов и свойств класса (вероятно, включая статические).
Я столкнулся с несколькими проблемами, относящимися к классам 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).
оператор 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;}
То, как я работал над этой проблемой, - это перемещение вашего пользовательского определения класса в пустой .ps1 файл с тем же именем (например, в Java/С#), а затем загрузить его как в определение модуля, так и в зависимый код путем точечного поиска. Я знаю, что это не здорово, но для меня это лучше, чем иметь несколько определений одного и того же класса для нескольких файлов...
Чтобы обновить определения классов во время разработки, выберите код для класса и нажмите F8
, чтобы запустить выбранный код. Не так чисто, как опция -Force
в команде Import-Module
. Использование Module
не имеет такой опции, и Remove-Module
является спорадическим в лучшем случае, это лучший способ, который я нашел для разработки класса и увидеть результаты без необходимости закрывать ISE и снова запускать его.
Я пробовал каждый подход на этой странице, и у меня есть YET, чтобы найти тот, который работает многократно. Я считаю, что кэширование модулей является самым неприятным аспектом этого... Я могу импортировать классы в качестве модулей и даже получать код для работы, но powershell только распознает новый класс с перерывами.. его почти как Powershell просто забывает, что вы когда-либо добавляли его...