如何在powershell v5模块中导出类

问题描述 投票:19回答:10

我有一个模块设置就像一个其他脚本的库。我无法弄清楚如何在调用Import-Module的脚本范围中获取类声明。我试图安排Export-Module-class论点,如-function,但没有-class可用。我只需要在每个脚本中声明该类吗?

设置:

  • 在\ \ documents \ windows \ powershell \ modules \ holidays \中的holidays.psm1
  • 活动脚本调用import-module holidays
  • holidays.psm1中有另一个函数正确返回一个类对象,但我不知道如何在导入后从活动脚本创建该类的新成员

这是这个类的样子:

Class data_block
{
    $array
    $rows
    $cols
    data_block($a,$r,$c)
    {
        $this.array = $a
        $this.rows = $r
        $this.cols = $c
    }
}
powershell powershell-v5.0
10个回答
14
投票

根据herehere,您可以通过在PowerShell 5中执行以下操作来使用模块中定义的类:

using module holidays

0
投票

我已经尝试了这个页面上的每一种方法,我已经找到了一个可以重复使用的方法。我发现模块缓存是最令人沮丧的方面...我可以将类作为模块导入,甚至可以使代码工作,但是powershell只能间歇性地识别新类...它几乎像Powershell一样忘记你曾经添加过一个.. 。


16
投票

我找到了一种加载类的方法,而无需“使用模块”。在MyModule.psd1文件中使用以下行:

ScriptsToProcess = @('Class.ps1')

然后将您的类放在Class.ps1文件中:

class MyClass {}

更新:虽然您不必在此方法中使用“使用模块MyModule”,但您仍需要:

  • 运行“使用模块MyModule”
  • 或者运行“Import-Module MyModule”
  • 或者调用模块中的任何函数(因此它会在途中自动导入模块)

15
投票

PSA:有一个已知的问题是将旧的类副本保存在内存中。如果您不了解它,那么使类的处理真的很混乱。你可以读一下here


using容易陷入陷阱

using关键字容易出现以下各种陷阱:

  • 除非您在using语句中指定模块的完整路径,否则PSModulePath语句不适用于不在using中的模块。这是相当令人惊讶的,因为虽然可以通过Get-Module获得一个模块,但using语句可能不起作用,具体取决于模块的加载方式。
  • using语句只能在“脚本”的最开头使用。没有[scriptblock]::Create()New-Module的组合似乎克服了这一点。传递给Invoke-Expression的字符串似乎充当了一种独立的脚本;在这种字符串类型的作品开头的using声明。也就是说,Invoke-Expression "using module $path"可以成功,但模块内容可用的范围似乎是不可理解的。例如,如果在Pester脚本块中使用Invoke-Expression "using module $path",则模块内的类不能从相同的Pester脚本块中获得。

以上陈述基于this set of tests

ScriptsToProcess阻止访问专用模块功能

在模块清单的ScriptsToProcess引用的脚本中定义类似乎乍一看从模块中导出类。但是,它不是出口类,而是"creates the class in the global SessionState instead of the module's, so it...can't access private functions"。据我所知,使用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()似乎可靠地工作

调用绑定到包含该类的模块的scriptblock似乎可靠地工作以导出类的实例,并且不会遭受using所做的陷阱。考虑这个包含类并已导入的模块:

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

在绑定到模块的scriptblock中调用[c]::new()会生成[c]类型的对象:

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

.NewBoundScriptBlock()的惯用语替代品

似乎有一个较短的,惯用的替代.NewBoundScriptBlock()。以下两行分别在Get-Module输出的模块的会话状态中调用scriptblock:

& (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

这必须是脚本中的第一行(注释后)。 造成这么大痛苦的原因是,即使从脚本调用类定义,也会为整个会话加载模块。你可以通过运行来看到这个:

    get-module

您将看到加载的文件的名称。无论你是否再次运行脚本,它都不会重新加载类定义! (甚至不会读取psm1文件。)这会导致牙齿咬牙切齿。 有时 - 有时 - 您可以在运行脚本之前运行此命令,该脚本将使用刷新的类定义重新加载模块:

    remove-module  file

其中file是没有路径或扩展名的名称。但是为了节省您的理智,我建议您重新启动PS会话。这显然很麻烦;微软需要以某种方式清理它。


3
投票

你几乎不能。根据about_Classes的帮助:

类关键字

定义一个新类。这是一个真正的.NET Framework类型。类成员是公共的,但只在模块范围内公开。您不能将类型名称称为字符串(例如,New-Object不起作用),并且在此版本中,您不能在脚本之外使用类型文字(例如,[MyClass])定义类的模块文件。

这意味着,如果你想让自己获得一个data_block实例或使用操作这些类的函数,那么创建一个函数,比如New-DataBlock并让它返回一个新的data_block实例,然后你可以使用它来获取类方法和属性(可能包括静态的)。


3
投票

我在v5中遇到了有关PowerShell类的多个问题。

我现在决定使用以下解决方法,因为它与.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域中加载程序集一样)。


1
投票

我解决这个问题的方法是将自定义类定义移动到一个具有相同名称的空.ps1文件中(就像在Java / C#中一样),然后将其加载到模块定义和依赖代码中点源。我知道这不是很好,但对我来说,最好不要在多个文件中维护同一个类的多个定义......


1
投票

要在开发时更新类定义,请选择类的代码,然后按F8运行所选代码。不像-Force命令中的Import-Module选项那样干净。看作使用Module没有那个选项,Remove-Module充其量只是零星的,这是我发现开发课程并查看结果而不必关闭ISE并重新启动它的最佳方法。


1
投票

using声明是它适合你的方式。否则这似乎也有效。

testclass.psm1

使用函数来提供课程

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

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

Export-ModuleMember -Function abc

someScript.ps1

Import-Module path\to\testclass.psm1
$testclass = 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;}
© www.soinside.com 2019 - 2024. All rights reserved.