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?
answer
- projection, not narrowing
- a new object is constructed
- methods belong to the old type
- Get-Member shows a Selected. prefix
- -First alone keeps the original objects
basics
~20 sSelect-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 sWhen 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# 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.Processgo deeper
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.
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.
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.
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