PowerShell: Custom Types, Type conversion and ETS

Before we get into the how, here is why you should care about this!  Consider the following oversimplified function.

Function new-Server {
    <#
    .SYNOPSIS
        Create a new GetAdmin.Server object
    .PARAMETER Name
        Server Name
    .PARAMETER Network
        Detailed per interfave network information
    .EXAMPLE
        Get-NavFiler
    .Outputs
        Netapp.SDK.NavFiler[]
    #>
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipelineByPropertyName=$TRUE)]
        [string]
        $Name,
 
        [Parameter()]
        [GetAdmin.Net[]]
        $Network
    )
    Process {
        return New-Object GetAdmin.Server -ArgumentList @($Name, $network)
    }
}
First off we need to load our custom types:
Add-Type -Language CSharpVersion3  -TypeDefinition @”
using System.Collections;
namespace GetAdmin
{
    public class Net
    {
        #region Paramaters 
 
        public string Interface { get; set; }
        public System.Net.IPAddress IPAddress { get; set; }
        public string Netmask { get; set; } 
 
        #endregion Parameters
 
        #region Constructors
        public Net()
        {
            Interface = null;
            IPAddress = new System.Net.IPAddress((long)16777343);
            Netmask = null;
        }
        public Net(
            string name,
            System.Net.IPAddress ipaddress,
            string netmask
        )
        {
            Interface = name;
            IPAddress = ipaddress;
            Netmask = netmask;
        }
        #endregion Constructors
    }
    public class Server
    {
        #region Paramaters 
 
        public string    Name    { get; set; }
        public ArrayList Network { get; set; } 
 
        #endregion Parameters
 
        #region Constructors
        public Server()
        {
            Name    = null;
            Network = new ArrayList();
        }
        public Server(
            string name,
            ArrayList network
        )
        {
            Name    = name;
            Network = network;
        }
        #endregion Constructors
    }
}
“@

Now let try and use this function:

[0:3]PS>$Network = 1..3 | Foreach {
    New-Object GetAdmin.Net("ns0", "192.168.1.$_", "255.255.255.0")
}
[0:4]PS> $network

Interface                               IPAddress                               Netmask
---------                               ---------                               -------
ns0                                     192.168.1.1                             255.255.255.0
ns0                                     192.168.1.2                             255.255.255.0
ns0                                     192.168.1.3                             255.255.255.0

[0:5]PS> New-Server -Name Server1 -Network $Network

Name                                                        Network
----                                                        -------
Server1                                                     {192.168.1.1, 192.168.1.2, 192.168.1.3}

Why that’s not that bad, consider the following, it could look like this.

[0:4]PS>New-Server –Name Server1 –Network "ns0,192.168.1.1,255.255.255.0", "ns0,192.168.1.2,255.255
.255.0", "ns0,192.168.1.3,255.255.255.0"

Name                                                        Network
----                                                        -------
Server1                                                     {192.168.1.1, 192.168.1.2, 192.168.1.3}


Now I really don’t want to get into which one you should be using.  I prefer instead to focus on how to enable either one!

First things first we’re going to have to get a little dirty with some c# (Again I’m not a developer so if anyone knows a better way to do this I’m all ears.) and create a type converter.

Add-Type -Language CSharpVersion3  -TypeDefinition @”
using System;
using System.Collections;
using System.Management.Automation;
 
namespace GetAdmin
{
    public class NetConverter : PSTypeConverter
    {
        /// Override for the CanConvertFrom Method.
        /// Returns true if the Source object
        /// is of type String and can be Converted to GetAdmin.Net type
        public override bool CanConvertFrom(Object sourceValue, Type destinationType)
        {
            string src = sourceValue as string;
            if (src != null)
            {
                try
                {
                    string[] Fields = src.Split(new char[1] { ',' });
                    if (Fields.GetLength(0) == 3)
                    {
                        return true;
                    }
                }
                catch (Exception)
                {
                    return false;
                }
            }
            return false;
        }
 
        /// Override for the ConvertFrom Method
        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
                {
                    // Cast our input as a string just in case
                    string src = sourceValue as string;
                    // create a new GetAdmin.Net object
                    GetAdmin.Net n = new GetAdmin.Net();
 
                    if (src != null || src != "")
                    {
                        //split our string into an array using commas as the delim
                        string[] Fields = src.Split(new char[1] { ',' });
 
                        //populate our new object
                        n.Interface  = Fields[0];
                        n.IPAddress  = System.Net.IPAddress.Parse(Fields[1]);
                        n.Netmask    = Fields[2];
                    }
                    //return our new GetAdmin.Net Object
                    return n;
                }
                catch (Exception)
                {
                    throw new InvalidCastException("no conversion possible");
 
                }
            }
            throw new InvalidCastException("no conversion possible");
        }
        /// Default to PowerShell conversion for other types.
        /// Return False here
        public override bool CanConvertTo(object Value, Type destinationType)
        {
            return false;
        }
        /// Do not handle conversion for other types
        public override object ConvertTo(object Value, Type destinationType,
        IFormatProvider provider, bool IgnoreCase)
        {
            throw new InvalidCastException("conversion failed");
        }
    }
    public class Net
    {
        #region Paramaters 
 
        public string Interface { get; set; }
        public System.Net.IPAddress IPAddress { get; set; }
        public string Netmask { get; set; } 
 
        #endregion Parameters
 
        #region Constructors
        public Net()
        {
            Interface = null;
            IPAddress = new System.Net.IPAddress((long)16777343);
            Netmask = null;
        }
        public Net(
            string name,
            System.Net.IPAddress ipaddress,
            string netmask
        )
        {
            Interface = name;
            IPAddress = ipaddress;
            Netmask = netmask;
        }
        #endregion Constructors
    }
    public class Server
    {
        #region Paramaters 
 
        public string    Name    { get; set; }
        public ArrayList Network { get; set; } 
 
        #endregion Parameters
 
        #region Constructors
        public Server()
        {
            Name    = null;
            Network = new ArrayList();
        }
        public Server(
            string name,
            ArrayList network
        )
        {
            Name    = name;
            Network = network;
        }
        #endregion Constructors
    }
}
“@

I told you we needed to get a little dirty!  Now that we’ve implemented a type converter that knows how to convert a string to a GetAdmin.Net object.  Now we need to inform PowerShell of our work.  This is accomplished via the a type formatting file.

<Types>
  <Type>
    <Name>GetAdmin.Net</Name>
    <TypeConverter>
      <TypeName>GetAdmin.NetConverter</TypeName>
    </TypeConverter>
  </Type>
</Types>

Next import your new type definition file, and try it out!

[0:1]PS> Update-FormatData ..\..\..\Documents\GetAdmin.Format.ps1xml
[0:2]PS> Update-TypeData ..\..\..\Documents\GetAdmin.Types.ps1xml
[0:3]PS> [GetAdmin.Net[]]("eth0,192.168.1.1,255.255.255.0","eth1,192.168.2.2,255.255.255.0")

Interface                               IPAddress                               Netmask
---------                               ---------                               -------
eth0                                    192.168.1.1                             255.255.255.0
eth1                                    192.168.2.2                             255.255.255.0
[0:3]PS> New-Server –Name Server1 –Network "ns0,192.168.1.1,255.255.255.0", "ns0,192.168.1.2,255.255
.255.0", "ns0,192.168.1.3,255.255.255.0"

Name                                                        Network
----                                                        -------
Server1                                                     {192.168.1.1, 192.168.1.2, 192.168.1.3}

Hope that was helpful, know a better way?

~Glenn Sizemore