PowerShell: Custom Objects with Format Files


I was recently working on something and realized that the default output (everything) was getting rather annoying. I then realized that the object I had wasnt going to cut it and I had to create my own. This led to two problems:

1. I needed to get formatting for my data and since I was going to post the script online I didn’t want to make it a multi file solution. I wanted it so the end-user could just copy and paste the entire thing in to one file and run it.

2. In knowing that I needed custom formating I realized the normal PSObject wasnt going to cut it.

Step 1 (Item 2) The Custom Object

This part isn’t really all that hard, but its something I thought I would show. If you know C# this will make perfect sense, if you don’t, it will likely still make perfect sense. In order to have a type name with our object we have to define it via a C# class (Other .NET languages work, but I know and like C#.) You can make this as simple or as complex as you like but for this example its going to be very simple. We’re going to make a Contact object.

add-type @'
namespace JRICH
{
    public class Contact
    {
public string FName;
public string LName;
public string DisplayName()
        {
return (this.FName + " " + this.LName);
        }
    }
}
'@

There are a couple of note worthy items here. First you’ll see I’ve provided a namespace for this class. This isn’t needed but I find it to be good practice to help prevent duplication of class names. Also you’ll want to be weary of case in the C# side of things because it is case-sensitive. Along those lines you’ll notice that each line ends in a ;. As I said if you know C# this is no big deal, but if you are a scripter/admin learning PowerShell these are important notes.

One other thing I’ll say here is that if you are not used to C# and you plan on doing this you might want to grab a copy of Visual C# Express which you can download for free. The debugging and Intellisense will really help with learning C#.

Another reason to use Visual C# is that when creating a class in PowerShell once you add the type there is no way to remove it or overwrite it so after you add the type and decide you want to change it, you’ll need to close PowerShell and open it again.

In this case I’ve created a class called Contact which has a first name, last name and a display name method to give a friendly view of this.

Now that we have this custom object lets use it.

$contact = new-object jrich.contact
$contact.fname = "Justin"
$contact.lname = "Rich"
$contact
#FName  LName
#———————–
#Justin Rich
$contact.displayname()
#Justin Rich

Pretty easy huh?

Now that we have an object to work with lets put it in a script. Lets make a function that returns a handful of users. I’m making this overly simple but I think you’ll see how to apply this in your own scripts.

function Get-MyUsers ([string[]] $User)
{
    Begin
    {
        $results = @()
    }
    Process
    {
foreach($usr in $user)
        {
$tmp = new-object jrich.contact
$tmp.lname, $tmp.fname = $usr.split(',')
$results += $tmp
        }
    }
    End
    {
        $results
    }
}
Get-MyUsers @("rich,justin","smith,john","doe,jane")

Shortcut: Ctrl+J will bring up the snippet menu (in ISE) which will allow you to generate a function body very quickly.

Step 2 (Item 1) The Format File

Our simple function is all well and good but lets say we are unhappy with the default output format and want to change it so the user doesn’t have to do it every time. In order to change the default display we need a Format File. The Help system has About_format.ps1xml and MSDN has pretty detailed documents on it, but lets just say its fairly complex. Thankfully James Brundage over at Start-Automating.com has created EZOUT, a very nice tool to help us create those format files.

Im not going to go in to details on how to create these files or use that tool since there is a very nice YouTube video for it.

Back to our function. Lets say we’d like to make the default view be the return of the Displayname method.

This is the XML that the EZOUT has spit out for us.

xml version="1.0" encoding="utf-16"?>
<Configuration><ViewDefinitions><View>
    <Name>jrich.contact</Name>
    <ViewSelectedBy>
        <TypeName>jrich.contact</TypeName>
    </ViewSelectedBy>
    <TableControl>
        <TableHeaders>
            <TableColumnHeader><Label>DisplayName</Label></TableColumnHeader>
        </TableHeaders>
        <TableRowEntries>
            <TableRowEntry>
                <TableColumnItems>
$_.displayname()
                </TableColumnItems>
            </TableRowEntry>
        </TableRowEntries>
    </TableControl>
</View></ViewDefinitions></Configuration>

Typically what you would do is save this file and use the Update-FormatData cmdlet to import this in to PowerShell’s current session environment.

In our case, because we don’t want to have more than one file we want to embed this in to our script. Since the only way to get this format data in to the environment is to use the Update-FormatData we’ll need to write this out to a file so we’ll inject this in to our Begin script block along with our class definition to make this a stand alone script.

function Get-MyUsers ([string[]] $User)
{
    Begin
    {
        try{
            add-type @'
namespace JRICH
{
    public class Contact
    {
public string FName;
public string LName;
public string DisplayName()
        {
            return (this.FName + " " + this.LName);
        }
    }
}
'@
            }
        catch{}

$formatfile = "$pshome\jrich.contact.format.ps1xml"
$typedata = @'
<?xml version="1.0" encoding="utf-16"?>
<Configuration><ViewDefinitions><View>
    <Name>jrich.contact</Name>
    <ViewSelectedBy>
        <TypeName>jrich.contact</TypeName>
    </ViewSelectedBy>
    <TableControl>
        <TableHeaders>
            <TableColumnHeader><Label>DisplayName</Label></TableColumnHeader>
        </TableHeaders>
        <TableRowEntries>
            <TableRowEntry>
                <TableColumnItems>
                    <TableColumnItem><ScriptBlock>$_.displayname()</ScriptBlock></TableColumnItem>
                </TableColumnItems>
            </TableRowEntry>
        </TableRowEntries>
    </TableControl>
</View></ViewDefinitions></Configuration
'@

if(!(Test-Path $formatfile))
        {
$typedata | out-file $formatfile
        }
        Update-FormatData -AppendPath $formatfile
        $results = @()
    }
    Process
    {
foreach($usr in $user)
        {
            $tmp = new-object jrich.contact
            $tmp.lname, $tmp.fname = $usr.split(',')
$results += $tmp
        }
    }
    End
    {
        $results
    }
}

There are a couple of things to note here. First is that we’ve enclosed our type definition in a Try/Catch statement. The reason for this is that once we load our type in to the environment we cant do it again as we saw with creating it and trying to update it.

The next thing is the actual process of adding in the formating data. You’ll notice I define a file based on the $PSHome variable (PowerShell install location) and tacked on a file name. To make sure it’s never overwritten I’ve named it based on the namespace and class. We then define the XML as a string and check to see if that file is there and if not write the file and update the format data.

One thing to note is that this method should only be used when you want to make a single script file to be distributed. If you are making a module the process of format files is a bit different. Want to know about that? Comment on this post and I’ll do a write-up on it.

Hope this helps.

Advertisements

About jrich

I am the Solutions Architect for Apex Learning in Seattle WA. I've been working with computers since I was 13. Started programming when I was 14. Had my first IT job as tech support at an ISP at the age of 15 and became a network admin at the age of 17. Since then I've worked at a variety of small to mid size companies supporting, maintaining and developing all aspects of IT. Mostly working with Windows based networks but have recently been working with Solaris system as well. I created this blog mostly as a place for me to take my own notes, but also share things that I had a hard time finding the info for.

Posted on January 20, 2012, in WMF (Powershell/WinRM) and tagged , , , , , . Bookmark the permalink. 1 Comment.

  1. Nicely done. Look forward to seeing more.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: