# Objects of Your Own: \`\[PSCustomObject\]\`

\[PSCustomObject\]@{ } builds an object of your own with named properties, so a tool you write hands back results that Sort-Object, Where-Object and Export-Csv can work with, the way they work with PowerShell's own commands, instead of lines of text.

Journeyman · PowerShell · Updated November 10, 2026

## After this lesson you can

- Build an object of your own with \[PSCustomObject\]@{ } and give each property a name and a value
- Hand back objects instead of text from a function, so its results can be sorted, filtered and exported

## Key takeaways

- A line of text has one property, its Length, so Sort-Object, Where-Object and Export-Csv can't get at what's inside it.
- `[PSCustomObject]@{ Name = 'Banana Bread'; Minutes = 70 }` builds an object with the properties you name, in the order you write them.
- A number written without quotes stays a number, so it sorts and compares as a number.
- A function hands back objects and leaves the formatting to whoever runs it: Format-Table inside the function breaks sorting and saving.

Your family's recipes live on index cards, in three notebooks and in a dozen browser tabs, so you start a recipe box in PowerShell: a few commands that keep every recipe in one place. The first version prints a neat line for each recipe, and it looks done. Then someone asks the first real question: "What can I make in 25 minutes?" The neat lines can't answer it.

This chapter is where the course's big idea pays off. A script is an application, and now yours becomes a real tool, with objects of its own, data it remembers, friendly output and even a window. It starts with the objects.

## Text looks right, until you ask a question

Make a practice folder, then type this first version of the recipe box at the prompt (or paste it):

```powershell
New-Item -ItemType Directory -Force -Path "$HOME\terminal-practice\recipe-box" | Out-Null
Set-Location "$HOME\terminal-practice\recipe-box"
function Get-Recipe {
    'Lemon Garlic Pasta - 30 min - Dinner'
    'Overnight Oats - 10 min - Breakfast'
    'Banana Bread - 70 min - Baking'
    'Black Bean Tacos - 25 min - Dinner'
}
```

`Get-Recipe` prints the four lines. Now sort them by how long they take, and save them for a spreadsheet:

```powershell
Get-Recipe | Sort-Object Minutes
Get-Recipe | Export-Csv recipes.csv
Get-Content recipes.csv
```

```text
Lemon Garlic Pasta - 30 min - Dinner
Overnight Oats - 10 min - Breakfast
Banana Bread - 70 min - Baking
Black Bean Tacos - 25 min - Dinner
"Length"
"36"
"35"
"30"
"34"
```

The sort did nothing, without a word of complaint, and the spreadsheet holds only numbers. A line of text is one thing to PowerShell: a string, whose only property is its `Length`. The minutes are in there for you to read, but no command can reach them.

## A card with labeled lines: \`\[PSCustomObject\]\`

A good index card has labeled lines: *Name:*, *Minutes:*, *Category:*. Anyone can find the minutes without reading the whole card. PowerShell's version of that card is a **custom object**, an object you design yourself, and you build one from a hashtable:

```powershell
[PSCustomObject]@{ Name = 'Lemon Garlic Pasta'; Minutes = 30; Category = 'Dinner' }
```

```text
Name               Minutes Category
----               ------- --------
Lemon Garlic Pasta      30 Dinner
```

Read it from the right. `@{ }` is a lookup of names and values, from *Lists and Lookups: Arrays and Hashtables*: `=` joins each name to its value and `;` separates the pairs. `[PSCustomObject]` in front (read "as a custom object") turns that lookup into one object, with a **property** for each name. It keeps the properties in the order you wrote them, so the columns come out Name, Minutes, Category. A plain hashtable shows a Name and Value row for each pair instead, in an order of its own choosing.

`30` has no quotes, so it stays a number. `'30'` would be text, and text sorts by its first character, so `'100'` would come before `'30'`.

## Your tool's results work like PowerShell's own

Now give `Get-Recipe` a card for each recipe. Defining it again replaces the first version:

```powershell
function Get-Recipe {
    [PSCustomObject]@{ Name = 'Lemon Garlic Pasta'; Minutes = 30; Category = 'Dinner' }
    [PSCustomObject]@{ Name = 'Overnight Oats'; Minutes = 10; Category = 'Breakfast' }
    [PSCustomObject]@{ Name = 'Banana Bread'; Minutes = 70; Category = 'Baking' }
    [PSCustomObject]@{ Name = 'Black Bean Tacos'; Minutes = 25; Category = 'Dinner' }
}
Get-Recipe | Where-Object Minutes -le 25
```

```text
Name             Minutes Category
----             ------- --------
Overnight Oats        10 Breakfast
Black Bean Tacos      25 Dinner
```

There's the answer to "what can I make in 25 minutes?". Every command from Module 3 works now: `Sort-Object Minutes` puts the oats first and the bread last, `Group-Object Category` counts two dinners, and `Get-Member` shows the cards for what they are:

```powershell
Get-Recipe | Get-Member -MemberType NoteProperty
```

```text
   TypeName: System.Management.Automation.PSCustomObject

Name     MemberType   Definition
----     ----------   ----------
Category NoteProperty string Category=Dinner
Minutes  NoteProperty int Minutes=30
Name     NoteProperty string Name=Lemon Garlic Pasta
```

A `NoteProperty` is a property you added yourself, and `Minutes` is an `int`, a whole number. Run `Get-Recipe | Export-Csv recipes.csv` again, and `recipes.csv` gets a header of `"Name","Minutes","Category"` and one line per recipe, ready for Excel.

That's the difference between a script that prints and a tool. The person who runs your command decides what happens next, the same way they do with `Get-ChildItem` or `Get-Process`.

## Leave the formatting to the end

It's tempting to make the function's output look tidy with `Format-Table` on its last line. Don't. Format-Table turns the objects into instructions for drawing a table, and nothing after it can read them. Say the function collects the four cards in `$recipes` and ends with `$recipes | Format-Table`. The table looks the same, but `Get-Recipe | Where-Object Minutes -le 25` shows all four recipes, and a saved spreadsheet is nonsense:

```powershell
Get-Recipe | Export-Csv recipes.csv
Get-Content recipes.csv -TotalCount 3
```

```text
"ClassId2e4f51ef21dd47e99d3c952918aff9cd","pageHeaderEntry","pageFooterEntry","autosizeInfo","shapeInfo","groupingEntry"
"033ecb2bc07a4d43b5ef94ed5a35d280",,,,"Microsoft.PowerShell.Commands.Internal.Format.TableHeaderInfo",
"9e210fe47d09416682b841769c78b8a3",,,,,
```

The same goes for `Write-Host` and for building a sentence around each recipe. A function's job ends at the object; `Format-Table`, `Sort-Object` and `Export-Csv` belong to whoever runs it. `[PSCustomObject]` works the same in Windows PowerShell 5.1 and on a Mac or Linux.

![Two ways to hand back a recipe. On the left, a strip of paper reading Lemon Garlic Pasta - 30 min - Dinner, and Sort-Object, Where-Object and Export-Csv each shrug at it. On the right, an index card with three labeled lines, Name: Lemon Garlic Pasta, Minutes: 30, Category: Dinner, and the same three commands each reading the line they need](https://fsdtrainingmedia.blob.core.windows.net/public/images/2026/11/516f2f975e5041d984005162436137f5.webp)

## Check yourself

2 questions to try on the lesson page.

## So what

Objects in, objects out: give your tool's results names and values with `[PSCustomObject]`, and everything PowerShell can do with its own commands' output, it can now do with yours.

## Key terms

- [PSCustomObject](https://www.ask-codey.com/glossary/pscustomobject)
- [Hashtable](https://www.ask-codey.com/glossary/hashtable)
- [Object Pipeline](https://www.ask-codey.com/glossary/object-pipeline)

## Cheat sheets

- [Objects of Your Own: \`\[PSCustomObject\]\`](https://www.ask-codey.com/cheat-sheets/powershell-custom-objects-cheat-sheet)

## Related

- [Lists and Lookups: Arrays and Hashtables](https://www.ask-codey.com/lessons/powershell-arrays-and-hashtables)
- [What an Object Is: Get-Member](https://www.ask-codey.com/lessons/powershell-what-is-an-object)
- [Saving Data: Export-Csv and Cleaning a CSV](https://www.ask-codey.com/lessons/powershell-export-csv)
- [Functions: Naming a Job](https://www.ask-codey.com/lessons/powershell-functions)

Source: https://www.ask-codey.com/lessons/powershell-custom-objects
