I am writing a binary PowerShell module that, much like the ActiveDirectory module, will have a number of cmdlets and types that can potentially return more than the default set of properties - and those properties (100+) are dependent on what the user requests. Just like the AD module, I would like to return the type with the properties that were requested so that it is transparent to the user i.e. not one huge class with lots of empty properties that they haven't requested.
I was looking at the documentation for the ActiveDirectory module and I noticed that things like ADUser and ADComputer ultimately are inherited from ADPropertyCollection which I assume has the properties required in its InnerDictionary after whatever searches it does.
This did not really explain how PowerShell presents the AD types flexibly i.e.
(Get-ADUser -Identity some.user).GetType()
(Get-ADUser -Identity some.user -Properties *).GetType()
# Both return a type of ADUser, despite have drastically different amounts of properties.
This was until I looked at the Types ps1xml file for the AD module, which presents something like this:
<Types>
<!-- other types -->
<Type>
<Name>Microsoft.ActiveDirectory.Management.ADUser</Name>
<TypeAdapter>
<TypeName>Microsoft.ActiveDirectory.Management.ADEntityAdapter</TypeName>
</TypeAdapter>
</Type>
</Types>
So I guess the source of the "magic" properties is ADEntityAdapter which is inherited from the abstract class PSPropertyAdapter.
The issue I have is now I am not sure how to implement it and there are not any easily searchable examples of it being implemented. I appreciate its a real edge case. I have had a small attempt at a very rough implementation below, please ignore any code faux pas - I will of course not actually write the code like below. I just wanted to at least try and show I have thought about this.
using System.Collections.Generic;
namespace Sample
{
public class PropertyCollection
{
public string Id { get; set; }
public Dictionary<string, object> Attributes { get; set; }
public PropertyCollection()
{
this.Id = "SampleID";
this.Attributes = new Dictionary<string, object>();
for (var i = 0; i < 10; i++)
{
this.Attributes.Add("KeyAttribute" + i, i);
}
for (var i = 10; i < 100; i++)
{
this.Attributes.Add("OtherAttribute" + i, i);
}
}
}
}
namespace Sample
{
public class ReturnedClass : PropertyCollection
{
public string SomeName { get; set; }
public ReturnedClass() : base() { }
}
}
using System;
using System.Collections.ObjectModel;
using System.Management.Automation;
namespace Sample
{
public class PropertyEntityAdapter : PSPropertyAdapter
{
public override Collection<PSAdaptedProperty> GetProperties(object baseObject)
{
PropertyCollection pc = baseObject as PropertyCollection;
if (pc == null)
{
throw new Exception("Some Exception");
}
Collection<PSAdaptedProperty> collection = new Collection<PSAdaptedProperty>();
foreach (string name in pc.Attributes.Keys)
{
collection.Add(new PSAdaptedProperty(name, null));
}
return collection;
}
public override PSAdaptedProperty GetProperty(object baseObject, string propertyName)
{
PropertyCollection pc = baseObject as PropertyCollection;
if (pc.Attributes.TryGetValue(propertyName, out object pcValue))
{
return new PSAdaptedProperty(propertyName, pcValue);
}
throw new Exception("Prop not found");
}
public override string GetPropertyTypeName(PSAdaptedProperty adaptedProperty)
{
if (adaptedProperty == null)
{
throw new Exception("prop null");
}
PropertyCollection pc = adaptedProperty.BaseObject as PropertyCollection;
if (pc == null)
{
throw new Exception("not found");
}
if (pc.Attributes.TryGetValue(adaptedProperty.Name, out object pcValue))
{
return pcValue.GetType().FullName;
}
return pc.GetType().FullName;
}
public override object GetPropertyValue(PSAdaptedProperty adaptedProperty)
{
if (adaptedProperty?.BaseObject == null || adaptedProperty?.Name == null)
{
throw new Exception("prop null");
}
PropertyCollection pc = adaptedProperty.BaseObject as PropertyCollection;
if (pc == null)
{
throw new Exception("not found");
}
if (pc.Attributes.TryGetValue(adaptedProperty.Name, out object pcValue))
{
return pcValue;
}
return null;
}
public override bool IsGettable(PSAdaptedProperty adaptedProperty)
{
if (adaptedProperty == null)
{
throw new Exception("prop null");
}
PropertyCollection pc = adaptedProperty.BaseObject as PropertyCollection;
if (pc == null)
{
throw new Exception("not found");
}
if (pc.Attributes.ContainsKey(adaptedProperty.Name))
{
return true;
}
return false;
}
public override bool IsSettable(PSAdaptedProperty adaptedProperty)
{
return false;
}
public override void SetPropertyValue(PSAdaptedProperty adaptedProperty, object value)
{
throw new System.NotImplementedException();
}
}
}
** I have purposely not added any code for the Set* methods to reduce the code required here. If the above seems right, then I am sure I know what to do.
Has anyone done this before? Are there any very basic examples out there? Am I on the right path, or am I misunderstanding? Thanks in advance.