Fun With PowerShell Objects – Modifying Existing Objects

Introduction

So far we’ve covered a lot in this series on PowerShell Objects. We began by creating a class using the class keyword introduced in PowerShell 5. We then looked at using PSCustomObject to create new objects, then add methods to those objects. In the previous post, we used C# code to create our PowerShell objects.

In this installment, we’ll see how to add properties and methods to existing objects created by someone else. For this example we’ll use objects returned by the Get-ChildItem cmdlet, but we could use any objects.

For all of the examples we’ll display the code, then (when applicable) under it the result of our code. In this article I’ll be using PowerShell Core, 7.2.1, and VSCode. The examples should work in PowerShell 5.1 in the PowerShell IDE, although they’ve not been tested there.

In the code samples be on the lookout for the backtick ` , PowerShell’s line continuation character, at the end of many lines. The blog formatting has a limited width, so using the line continuation character makes the examples much easier to read. My post Fun With PowerShell Pipelined Functions dedicates a section to the line continuation character if you want to learn more.

To run a snippet of code highlight the lines you want to execute, then in VSCode press F8 or in the IDE F5. You can display the contents of any variable by highlighting it and using F8/F5.

Getting The Objects to Work With

As stated in the Introduction, we will use the objects returned by the Get-ChildItem cmdlet to add a new property and method to. We’ll set our location (in my case to the folder where these samples are stored), call the Get-ChildItem cmdlet and store the result in a variable, $items.

Set-Location C:\Users\arcan\OneDrive\BlogPosts\Markdown
$items = Get-ChildItem

Let’s look a the first item in the $items collection to see what its datatype is.

$items[0].GetType()

Result:

IsPublic IsSerial Name     BaseType
-------- -------- ----     --------
True     False    FileInfo System.IO.FileSystemInfo

As you can see, each item is of type FileInfo. Each object contains information about the corresponding file it represents. You can find a full list of properties and methods for the FileInfo class in the online Microsoft Documentation for FileInfo.

Coding Our New Method

For this example, we are going to iterate over our collection of FileInfo objects. To each one, we’ll be adding one property and one method. The property will just be a number that can be used to uniquely identify each object. We’ll look more at that momentarily.

The method we’ll add will analyze the existing Extension property of the FileInfo object. As I’m sure you know, the extension is the data that occurs after the period in the file name, and is used to identify the type of file it is.

Here is the script block for this method.

# Define the custom script property
$script = {

  switch ($this.Extension)
  {
    '.cs'   {$retValue = 'C#'}
    '.md'   {$retValue = 'Markdown'}
    '.ps1'  {$retValue = 'Script'}
    '.psd1' {$retValue = 'Module Definition'}
    '.psm1' {$retValue = 'Module'}
    '.xml'  {$retValue = 'XML File'}
    '.pptx' {$retValue = 'PowerPoint'}
    '.csv'  {$retValue = 'Comma Separated Values file'}
    '.json' {$retValue = 'JavaScript Object Notation data'}
    default {$retValue = 'Sorry dude, no clue.'}
  }

  return $retValue
}

Within the switch statement, I use $this to access the current object. It then access the Extension property of the current object.

It then goes down the list, comparing the extension value to the text to the left of the squiggly brace. If it finds a match, it will run the code inside the script block. This simply sets a return value variable to a more human friendly text representation of the file type.

If no match is found, it runs the code by the default value. For more in the switch statement, see my post Fun With PowerShell Logic Branching.

Adding The Property and Method to the FileInfo Object

Now that our script is defined, it’s time to add it as well as a property to our FileInfo objects. Let’s see the code, then we’ll break it down.

# Create an item count variable
$itemCount = 0

# Iterate over each DirectoryInfo object in the $items collection
foreach($item in $items)
{
  # Add a note property, setting it to the current item counter
  $itemCount++
  $item | Add-Member –MemberType NoteProperty `
                     –Name ItemNumber `
                     –Value $itemCount

  # Add script property to the individual file object
  Add-Member -InputObject $item `
             -MemberType ScriptMethod `
             -Name 'ScriptType' `
             -Value $script

  # Now display the already existing Name property along with the
  # property and method we just added.
  "$($item.ItemNumber): $($item.Name) = $($item.ScriptType())"
}

We begin by creating a variable, $itemCount. Within the foreach loop we’ll increment it, then use it as our index for the ItemNumber property.

The foreach loop is entered, where we iterate over the collection of FileInfo objects stored in $items. Each time through the loop, the current item is copied into the $item variable. For more info on foreach, see my post Fun With PowerShell Loops.

The next line is straightforward, we simply increment the $itemCount by one.

You’ve seen the Add-Member cmdlet used in recent posts on adding properties and methods to a PSCustomObject, so I won’t delve deep into it here.

The first call to Add-Member takes the current FileInfo object, stored in $item, and adds a new property we’ll name ItemNumber. When we add it, we’ll go ahead and assign the value in $itemCount. Note that this property could be updated at a future time, although we won’t need to for this example.

We then call Add-Member a second time, adding in the script you saw earlier and naming it ScriptType.

Finally, we use string interpolation to build a nicely formatted string with the item number, the name of the file (a native property of the FileInfo object), and finally we call the method we just added ScriptType.

Here is the final output of our effort.

Result:

1: blog-template.md = Markdown
2: fun-with-powershell-classes-the-basics.md = Markdown
3: fun-with-powershell-classes-the-basics.ps1 = Script
4: fun-with-powershell-enum-flags-header.png = Sorry dude, no clue.
5: Fun-With-PowerShell-Enum-Flags.md = Markdown
6: Fun-With-PowerShell-Enums.md = Markdown
7: Fun-With-PowerShell-Objects-Part 1.md = Markdown
8: Fun-With-PowerShell-Objects-Part 1.ps1 = Script
9: Fun-With-PowerShell-Objects-Part 2.md = Markdown
10: Fun-With-PowerShell-Objects-Part 2.ps1 = Script
11: Fun-With-PowerShell-Objects-Part 3.cs = C#
12: Fun-With-PowerShell-Objects-Part 3.md = Markdown
13: Fun-With-PowerShell-Objects-Part 3.ps1 = Script
14: Fun-With-PowerShell-Objects-Part 4.md = Markdown
15: Fun-With-PowerShell-Objects-Part 4.ps1 = Script
16: fun-with-powershell-objects-part-1.png = Sorry dude, no clue.
17: Fun-With-PowerShell-Providers.md = Markdown
18: Fun-With-PowerShell-Write-Debug.md = Markdown
19: Fun-With-PowerShell-Write-Verbose-and-Write-Debug.md = Markdown
20: Fun-With-VSCode-Code-Snippets.md = Markdown
21: IMG_0965.JPG = Sorry dude, no clue.
22: more-fun-with-powershell-enums-header.png = Sorry dude, no clue.
23: More-Fun-With-PowerShell-Enums.md = Markdown
24: More-Fun-With-PowerShell-Enums.ps1 = Script
25: security-for-apartment-dwellers-01.png = Sorry dude, no clue.
26: Security-for-Apartment-Dwellers-header.png = Sorry dude, no clue.
27: Security-for-Apartment-Dwellers.md = Markdown

I author these blog posts in markdown within VSCode first, prior to copying them into my WordPress based blog. This folder contains a markdown file for each post, most of which have one or more accompanying code files associated with it.

I deliberately omitted the image type extensions (.jpg, .png) from the switch statement so you could see the default option being taken.

Persistance

There is an important fact you need to recall when using this technique. The new property and method only exist for the set of FileInfo objects contained in our $items collection. If I were to get another collection, perhaps using $moreItems = Get-ChildItem, the FileInfo objects stored in $moreItems will NOT have our ItemNumber and ScriptType in them.

You will have to explicitly add custom properties and methods to objects each time you need them.

Conclusion

As you can see, this technique offers many possibilities. You could define a script block at the top of your script that does a complex calculation, or perhaps formats data for easier reading by the end user. Then you simply add this script block to the objects you generate.

This makes your code much more readable. The complex part is at the top where we define the script block, out of the way of your loops.

It can also promote code reuse. You can create a separate script with many script blocks that you commonly use. You call that script from the one you are currently developing, and you instantly have a set of new properties and methods that can be added to the set of objects you are working with.

This method provides for extensibility with objects you did not author, or have the source code for. Perhaps you have a compiled PowerShell module that came from a vendor, but it lacks that one method that would, for your unique situation, make it much easier to work with. You can now code that method for yourself and use it.

As time goes on I’m sure you’ll find many situations you can use these techniques to solve problems.

In the next three installments of this series we’ll return to the PowerShell class type. We’ll look at some of the advanced abilities that it offers.

The demos in this series of blog posts were inspired by my Pluralsight course PowerShell 7 Quick Start for Developers on Linux, macOS and Windows, one of many PowerShell courses I have on Pluralsight. All of my courses are linked on my About Me page.

If you don’t have a Pluralsight subscription, just go to my list of courses on Pluralsight . At the top is a Try For Free button you can use to get a free 10 day subscription to Pluralsight, with which you can watch my courses, or any other course on the site.

Leave a comment