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.
Creating a module for powershell with a format file
So a little while ago I created a netstat function for powershell that doesn’t use the netstat.exe from windows but rather uses pinvoke to call some API’s. Perhaps a bit over kill, but was a fun little project. The problem I ran in to was the display of that info. So, I took what was there and turned it in to a module, and here is how its done.
So, for a module to be easily accessible it needs to be in one of two locations depending if you want it user or system level.
Step 1 – Create the modules folder!
User Level Module Location: $home\Documents\WindowsPowershell\Modules
Machine Level Module Location: $HOME\Modules
Where you store it is up to you. Each module must have a folder, in this case, I called it NetworkUtil and the way modules work is it loads the PSM1 file with the same name as the folder (which is module name).
Step 2 – The files
you could have a very simple module that is one ps1 file, and just name it the same as the folder and you’re good, but because our script may down the road involve a few more functions (in the form of PS1 files) as well as special formatting, we’re going to create a few files.
NetworkUtil.psm1 – The module file that tells it what files are involved in this module.
Network.Format.ps1xml – The file that contains info on how we want our function displayed.
NetworkUtil.psd1 – a manifest module, basically it gives some info about it and is of a standard format. This can be copied from http://msdn.microsoft.com/en-us/library/dd878337(VS.85).aspx and modified to suit your modules needs. There is ONE important modification here, and that’s for our format file. in the FormatsToProcess assign ‘NetworkUtil.Format.ps1xml’.
Get-Netstat.ps1 – And of course, our script file
Step 3 – Give those files some content!
Easy ones first.
The Get-Netstat.ps1 should contain the netstat function we created before
Next, the NetworkUtil.psm1 needs to contain one lonely line for now.
. $psScriptRoot\Get-Netstat.ps1
the NetworkUtil.psd1 which as indicated above can be copied from http://msdn.microsoft.com/en-us/library/dd878337(VS.85).aspx with that one minor modification;
FormatsToProcess = ‘NetworkUtil.Format.ps1xml’
Now for the fun part, the format file. More info can be found here, but basically its just an XML doc with some pretty basic info. We define the header names and size and then defile the properties that it should display under those headers. This is a real basic format file, but it does the trick
<?xml version="1.0" encoding="utf-8"?>
<Configuration>
<ViewDefinitions>
<View>
<Name>DefaultView</Name>
<ViewSelectedBy>
<TypeName>Connection</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Width>4</Width>
</TableColumnHeader>
<TableColumnHeader>
<Width>6</Width>
</TableColumnHeader>
<TableColumnHeader>
<Width>15</Width>
</TableColumnHeader>
<TableColumnHeader>
<Width>6</Width>
</TableColumnHeader>
<TableColumnHeader>
<Width>15</Width>
</TableColumnHeader>
<TableColumnHeader>
<Width>6</Width>
</TableColumnHeader>
<TableColumnHeader>
<Width>15</Width>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>protocol</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>PID</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>LocalIP</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>localport</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>remoteip</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>remoteport</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>state</PropertyName>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
</ViewDefinitions>
</Configuration>
Nothing to special going on here, but it surely makes it a lot easier to read!
Now we just need to import our module
Import-Module NetworkUtil
And we’re ready to go!
Get-Netstat
Because our netstat is now so cool we can take a look at all TCP connections that are not on the loop back connection.
Get-Netstat –TCPonly | where {$_.localip –ne “172.0.0.1”}
or perhaps only established connections and in order of local port.
Get-Netstat | where {$_.state –eq “established”} | sort localport
Maybe we want to see all web sessions we are connected to.
Get-Netstat | where {$_.remoteport –eq 80}
Now we want to see the names (name resolution is done on request so this might slow down to regular netstat speed)
Get-Netstat | where {$_.remoteport –eq 80} | select RemoteHostName,State
Once I found a good place to upload this I’ll make this module downloadable.
Enjoy!
