【问题标题】:How do I pass a class object in a argument list to a another computer and call a function on it?如何将参数列表中的类对象传递给另一台计算机并在其上调用函数?
【发布时间】:2020-01-27 06:31:08
【问题描述】:

我正在尝试创建一个类对象并使用Invoke-Command 在远程机器上调用该类的函数。当我使用没有计算机名称的Invoke-Command 时,这可以正常工作,但是当我尝试在远程计算机上执行此操作时,我收到一个错误,指出该类型不包含我的方法。这是我用来测试的脚本。

$ComputerName = "<computer name>"

[TestClass]$obj = [TestClass]::new("1", "2")

Get-Member -InputObject $obj

$credentials = Get-Credential

Invoke-Command -ComputerName $ComputerName -Credential $credentials -Authentication Credssp -ArgumentList ([TestClass]$obj) -ScriptBlock {
    $obj = $args[0]

    Get-Member -InputObject $obj

    $obj.DoWork()
    $obj.String3
}

class TestClass {
    [string]$String1
    [string]$String2
    [string]$String3

    [void]DoWork(){
        $this.String3 = $this.String1 + $this.String2
    }

    TestClass([string]$string1, [string]$string2) {
        $this.String1 = $string1
        $this.String2 = $string2
    }
}

这是我得到的输出。

PS > .\Test-Command.ps1

cmdlet Get-Credential at command pipeline position 1
Supply values for the following parameters:
User: <my user>
Password for user <my user>: *

   TypeName: TestClass

Name        MemberType Definition
----        ---------- ----------
DoWork      Method     void DoWork()
Equals      Method     bool Equals(System.Object obj)
GetHashCode Method     int GetHashCode()
GetType     Method     type GetType()
ToString    Method     string ToString()
String1     Property   string String1 {get;set;}
String2     Property   string String2 {get;set;}
String3     Property   string String3 {get;set;}


   TypeName: Deserialized.TestClass

Name     MemberType Definition
----     ---------- ----------
GetType  Method     type GetType()
ToString Method     string ToString(), string ToString(string format, System.IFormatProvider formatProvider), string IFormattable.ToString(string format, System.IFormatProvider formatProvider)
String1  Property   System.String {get;set;}
String2  Property   System.String {get;set;}
String3  Property    {get;set;}
Method invocation failed because [Deserialized.TestClass] does not contain a method named 'DoWork'.
    + CategoryInfo          : InvalidOperation: (DoWork:String) [], RuntimeException
    + FullyQualifiedErrorId : MethodNotFound
    + PSComputerName        : <computer name>

我可以看到类型从TestClass 变为Deserialized.TestClass,我想知道是否有办法解决这个问题?我的目标是能够将我需要的功能运送到我正在运行脚本的每台机器上,这样我就不必在 Invoke-Command 脚本块的上下文中重写这些功能。

【问题讨论】:

标签: powershell serialization invoke-command


【解决方案1】:

简而言之:PowerShell 在远程处理期间在幕后使用的基于 XML 的序列化/反序列化以及在后台作业中仅处理少数已知类型 类型保真度。

自定义类的实例(例如您的类)模拟,无方法“属性包”以[pscustomobject] 实例的形式,这就是为什么类实例的模拟实例在远程机器上没有方法的原因。

有关 PowerShell 的序列化/反序列化的更详细概述,请参阅this answer 的底部部分。


正如Mike Twc 建议的那样,您可以通过将您的类定义也传递给您的远程命令来解决问题,允许您在那里重新定义类并然后在远程会话中重新创建自定义类的实例。

但是,由于您无法动态获取自定义类的定义,因此您必须复制类定义代码或将其定义为 string 开头,需要通过 Invoke-Expression, which should generally be avoided 进行评估.

一个简化的示例,它使用$using: scope 而不是参数(通过-ArgumentList)来包含来自调用者范围的值。

# Define your custom class as a *string* first.
$classDef = @'
class TestClass {
    [string]$String1
    [string]$String2
    [string]$String3

    [void]DoWork(){
        $this.String3 = $this.String1 + $this.String2
    }

    TestClass([string]$string1, [string]$string2) {
        $this.String1 = $string1
        $this.String2 = $string2
    }

    # IMPORTANT:
    # Also define a parameter-less constructor, for convenient
    # construction by a hashtable of properties.
    TestClass() {}

}
'@

# Define the class in the caller's session.
# CAVEAT: This particular use of Invoke-Expression is safe, but
#         it should generally be avoided.
#         See https://blogs.msdn.microsoft.com/powershell/2011/06/03/invoke-expression-considered-harmful/
Invoke-Expression $classDef

# Construct an instance
$obj = [TestClass]::new("1", "2")

# Invoke a command remotely, passing both the class definition and the input object. 
Invoke-Command -ComputerName . -ScriptBlock {
    # Define the class in the remote session too.
    Invoke-Expression $using:classDef
    # Now you can cast the emulated original object to the recreated class.
    $recreatedObject = [TestClass] $using:obj
    # Now you can call the method...
    $recreatedObject.DoWork()
    # ... and output the modified property
    $recreatedObject.String3
}

【讨论】:

  • 我很高兴听到它有帮助,@MaxYoung;我的荣幸。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-11-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-28
  • 1970-01-01
相关资源
最近更新 更多