【问题标题】:How to export a class in a PowerShell v5 module如何在 PowerShell v5 模块中导出类
【发布时间】:2015-06-25 12:59:19
【问题描述】:

我已经将模块设置为类似于其他一些脚本的库。我不知道如何将类声明放入调用Import-Module 的脚本范围内。我尝试使用-class 参数来安排Export-Module,例如-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
    }
}

【问题讨论】:

  • Import-Module holidays -verbose 是否列出您的data_block
  • 那里只有 1 个其他功能,全部显示出来:PS ~> import-module holidays -verbose VERBOSE: Importing function 'get-holidays'.
  • 你可以。考虑将接受的回复更改为:stackoverflow.com/a/38701492/2502814
  • Export-Module 真的存在吗(不是反问句)?是Export-ModuleMember吗?

标签: powershell powershell-5.0


【解决方案1】:

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() 似乎工作可靠

调用绑定到包含该类的模块的脚本块似乎可以可靠地导出类的实例,并且不会受到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() 收集写入管道的所有对象,并且仅在整个脚本块的执行完成后才产生。

【讨论】:

  • 很高兴知道,虽然 using module 需要 literal 路径(不允许使用变量),但它也可以是 relative 路径。此外,不要默认使用该名称的任何模块碰巧被加载
  • @axl9r,你解释的问题也很有趣!
【解决方案2】:

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

ScriptsToProcess = @('Class.ps1')

然后将你的类放到 Class.ps1 文件中:

class MyClass {}

更新:尽管您不必在此方法中使用“使用模块 MyModule”,但您仍然必须:

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

Update2:这会将类加载到当前范围,因此如果您从函数内导入模块,例如,类将无法在函数外访问。可悲的是,我看到的唯一可靠的方法是用 C# 编写您的类并使用Add-Type -Language CSharp -TypeDefinition 'MyClass...' 加载它。

【讨论】:

【解决方案3】:

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

using module holidays

【讨论】:

  • using 非常令人沮丧。除了最琐碎的情况外,我发现它基本上无法使用。但事实证明,.NewBoundScriptBlock() 可以很好地导出类的实例。看我的回答here
  • 危险!危险!在这件事上,我已经无数次用头撞墙了。显然,在这方面并非所有 5.0 都是相同的。这在 5.0.10514.6 中不起作用(使用 (Get-Host).Version 检查)。更新您的 WinRM 5.0 副本,它将工作(5.0.10586.117 工作)!
  • 第二个链接表明您可以创建一个函数来创建类的实例并调用它来获取类的对象。
【解决方案4】:

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;}

【讨论】:

  • 你不喜欢它什么?
  • 以我的拙见,这是我见过的将对象边界内的事物模块化并从模块访问它的最佳方式。它可能不会隔离课堂中私有化事物的范围,但考虑到 PS 的一些限制,这是我们能做的最好的事情。
  • 我喜欢这种方式,它是 PS 惯用的,让我避免使用 using module
【解决方案5】:

这肯定不会按预期工作。
PowerShell 5 中的想法是,您可以在扩展名为 .psm1 的单独文件中定义您的类。
然后您可以使用以下命令(例如)加载定义:

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

这必须是脚本中的第一行(在 cmets 之后)。

造成如此痛苦的原因是,即使从脚本调用类定义,模块也会为整个会话加载。你可以通过运行看到这一点:

Get-Module

您将看到您加载的文件的名称。无论您是否再次运行该脚本,它都不会重新加载类定义! (它甚至不会读取 psm1 文件。)这会导致咬牙切齿。

Sometimes - sometimes - 您可以在运行脚本之前运行此命令,这将使用刷新的类定义重新加载模块:

Remove-Module  file

其中 file 是没有路径或扩展名的名称。但是,为了保持理智,我建议重新启动 PowerShell 会话。这显然很麻烦; Microsoft 需要以某种方式对此进行清理。

【讨论】:

【解决方案6】:

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

类关键字

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

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

【讨论】:

【解决方案7】:

我在 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 域中加载程序集一样)。

【讨论】:

    【解决方案8】:

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

    【讨论】:

      【解决方案9】:

      要在开发过程中更新类定义,请选择类的代码并按 F8 运行选定的代码。它不如Import-Module 命令上的-Force 选项干净。

      看到using Module 没有该选项,而Remove-Module 充其量是零星的,这是我发现的最好的方法来开发一个类并查看结果而无需关闭PowerShell ISE 并开始又来了。

      【讨论】:

        猜你喜欢
        • 2022-11-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-06-27
        • 2019-11-14
        • 2019-10-27
        • 2013-06-08
        相关资源
        最近更新 更多