A PowerShell script calls Invoke-Command against a remote server to get process objects back, but calling .Kill() on one of the returned objects fails with a method-not-found error, while the same code works locally. What is going on?
answer
- nothing live crosses the wire
- property bag, not the object
- check the type-name prefix
- act where the object lives
basics
~20 sObjects crossing a PowerShell remoting boundary are serialized to XML and rebuilt on the other side as inert property bags. The returned object carries the remote object's property values but not its live methods or its connection to the remote process, so .Kill() does not exist.
solid answer
~40 sRemoting cannot send a live .NET object across the wire, so PowerShell serializes it (a CLIXML representation) and the client rebuilds a *deserialized* copy. That copy is a `PSObject` whose type name is prefixed `Deserialized.` — for a process, `Deserialized.System.Diagnostics.Process` — and it has the property values as static snapshots but essentially no methods, because the real object still lives in the remote runspace. Serialization is also shallow, so deeply nested properties get flattened, and a handful of well-known types are deliberately rehydrated back into live local objects. The fix is not to bring the object home and act on it; do the work where the live object is: `Invoke-Command -ComputerName Server01 -ScriptBlock { Stop-Process -Name pwsh }`, and return only the data you actually want to look at.
code
powershell · 9 lines$procs = Invoke-Command -ComputerName Server01 -ScriptBlock { Get-Process -Name pwsh }
$procs[0].PSObject.TypeNames[0]
$procs[0] | Get-Member -MemberType Method
Invoke-Command -ComputerName Server01 -ScriptBlock { Stop-Process -Name pwsh -Force }
Invoke-Command -ComputerName Server01 -ScriptBlock {
Get-Process -Name pwsh | Select-Object Name, Id, StartTime
}go deeper
Know that data crossing a remoting boundary is copied, not shared, and that the copy has values but not working methods. If you need to act on something remote, run the action remotely.
Explain the mechanism: CLIXML serialization, a PSObject whose type name is prefixed Deserialized., properties as static NoteProperty snapshots, shallow depth, and a small set of rehydrated well-known types.
Turn it into a design rule for remote scripts: compute and mutate on the remote side, project with Select-Object so only flat data crosses, and never write code whose correctness depends on type-based binding of a returned object.
Own the boundary as an interface contract. Treat what crosses the wire as a versioned data shape your tooling depends on, so fleet scripts do not break when a remote module changes an object's internals, and keep the payload small enough to aggregate across thousands of hosts.
## Why anything is serialized at all PowerShell remoting runs your code in a runspace on another machine. A .NET object such as `System.Diagnostics.Process` is a handle into *that machine's* memory and OS state — it is not something that can be teleported. So the remoting layer takes the object apart into a text representation (the CLIXML format you also see from `Export-Clixml`), sends that, and the receiving side reconstructs an object from it. What comes back is therefore a **copy of the data**, not the thing itself. ## What the copy looks like ```powershell $procs = Invoke-Command -ComputerName Server01 -ScriptBlock { Get-Process -Name pwsh } $procs[0].PSObject.TypeNames[0] # Deserialized.System.Diagnostics.Process $procs[0].Id # works: a snapshot value $procs[0] | Get-Member -MemberType Method # almost nothing; no Kill() ``` Three things to notice: 1. **The type name is prefixed `Deserialized.`** That prefix is your diagnostic. If you ever wonder why a script behaves differently against a remote host, check `PSObject.TypeNames[0]` (or `pstypenames`). 2. **Properties survive; methods do not.** The properties arrive as `NoteProperty` values — plain data attached to a `PSObject`. Instance methods that would have to talk to the live object are gone, because there is nothing behind them to talk to. A few universal members such as `ToString()` remain. 3. **The values are a snapshot.** `WorkingSet` is what it was at serialization time. Re-reading the property does not re-query the remote machine; there is no connection behind it. Because the type is not the real type, type-based dispatch changes too: parameters declared as `[System.Diagnostics.Process]` will not bind a deserialized process, and `-is [System.Diagnostics.Process]` is false. Formatting usually still looks right, because PowerShell's formatter matches on the type-name list and the original name is retained after the `Deserialized.` entry. ## Serialization is shallow The remoting serializer does not walk an object graph indefinitely — it expands properties only to a limited depth and then renders what is left as strings. This is why a rich nested object can come back with an inner property that is now the string `System.Something.Something` instead of a structure you can drill into. Practical consequence: **project the data you need on the remote side** (`Select-Object Name, Id, StartTime`) rather than shipping a fat object and hoping the shape survives. ## Rehydration: the exceptions Some types are deliberately reconstructed as live local objects rather than left inert — primitives, strings, dates, and a set of well-known types that PowerShell's extended type system knows how to rebuild. That is why a returned `[datetime]` behaves normally and you can call methods on it. Do not generalise from those cases: for arbitrary .NET objects, assume you get a property bag. ## The correct pattern The question 'how do I call the method on the object I got back' is the wrong question. Act where the object is alive, and return only results: ```powershell # wrong instinct: bring the object home and operate on it $p = Invoke-Command -ComputerName Server01 -ScriptBlock { Get-Process -Name pwsh } $p[0].Kill() # method does not exist # right: do the work in the remote runspace Invoke-Command -ComputerName Server01 -ScriptBlock { Stop-Process -Name pwsh -Force } # right: bring back only the data you want to read Invoke-Command -ComputerName Server01 -ScriptBlock { Get-Process -Name pwsh | Select-Object Name, Id, StartTime } ``` The same discipline applies in reverse. When you send an object *into* a remote session with `$using:` or `-ArgumentList`, it is serialized on the way in, so the remote side receives a deserialized copy too — passing a live connection, stream or handle across the boundary does not work. ## Why interviewers like this It separates people who have run remoting in anger from people who have read the cmdlet help. The failure is confusing precisely because the script works perfectly on the local machine and the returned object *prints* identically; only the behaviour differs. Being able to name the boundary, recognise the `Deserialized.` prefix, and state the fix — compute remotely, return data — is the whole answer.
- How would you confirm at the prompt that an object you received is deserialized?Inspect its type-name list — `$obj.PSObject.TypeNames[0]` (or `$obj.pstypenames[0]`) shows a name prefixed `Deserialized.`, for example `Deserialized.System.Diagnostics.Process`. `Get-Member` is the other tell: you will see the properties as NoteProperty entries and almost no instance methods, which is exactly the shape of a property bag.
- Some returned objects, such as dates, behave completely normally. Why the inconsistency?Primitives and a set of well-known types are rehydrated: the deserializer knows how to rebuild them as real local objects, so a `[datetime]` arrives with its methods intact. That set is fixed and small, so it is not something to design around — for arbitrary .NET types, assume you get an inert copy.
- Does the same serialization apply to objects you send into a remote session with $using: or -ArgumentList?Yes — the boundary works both ways. Values passed in are serialized and arrive as deserialized copies, so live handles, open connections, streams or objects with meaningful methods do not survive the trip. Pass simple data such as strings, numbers and hashtables, and construct anything stateful inside the remote script block.
- Why can a nested property come back as a string like "System.Collections.Hashtable"?Remoting serialization expands an object graph only to a shallow depth and renders anything deeper by its ToString() value. That is why fat, deeply nested objects lose structure across the wire. The remedy is to shape the data remotely — `Select-Object` the specific fields, or build a small `[pscustomobject]` — so what crosses the boundary is already flat.
saying these in an interview costs you the question
- Says the returned object is the same object, just slower to call
- Blames permissions or the remote firewall for the missing method
- Assumes properties keep refreshing from the remote machine
- Thinks retrieving the object again will restore its methods
- Believes only the transport, not the object model, changes