Как передать объект класса в списке аргументов другому компьютеру и вызвать на нем функцию?

Я пытаюсь создать объект класса и использовать 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.


person Max Young    schedule 24.01.2020    source источник
comment
Я думаю, вам нужно сериализовать объект. См. S.O. Есть ли способ передать сериализуемые объекты в сценарий PowerShell с помощью start-process?   -  person William Charlton    schedule 24.01.2020
comment
просто включите определение класса в блок скрипта и передайте аргументы конструктора в качестве параметра. Поскольку 2 компьютера не используют совместно используемую память, они могут обмениваться только с сериализованными (пара ключ / значение) объектами.   -  person Mike Twc    schedule 24.01.2020


Ответы (1)


Вкратце: Сериализация / десериализация на основе XML, которую PowerShell использует за кулисами во время удаленного взаимодействия и в фоновых заданиях, обрабатывает только несколько известных типов с помощью верность шрифту.

Экземпляры пользовательских классов, таких как ваш, эмулируются с помощью безметодных "пакетов свойств" в форме [pscustomobject] экземпляров, поэтому эмулируемые у экземпляров вашего класса нет методов на удаленном компьютере.

Более подробный обзор сериализации / десериализации PowerShell см. В нижнем разделе этого ответа.


Как предлагает Майк Твк, вы можете обойти проблему, передав определение вашего класса вместе с вашей удаленной командой, что позволяет вам переопределить класс там, а затем воссоздать экземпляры вашего настраиваемого класса в удаленном сеансе.

Однако, поскольку вы не можете динамически получить определение настраиваемого класса, вам придется либо продублировать код определения класса, либо определить его как строку для начала, требуя оценки через _ 2_, чего обычно следует избегать.

Упрощенный пример, в котором используется $using: область, а не параметры (через -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
}
person mklement0    schedule 26.01.2020
comment
Я рад слышать, что это было полезно, @MaxYoung; Не за что. - person mklement0; 27.01.2020