Logged in to the system directly, I run this statement, and get this output:
(Get-ClusterNetwork 'cluster backups').role
None
This is perfect... beautiful even, in it's simplicity.
However, when I run the exact same statement from a remote machine using invoke-command, which up until now i always just assumed was like typing this exact statement into the CLI of the machine, I get THIS output instead
Invoke-Command -Session $hi -ScriptBlock {(Get-ClusterNetwork 'cluster backups').role}
PSComputerName RunspaceId Value
-------------- ---------- -----
dumdum a84b6c89-dumdum-80d3-ed43230ee8ab None
Now here's the really funny thing. If i assign a variable to the invoke-command output, it'll have the same output shown above UNLESS - i pipe it to set-clipboard
So the variable
$hello = invoke-command -session $hi -scriptblock {(get-networkcluster 'cluster backups').role}
Now type $hello into prompt and I get:
PSComputerName RunspaceId Value
-------------- ---------- -----
dumdum a84b6c89-dumdum-80d3-ed43230ee8ab None
Which is expected. But now when I pipe that to set-clipboard and paste - the value is:
$hello | set-clipboard;
get-clipboard
None
Which is the actual value I want. Somehow piping to set-clipboard knows to only pull the property that i originally asked for. Even though the variable, has all the properties. When i run $hello.gettype() - i see the value as Int32. Which makes sense if $hello was only returning the value I wanted, but it's... not.
But if that wasn't weird enough - I'm running a few functions within the invoke-command, this is only one piece - all of the functions return a value i'm trying to report on. So:
$row = '' | select computername, ClusterNetworkRole, IP;
$row.computername = $name;
$row.clusternetworkrole = $hello;
$row.ip = dum.dum.dum.dum;
Return $row;
Do you know what the output of $row.clusternetworkrole is? Take a wild guess. It's every property EXCEPT the one I want.
$row
PSComputerName : dumdum
RunspaceId : b898bdad-dumdum-9eff-8a2beeefe78a
ClusterNetworkRole :
Computername : dum
IP : dum.dum.dum.dum
Not only does it give me the exact properties i DON'T want - it actually adds those properties as members of $row.
$row.RunspaceID
b898bdad-dumdum-9eff-8a2beeefe78a
Now i can get the value i want by appending ".value" at the end of the statement, so this isn't so much a problem to be solved as much as it is a question of just what the hell powershell is doing. It's taken this simple, beautiful tiny statement - and wreaked havoc on my life.
In your specific case of an instance of an enum value (an instance of a System.Enum
-derived type):
Use [int] $hello
to get the numeric value of the original, enum (System.Enum
-derived) value, without the extra NoteProperty
members such as PSComputerName
that the remoting infrastructure adds (see below).
Use $hello.Value
to get the string representation of the enum value (its symbolic name rather than its number).
If you know the original System.Enum
-derived type, and that type is also available in your local session, you can cast the deserialized object back to its original type; e.g.:
[Microsoft.Foo.Bar.ClusterRole] $hello
$hello
is technically an [int]
, but decorated with extra properties, and information about the original type recorded in the hidden .pstypenames
array, which reflects the original type's inheritance hierarchy with the type names prefixed with Deserialized.
; e.g. Deserialized.Microsoft.Foo.Bar.ClusterRole
; PowerShell's output formatting system causes such an object to be formatted via (implicitly applied) Format-Table
, which in this case shows everything but the actual [int]
value - only the NoteProperty
members are shown.
Generally, you can exclude the unwanted properties as follows:
For [string]
instances only, you can access .psobject.BaseObject
to get the underlying object without any NoteProperty
members.
For others, you can create a new object (invariably of type [pscustomobject]
), by piping to Select-Object
with the unwanted properties excluded, as suggested by Lee Dailey):
$hello | Select-Object * -Exclude PSComputerName, PSShowComputerName, RunspaceId
Alternatively, you can focus on selecting just the properties you do want.
Read on for why that is necessary.
PowerShell's remoting infrastructure decorates every object returned from a remote invocation with the following NoteProperty
ETS (Extended Type System) members:
PSComputerName
... the name of the remote computer
RunspaceId
... the ID of the runspace in which the remote command executed.
PSShowComputerName
... a hidden property that, when set to $true
on all objects returned via Invoke-Command
's -HideComputerName
switch, suppresses display of the PSComputerName
property in the default output (but the property is still there); you can only see the PSShowComputerName
itself if you pipe a remotely received object to Get-Member -Force
.
Additionally, System.Enum
-derived types, which are returned as [int]
instances, are decorated with a [string]
-typed Value
NoteProperty
that contains the enum value's symbolic name (the enum's type name can be inferred from .pstypenames[0] -replace '^Deserialized\.'
).
The PSComputerName
and RunspaceId
properties are useful in remoting commands that target multiple computers at once: given that the order in which output is received is not guaranteed, these properties tell you where a given output object originated from.
The PSShowComputerName
property allows you to control default display behavior - though, curiously, it has no effect on whether RunspaceId
is displayed.
The Value
property for System.Enum
-derived types compensates for the loss of type fidelity that typically occurs in remoting commands (and background jobs) - only a limited set of known types deserialize with type fidelity - see this answer.
While these properties always exist, whether they show by default depends on the specific types of the object returned and either what formatting data is associated with them or applied by default by PowerShell.
Also, they may show when you pipe to Format-*
cmdlets explicitly, and during serialization, such as with ConvertTo-Json
.