Welcome PowerShell User! This recipe is just one of the hundreds of useful resources contained in the PowerShell Cookbook.

If you own the book already, login here to get free, online, searchable access to the entire book's content.

If not, the Windows PowerShell Cookbook is available at Amazon, or any of your other favourite book retailers. If you want to see what the PowerShell Cookbook has to offer, enjoy this free 90 page e-book sample: "The Windows PowerShell Interactive Shell".

3.15 Create and Initialize Custom Objects

Problem

You want to return structured results from a command so that users can easily sort, group, and filter them.

Solution

Use the [PSCustomObject] type cast to a new PSCustomObject, supplying a hashtable with the custom information as its value, as shown in Example 3-8.

Example 3-8. Creating a custom object
$output = [PSCustomObject] @{
    'User' = 'DOMAIN\User';
    'Quota' = 100MB;
    'ReportDate' = Get-Date;
}

If you want to create a custom object with associated functionality, write a PowerShell class in a module, and create an instance of that class:

using module c:\modules\PlottingObject.psm1

$obj = [PlottingObject]::new()
$obj.Move(10,10)
$obj.Points = SineWave
while($true) { $obj.Rotate(10); $obj.Draw(); Sleep -m 20 }

Discussion

When your script outputs information to the user, always prefer richly structured data over hand-formatted reports. By emitting custom objects, you give the end user as much control over your script’s output as PowerShell gives you over the output of its own commands.

Despite the power afforded by the output of custom objects, user-written scripts have frequently continued to generate plain-text output. This can be partly blamed on PowerShell’s previously cumbersome support for the creation and initialization of custom objects, as shown in Example 3-9.

Example 3-9. Creating a custom object in PowerShell version 1
$output = New-Object PsObject
Add-Member -InputObject $output NoteProperty User 'DOMAIN\user'
Add-Member -InputObject $output NoteProperty Quota 100MB
Add-Member -InputObject $output NoteProperty ReportDate (Get-Date)

$output

In PowerShell version 1, creating a custom object required creating a new object (of the type PsObject), and then calling the Add-Member cmdlet multiple times to add the desired properties. PowerShell version 2 made this immensely easier by adding the -Property parameter to the New-Object cmdlet, which applied to the PSObject type as well. PowerShell version 3 made this as simple as possible by directly supporting the [PSCustomObject] type cast.

While creating a PSCustomObject makes it easy to create data-centric objects (often called property bags), it doesn’t let you add functionality to those objects. When you need functionality as well, the next step is to create a PowerShell class (see Example 3-10). Like many other languages, PowerShell classes support constructors, public properties, and public methods, as well as internal helper variables and methods.

Example 3-10. Creating a module that exports a custom class
##############################################################################
##
## PlottingObject.psm1
##
## From PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################

<#

.SYNOPSIS

Demonstrates a module that implements a custom class

.EXAMPLE

function SineWave { -15..15 | % { ,($_,(10 * [Math]::Sin($_ / 3))) } }
function Box { -5..5 | % { ($_,-5),($_,5),(-5,$_),(5,$_) } }

using module PlottingObject

$obj = [PlottingObject]::New(@())
$obj.Points = Box
$obj.Move(10,10)
while($true) { $obj.Rotate(10); $obj.Draw(); Start-Sleep -m 20 }

$obj = [PlottingObject]::New((SineWave))
while($true) { $obj.Rotate(10); $obj.Draw(); Start-Sleep -m 20 }

#>

class PlottingObject
{
    ## Constructors: one with no arguments and another that takes a
    ## set of initial points.
    PlottingObject()
    {
        $this.Points = @()
    }

    PlottingObject($initialPoints)
    {
        $this.Points = $initialPoints
    }

    ## An external property holding the points to plot
    $Points = @()

    ## Internal variables
    hidden $x = 0
    hidden $y = 0
    hidden $angle = 0
    hidden $xScale = -50,50
    hidden $yScale = -50,50
    hidden $windowWidth = [Console]::WindowWidth
    hidden $windowHeight = [Console]::WindowHeight

    ## A public method to rotate the points by a certain amount
    [void] Rotate([int] $angle)
    {
        $this.angle += $angle
    }

    ## A public method to move the points by a certain amount
    [void] Move([int] $xDelta, [int] $yDelta)
    {
        $this.x += $xDelta
        $this.y += $yDelta
    }

    ## A public method to draw the given points
    [void] Draw()
    {
        $degToRad = 180 * [Math]::Pi

        ## Go through each of the supplied points,
        ## move them the amount specified, and then rotate them
        ## by the angle specified
        $frame = foreach($point in $this.Points)
        {
            $pointX,$pointY = $point
            $pointX = $pointX + $this.x
            $pointY = $pointY + $this.y

            $newX = $pointX * [Math]::Cos($this.angle / $degToRad ) -
                $pointY * [Math]::Sin($this.angle / $degToRad )
            $newY = $pointY * [Math]::Cos($this.angle / $degToRad ) +
                $pointX * [Math]::Sin($this.angle / $degToRad )

            $this.PutPixel($newX, $newY, 'O')
        }

        ## Draw the origin
        $frame += $this.PutPixel(0, 0, '+')

        Clear-Host
        Write-Host "`e[?25l" -NoNewline
        Write-Host $frame -NoNewline
   }

    ## A helper function to draw a pixel on the screen
    hidden [string] PutPixel([int] $x, [int] $y, [char] $character)
    {
        $scaledX = ($x - $this.xScale[0]) / ($this.xScale[1] - $this.xScale[0])
        $scaledX = [int] ($scaledX * $this.windowHeight * 2.38)

        $scaledY = (($y * 4 / 3) - $this.yScale[0]) / ($this.yScale[1] - $this.yScale[0])
        $scaledY = [int] ($scaledY * $this.windowHeight)

        return "`e[$scaledY;${scaledX}H$character"
    }
}

For more information about creating modules, see Recipe 11.6. For more information about the syntax of PowerShell classes, see “Classes”.

See Also

Recipe 7.13, “Create a Hashtable or Associative Array”

Recipe 11.6, “Package Common Commands in a Module”

“Classes”