Monthly Archives: January 2012

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.

Managing Remote (WSMAN) sessions with PowerShell


There was a post on the forums asking about how to remove a session if the max sessions (5) had been reached. You could use Get-Process to find and kill a WsmProvHost but killing things is never a good idea. There is a Get-WSManInstance but it’s not very friendly so I’ve wrapped that and Remove-WSManInstance in to two functions that are much more user-friendly. Just copy and past this in to a file and you can use Import-Modulein your profile to add these functions in to your environment.

<#
.Synopsis
Display WSMan Connection Info
.DESCRIPTION
This is a wrapper to Get-WSManInstance
.EXAMPLE
Get-RemotePSSession ServerABC
.EXAMPLE
$s = Get-RemotePSSession ServerABC
$s | Remove-RemotePSSession
#>
function Get-RemotePSSession
{
[CmdletBinding()]
    Param
    (
        # Computer to query.
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   Position=0)]
        [string]
$ComputerName,

# use SSL
        [switch]
$UseSSL
    )

    Begin
    {
        try{
            Add-Type  @"
namespace JRICH{
public class PSSessionInfo
            {
                public string Owner;
public string ClientIP;
public string SessionTime;
public string IdleTime;
public string ShellID;
public string ConnectionURI;
public bool UseSSL=false;
            }}
"@
        }
        catch{}
        $results = @()

    }
    Process
    {

$port = if($usessl){5986}else{5985}
$URI = "http://$($computername):$port/wsman"
$sessions = Get-WSManInstance -ConnectionURI $URI shell -Enumerate

foreach($session in $sessions)
        {
            $obj = New-Object jrich.pssessioninfo
            $obj.owner = $session.owner
            $obj.clientip = $session.clientIp
$obj.sessiontime = [System.Xml.XmlConvert]::ToTimeSpan($session.shellRunTime).tostring()
$obj.idletime = [System.Xml.XmlConvert]::ToTimeSpan($session.shellInactivity).tostring()
            $obj.shellid = $session.shellid
            $obj.connectionuri = $uri
            $obj.UseSSL = $usessl
            $results += $obj
        }
    }
    End
    {
        $results
    }
}

<#
.Synopsis
Logoff remote WSMAN session
.DESCRIPTION
   This function will take a JRICH.PSSessionInfo object and disconnect it
.EXAMPLE
$s = Get-RemotePSSession ServerABC
$s | Remove-RemotePSSession
#>
function Remove-RemotePSSession
{
[CmdletBinding()]
    Param
    (
        # Session to be removed.
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   Position=0)]
        [jrich.pssessioninfo[]]
        $Session

    )

    Process
    {
foreach($connection in $session)
        {
Remove-WSManInstance -ConnectionURI $connection.connectionuri shell @{ShellID=$connection.shellid}
        }
    }

}

Edit: Thanks Alex for catching the use of “CredSSP” vs “UseSSL” which is the common arg on PSSession cmdlets!

PowerShell – The Trouble With All That Power


Lately I’ve seen a lot more conversations on the forums about how much error checking you should do, how careful you should be, should you include aliases in your modules. Silly questions you may say.

This comes down to two problems.

The Problems

The Admin

Think of this, PowerShell is the only language that has an interactive environment that allows you to deal with objects. Ok so there might be some others out there, but they aren’t as popular (and I don’t use them!). Whats the big deal you ask? If you are at all like me you leave your console (ISE for me) open all the time and by all the time I mean it loads when the system is booted and stays open (my system is only rebooted for patches). I don’t have a good reason other than I like PowerShell at my finger tips. Still with me? Now chances are if you do this you also tend to work in PowerShell and store data here and there to variables to quickly test or work with something and lets say its a server you’re working with, do you do this too?


$s = "MyServer"

gwmi win32_Computersystem -co $s

gwmi win32_share -co $s

Whats wrong with this? I stored my server name in $s which is certainly easy for me to type as I work on the fly. Problem is, a few hours later when I start to do something different I reuse that $s for another server, or perhaps something else. It’s just a server name, no big deal right? What about $dt to store a datetime object? I do that a lot, or $dt to store a data table. Perhaps you see where this is going. As this shell stays open for multiple days I might overwrite a variable and then when I want to look at it again  I have a problem (that datatable with all the SQL data now contains a date, opps.) This is obviously a user error and I’ve trained myself to open tabs to deal with problems like this (I really don’t want to type long variable names)

Problem solved!

No, the problem is that more and more people are developing some really neat modules. I personally try to stay away from them because I tend to take what I do on the fly and turn it in to a script that will be run from a server, and I’m not going to install modules there. Even with that said I find that my module collection is growing.

Where am I going with this? As you install more modules, and create your own and start working more and more in PowerShell you’re likely to run in to the problem of variable/alias confusion. Even though its a ‘Best Practice’ to label your functions with meaningful names (good job Quest: Get-QADUser)  you don’t always see that in practice. On the plus side MS is cutting this off with the new Intillisense that PS3 has which will ask which one you want to use. But still, as the years go by and more and more cool stuff comes out this will start to be a rather large problem. This isn’t something that we needed to deal with back in the day, once you ran the script, all of those variables were gone upon completion. If you worked in *nix, you likely didn’t use the shell like that (plus its all text and not objects, no fun.)

On top of this, more modules are creating variables. Most of them are fairly verbose with their names, but not all. I often find myself trying to use $host to store a hostname. As the PowerShell environment grows we’ll need to be more and more careful about what variables we use.

Advice?  First read on.

The Developer

The developer problem isn’t a new one, but it’s probably new to a lot of you PowerShell guys who are admins and want to use PowerShell to automate things and like the interactive console idea. My background is a mix bag of admin and development. My admin experience is much stronger, but I know enough in both areas to understand this problem.

As you write code you’re first goal is to make it do what you want it to do, which is the fun part. Once you get the code working the way you want, you should ask yourself, what could break this and how can I stop/trap it?

If you spend any deal of time on this you are likely to become overwhelmed with just how many things can break your code. I once heard of the 80/20 rule (there are many versions) for programming.

20% logic (the work) and 80% error checking. If you think that’s a lot, think of this. Lets say you want to modify a file on a remote system you’ll first want to verify that the system is online with a test-connection because its quick. Then you’ll want to make sure you can access the file location (timeout is generally slower for this) and then you want to check if the file is there and if it is you want to make sure you can edit it.

There are two methods of thought, test first, or trap errors. I wont say one method is better than the other, I use both depending on whats faster/safer.

Where am I going with this?

To the Point – The Knowledge:

The problem I see is that people do one of two things, they over think it and get overwhelmed with how many things can go wrong, or they just say it works, I’m done!

There is some great documents out there on this, but this isn’t a programming class, it’s an adaptation for the admin who wants to write code. It comes down to two main things:

Know The Audience – I think this is a big one, who will use this code? What is their ability level? Is it a helpdesk team that wont be able to figure out what the PowerShell error means or is it technical group?  Is it to be deployed as a server job? Is it being sold or published to the community?

Know The Risk – What is the repercussion of this script failing? If it’s a server job it could be really bad. You might lose data over it. If it’s for a client you run the risk of making them unhappy and increase support costs. If it’s for the community then the project could fail. If it’s for you, well, you probably don’t care.

My point here is that there is a middle ground on how much error checking needs to be done. Most aren’t doing enough, and some are over thinking it. So the next time you write a script and think you are down with it, think about your audience and what will happen if this script fails.