问题
I am attempting to create a class object and use Invoke-Command
to call a function on the class on a remote machine. When I use Invoke-Command
with no computer name this works fine but when I attempt to do this on a remote computer I get an error saying the that the type does not contain my method. Here is the script I am using for testing this.
$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
}
}
Here is the output I get.
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>
I can see that the type changes from TestClass
to Deserialized.TestClass
and I am wondering if there is a way around this? My goal is to be able to ship the functions I need to each of the machines I am running a script on so that I don't have to rewrite the functions in the context of the Invoke-Command
script block.
回答1:
In short: The XML-based serialization / deserialization that PowerShell employs behind the scenes during remoting and in background jobs only handles a handful of known types with type fidelity.
Instances of custom classes such as yours are emulated with method-less "property bags" in the form of [pscustomobject]
instances, which is why the emulated instances of your class instances have no methods on the remote machine.
For a more detailed overview of PowerShell's serialization/deserialization, see the bottom section of this answer.
As Mike Twc suggests, you can work around the problem by passing your class definition along to your remote command as well, allowing you to redefine the class there and then recreate instances of your custom class in the remote session.
However, since you cannot dynamically obtain a custom class' definition, you'd have to either duplicate the class definition code or define it as a string to begin with, requiring evaluation via Invoke-Expression, which should generally be avoided.
A simplified example, which uses the $using: scope rather than parameters (via -ArgumentList
) to include values from the caller's scope.
# 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
}
来源:https://stackoverflow.com/questions/59899360/how-do-i-pass-a-class-object-in-a-argument-list-to-a-another-computer-and-call-a