Intro to Object-Oriented Programming with PowerShell

In this post, I go over the fundamentals of object-oriented programming and some simple ways to interact with objects in PowerShell

8 months ago   •   9 min read

By 0xBEN
Table of contents

In the previous post, we took a look at some fundamental concepts in programming – variables, data types, arrays, operators, functions, control flow logic, and loops. Knowing about these is a critical first step to becoming a better programmer.

An Intro to Programming with PowerShell
In this post, I go over the fundamentals of programming using PowerShell and demonstrate why it’s a great way to learn to code

The next step up from there is to learn about object-oriented programming (OOP). That's what really makes PowerShell a powerful shell, much more so than any Unix-style shell. Unix-style shells are text-based and are based on string input and output.

PowerShell deals with objects. When you create a script or function in PowerShell, you should always ensure that you are outputting objects. Any time you take input from the user, you should try to accept objects wherever applicable.





Classes and Objects

You can think of a class as a template for an object. Let me demonstrate this with some simple code.

class Person {
    [String]$FirstName
    [String]$LastName
    [String]$EyeColor
    [Int]$Age
}

$johnDoe = New-Object Person
$johnDoe.FirstName = 'John'
$johnDoe.LastName = 'Doe'
$johnDoe.EyeColor = 'Brown'
$johnDoe.Age = '33'

# Print the object to output
$johnDoe

I've created a Person class, so that any time I need to create an object about a Person, I can just use the template to build a new person. If I need to make changes, I just modify the template. Let's take a look at that.

class Person {
    [String]$FirstName
    [String]$LastName
    [String]$EyeColor
    [Int]$Age
    [String[]]$Nicknames
}

$johnDoe = New-Object Person
$johnDoe.FirstName = 'John'
$johnDoe.LastName = 'Doe'
$johnDoe.EyeColor = 'Brown'
$johnDoe.Age = '33'
$johnDoe.Nicknames = @('johnny', 'jim', 'anonymous')

# Print the object to output
$johnDoe

I added the [String[]]$Nicknames property to the class. The [String[]] syntax – as opposed to [String] – indicates that this property will take an array of strings.  I have populated it below on the $johnDoe object.





Object Properties and Methods

The most common parts of an object are its properties and methods. These are the properties of the Person class:

  • [String]$FirstName
  • [String]$LastName
  • [String]$EyeColor
  • [Int]$Age
  • [String[]]$Nicknames


What is a Method?

A method is a function that is common to a particular type of class. In other words, what is something that every person would do? If you had a Vehicle class, what kinds of methods would you have?

I am going to add some methods to my Person class.

  • Eat
  • Walk
  • Sleep

These are things that every person would normally do.

class Person {
    [String]$FirstName
    [String]$LastName
    [String]$EyeColor
    [Int]$Age
    [String[]]$Nicknames
    
    [String] Eat([String]$Food) {
    	return "That $Food was delicious!"
    }
    [String] Walk([Int]$Steps) {
    	return "I walked $Steps steps."
    }
    [Void] Sleep() {
    	Start-Sleep -Seconds 10
    }
}

$johnDoe = New-Object Person
$johnDoe.FirstName = 'John'
$johnDoe.LastName = 'Doe'
$johnDoe.EyeColor = 'Brown'
$johnDoe.Age = '33'
$johnDoe.Nicknames = @('johnny', 'jim', 'anonymous')

# Print the object to output
$johnDoe

# Feed John Doe
# The Eat method takes a string
$johnDoe.Eat('spaghetti')

# John Doe needs a walk
# The Walk method take an integer
$johnDoe.Walk(10000)

# John Doe needs a nap
# The Sleep method does not take input
$johnDoe.Sleep()

Let's review the method structure.

RED indicates the kind of output this method will return. GREEN indicates the kind of input this method will accept. The Eat() method accepts [String] input, whereas the Walk() method accepts [Int] input.

As you notice with the Sleep() method, it has a return type of [Void], because there is no output returned from this method. Whereas with Walk() and Eat(), there is a return type of [String].





Get-Member Cmdlet

The Get-Member cmdlet in PowerShell is very convenient for inspecting objects. With the Get-Member cmdlet, we can look at the various properties and methods of an object.

As I mentioned before, everything in PowerShell is an object – even files and directories. They all have properties and methods that can be inspected with the Get-Member cmdlet.

Let's inspect our $johnDoe object with the Get-Member cmdlet.

$johnDoe | Get-Member
   TypeName: Person

Name        MemberType Definition
----        ---------- ----------
Eat         Method     string Eat(string Food)
Equals      Method     bool Equals(System.Object obj)
GetHashCode Method     int GetHashCode()
GetType     Method     type GetType()
Sleep       Method     void Sleep()
ToString    Method     string ToString()
Walk        Method     string Walk(int Steps)
Age         Property   int Age {get;set;}
EyeColor    Property   string EyeColor {get;set;}
FirstName   Property   string FirstName {get;set;}
LastName    Property   string LastName {get;set;}
Nicknames   Property   string[] Nicknames {get;set;}

The first thing you should notice is the TypeName: Person line. The Get-Member cmdlet tells us what kind of object we are dealing with. The next thing we can see is the object's properties and methods.

So, by looking at this, we can know that the Person class has the following:

Properties

  • Age
  • EyeColor
  • FirstName
  • LastName
  • Nicknames

Methods

  • Eat
  • Equals (automatically added by PowerShell)
  • GetHashCode (automatically added by PowerShell)
  • GetType (automatically added by PowerShell)
  • Sleep
  • ToString (automatically added by PowerShell)
  • Walk




Exploring with Get-Member

I am going to create a file on my desktop with this command: New-Item -ItemType File -Name 'special-file.txt'. Now, I'll store it in a variable: $file = Get-Item ~/Desktop/special-file.txt

$file | Get-Member
   TypeName: System.IO.FileInfo

Name                      MemberType     Definition
----                      ----------     ----------
LinkType                  CodeProperty   System.String LinkType{get=GetLinkType;}
Mode                      CodeProperty   System.String Mode{get=Mode;}
Target                    CodeProperty   System.Collections.Generic.IEnumerable`1[[System.String, mscorlib, Version=4.0.0.0...
AppendText                Method         System.IO.StreamWriter AppendText()
CopyTo                    Method         System.IO.FileInfo CopyTo(string destFileName), System.IO.FileInfo CopyTo(string d...
Create                    Method         System.IO.FileStream Create()
CreateObjRef              Method         System.Runtime.Remoting.ObjRef CreateObjRef(type requestedType)
CreateText                Method         System.IO.StreamWriter CreateText()
Decrypt                   Method         void Decrypt()
Delete                    Method         void Delete()
Encrypt                   Method         void Encrypt()
Equals                    Method         bool Equals(System.Object obj)
GetAccessControl          Method         System.Security.AccessControl.FileSecurity GetAccessControl(), System.Security.Acc...
GetHashCode               Method         int GetHashCode()
GetLifetimeService        Method         System.Object GetLifetimeService()
GetObjectData             Method         void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Run...
GetType                   Method         type GetType()
InitializeLifetimeService Method         System.Object InitializeLifetimeService()
MoveTo                    Method         void MoveTo(string destFileName)
Open                      Method         System.IO.FileStream Open(System.IO.FileMode mode), System.IO.FileStream Open(Syst...
OpenRead                  Method         System.IO.FileStream OpenRead()
OpenText                  Method         System.IO.StreamReader OpenText()
OpenWrite                 Method         System.IO.FileStream OpenWrite()
Refresh                   Method         void Refresh()
Replace                   Method         System.IO.FileInfo Replace(string destinationFileName, string destinationBackupFil...
SetAccessControl          Method         void SetAccessControl(System.Security.AccessControl.FileSecurity fileSecurity)
ToString                  Method         string ToString()
PSChildName               NoteProperty   string PSChildName=special-file.txt
PSDrive                   NoteProperty   PSDriveInfo PSDrive=C
PSIsContainer             NoteProperty   bool PSIsContainer=False
PSParentPath              NoteProperty   string PSParentPath=Microsoft.PowerShell.Core\FileSystem::C:\Users\TestUser\...
PSPath                    NoteProperty   string PSPath=Microsoft.PowerShell.Core\FileSystem::C:\Users\TestUser\Deskto...
PSProvider                NoteProperty   ProviderInfo PSProvider=Microsoft.PowerShell.Core\FileSystem
Attributes                Property       System.IO.FileAttributes Attributes {get;set;}
CreationTime              Property       datetime CreationTime {get;set;}
CreationTimeUtc           Property       datetime CreationTimeUtc {get;set;}
Directory                 Property       System.IO.DirectoryInfo Directory {get;}
DirectoryName             Property       string DirectoryName {get;}
Exists                    Property       bool Exists {get;}
Extension                 Property       string Extension {get;}
FullName                  Property       string FullName {get;}
IsReadOnly                Property       bool IsReadOnly {get;set;}
LastAccessTime            Property       datetime LastAccessTime {get;set;}
LastAccessTimeUtc         Property       datetime LastAccessTimeUtc {get;set;}
LastWriteTime             Property       datetime LastWriteTime {get;set;}
LastWriteTimeUtc          Property       datetime LastWriteTimeUtc {get;set;}
Length                    Property       long Length {get;}
Name                      Property       string Name {get;}
BaseName                  ScriptProperty System.Object BaseName {get=if ($this.Extension.Length -gt 0){$this.Name.Remove($t...
VersionInfo               ScriptProperty System.Object VersionInfo {get=[System.Diagnostics.FileVersionInfo]::GetVersionInf...

The first thing you should notice is the TypeName: System.IO.FileInfo line. This tells you the class that this file comes from. So, any file on your system inherits its properties and methods from its parent class.

As you can you see, the System.IO.FileInfo class has the following attributes:

  • CodeProperty
  • Property
  • NoteProperty
  • ScriptProperty
  • Method

Let's try calling the CreationTime property from this object.

$file.CreationTime

Thursday, January 13, 2022 2:01:14 AM




Passing Objects Down the Pipeline

First, let me add some content to my test file.

foreach ($number in (1..10)) {
	"Line number $number" >> $file.FullName
}

Get-Content $file.FullName

Let's test passing objects down the pipeline.

$file | Get-Content

Line number 1
Line number 2
Line number 3
Line number 4
Line number 5
Line number 6
Line number 7
Line number 8
Line number 9
Line number 10

Why does that work? Because, the Get-Content cmdlet takes pipeline input and the pipeline input is expecting an object – specifically a file. This isn't a particularly impressive example of pipeline input and passing objects, but it is something that you should explore further.

NOTE: Not every cmdlet will accept pipeline input.





Selecting Object Properties

As you saw above, the System.IO.FileInfo class has a lot of properties that you can inspect. There are a couple of ways to select only certain properties that you wish to view.

With my special-file.txt file that is stored in the $file variable, I am particularly interested in viewing the Length, CreationTime, and LastAccessTime properties.

# One by one
$file.Length
304

$file.CreationTime
Thursday, January 13, 2022 2:01:14 AM

$file.LastAccessTime
Thursday, January 13, 2022 2:12:42 AM


# Together
$file | Select-Object Length, CreationTime, LastAccessTime

Length CreationTime         LastAccessTime
------ ------------         --------------
   304 1/13/2022 2:01:14 AM 1/13/2022 2:12:42 AM




Sorting Objects by Property

# Move to the Desktop folder
cd ~\Desktop

# Get all the files on the Desktop
$files = Get-ChildItem

# Sort files by name
$files | Sort-Object Name




Filtering Objects

Let's go back to the Person example from before using the custom class.

class Person {
    [String]$FirstName
    [String]$LastName
    [String]$EyeColor
    [Int]$Age
    [String[]]$Nicknames
    
    [String] Eat([String]$Food) {
    	return "That $Food was delicious!"
    }
    [String] Walk([Int]$Steps) {
    	return "I walked $Steps steps."
    }
    [Void] Sleep() {
    	Start-Sleep -Seconds 10
    }
}

$johnDoe = New-Object Person
$johnDoe.FirstName = 'John'
$johnDoe.LastName = 'Doe'
$johnDoe.EyeColor = 'Brown'
$johnDoe.Age = '33'
$johnDoe.Nicknames = @('johnny', 'jim', 'anonymous')

$janeDoe = New-Object Person
$janeDoe.FirstName = 'Jane'
$janeDoe.LastName = 'Doe'
$janeDoe.EyeColor = 'Brown'
$janeDoe.Age = '31'
$janeDoe.Nicknames = @('janet', 'anonymous')

$johnSmith = New-Object Person
$johnSmith.FirstName = 'John'
$johnSmith.LastName = 'Smith'
$johnSmith.EyeColor = 'Blue'
$johnSmith.Age = '26'
$johnSmith.Nicknames = @('JS', 'Bro')

$people = $johnDoe, $janeDoe, $johnSmith

So, now I have an array of people. Let's try filtering the objects using the Where-Object cmdlet.

# Select objects where the last name is Doe
$people | Where-Object {$_.LastName -eq 'Doe'}

FirstName : John
LastName  : Doe
EyeColor  : Brown
Age       : 33
Nicknames : {johnny, jim, anonymous}

FirstName : Jane
LastName  : Doe
EyeColor  : Brown
Age       : 31
Nicknames : {janet, anonymous}


# Select objects where the first name is John
$people | Where-Object {$_.FirstName -eq 'John'}

FirstName : John
LastName  : Doe
EyeColor  : Brown
Age       : 33
Nicknames : {johnny, jim, anonymous}

FirstName : John
LastName  : Smith
EyeColor  : Blue
Age       : 26
Nicknames : {JS, Bro}


# Select objects where the age is less than 30
$people | Where-Object {$_.Age -lt 30}

FirstName : John
LastName  : Smith
EyeColor  : Blue
Age       : 26
Nicknames : {JS, Bro}


# Select objects where the word 'Bro' occurs in the nicknames
$people | Where-Object {$_.Nicknames -contains 'Bro'}

FirstName : John
LastName  : Smith
EyeColor  : Blue
Age       : 26
Nicknames : {JS, Bro}




Final Project

Project 1: Filter files on your desktop by passing them down the pipeline

cd ~\Desktop

# Get files on the Desktop
$files = Get-ChildItem

# Get files older than a week
$files | Where-Object {$_.CreationTime -lt (Get-Date).AddDays(-7)} | Sort-Object CreationTime | Select-Object Name, CreationTime, LastAccessTime, Length


Project 2: Remove files by passing them down the pipeline

Create the files in a folder called Test Folder on your desktop

Set-Location ~\Desktop

# Create a folder on the Desktop
# Call it Test Folder
New-Item -ItemType Directory -Name 'Test Folder'

Set-Location 'Test Folder'

# Create some files
foreach ($number in (1..10)) {
	
    # Out-Null silences output from the cmdlet
    New-Item -ItemType File -Name "DeleteMe-$number" | Out-Null
    New-Item -ItemType File -Name "DontDeleteMe-$number" | Out-Null

}

Let's delete the files that start with DeleteMe.

# We have to wrap it in " "
# Double quotes because
# The name Test Folder
# Contains a space
cd "~\Desktop\Test Folder"

$files = Get-ChildItem
$deleteTheseFiles = $files | Where-Object {$_.Name -like 'DeleteMe-*'}
$deleteTheseFiles | Remove-Item -Confirm:$true





Functional Programming with PowerShell

Once you're comfortable with the concepts here, move on to the next post in this series and learn the fundamentals of functional programming with PowerShell.

Functional Programming in PowerShell
In this post, we will take a look at the concept of functional programming and how to write a basic function in PowerShell.

Spread the word

Keep reading