skip to content

In PowerShell, `$p = Get-Process -Id $PID | Select-Object Name, CPU` and then `$p.Kill()` fails with a method-not-found error, although `Get-Process -Id $PID` alone supports `.Kill()`. What did `Select-Object` do to the object?

level: middleimportance: nice to knowfreq 32%

answer

  1. projection, not narrowing
  2. a new object is constructed
  3. methods belong to the old type
  4. Get-Member shows a Selected. prefix
  5. -First alone keeps the original objects

basics

~20 s

Select-Object with a property list does not trim the original object — it builds a brand-new PSCustomObject carrying only the named properties as plain values. The original .NET type, and therefore every method on it such as Kill(), is gone.

solid answer

~50 s

When you pass properties to `Select-Object`, it projects rather than filters: each input object becomes a new `PSCustomObject` whose members are the requested properties copied across as note properties. Methods do not come along, because a method belongs to the original type and the new object is not that type. `Get-Member` makes it visible — the type name becomes `Selected.System.Diagnostics.Process` instead of `System.Diagnostics.Process`. This bites beyond method calls: cmdlets that bind pipeline input by property name can stop working because a property they needed, such as `PSPath`, was not in your select list. The fixes are to keep the real objects until the last moment and select only for display, use `-ExpandProperty` when you want one property's value with its own type intact, or call `ForEach-Object` on the original objects. Note that `Select-Object -First 5` with no property list passes the originals through untouched — only property projection re-wraps them.

code

powershell · 9 lines
powershell
# Property projection replaces the type
(Get-Process -Id $PID | Select-Object Name, CPU).GetType().Name        # PSCustomObject
Get-Process -Id $PID | Select-Object Name, CPU | Get-Member            # Selected.System.Diagnostics.Process

# -ExpandProperty keeps the value's own type
(Get-Process -Id $PID | Select-Object -ExpandProperty Name).GetType().Name  # String

# Stream-position parameters alone pass originals through
Get-Process | Select-Object -First 3 | Get-Member                       # System.Diagnostics.Process

go deeper

for a junior

Know that Select-Object with property names produces a new, simpler object, so methods from the original type are no longer available. Use -ExpandProperty when you want the bare value.

for a middle

Explain the projection mechanism and prove it with Get-Member showing the Selected. type prefix, and note that -First on its own passes the original objects through unchanged.

for a senior

Recognise the downstream symptom: a cmdlet that suddenly cannot bind pipeline input because a property such as PSPath was projected away. Order pipelines so shaping happens last.

for a principal

Treat object shape as an interface between pipeline stages, and set the convention that projection is an output step — code that hands PSCustomObjects to other code has silently dropped its contract.

## Projection, not filtering `Select-Object` looks like it narrows an object. It does not. Given a `-Property` list it constructs a **new** object per input item — a `System.Management.Automation.PSCustomObject` — and copies the requested property values onto it as note properties. The original instance is not modified and is not passed along; it is the source for a copy. That single fact explains every symptom: ```powershell Get-Process -Id $PID | Get-Member | Select-Object -First 1 TypeName # System.Diagnostics.Process -> has Kill(), WaitForExit(), Refresh(), ... Get-Process -Id $PID | Select-Object Name, CPU | Get-Member # TypeName: Selected.System.Diagnostics.Process # Only the two note properties. No methods. ``` The `Selected.` prefix is PowerShell recording where the projection came from — useful for formatting and for your own diagnosis — but it is not the original type, and `.Kill()` is not there to call. The error reads roughly "method invocation failed because ... PSCustomObject does not contain a method named 'Kill'". ## The subtler failure: pipeline binding Method calls at least fail loudly. The quieter version involves cmdlets that bind their parameters from properties of the incoming object. Many file cmdlets bind on `PSPath` or `FullName`; process cmdlets bind on `Id` or `Name`. Project away the property a downstream cmdlet needed and the binding fails — sometimes with a parameter-binding error, sometimes by prompting for the missing parameter: ```powershell # Fine: FileInfo objects still carry everything Remove-Item binds to Get-ChildItem .\tmp -Filter *.log | Remove-Item # Broken: the projected object no longer carries the path properties Get-ChildItem .\tmp -Filter *.log | Select-Object Name, Length | Remove-Item ``` The lesson is ordering: **select last**. Keep the rich objects flowing while cmdlets still need to bind to them, and project only when you are producing output for a human or a serialiser. ## -ExpandProperty is a different operation `-Property` wraps; `-ExpandProperty` unwraps. With `-ExpandProperty` you get the property's **value**, with its own type preserved: ```powershell (Get-Process -Id $PID | Select-Object -Property Name).GetType().Name # PSCustomObject (Get-Process -Id $PID | Select-Object -ExpandProperty Name).GetType().Name # String ``` So when you want a plain list of names to feed somewhere else, `-ExpandProperty Name` is the right tool, and it keeps the string as a string rather than a one-property wrapper that renders confusingly. ## Calculated properties Projection is also where you reshape values, using a hashtable with `Name` (or `Label`) and `Expression`: ```powershell Get-Process | Select-Object Name, @{ Name = 'MemMB'; Expression = { [math]::Round($_.WorkingSet64 / 1MB, 1) } } | Sort-Object MemMB -Descending | Select-Object -First 10 ``` The result is still a `PSCustomObject`, so the same rules apply — but note that sorting on the calculated property works fine, because `Sort-Object` reads properties and does not need the original type. Property-based cmdlets keep working; type- and method-based ones do not. ## Selecting without projecting A point candidates frequently miss: `Select-Object` only re-wraps when you ask for properties. Used purely for its stream-position parameters — `-First`, `-Last`, `-Skip`, `-SkipLast`, `-Unique`, `-Index` — it passes the **original objects** through untouched: ```powershell Get-Process | Select-Object -First 3 | Get-Member # still System.Diagnostics.Process ``` So `Get-Process | Select-Object -First 1 | Stop-Process` is fine, while `Get-Process | Select-Object Name -First 1 | Stop-Process` has thrown the type away. Note also that `Select-Object *` still produces a `PSCustomObject`: copying every property is still copying, and the methods still do not come along. ## How to work with it - Do the real work on the real objects; project at the end. - Use `-ExpandProperty` when you want values, not wrappers. - Use `ForEach-Object { $_.Kill() }` (or the appropriate cmdlet) when you need behaviour rather than data. - When something downstream suddenly cannot bind, run `Get-Member` at that point in the pipeline — the `Selected.` prefix in the type name usually names the culprit immediately.

  • How do you get a plain list of names rather than a list of one-property objects?
    Use `Select-Object -ExpandProperty Name`, which emits the property's value with its own type — strings stay strings. `Select-Object Name` instead emits a PSCustomObject per item that happens to have a Name property, which renders with a header and misbehaves when passed to something expecting bare strings. `ForEach-Object Name` is an equivalent shorthand.
  • Does `Select-Object -First 5` also strip the object's type?
    No. With no property list, Select-Object is only choosing which items pass, so the original objects flow through unchanged — `Get-Process | Select-Object -First 5 | Get-Member` still reports System.Diagnostics.Process. Only property projection, including `-Property *`, constructs new PSCustomObjects.
  • You projected some properties and now a downstream cmdlet cannot bind its parameters. How do you diagnose it?
    Insert `Get-Member` at that stage. A type name beginning `Selected.` tells you a projection happened, and the member list shows which properties survived. Compare that with the downstream cmdlet's pipeline-binding parameters in `Get-Help -Full`: the missing one — often PSPath, FullName or Id — is the property you dropped.

saying these in an interview costs you the question

  • Thinks Select-Object hides properties on the same object
  • Expects methods to survive property selection
  • Believes Select-Object * preserves the original type
  • Uses Select-Object Name where -ExpandProperty is meant
  • Projects early and then wonders why binding broke

context