PowerShell Select-Object: Properties, -First, -Unique and More

Select-Object is the PowerShell cmdlet that picks properties from objects, or picks a number of objects from a list. The examples below use this list of employees:

$employees = @(
    [pscustomobject]@{ Name = 'Maria Garcia'; Department = 'Finance'; City = 'Austin'; Salary = 86000 }
    [pscustomobject]@{ Name = 'James Smith'; Department = 'IT'; City = 'Dallas'; Salary = 92000 }
    [pscustomobject]@{ Name = 'Emily Chen'; Department = 'IT'; City = 'Austin'; Salary = 98000 }
    [pscustomobject]@{ Name = 'Robert Brown'; Department = 'Sales'; City = 'Houston'; Salary = 74000 }
)

To keep only some properties, list them after -Property:

$employees | Select-Object -Property Name, City

Output:

Name         City
----         ----
Maria Garcia Austin
James Smith  Dallas
Emily Chen   Austin
Robert Brown Houston

I ran every example in real PowerShell 7.6 and Windows PowerShell 5.1 consoles. Where a parameter exists in only one version, I say so.

Select-Object creates new objects

Select-Object -Property doesn’t just hide columns. It builds new objects that have only the properties you asked for:

$row = $employees | Select-Object -First 1 -Property Name, City
"Type:       $($row.GetType().Name)"
"Properties: $($row.PSObject.Properties.Name -join ', ')"

Output:

Type:       PSCustomObject
Properties: Name, City

That’s usually what you want before Export-Csv or ConvertTo-Json, because the file then contains only those columns. It’s also why the selected objects no longer have the original object’s methods.

Select the first or last items

-First and -Last pick items from the start or end of the list, and -Skip skips some first:

"First 2:         $(($employees | Select-Object -First 2).Name -join ', ')"
"Last 1:          $(($employees | Select-Object -Last 1).Name)"
"Skip 1, then 2:  $(($employees | Select-Object -Skip 1 -First 2).Name -join ', ')"

Output:

First 2:         Maria Garcia, James Smith
Last 1:          Robert Brown
Skip 1, then 2:  James Smith, Emily Chen
PowerShell Select-Object -First, -Last and -Skip examples
-First, -Last and -Skip (PowerShell 7)

-First stops the pipeline early

With -First, PowerShell stops the command that produces the objects as soon as it has enough. -Last has to read everything:

$first = Measure-Command { 1..1000000 | Select-Object -First 3 }
$last  = Measure-Command { 1..1000000 | Select-Object -Last 3 }

'-First 3: {0,7:N0} ms' -f $first.TotalMilliseconds
'-Last 3:  {0,7:N0} ms' -f $last.TotalMilliseconds

Output in PowerShell 7:

-First 3:       4 ms
-Last 3:      886 ms

Windows PowerShell 5.1 showed the same pattern: 5 ms for -First and 1,353 ms for -Last. So Get-ChildItem -Recurse | Select-Object -First 1 returns quickly even on a huge drive.

Get unique values with -Unique

-Unique removes duplicates, but it’s case-sensitive. Sort-Object -Unique isn’t:

$cities = 'Austin', 'austin', 'Dallas', 'AUSTIN'

"Select-Object -Unique: $(($cities | Select-Object -Unique) -join ', ')"
"Sort-Object -Unique:   $(($cities | Sort-Object -Unique) -join ', ')"

Output in PowerShell 7:

Select-Object -Unique: Austin, austin, Dallas, AUSTIN
Sort-Object -Unique:   Austin, Dallas

Windows PowerShell 5.1 gave the same result, except that Sort-Object kept “AUSTIN”. Which spelling survives isn’t something to rely on.

PowerShell 7.4 added -CaseInsensitive to Select-Object, which keeps the first spelling it sees and the original order:

$cities = 'Austin', 'austin', 'Dallas', 'AUSTIN'
($cities | Select-Object -Unique -CaseInsensitive) -join ', '

Output in PowerShell 7:

Austin, Dallas

With objects, -Unique compares the selected properties, so you can list the distinct values of one column:

$employees | Select-Object -Property City -Unique

Output:

City
----
Austin
Dallas
Houston

How do I select only the value, without the property name?

-Property Name gives you objects that have a Name property. -ExpandProperty Name gives you the values themselves:

$withProperty = $employees | Select-Object -Property Name
$valueOnly    = $employees | Select-Object -ExpandProperty Name

"-Property:       $($withProperty[0])  ($($withProperty[0].GetType().Name))"
"-ExpandProperty: $($valueOnly[0])  ($($valueOnly[0].GetType().Name))"

Output:

-Property:       @{Name=Maria Garcia}  (PSCustomObject)
-ExpandProperty: Maria Garcia  (String)
PowerShell Select-Object -ExpandProperty returns the value only instead of an object
-Property vs -ExpandProperty (Windows PowerShell 5.1)

Use -ExpandProperty when you need plain strings, for example to join them or to pass them to a parameter like -ComputerName. If the property doesn’t exist, it tells you:

try {
    $employees | Select-Object -ExpandProperty FullName -ErrorAction Stop
}
catch {
    $_.Exception.Message
}

Output:

Property "FullName" cannot be found.

For a quick list, you can also use dot notation on the whole array. PowerShell collects the property from every item:

$employees.Name -join ', '

Output:

Maria Garcia, James Smith, Emily Chen, Robert Brown

Select-Object without the header

Select-Object never adds a header; the header comes from how the objects are displayed. For a single column, use -ExpandProperty as above. For CSV text without the header row, skip the first line:

$employees | Select-Object -Property Name, City | ConvertTo-Csv -NoTypeInformation | Select-Object -Skip 1

Output:

"Maria Garcia","Austin"
"James Smith","Dallas"
"Emily Chen","Austin"
"Robert Brown","Houston"

PowerShell 7.4 and later have a switch for this, and it gives the same result:

$employees | Select-Object -Property Name, City | ConvertTo-Csv -NoHeader

Output in PowerShell 7:

"Maria Garcia","Austin"
"James Smith","Dallas"
"Emily Chen","Austin"
"Robert Brown","Houston"

For a table on screen without headers, see PowerShell Format-Table, which has -HideTableHeaders.

Add calculated properties

A hashtable with Name and Expression adds a column that you compute, such as a formatted salary and a monthly amount:

$employees | Select-Object -Property Name,
    @{ Name = 'Salary'; Expression = { '{0:C0}' -f $_.Salary } },
    @{ Name = 'Monthly'; Expression = { '{0:C0}' -f ($_.Salary / 12) } }

Output:

Name         Salary  Monthly
----         ------  -------
Maria Garcia $86,000 $7,167
James Smith  $92,000 $7,667
Emily Chen   $98,000 $8,167
Robert Brown $74,000 $6,167
PowerShell Select-Object calculated properties with Name and Expression
Calculated properties (PowerShell 7)

Inside the expression, $_ is the current object. The result is a real property, so you can sort or export it like any other.

Exclude properties

To keep everything except a few properties, combine -Property * with -ExcludeProperty:

$employees | Select-Object -Property * -ExcludeProperty Salary, Department

Output:

Name         City
----         ----
Maria Garcia Austin
James Smith  Dallas
Emily Chen   Austin
Robert Brown Houston

PowerShell 6 and later don’t need the -Property * part. In Windows PowerShell 5.1, leaving it out makes -ExcludeProperty do nothing, and I got every column back, salary included.

So keep -Property * if a script might run in 5.1. The Select-Object documentation lists the rest, including -Index and -SkipLast.

Frequently Asked Questions

What does Select-Object do in PowerShell?

It picks properties from objects, like Select-Object -Property Name, City, or picks items from a list, like Select-Object -First 5.

How do I get only the value with Select-Object?

Use -ExpandProperty: Get-Process | Select-Object -ExpandProperty Name returns plain strings instead of objects with a Name property.

Is Select-Object -Unique case-sensitive?

Yes. “Austin” and “austin” are both kept. Add -CaseInsensitive in PowerShell 7.4 and later, or use Sort-Object -Unique.

How do I remove the header from Select-Object output?

Use -ExpandProperty for one column. For CSV text, add | Select-Object -Skip 1 after ConvertTo-Csv, or use ConvertTo-Csv -NoHeader in PowerShell 7.4+.

What’s the difference between -First and -Last?

-First takes items from the start and stops the pipeline early, so it’s fast. -Last takes items from the end, so it has to read the whole list.

Next, pair Select-Object with these: