Enough PowerShell to Be Useful: Six Commands and One Idea

Learning PowerShell is unnecessary to use PowerShell. PowerShell scripts for SharePoint administration involve six commands and an idea, and once you understand these, the rest is just referencing what you need along the way.

Here are the six, demonstrated on your actual data: list items with Title, Status, Budget, and Owner. All of this was executed on PowerShell 7.

The one idea: the pipeline moves objects, not text

This is the reason why PowerShell is special from all the other shells out there and this is also why the six commands have come to be.
By using the symbol ‘|’, you don’t just pass on text. No! In fact, what you do is you pass objects, which come with properties.

$items | Get-Member -MemberType NoteProperty
Budget
Owner
Status
Title

Get-Member is how one finds out what an object contains. If at some point during running a script an unknown object is returned, pipe it into Get-Member to learn about its properties and methods. It is the most powerful cmdlet when it comes to PowerShell.

1. Where-Object: keep the rows you want

$items | Where-Object { $_.Status -eq 'Complete' }
Project 01
Project 04

$_ is “the current object”. Inside the braces it means the one item Where-Object is looking at right now.

Since PowerShell 3 there is a shorter form for simple checks:

$items | Where-Object Status -eq 'Complete'

Same result, no braces, no $_. Use the short form for one condition and the brace form when you need more than one.

The operators are words, not symbols

This trips up everyone coming from another language:

-eq  -ne  -gt  -lt  -ge  -le      not  ==  !=  >  <

The reason is that > already means “redirect output to a file” in a shell. So PowerShell spells its comparisons out. Type $a > $b expecting a comparison and you will silently create a file called $b.

Two more you will use constantly:

$items | Where-Object { $_.Title -like 'Project*' }    # wildcard
$items | Where-Object { $_.Title -match 'Q[0-9]' }     # regex

-like takes wildcards (* and ?). -match takes a regular expression. Reach for -like first; it is simpler and it is usually enough.

And one that causes real bugs, -eq is case-insensitive by default:

'Complete' -eq 'complete'    # True
'Complete' -ceq 'complete'   # False - the c prefix makes it case-sensitive

Usually the case-insensitive default is what you want. Just know it is the default, so a comparison you expected to fail on case will quietly pass.

2. Select-Object: choose columns, or take the first few

$items | Select-Object Title, Budget -First 3
Title      Budget
-----      ------
Project 01   1000
Project 02   2000
Project 03   3000

Two jobs in one command: pick which properties you want, and -First / -Last to take a slice.

The one worth knowing separately is -ExpandProperty. Plain Select gives you objects with one property. ExpandProperty gives you the raw values:

$items | Select-Object -ExpandProperty Owner -Unique
alice@contoso.com, bob@contoso.com, carol@contoso.com

When you want a plain list of email addresses to loop over rather than a table to look at, that is the one.

3. Sort-Object

$items | Sort-Object Budget -Descending | Select-Object -First 2 Title, Budget
Title      Budget
-----      ------
Project 05   5000
Project 04   4000

Sort, then take the first two, and you have “the two biggest”, which is most of what “top N” reporting ever is.

4. Measure-Object: the sums you would otherwise export to Excel for

$items | Measure-Object Budget -Sum -Average -Maximum
Count   : 5
Sum     : 15000
Average : 3000
Maximum : 5000

Count on its own is the everyday one. “How many items match” is just (... | Measure-Object).Count, or on most objects simply .Count.

5. Group-Object: counts per category, in one line

$items | Group-Object Status | Select-Object Count, Name
Count Name
----- ----
    1 Blocked
    2 Complete
    2 In Progress

This is a pivot table in one command. “How many sites per template”, “how many items per status”, “how many files per owner”, all Group-Object. It is the report people are most surprised PowerShell writes for free.

6. ForEach-Object: do a thing to each one

$items | ForEach-Object { "$($_.Title) = $($_.Budget)" }

Same $_ as Where-Object, but instead of keeping or dropping the item you do something with it. This is where the actual work happens, updating each item or building each line of a report.

ForEach-Object is not foreach

There are two, they look similar, and the difference matters:

$items | ForEach-Object { ... }     # a cmdlet, works IN a pipeline, uses $_

foreach ($item in $items) { ... }   # a statement, its own variable, NOT pipeable

ForEach-Object works in the pipeline while foreach is a language keyword and therefore can’t be piped to. Put a pipe right after the closing curly bracket of foreach and the error message will be “An empty pipe element is not allowed,” and nothing more.

Use ForEach-Object in the pipeline, use foreach when you are already writing some scripting. In large lists foreach is faster, but only noticeable from ten thousand objects and higher.

.

One trap that is worth ten minutes now

When a filter returns exactly one item, older PowerShell gave you that single object, not a collection of one, so .Count was $null instead of 1:

$one = $items | Where-Object { $_.Status -eq 'Blocked' }
$one.Count        # 1 in PowerShell 7, was $null in 5.1

PowerShell 7 fixed this, but plenty of tenants still run 5.1, and a script that checks if ($result.Count -gt 0) will silently do the wrong thing there. Wrap anything you are going to count:

$one = @($items | Where-Object { $_.Status -eq 'Blocked' })
$one.Count        # 1 - reliable on every version

The @() forces an array even around a single result. It is the cheapest habit in PowerShell and it saves a specific, maddening bug.

That is the toolkit

Use Where-Object to filter, use Select-Object to select, use Sort-Object to sort, use Measure-Object to total, use Group-Object to pivot, use ForEach-Object to do something about it. Six cmdlets, one pipeline, and Get-Member to show you what you have when you’re not sure.

Almost everything I post about SharePoint PowerShell uses those six against actual data. Learn them on objects I make up, where nothing can possibly go wrong, and the SharePoint flavor is just that but with a list in front.

Related posts

Similar Posts