Blog Archives
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.
