Tuesday, November 25, 2014

Writing Classes With PowerShell V5 – Part 2

In a previous article, I set out what a class was and what it contains, and showed examples of using those classes in your PowerShell scripts. As I mentioned last time, you should use cmdlets where possible to create and manage objects. But when you can't you can always delve into the .NET Class Framework and it's huge class library.
But what if there are no applicable .NET objects and you need to create your own class? Some admins might be asking: why bother? The answer is one of flexibility and reuse. If you are writing scripts to automate your day to  day operations, you are inevitably passing objects between scripts, functions, cmdlets. There are always going to be cases where you'd like to create your own object simple as a means of transporting sets of data between hosts/scripts/etc.
In PowerShell V5, Microsoft has included the ability to create your own classes. When I started writing this set of articles, I had initially intended to just introduce Classes in V5, but as I looked at it, you can already create your own objects using earlier versions of PowerShell. These are not fully fledged classes, but are more than adequate when you just want to create a simple object to pass between your scripts/functions.
Creating Customised Objects
There are several ways you can achieve this. The first, but possibly hardest for the IT pro: use Visual Studio, author your classes in C# then compile them into a DLL. Then in PowerShell, you use Add-Type to add the classes to your PowerShell environment. The fuller details of this, and how to speed up loading by using Ngen.Exe are outside the scope of this blog post.
Bringing C# Into PowerShell
Now for the semi-developer audience amongst you, there's a kind of halfway house. In my experience, IT pros typically want what I call data-only classes. That is a class that just holds data and can be exchanged between scripts. For such cases, there's a simple way to create your class, although It does require a bit of C# knowledge (and some trial and error).
Here's a simple example of how to so this:
Add-Type @' 
public class Aeroplane
   {
     public string Model = "Boeing 737";   
     public int    InFleet = 12;
     public int    Range = 2400;
     public int    Pax   = 135;
   } 
'@
This code fragment defines a very small class – one with just 4 members (Model, number in fleet, range, and max number of passengers).  Once you run this, you can create objects of the type AeroPlane, like this:
image
As you can see from the screen shot, you can create a new instance of the class by using New-Object and selecting your newly created class.
If you are just creating a data-only class – one that you might pass from a  lower level working function or script to some higher level bit of code – then this method works acceptably. Of course, you have to be quite careful with C# syntax.  Little things like capitalising the token Namespace or String will create what I can only term unhelpful error messages.
Using Select-Object and Hash Tables
Another way to create a custom object is to use Select-Object. Usually, Select object is used to subset an occurrence – to just select a few properties from an object in order to reduce the amount of data that is to be transferred. In some cases, this may be good enough and would look like this:
Dir c:\foo\*.ps1 | Select-Object name,fullname| gm
  TypeName: Selected.System.IO.FileInfo
Name        MemberType   Definition                                  
----        ----------   ----------                                  
Equals      Method       bool Equals(System.Object obj)              
GetHashCode Method       int GetHashCode()                           
GetType     Method       type GetType()                              
ToString    Method       string ToString()                           
FullName    NoteProperty System.String FullName=C:\foo\RESTART-DNS.PS1
Name        NoteProperty System.String Name=RESTART-DNS.PS1          
Note that when you use Select-Object like this, the object's type name changes. In this case, the dir (Get-ChildItem) cmdlet was run against the File Store provider and yielded objects of the type: System.Io.FileInfo. The Select-Object, however, changes the type name to SELECTED.System.IO.FileInfo (emphasis here is mine). This usually is no big deal, but it might affect formatting in some cases. 
But you can also specify a hash table with the select object to create new properties, like this:
PSH [C:\foo]: $Filesize = @{
    Name = 'FileSize   '
    Expression = { '{0,8:0.0} kB' -f ($_.Length/1kB) }
}

Dir c:\foo\*.ps1 | Select-Object name,fullname, $Filesize
Name                   FullName                  FileSize               
----                   --------                  -----------               
RESTART-DNS.PS1       C:\foo\RESTART-DNS.PS1         1.1 kB               
s1.ps1                C:\foo\s1.ps1                  0.1 kB               
scope.ps1             C:\foo\scope.ps1               0.1 kB               
script1.ps1           C:\foo\script1.ps1             0.5 kB               

PSH [C:\foo]: Dir c:\foo\*.ps1 | Select-Object name,fullname, $Filesize| gm
   TypeName: Selected.System.IO.FileInfo
Name        MemberType   Definition                                  
----        ----------   ----------                                  
Equals      Method       bool Equals(System.Object obj)              
GetHashCode Method       int GetHashCode()                           
GetType     Method       type GetType()                              
ToString    Method       string ToString()                           
FileSize    NoteProperty System.String FileSize = 1.1 kB       
FullName    NoteProperty System.String FullName=C:\foo\RESTART-DNS.PS1
Name        NoteProperty System.String Name=RESTART-DNS.PS1          
As you can see form this code snippet, you can use Select-object to create subset objects and can extend the object using a hash table. One issue with this approach is that the member type for the selected properties (the ones included from the original object and those added) become NoteProperties, and not String, or Int, etc. In most cases, IT Pros will find this good enough.
In the next instalment in this series, I will be looking at using New-Object to create a bare bones new object and then adding members to it by using the Add-Member cmdlet and how to change the generated type name to be more format-friendly.

No comments: