Data Transformations with PowerShell Classes, Part 2

PowerShell-and-Data
I’m having fun with this series and hope you are as well. In my previous article, I introduced you to PowerShell classes and I built a class that I can use with my movie data file. If you are just joining us, you really should take a few minutes to read the previous articles (Making Data Dance with PowerShell & Dancing on the Table with PowerShell), plus part one of this article, Data Transformations with PowerShell Classes, Part 1. Otherwise, let’s pick up where we left off.

First, I’m going to use the same CSV file I’ve been using in the last few articles.

$data = Import-CSV C:\scripts\moviedata.csv

Then I’m going to need my class definition, which I explained in the previous article.

Class MyUpcoming {
#properties
[string]$Title
[datetime]$ReleaseDate
[string]$Comments
[int]$OpensIn
[string]$Rating
[boolean]$NowPlaying = $False
#methods
[MyUpcoming]Update() {
    $this.OpensIn = ($this.ReleaseDate - (Get-Date)).TotalDays
    if ((Get-Date) -ge $this.ReleaseDate ) {
        $this.NowPlaying = $True
    }
    return $this
}
#constructor
MyUpcoming([string]$Title,[datetime]$ReleaseDate,[string]$Rating,[string]$Comments) {
    $this.Title = $Title
    $this.ReleaseDate = $ReleaseDate
    $this.Rating = $Rating
    $this.Comments = $Comments
    $this.Update()
}
} #close class definition

Now I’m ready to start creating instances of the class using my data. To create an instance of my class, I need to invoke the constructor and pass it parameter values from the data. Here’s a quick proof of concept with a few items from $data.

$data[0..2] | foreach {
    [myupcoming]::new($_.Title,$_.ReleaseDate,$_.Rating,$_.comment)
}

Testing new objects
Testing new objects (Image Credit: Jeff Hicks)

See how this works? Or I can use New-Object.

$data[3..5] | foreach {
    New-Object -TypeName MyUpcoming -ArgumentList $_.Title,$_.ReleaseDate,$_.Rating,$_.comment
}

Testing with New-Object
Testing with New-Object (Image Credit: Jeff Hicks)

If I were building a toolset around this, I might prefer to create a function to abstract the process a bit.

Function New-MyUpcoming {
[cmdletbinding()]
Param(
[Parameter(Mandatory,ValueFromPipelineByPropertyName)]
[ValidateNotNullorEmpty()]
[string]$Title,
[Parameter(Mandatory,ValueFromPipelineByPropertyName)]
[ValidateNotNullorEmpty()]
[datetime]$ReleaseDate,
[Parameter(ValueFromPipelineByPropertyName)]
[ValidateSet("G","PG","PG-13","R","NC-17","UR","NR")]
[string]$Rating = "PG-13",
[Parameter(ValueFromPipelineByPropertyName)]
[string]$Comments
)
Begin {
    Write-Verbose "[BEGIN  ] Starting: $($MyInvocation.Mycommand)"
} #begin
Process {
    Write-Verbose "[PROCESS] Creating instance for $Title"
    New-Object -TypeName MyUpcoming -ArgumentList $Title,$ReleaseDate,$Rating,$Comments
} #process
End {
    Write-Verbose "[END    ] Ending: $($MyInvocation.Mycommand)"
} #end
}


There are some added benefits with using a function. One, it makes it easier to use in a pipelined expression since you can define parameters to take pipeline input. You can also set default values, make critical values mandatory and use parameter validation. Note that I am using a validation set for the Rating parameter because I know what the accepted values will be. This means I can catch discrepancies when I pull in external data. Let’s test it out.

New-MyUpcoming -Title "The Monad Manifesto" -ReleaseDate "12/1/2016" -Comment "starring Tom Hanks as Jeffrey Snover" -Verbose

Testing the new function
Testing the new function (Image Credit: Jeff Hicks)

Excellent. I didn’t have to specify a rating and the function used the default value. The function should also accept pipeline input.
Testing pipeline input
Testing pipeline input (Image Credit: Jeff Hicks)

Perfect. Now I can import all of my data and create instances of my class.
Attempting to import data
Attempting to import data (Image Credit: Jeff Hicks)

Here’s an example of where using the function with parameter validation is valuable. I have an incorrect value in my original CSV file. How you handle this error is really up to you and depends on the larger scope of the toolset you are building around the data. In my case, I’ll simply go back and correct the error then re-run the command.
Importing and creating custom data objects
Importing and creating custom data objects (Image Credit: Jeff Hicks)

I now have  a collection of objects that I can work with.
Using the custom data objects
Using the custom data objects (Image Credit: Jeff Hicks)

The last thing I can do is add custom type or formatting extensions. If you recall from the first article in this series, I used Update-TypeData. I can do the same thing here. The class name is the type name.

Update-TypeData -TypeName MyUpcoming -DefaultDisplayPropertySet "Title","ReleaseDate","Rating","Comments" -Force

Now when I look at the objects in $all I get the default properties.

$all | where {-Not $_.NowPlaying -and $_.releaseDate.year -eq 2016} | sort ReleaseDate

Getting default properties
Getting default properties (Image Credit: Jeff Hicks)

In the data I am using, I really don’t have a need to modify anything or devise any other methods. But you might depending on your data and your expectations for working with it. If you need to build other tooling around it, I’d suggest creating external functions that reference the class and its methods. Then you can include all the benefits of a function from help to parameter validation to support for WhatIf.

Working with data sources in PowerShell can be very rewarding and even fun. Once you have objects then you can exploit all of the PowerShell commands and the pipeline. Your biggest challenge will be finding the best way to bring your data into PowerShell and expose it to a user or yourself. Hopefully I’ve given you a number of options to explore and test.
I’d love to know what you thought about this series of articles so comments are always welcome.