Skip to content

Instantly share code, notes, and snippets.

@santisq
Last active May 26, 2026 11:09
Show Gist options
  • Select an option

  • Save santisq/0dd876d5fd868cc0194dc27e9cb3b089 to your computer and use it in GitHub Desktop.

Select an option

Save santisq/0dd876d5fd868cc0194dc27e9cb3b089 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Management.Automation;
public sealed class MyCustomClass
{
// Ideally this should be a hardcoded set of Methods or Properties that can be set on this object
private static readonly HashSet<string> _selfMethods = new(
typeof(MyCustomClass).GetMethods().Select(e => e.Name),
StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, object?> _properties = new(StringComparer.OrdinalIgnoreCase);
public object? this[string property]
{
get => _properties.TryGetValue(property, out object? value) ? value : null;
set => _properties[property] = value;
}
internal Collection<PSAdaptedProperty> Properties
{
get => new([.. _properties.Select(kv => new PSAdaptedProperty(kv.Key, kv.Value))]);
}
// Check if the property passed to the adapter is actually a method, if so,
// GetProperty has to return null so the adapter falls back to calling it
internal static bool IsMethod(string property) => _selfMethods.Contains(property);
}
public sealed class MyPropertyAdapter : PSPropertyAdapter
{
public override Collection<PSAdaptedProperty>? GetProperties(object baseObject)
=> baseObject is MyCustomClass customClass
? customClass.Properties : null;
public override PSAdaptedProperty? GetProperty(object baseObject, string propertyName)
=> baseObject is MyCustomClass customClass && !MyCustomClass.IsMethod(propertyName)
? new(propertyName, customClass[propertyName]) : null;
public override string? GetPropertyTypeName(PSAdaptedProperty adaptedProperty)
=> adaptedProperty.Tag?.GetType().Name;
public override object? GetPropertyValue(PSAdaptedProperty adaptedProperty)
=> adaptedProperty.Tag;
// Should always be True, makes no sense to have Property { set; } only
public override bool IsGettable(PSAdaptedProperty adaptedProperty) => true;
// Should be defined by the implementer, in this case, any dynamic property is settable
public override bool IsSettable(PSAdaptedProperty adaptedProperty) => true;
public override void SetPropertyValue(PSAdaptedProperty adaptedProperty, object value)
{
if (adaptedProperty.BaseObject is MyCustomClass customClass)
customClass[adaptedProperty.Name] = value;
}
}

Compile and load the type... this could be pre-compiled e.g. via dotnet CLI then the type loaded via Assembly.LoadFrom(...) or Add-Type or Import-Module if part of an actual PowerShell Module.

Once loaded you can Update-TypeData -TypeAdapter like shown in the example:

Add-Type -Path .\adapterExample.cs -WA 0 -IgnoreWarnings
Update-TypeData -TypeAdapter ([MyPropertyAdapter]) -TypeName ([MyCustomClass])

If the adapter is part of the a module you can have a ...Types.ps1xml instead and loaded via TypesToProcess:

<?xml version="1.0" encoding="utf-8"?>
<Types>
  <Type>
    <Name>MyCustomClass</Name>
    <TypeAdapter>
      <TypeName>MyPropertyAdapter</TypeName>
    </TypeAdapter>
  </Type>
</Types>

Then the actual usage, pretty much like an AD Object... it is actually ADEntityAdapter the one in charge of this...

$class = [MyCustomClass]::new()
$class.Id = [guid]::NewGuid()
$class.Name = 'john.galt'
$class

# Id                                   Name
# --                                   ----
# bbd94707-59b8-4de8-9624-89bfa2ca6700 john.galt

$class.psobject.Properties

# BaseObject      : MyCustomClass
# Tag             : bbd94707-59b8-4de8-9624-89bfa2ca6700
# MemberType      : Property
# Value           : bbd94707-59b8-4de8-9624-89bfa2ca6700
# IsSettable      : True
# IsGettable      : True
# TypeNameOfValue : Guid
# Name            : Id
# IsInstance      : True
#
# BaseObject      : MyCustomClass
# Tag             : john.galt
# MemberType      : Property
# Value           : john.galt
# IsSettable      : True
# IsGettable      : True
# TypeNameOfValue : String
# Name            : Name
# IsInstance      : True
@mklement0
Copy link
Copy Markdown

mklement0 commented May 25, 2026

Thanks for putting this together - good to have a simple example of how PowerShell type adapters work.

However, while your sample works for properties, calling methods appears to be broken; e.g. calling $class.ToString() results in the following error:
InvalidOperation: Method invocation failed because [MyCustomClass] does not contain a method named 'ToString'.

Curiously - for reasons unknown to me - a method call such as .ToString() also calls the GetProperty method, and unconditionally returning a PSAdaptedProperty instance from it seems to be what makes the method call fail.

While you could include code in GetProperty() to detect method names being passed to the propertyName parameter, and, if so, return null for them, I wonder if there's a simpler solution.

@santisq
Copy link
Copy Markdown
Author

santisq commented May 26, 2026

@mklement0 apparently we can't return new PSAdaptedProperty(propertyName, customClass[propertyName]) always, it has to return null when it's a method so the adapter falls back to calling it. I added a comment to the code but having a fully dynamic object is clearly not the best idea. Probably the implementer should decide on a list of properties that can be set on the object instead of self reflection like I did there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment