Python to .NET Type Information
This is a guide on how the .NET type system and serialization works in psrpcore.
Behaviour of PowerShell Objects
PowerShell objects are effectively .NET types with an Extended Type System (ETS). This allows an existing .NET types to be extended with extra properties and methods that don’t exist in the base .NET type. While there are many types of properties used in PowerShell, when an object is serialized it categorizes them into 2 different types:
Adapted Properties
<Props>: The base properties that exist on the .NET typeExtended Properties
<MS>: Extra properties added by the PowerShell ETS
Some key behaviours of PowerShell objects are:
An adapted and extended property can share the same name, when accessed in PowerShell the extended property is favoured
Property names can be any anything except
$null, or one of the following reserved namesPSObject- the metadata of the object, i.e. properties, type names of the objectPSBase- the object but with the extended properties stripped outPSAdapted- all the adapted propertiesPSExtended- all the extended propertiesPSTypeNames- a list of type names that the object implements
Property names in PowerShell are also case insensitive
Properties can be accessed through the dot notation but not as an index, i.e.
$obj.PropertyNameworks but$obj['PropertyName']does notIf a dict like object has a propert with the same name as a key it contains, the property is favoured over the key value
Note: While dict types can use both the dot and index notation to find key values, actual object properties are only accesible using the dot notation.
$hash = @{foo = 'bar'}
# PowerShell can get the key/value using the dot notation normally
$hash.foo -eq 'bar'
# Adds a property to the hashtable - only accesible using the dot notation
Add-Member -InputObject $hash -NotePropertyName my_prop -NotePropertyValue prop_value
$hash.my_prop -eq 'prop_value'
$hash['my_prop'] -ne 'prop_value'
# Adds a property with the same name as a contained key
Add-Member -InputObject $hash -NotePropertyName foo -NotePropertyValue override
# Dot notation favours the property added
$hash.foo -eq 'override'
# Index still favours the index lookup behaviour of the object
$hash['foo'] -eq 'bar'
Accessing Properties in Python
The goal of the type implementation in psrpcore is to try and replicate the same behaviour around .NET objects that are deserialized to a Python object as best as it can.
It is largely successful except for these key differences:
Properties are case sensitive in Python
When dealing with a dict and using the index lookup, it will lookup the dict elements first before it looks at the properties
This is unlike PowerShell where the index lookup never gets property values
This is only done to allow access to complex property names in Python that aren’t allowed normally, e.g. properties with
-or other illegal characters
A
System.Boolean/boolin PowerShell can have extended properties, Python does not allow us to subclass thebooltype so these properties are droppedThe
PSBase,PSAdapted, andPSExtendedproperties are not implemented is not implementedA
NotImplementedError()will be raised when accessing these properties to reserve it for future use
PowerShell automatically adds the
PSComputerNameandRunspaceIdextended properties to each object returned from a remote runspace,psrpcoredoes not
Accessing a property for a .NET aware object in Python can be done in 3 ways:
# Using the dot notation
obj.PropertyName
# Using an index lookup
obj['PropertyName']
# Accesing it through the PSObject metdata
obj.PSObject.adapted_properties # List of adapted properties, filter by name
obj.PSObject.extended_properties # List of extended properties, filter by name
One major caveat to consider is that legal attribute names in Python is a tiny subset of what’s legal in .NET/PowerShell.
For example the property Property Name (with a space) is a illegal attribute in Python.
To access this, use the index lookup notation
# Does not work!
obj."Property Name"
# Works
obj["Property Name"]
This also applies to properties with other illegal characters like -, pure integers, non-ASCII characters, etc.
You can also access the same reserved property names like PSObject and PSTypeNames, etc on a Python instance of a .NET object.
The information it returns is similar to PowerShell but not exactly the same.
If you want to see all the properties of an object, similar to how $obj | Select-Object -Property * works you can use the vars(obj) function just like a normal Python object.
import psrpcore
record = psrpcore.types.VerboseRecord("message")
vars(record)
# {
# 'PSObject': <psrpcore.types._base.PSObjectMeta object at 0x7fb9ed36b220>,
# 'serialize_extended_info': False,
# 'Message': 'message',
# 'InvocationInfo': None,
# 'PipelineIterationInfo': None
# }
You could also loop through obj.PSObject.adapted_properties and obj.PSObject.extended_properties and view each property manually.
Class Mapping
So far we’ve covered object properties in PowerShell and how they work in their Python equivalents. The next step is to talk about the underlying class types and how they translate into Python and vice versa.
In MS-PSRP there are three different type classes:
Enum types - they are complex types but we consider them separate here
Primitive Types
Primitive types are the fundamental types that contain a value and optional properties.
When it comes to working with primitive types for PSRP in Python, there exists a Python class for each primitive in psrpcore.types that subclasses both PSObject as well as the Python type it closely resembles.
Here is the mapping of the primitive types:
.NET |
psrpcore.type |
Python Type |
Native |
|---|---|---|---|
str |
Y |
||
int¹ |
N |
||
bool |
Y |
||
datetime.datetime |
Y |
||
str |
N |
||
int |
N |
||
int |
N |
||
int |
N |
||
int |
N |
||
int |
N |
||
int |
Y |
||
int |
N |
||
int |
N |
||
float |
Y |
||
float |
N |
||
decimal.Decimal |
Y |
||
|
bytes |
Y |
|
uuid.UUID |
Y |
||
str |
N |
||
|
|
None |
Y |
N/A |
N |
||
str |
N |
||
str |
N |
||
N/A |
N |
¹ - While the base Python type is an int, doing PSChar('1') will get the char based on the string value. Do PSChar(1) if you want the PSChar to represent \u0001
² - While there is a psrpcore.types class for these .NET types, they do not inherit PSObject so they cannot handle extended properties
³ - A PSSecureString can be used to encrypt strings that traverse across the wire but the string in Python is not encrypted in memory
If Native is Y, then psrpcore will automatically convert that native Python type to the .NET type for you.
Otherwise the psrpcore.types implementation must be used if you want to serialize a particular .NET type.
For example if I was to pass in an int as an object, psrpcore will automatically serialize that to a System.Int32 but say I wanted that to be a System.UInt16 object I would need to pass in a PSUInt16 instance instead.
When a primitive type is deserialized, the instance will be the psrpcore.types type.
Due to how inheritance works you can do all of the following:
import psrpcore.types
...
output = ps.invoke()[0] # Our example outputs an Int32 value
assert isinstance(output, psrpcore.types.PSObject) # Works for all except bool and $null
assert isinstance(output, psrpcore.types.PSInt)
assert isinstance(output, int)
Complex Types
Complex types are the opposite of a primitive type. While primitive objects can be extended to include extended properties they are still considered a primitive object because it’s a single value. A complex object typically is a class instance that contains both adapted and extended properties. They can also include container like object such as a dict, list, stack, queue, etc.
While psrpcore supports (de)serialization of effectively any complex object, there are a few important .NET complex types that are good to remember:
.NET |
psrpcore.type |
Python Type |
Native |
|---|---|---|---|
list |
Y |
||
dict |
Y |
||
list |
N |
||
queue.Queue |
Y |
||
type² |
Y |
¹ - Other .NET types that are similar to this type are always deserialized to this .NET type, psrpcore acts the same, e.g. an [Object[]] will become a PSList
² - Unless the Python type hash been marked as a specific .NET type, it will automatically be serialized as a PSCustomObject
Like a primitive object, when Native is Y, those Python types are automatically serialized to the .NET type it represents.
If you wish to use a specific .NET type that does not natively do this, you need to use the psrpcore.types.* class that represents the .NET type you desire or create your own.
Enum Types
While technically a complex type I consider enums in PSRP to be a separate type of object that straddles both a primitive and complex type. Because of its uniqueness they are implemented slightly differently and have a few caveats in Python.
There are 2 base enum types that can be used
psrpcore.types.PSEnumBase: Inherits
PSObject, used for enum types that should be set with a single valuepsrpcore.types.PSFlagBase: Same as
PSEnumBasebut has special behaviour to allow multiple values to be set
Like with .NET enums, the enums that inherit PSEnumBase or PSFlagBase must represent one of the numeric types like:
psrpcore.types.PSBytepsrpcore.types.PSSBytepsrpcore.types.PSUInt16psrpcore.types.PSInt16psrpcore.types.PSUIntpsrpcore.types.PSInt- defaultpsrpcore.types.PSUInt64psrpcore.types.PSInt64
This is defined by adding the base_type kwarg to the class definition:
from psrpcore.types import PSEnumBase, PSType, PSUInt
@PSType(type_names=[f"System.MyEnum"])
class MyEnum(PSEnumBase, base_type=PSUInt):
none = 0
Value1 = 1
Value2 = 2
Value3 = 3
From there, the enum is defined as a normal Python enum class. There are a few things when it comes to implementing your own enum type
The enum names should match the .NET type, don’t implement your own custom labels as the label is used to compute the
<ToString>value in the CLIXMLIf there is an enum label called
None, usenoneinsteadIf the PSType is marked with
rehydrate=Falsethen a deserialized instance of that enum will just be the primitive value of whatever numeric type the enum is based on
Here is an example of an basic enum System.IO.FileMode:
import psrpcore.types as pstype
@pstype.PSType(type_names=["System.IO.FileMode"])
class IOFileMode(pstype.PSEnumBase):
Append = 6
Create = 2
CreateNew = 1
Open = 3
OpenOrCreate = 4
Truncate = 5
When you want to get an enum value just do IOFileMode.Append or with whatever label you need.
Any instances of PSEnumBase will automatically convert the raw int value to an instance of that type, i.e. IOFileMode.Append will be IOFileMode(6).
Here is an example of a basic enum System.IO.FileShare that uses the [FlagsAttributes] to allow multiple values to be set.
import psrpcore.types as pstype
@pstype.PSType(type_names=["System.IO.FileShare"])
class IOFileShare(pstype.PSFlagBase):
Delete = 4
Inheritable = 16
none = 0
Read = 1
ReadWrite = 3
Write = 2
Enums, like primitive objects can have further extended properties added to it if that is desired.
Implementing .NET Type in Python
When implementing your own Python class to represent a .NET type there are few things you need to consider/understand:
The property names of the .NET class must match up with the ones defined on the Python class
When an object is serialized, property values are sourced from the property object inside
PSObjectUsing a
@propertydecorator to generate a calculated property value won’t workThe workaround is to use a PSAliasProperty or PSScriptProperty that is automatically called during the serialization process
Methods defined on the Python class are not transferred to PowerShell, only properties are
Whether you want the type to be rehydrated on deserialization or not
All custom .NET types in PowerShell that you implement SHOULD inherit from psrpcore.types.PSObject and use the psrpcore.types.PSType decorator to define the .NET metadata.
Any classes that do not do this will be treated as a PSCustomObject which is explained a bit later.
The PSType decorator accepts the following arguments:
type_names: A list of .NET types the object implements, i.e.['System.String']adapted_properties: A list of psrpcore.types.PSPropertyInfo instances that define the adapted properties of the objectextended_properties: A list of psrpcore.types.PSPropertyInfo instances that define the extended properties of the objectskip_inheritance: Do not inherit the type names and properties of the base classrehydrate: Whether this type can be rehydrated (deserialized to this type) or not (default:True)tag: The CLIXML tag element value to use, this should not be used for end users as all complex types areObj
The type names and properties of the base object will also be inherited onto the defined class.
For example PSObject defines the type names as ["System.Object"] and thus anything that inherits PSObject will have those types appended on it’s custom types, e.g. ["System.MyType", "System.Object"].
To skip this behaviour and have a blank starting slate, set skip_inheritance=True.
The adapted_properties and extended_properties kwargs take a list of PSPropertyInfo objects that define the properties of the object itself.
Once a property has been defined on the object it is immediately gettable/settable like a normal attribute of an instance.
The rehydrate kwarg is used during serialization to determine the Python type that is used for the deserialized value.
If True then any .NET objects that implement the same type_names will be a Python instance of the actual type.
If False then the returned Python object will be a psrpcore.types.PSCustomObject with all the same properties set and the obj.PSObject.type_names will have Deserialized.<type name> on them.
The main benefit rehydrate=True offers is it allows you do an isinstance(obj, MyType) check and call methods defined on that object.
Here is an example of how psrpcore implemented the PSCredential
type.
from psrpcore.type import (
PSNoteProperty,
PSObject,
PSString,
PSSecureString,
PSType,
)
@PSType(
type_names=["System.Management.Automation.PSCredential"],
adapted_properties=[
PSNoteProperty("UserName", mandatory=True, ps_type=PSString),
PSNoteProperty("Password", mandatory=True, ps_type=PSSecureString),
],
)
class PSCredential(PSObject):
pass
We can see that the type_names is set to the types that the object inherits starting from top down and the adapted properties and the types they should be coerced to when being serialized.
Creating your own types is usually just about getting the metadata of the type and defining it under PSObject.
You can expand on it however you wish.
To help with creating your own classes I’ve written a PowerShell function called ConvertTo-PythonClass that you can use to generate a skeleton class in Python.
ConvertTo-PythonClass
# Copyright: (c) 2021, Jordan Borean (@jborean93) <jborean93@gmail.com>
# MIT License (see LICENSE or https://opensource.org/licenses/MIT)
Function ConvertTo-PythonPrimitiveType {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[AllowNull()]
[Type]
$InputObject
)
if (-not $InputObject) {
return
}
switch ($InputObject.FullName) {
System.String { 'PSString' }
System.Char { 'PSChar' }
System.Boolean { 'PSBool' }
System.DateTime { 'PSDateTime' }
System.TimeSpan { 'PSDuration' }
System.Byte { 'PSByte' }
System.SByte { 'PSSbyte' }
System.UInt16 { 'PSUInt16' }
System.Int16 { 'PSInt16' }
System.UInt32 { 'PSUInt' }
System.Int32 { 'PSInt' }
System.UInt64 { 'PSUInt64' }
System.Int64 { 'PSInt64' }
System.Single { 'PSSingle' }
System.Double { 'PSDouble' }
System.Decimal { 'PSDecimal' }
System.Byte[] { 'PSByteArray' }
System.Guid { 'PSGuid' }
System.Uri { 'PSUri' }
System.Version { 'PSVersion' }
System.Xml.XmlDocument { 'PSXml' }
System.Management.Automation.ScriptBlock { 'PSScriptBlock' }
System.Security.SecureString { 'PSSecureString' }
}
}
Function ConvertTo-PythonClass {
<#
.SYNOPSIS
Generates Python code to use as a skeleton for a .NET class implemented in Python.
.PARAMETER InputObject
The type or an instance of a type to make the skeleton for.
.PARAMETER AddDoc
Whether to add a Python doc string to the generated skeleton.
.PARAMETER EnumAsHex
Whether to format the enum values as a hex code and not a decimal.
.NOTES
It is recommended to use an instance of a type as '-InputObject'. Some properties are only added once the object
has been initialised so passing by type may miss some.
Do not use this to create a class for a 'PSCustomObject' instance.
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[Object]
$InputObject,
[Switch]
$AddDoc,
[Switch]
$EnumAsHex
)
begin {
$typeSb = New-Object -TypeName System.Text.StringBuilder
$null = $typeSb.Append(@'
import psrpcore.types
'@)
}
process {
$type = $null
$typeNames = [System.Collections.Generic.List[String]]@()
$properties = [Ordered]@{
adapted = [Ordered]@{}
extended = [Ordered]@{}
}
$isEnum = $false
if ($InputObject -is [Type]) {
$type = $InputObject
$baseType = $InputObject
do {
$typeNames.Add($baseType.FullName)
$baseType = $baseType.BaseType
} while ($baseType.FullName.BaseType)
if ($InputObject.IsSubclassOf([System.Enum])) {
$isEnum = $true
}
else {
foreach ($adapted in $InputObject.GetProperties()) {
$properties.adapted.Add($adapted.Name, $adapted.PropertyType)
}
foreach ($extended in (Get-TypeData -TypeName $InputObject.FullName).Members.GetEnumerator()) {
$name = $extended.Key
$data = $extended.Value
switch ($data.GetType().Name) {
AliasPropertyData {
$properties.extended.Add($name, $data.MemberType)
}
CodePropertyData {
$properties.extended.Add($name, $data.GetCodeReference.ReflectedType)
}
NotePropertyData {
$properties.extended.Add($name, $data.Value.GetType())
}
ScriptPropertyData {
# Cannot determine the type as a ScriptBlock could output anything.
$properties.extended.Add($name, $null)
}
default {}
}
}
}
}
else {
$type = $InputObject.GetType()
$typeNames = $InputObject.PSTypeNames | Where-Object { $_ -ne 'System.Object' }
if ($type.IsSubclassOf([System.Enum])) {
$isEnum = $true
}
else {
foreach ($adapted in $InputObject.PSAdapted.PSObject.Properties) {
$properties.adapted.Add($adapted.Name, $adapted.TypeNameOfValue -as [Type])
}
foreach ($extended in $InputObject.PSExtended.PSObject.Properties) {
$properties.extended.Add($extended.Name, $extended.TypeNameOfValue -as [Type])
}
}
}
$enumValues = [System.Collections.Generic.List[String]]@()
if ($isEnum) {
$typeNames = @($typeNames[0])
$rawType = [System.Enum]::GetUnderlyingType($type)
foreach ($name in ([System.Enum]::GetValues($type))) {
$rawName = $name.ToString()
# None is a reserved character in Python, need to lowercase this
if ($rawName -ceq 'None') {
$rawName = 'none'
}
$rawValue = $name -as $rawType
if ($EnumAsHex) {
$hexLength = ("{0:X}" -f $rawType::MaxValue).Length
$rawValue = ("0x{0:X$($hexLength)}" -f $rawValue)
}
$enumValues.Add("$rawName = $rawValue")
}
if ($type.CustomAttributes | Where-Object { $_.AttributeType.FullName -eq 'System.FlagsAttribute' }) {
$enumType = 'PSFlagBase'
}
else {
$enumType = 'PSEnumBase'
}
$rawType = ConvertTo-PythonPrimitiveType -InputObject $rawType
$classDef = "class PSEnum$($type.Name)(psrpcore.types.$enumType, base_type=psrpcore.types.$rawType):"
}
else {
$classDef = "class PS$($type.Name)(psrpcore.types.PSObject):"
}
$null = $typeSb.AppendLine("@psrpcore.types.PSType(")
# Define the object's types
$null = $typeSb.AppendLine(" type_names=[")
foreach ($name in $typeNames) {
$null = $typeSb.AppendLine(" '$name',")
}
$null = $typeSb.AppendLine(" ],")
foreach ($propType in $properties.GetEnumerator()) {
if (-not $propType.Value.Count) {
continue
}
$null = $typeSb.AppendLine(" $($propType.Key)_properties=[")
foreach ($prop in $propType.Value.GetEnumerator()) {
$psType = ConvertTo-PythonPrimitiveType -InputObject $prop.Value
$typeValue = $null
if ($psType) {
$typeValue = ", ps_type=psrpcore.types.$psType"
}
$null = $typeSb.AppendLine(" psrpcore.types.PSNoteProperty('$($prop.Key)'$typeValue),")
}
$null = $typeSb.AppendLine(" ],")
}
$null = $typeSb.AppendLine(")")
$null = $typeSb.AppendLine($classDef)
if ($AddDoc) {
$null = $typeSb.AppendLine(@"
"""Python class for $($type.FullName)
This is an auto-generated Python class for the $($type.FullName) .NET class.
"""
"@)
}
if ($enumValues) {
foreach ($enumValue in $enumValues) {
$null = $typeSb.AppendLine(" $enumValue")
}
$null = $typeSb.AppendLine()
}
else {
$null = $typeSb.AppendLine(" pass")
}
}
end {
$typeSb.ToString()
}
}
The generated skeleton can adjusted based on your requirements to include extra methods/properties for use on the Python side.
$obj = Get-Item -Path C:\Windows
$obj | ConvertTo-PythonClass -AddDoc
# import psrpcore.types
#
# @psrpcore.types.PSType(
# type_names=[
# 'System.IO.DirectoryInfo',
# 'System.IO.FileSystemInfo',
# 'System.MarshalByRefObject',
# ],
# adapted_properties=[
# psrpcore.types.PSNoteProperty('Name', ps_type=psrpcore.types.PSString),
# psrpcore.types.PSNoteProperty('FullName', ps_type=psrpcore.types.PSString),
# psrpcore.types.PSNoteProperty('Parent'),
# psrpcore.types.PSNoteProperty('Exists', ps_type=psrpcore.types.PSBool),
# psrpcore.types.PSNoteProperty('Root'),
# psrpcore.types.PSNoteProperty('Extension', ps_type=psrpcore.types.PSString),
# psrpcore.types.PSNoteProperty('CreationTime', ps_type=psrpcore.types.PSDateTime),
# psrpcore.types.PSNoteProperty('CreationTimeUtc', ps_type=psrpcore.types.PSDateTime),
# psrpcore.types.PSNoteProperty('LastAccessTime', ps_type=psrpcore.types.PSDateTime),
# psrpcore.types.PSNoteProperty('LastAccessTimeUtc', ps_type=psrpcore.types.PSDateTime),
# psrpcore.types.PSNoteProperty('LastWriteTime', ps_type=psrpcore.types.PSDateTime),
# psrpcore.types.PSNoteProperty('LastWriteTimeUtc', ps_type=psrpcore.types.PSDateTime),
# psrpcore.types.PSNoteProperty('Attributes'),
# ],
# extended_properties=[
# psrpcore.types.PSNoteProperty('PSPath', ps_type=psrpcore.types.PSString),
# psrpcore.types.PSNoteProperty('PSParentPath', ps_type=psrpcore.types.PSString),
# psrpcore.types.PSNoteProperty('PSChildName', ps_type=psrpcore.types.PSString),
# psrpcore.types.PSNoteProperty('PSDrive'),
# psrpcore.types.PSNoteProperty('PSProvider'),
# psrpcore.types.PSNoteProperty('PSIsContainer', ps_type=psrpcore.types.PSBool),
# psrpcore.types.PSNoteProperty('Mode', ps_type=psrpcore.types.PSString),
# psrpcore.types.PSNoteProperty('BaseName'),
# psrpcore.types.PSNoteProperty('Target'),
# psrpcore.types.PSNoteProperty('LinkType', ps_type=psrpcore.types.PSString),
# ],
# )
# class PSDirectoryInfo(psrpcore.types.PSObject):
# """Python class for System.IO.DirectoryInfo
#
# This is an auto-generated Python class for the System.IO.DirectoryInfo .NET class.
# """
# pass
One really common type that is used in PowerShell is the PSCustomObject class.
This is typically created using [PSCustomObject]@{ ... } and psrpcore has 2 ways of implementing the same concept.
from psrpcore.types import PSCustomObject
# Using PSObjectObject({'key': 'value'})
ps_custom_object = PSCustomObject(
PropertyName='name',
AnotherProperty=1,
)
# Using a plain Python class
class MyPSCustomObject:
def __init__(self, PropertyName, AnotherProperty):
self.PropertyName = PropertyName
self.AnotherProperty = AnotherProperty
ps_custom_object = MyPSCustomObject('name', 1)
The first example is a lot simpler and works in a similar way to how [PSCustomObject]$hash works but the latter allows you to control more aspect when generating the object such as mandatory arguments, calculated properties, custom methods on the Python side, etc.
When the serializer detects an object that does not contain any of the .NET type metadata it does the following:
If it’s a known native type like
str,int, it will serialize it as the primitive type it maps toOtherwise is creates a
PSObjectwith it’s properties set to all the instances properties and attributes
When it comes to deserializing a PSCustomObject, there is no rehydration behaviour. It will always be deserialized as a psrpcore.types.PSCustomObject.
Deserialization Behaviour
Here is a brief overview of how psrpcore deserializes CLIXML to an object
Check if the CLIXML is a basic primitive type or not (XML tag !=
Obj).If it’s a primitive type, create new instance for the matching type and return it
When it’s a complex object, it will search the
TypeRegistryto see if the type has been registeredIf the type is registered a new blank instance of the registered class for that type is created
If the type is not registered, or the init above failed, a blank
PSObjectis createdIn the latter case the
PSTypeNamesfor the next object are prefixed withDeserialized.<TypeName>
If the CLIXML contains a
<ToString>value, that is registered to the object’s metadata sostr(obj)outputs that valueIt will scan all adapted and extended properties in the CLIXML and add them to the value
Even if a rehydrated object was used and did not have that property in the class metadata it will still be added to the new instance
This also applies to enums and extended primitive objects
If the object wraps a dictionary (XML tag ==
DCT)The value becomes psrpcore.types.PSDict and is populated with the dict elements
If the object wraps a stack (XML tag ==
STK)The value becomes psrpcore.types.PSStack and is populated with the stack elements
If the object wraps a queue (XML tag ==
QUE)The value becomes psrpcore.types.PSQueue and is populated with the queue elements
If the object wraps a list (XML tag ==
LST)The value becomes psrpcore.types.PSList and is populated with the list elements
If the object wraps a IEnumerable (XML tag ==
IE)The value becomes psrpcore.types.PSIEnumerable and is populated with the enumerable elements
If the object contains a remaining value
If the type names match a registered rehydratable enum, the enum value is set to this primitive value
Else the value now becomes an instance of the primitive value specified instead of a
PSObject
The end result is:
Primitive objects are returned as primitive objects with any extra extended properties that may be present
Enums are returned as that enum if the enum type was registered with
rehydrate=Trueat the class definition, otherwise the returned object is the primitive value the enum representsDictionaries are returned as
PSDictStacks are returned as
PSStackQueues are returned as
PSQueueLists are returned as
PSListOther IEnumerables are returned
PSIEnumerableOther objects are returned as that object if the type was registered with
rehydrate=Trueat the class definition, otherwise aPSObjectis created and has the extended properties set
The TypeRegistry mentioned above is a special singleton created by psrpcore that contains all the known registered .NET types.
This registry is used to deserialize CLIXML to the proper Python type if available.
The only differences between a rehydrated and plain PSObject object are:
A rehydrated object is an instance of that registered type, so any methods or properties are accessible
A non-rehydrated object is an instance of
psrpcore.types.PSObject, all the properties are still availableA rehydrated object keeps the type names under
obj.PSTypeNamesthe way they were in the CLIXMLA non-rehydrated object will prefix all of its type names under
obj.PSTypeNameswithDeserialized..
Add-Member and Update-TypeData
In PowerShell you can add extra properties to an existing object using the Add-Member cmdlet. The Update-TypeData does a similar thing but applies the type information to the type itself rather than a specific object.
You can achieve a similar thing in psrpcore by using one of the following methods:
import psrpcore.types
# The property only applies to the object passed in
obj = psrpcore.types.PSString("testing")
psrpcore.types.add_note_property(obj, "MyProperty", "value")
obj.MyProperty == "value"
# When adding to a type, the property now applies to any new instance of that type
psrpcore.types.add_script_property(psrpcore.types.PSString, "Length", lambda o: len(o))
obj = psrpcore.types.PSString("testing")
obj.Length == 7