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
LucD | 12-Feb-10 at 3:27 am | Permalink
Great post!
One question, why don’t you treat the netmask as a System.Net.IPAddress as well ?
Would give you validation and avoid a netmask like “345.768.999.876″
glnsize | 12-Feb-10 at 8:04 am | Permalink
Thank you,
I modified some code from PoshOnTap for the purpose of this post, and well… I never thought about it. I looked for a .net type that was netmask, but couldn’t find one. As I think about it there isn’t any reason I couldn’t have casted netmask as an IPAddress as well! In my source code I used a regex to validate the input on netmask, but I removed it for simplicity. I am going to have to play with it some more b/c that could be a very elegant solution.
As a side the real advantage to casting to IPAddress is the flexibility. [System.Net.IPAddress] is aware of both IPv4 and IPv6! IPv6 being the real pain as the shorthand makes “manual” validation a real pain.