Create Custom Objects in Your PowerShell Script

Create Custom Objects in Your PowerShell Script

  • Comments 10
  • Likes

Summary: Learn how to create custom objects in your Windows PowerShell script to maximize utility and reusability of your script.

Microsoft Scripting Guy, Ed Wilson, is here. Today we have Steven Murawski as our guest blogger. Steven will tell us a little about himself then go on to talk about objects.

Photo of Steven Murawski

I'm a senior Windows system engineer for Edgenet. In this role, I support a dynamic infrastructure that pushes the boundaries of the Windows platform. Windows PowerShell allows me to support this infrastructure in a more consistent and efficient manner. I'm an advocate for Windows PowerShell because I believe it encourages administrative best practices, and it allows developers to provide additional access to their applications, without needing to explicitly write code for each administrative feature. Part of my advocacy for Windows PowerShell includes my blog, appearances on various podcasts, and involvement with local user groups (Greater Milwaukee Script Club, and the Greater Milwaukee IT Pro User Community). I am also a cohost of Mind of Root (a weekly audio podcast about systems administration, tech news, and topics). We stream our show live on UStream.tv Sunday evenings at 7:30 PM CST.

Steven's contact information:
Blog: Use PowerShell: The Shell Is Calling
Websites:
Greater Milwaukee Script Club
Greater Milwaukee IT Pro User Community
Mind of Root on UStream

It’s All About the Objects

I had the honor of serving as one of the judges for the 2011 Scripting Games. Having the opportunity to read so many scripts and watch the intense involvement that the competitors displayed was truly a treat. I saw some very interesting solutions to problems, and I want to thank everyone who contributed a script for any event.

The 2011 Scripting Games provided an opportunity to showcase several themes that make Windows PowerShell a unique automation framework and scripting language. In my opinion, one of those key themes is the use of objects. Windows PowerShell is an object-based environment. Before you run screaming off into the woods, if you’ve dealt with WMI, VBScript, or automated Microsoft Office at all, you are already familiar with working with objects. Objects represent a logical grouping of data, and they provide a way for us as scripters to more easily find the information we are looking for. All of those previous technologies (and many other areas of the Microsoft ecosystem) express their structure as objects. Getting comfortable with using objects is an important ability in the PowerShell world.

In Windows PowerShell, everything is represented by an object (yes, everything, including text). Being designed to work with objects provides Windows PowerShell a great advantage in its pipeline. Objects can contain data called properties. Those properties can be used to provide input to the next command. This is a key strength that allows you, the scripter, to allow your scripts or functions to become the input for other commands, perhaps enabling scenarios that you never imagined.

Here is where I put on my infomercial salesman hat…

But wait, there’s more! For the same price of embracing an object-based shell and designing your output to be objects, we’ll include a great number of cmdlets that provide support for formatting, converting, and exporting the objects emitted from your scripts. Now how much would you expect to pay?

When your scripts and functions are designed to output objects, the formatting and export commands in Windows PowerShell are instantly available to work with the output of your script. Need to export your data as a CSV file? That is covered by Export-CSV or ConvertTo-CSV (if you are not ready to put your output in a file). How about turning the results into HTML or XML? ConvertTo-HTML and ConvertTo-XML have you covered. I can go on and on, but I will leave you with a quick command so that you can see some of the options you have:

Get-Command –verb Format, Out, Export, ConvertTo

Because these output and formatting options exist, you can stop worrying about the format of and export of the results of your function and concentrate on the functionality you want to provide. Let’s run through an example. This example is based on a task my boss asked me to do in preparation for moving a data center.

I need to create a script to gather network information about the servers in my environment in preparation for a data center move and network redesign. I would like to know the physical interface, IP address assigned, subnet mask, default gateway, DNS servers, and domain suffix. I only care about adapters that are enabled and have IP address information.

I need to get this information from several hundred servers of varying operating system versions and in different domains, so I’ve decided to use WMI as my tool to gather the network stack information. The first version of this script manually creates a report and writes it to the console, as shown here.

Function Get-NetworkConfiguration

{

    param (

        [parameter(

            ValueFromPipeline=$true,

            ValueFromPipelineByPropertyName=$true,

            Position=0)]

        [Alias('__ServerName', 'Server', 'Computer', 'Name')]   

        [string[]]

        $ComputerName = $env:COMPUTERNAME,

        [parameter(Position=1)]

        [System.Management.Automation.PSCredential]

        $Credential

    )

    process

    {

        $WMIParameters = @{

            Class = 'Win32_NetworkAdapterConfiguration'

            Filter = "IPEnabled = 'true'"

            ComputerName = $ComputerName

        }

       

        if ($Credential -ne $Null)

        {

            $WmiParameters.Credential = $Credential

        }       

        foreach ($adapter in (Get-WmiObject @WMIParameters))

        {

            $OFS = ', '

            Write-Host "Server: $($adapter.DNSHostName)"

            Write-Host "Adapter: $($adapter.Description)"

            Write-Host "IP Address: $($adapter.IpAddress)"

            Write-Host "Subnet Mask: $($adapter.IPSubnet)"

            Write-Host "Default Gateway: $($adapter.DefaultIPGateway)"

            Write-Host "DNS Servers: $($adapter.DNSServerSearchOrder)"

            Write-Host "DNS Domain: $($adapter.DNSDomain)"

            Write-Host

        }

       

    }

}

Here is the script output:

Image of command output

Not too shabby for a quick script, if I don’t say so myself. The script will run through and give me the information I need and display it to the console; however, that would be the end of its utility.

How can I make this more useful? Changing Write-Host to Write-Output would allow me to redirect the output to a file or to use a text parsing tool to manipulate the results, but that is still too limited.

Let’s jazz this script up a bit by making one critical change. Instead of the Write-Host or Write-Output commands with some text, we are going to change this to emit a custom object. We’ve already done most of the work. The New-Object cmdlet offers a way to turn a hash table into the properties of a custom object.

$My SampleProperties = @{name=’Steve’;title=’Guest Blogger’}

New-Object PSObject –Property $MySampleProperties

Here is the output:

Image of command output

We’ll use this same technique and swap out the Write-Host statements in the Foreach loop and create a hash table with the same properties that we wanted in the text report, and then we can create a custom object with those properties. The Foreach loop will then look like this:

foreach ($adapter in (Get-WmiObject @WMIParameters))

        {

            $AdapterProperties = @{

                Server = $adapter.DNSHostName

                Adapter =  $adapter.Description

                IPAddress = $adapter.IpAddress

                SubnetMask = $adapter.IPSubnet

                DefaultGateway = $adapter.DefaultIPGateway

                DNSServers = $adapter.DNSServerSearchOrder

                DNSDomain = $adapter.DNSDomain

            }

           

            New-Object PSObject -Property $AdapterProperties

        }

The output looks like this:

Image of command output

The output looks pretty similar, but now there are some key differences. Windows PowerShell also offers a variety of formatting resources, so I can turn my output into a customized table (or list) as follows:

Get-NetworkConfiguration ‘Server1’, ‘Server2’, ‘Server3’ | Format-Table Server, Adapter, SubnetMask, DefaultGateway –auto –wrap

As an object, I can work with these various properties in other ways as well. For example, if I had a DNS server go bad (this happened just the other week to me), I can run this against all the computers in a certain site and find which machines have that DNS setting:

Get-ADComputer –filter * | Get-NetworkConfiguration | Where-Object {$_.DNSServers –contains $IPofMyTroublesomeDNSServer}

Or if you would like to get a count of how many machines are using different default gateways, you could do something like this:

Get-ADComputer -filter * | Get-NetworkConfiguration | Group-Object DefaultGateway -NoElement

This is shaping up to become a great auditing tool, in addition to solving my initial reporting problem. The final script is available in the Technet ScriptCenter Repository.

When you begin to output options from your functions, the reusability and extensibility options soar. Objects allow you to take advantage of the pipeline (which allows your objects to be the input to other commands), allow your commands to use incoming objects (via the Parameter attribute and ValueFromPipelineByPropertyName switch), and most importantly, allow you to leverage the formatting and output system that Windows PowerShell provides, which keeps you from having to design your own output options and saves your effort for the real work—getting your job done!

Thank you, Steven, for writing this explanation of working with objects in Windows PowerShell. Join us tomorrow, as Andy Schneider is the guest blogger.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Great information Steven!

    Though i doubt, that I will always keep this in mind, I'll go for returning objects from (advanced) functions!

    This is the powershell way.

    It supports the pipeline and that's the way we should think and script!

    kind regards, Klaus

  • Great article Steven. When you're doing ad-hoc work, you don't have to worry much about this but if you return custom objects from functions you should always give the object an ETS type name that is unique to that object. Type names are very important in PowerShell, allowing the formatting engine to pick up default formatting, allowing parameters to be created that only accept a certain type, identifying what object types are input into commands and output from commands. PowerGUI also uses types to uniquely identify objects so that it can show actions appropriate for the object type in the Admin Console. For these reasons, I never create custom objects without giving them a type anymore.

    For the reader, customizing an object type is very simple. In Steven's example above, I would replace his call to New-Object with this:

    $newObject = New-Object PSObject -Property $AdapterProperties

    $newObject.PSTypeNames.Insert(0, 'NetworkConfiguration')

    $newObject

    The additional lines simply add a custom type name to the object and then return the object to the caller. It's so easy, there's really no reason you shouldn't do it! :)

  • @Poshoholic

    I'm unable to find a link that explains ETS and it's usage, I found a page that linked to what I *thought* would define it but the links on that page are broken.

    original page: technet.microsoft.com/.../system.management.automation.psadaptedproperty.ctor(d=lightweight).aspx

    broken link1 (Windows Powershell Extended Type System): technet.microsoft.com/.../ms714419.aspx

    brokem link2 (ETS Properties): technet.microsoft.com/.../ms714457.aspx

    There was no re-direct, just a, "we're sorry this page doesn't exist" note :(

  • Steve, great post btw! Really nice script as well!

  • Jeff, All

    Custom 'types' AKA Exended Output Objects

    msdn.microsoft.com/.../dd878306(v=VS.85).aspx

  • @Klaus - Thank you for the kind words.  The pipeline is the real power in this shell and objects help us to use that effectively.

    @Poshoholic - You are absolutely right.  I was remiss in not covering that.

    @Jeffrey - Thank you.  It's a pretty basic script, but it gets the job done. ;) There is a good blog post on the PowerShell Team blog about the Adaptive Type System - blogs.msdn.com/.../hate-add-member-powershell-s-adaptive-type-system.aspx which might provide some more insight.  I've also blogged about another way to use the adaptive type system via a ps1xml file - blog.usepowershell.com/.../adding-custom-properties-to-functions

    @JV - Thanks for the link!

  • I need to create a custom object where it's called, say, "Roster". a roster object needs 3 things: a coursename (such as "MATH 101", a LIST of instructors, and a LIST of students.

    here's the fun part, the course name is a piece of the .name of an AD object. The instructor list is from a custom ldap attribute of the ad object, and the list of students are the *members* of the ad object (it's a universal security group). Fun, eh?

    Your example shows nicely how to create new objects of type $adapter, but that simple object merely has three attributes, all of which are type "string". I need a custom object, with three attributes, one of which is a string, but the other two are arrays. or lists. or something. HOW TO DO?? :(

  • Great article, this was exactly the content I was looking for on objects. Thanks!!

  • Hello I'm trying to read IP address from text file and output the IP address to custom field in SCOM, I'm having trouble with which Get cmdlet to use.  Any help would be greatly appreciated, thanks

  • thank you