PowerShell ETS (Extended Type System) is part of PowerShell’s guts, and it’s the part that lets .Net, COM, ADSI and WMI types live together while showing up to us as first-class PowerShell objects.
ETS is very powerful and it isn’t always easy to find documentation on how to do things, but in this post I want to leave my future self a little gift for when I need to do .Net type conversion in PowerShell.
The example is very simple and came up in a real context, and to explain it I’m going to simplify it as much as I can. Let’s imagine we have a custom type like this one:
Add-Type -TypeDefinition @"
using System;
using System.Collections;
namespace Configuration
{
public class ConfigFolder {
public ConfigFolder() {}
public ConfigFolder(string path) {
Path = path;
}
public string Path { get; set; }
public override string ToString() {
return Path;
}
}
}
"@
Overriding the type’s ToString() method lets us do things like this:
Function Write-AsString($folder) {
"as string we get: {0}" -f $folder
}
$configFolder = New-Object Configuration.ConfigFolder
$configFolder.Path = "c:\anyPath\"
Write-AsString $configFolder
Since the Write-AsString function doesn’t specify the type of $folder, when we call the function with a parameter of type ConfigFolder, the ToString() method gets called to convert the value automatically. The thing is, if we want to get a bit more out of it and give $folder a type other than string, what we’ll get is an error. For example:
Function Write-FullName([System.IO.DirectoryInfo]$folder) {
"BaseName is: {0}" -f $folder.BaseName
}
$configFolder = New-Object Configuration.ConfigFolder
$configFolder.Path = "c:\anyPath\"
Write-FullName $configFolder
Running this code gives us a type conversion error, because PowerShell doesn’t have the faintest idea how to convert a ConfigFolder into a System.IO.DirectoryInfo. This is where ETS comes to the rescue. Besides adding the ConfigFolder type, we need to add a type that does the conversion automatically:
Add-Type -TypeDefinition @"
using System;
using System.Collections;
using System.Management.Automation;
namespace Configuration
{
public class ConfigFolderConverter : PSTypeConverter {
public override bool CanConvertFrom(Object sourceValue, Type destinationType)
{
string src = sourceValue as string;
if (src != null) {
return true;
}
return false;
}
public override object ConvertFrom(object sourceValue, Type destinationType, IFormatProvider provider, bool IgnoreCase) {
if (sourceValue == null) { throw new InvalidCastException("no conversion possible"); }
if (this.CanConvertFrom(sourceValue, destinationType)) {
try {
string src = sourceValue as string;
return new Aida.ConfigFolder(src);
}
catch (Exception) { throw new InvalidCastException("no conversion possible"); }
}
throw new InvalidCastException("no conversion possible");
}
public override bool CanConvertTo(object value, Type destinationType) {
if((destinationType == typeof(System.IO.DirectoryInfo)) && (value.GetType() == typeof(Configuration.ConfigFolder))) {
return true;
}
return false;
}
public override object ConvertTo(object value, Type destinationType, IFormatProvider provider, bool IgnoreCase) {
if (CanConvertTo(value, destinationType)) {
return new System.IO.DirectoryInfo((value as Configuration.ConfigFolder).Path);
}
throw new InvalidCastException("no conversion possible");
}
}
}
"@
Of course, this type is just an example and the code could be much cleaner, but anyone who knows what it’s like to experiment with PowerShell’s Add-Type will understand why I’d rather not spend much time cleaning it up and testing it ;).
After adding this type, we need to tell PowerShell that it has to use it. For that we need to create a file (for example: ConfigFolder.Converter.ps1xml) that would look like this:
<Types>
<Type>
<Name>Configuration.ConfigFolder</Name>
<TypeConverter>
<TypeName>Configuration.ConfigFolderConverter</TypeName>
</TypeConverter>
</Type>
</Types>
And then run the command:
Update-TypeData .\ConfigFolder.Converter.ps1xml
Once that’s done, the example that failed before will now work perfectly, allowing automatic conversions from the ConfigFolder type to System.IO.DirectoryInfo.
This is just one example of what PowerShell ETS has to offer. Besides custom converters, it also lets you extend existing classes, add aliases and a few other neat little things.