Practical PowerShell Command Recipes — Growing the Small Tools You Use Every Day

· Updated: · · PowerShell, Windows, Command Line, Automation, Business Efficiency, Legacy Asset Reuse

Revision history (1 updates, last updated Sep 1, 2026)

A log of the changes made to this article. Where a pre-update version was archived, it stays readable at a permanent DOI link.

Retranslated as a full translation of the Japanese original. The previous English version was an abridgement that carried only part of the source, so sections, tables, Mermaid diagrams, figure captions and FAQ entries were missing. All of them have been restored to match the Japanese original, and the technical claims are the same as in the Japanese version. Read the version before this update (DOI: 10.5281/zenodo.21614645)
First published
Cite this article(DOI: 10.5281/zenodo.21614644)

This article is archived on Zenodo. Below are both the DOI that always resolves to the latest version and the DOI pinned to the version you are reading.

Go Komura (2026). Practical PowerShell Command Recipes — Growing the Small Tools You Use Every Day. KomuraSoft LLC. https://doi.org/10.5281/zenodo.21614644 https://comcomponent.com/en/blog/2026/06/02/002-powershell-practical-command-recipes/

DOI (latest version)
10.5281/zenodo.21614644
DOI (this version)
10.5281/zenodo.22220497

1. What to Grasp First

Once you have learned the basics of PowerShell, the next step is to grow your set of “small, frequently used tools” — that is what makes it practical in real work.

By small tools, we do not mean short aliases. In this article, instead of abbreviations like ls and cat, we use the full command names such as Get-ChildItem, Get-Content, and Select-String.

On that basis, we will make the following kinds of work doable in one line or a few lines:

Count items
Check total size
Remove duplicates
Aggregate by category
Extract only the values you need
Compare two sets of results
See the lines around a log hit
Send results to the clipboard
Save the output while also showing it on screen
Check environment variables and the network

PowerShell is useful even without writing long scripts. Start by growing your set of “frequently used building blocks” so you can gradually combine them in everyday work.

Rather than memorizing commands, what matters is being able to recall “in this situation, that building block applies.”

The code in this article is published on GitHub as a runnable sample set organized theme by theme following the chapter structure (recipe scripts and Pester tests).

powershell-practical-command-recipes - komurasoft-blog-samples (GitHub)

How This Differs From the Previous Article

This article continues from PowerShell Command Basics — The Operations to Learn First and How to Use Them Safely. The two divide the work as follows.

  PowerShell Command Basics This article
Aim Get comfortable working in PowerShell Count, summarize, compare, and keep the results you pulled out
Focus Syntax and the pipeline, finding commands Building blocks for aggregation, comparison, extraction, and output
CSV / JSON The basics of reading and writing Filtering and aggregating what you read in
Processes / services / event logs The basics of retrieving a listing Counting a retrieved listing by category and diffing it
Change commands The safe execution order itself The investigation and audit-trail commands you slot into that order

In short, the previous article took you as far as “being able to pull values out,” and this one is about what to do with the values once you have them.

The basics of retrieval commands such as Get-ChildItem, Get-Content, Import-Csv, and Get-Service, along with the execution policy, how to write a .ps1, and error handling, are all in the previous article. This one assumes them and moves on. A few topics appear in both — using the Format commands last, or Export-Csv -Encoding UTF8 — but here they are covered from the angle of “what to do when you carry the data into aggregation or comparison.”

In the diagram a solid line marks a relation that always holds and a dashed line marks a conditional one (the conditions are given per relation on the detail page). The full list of relations (26 in total, with evidence and certainty) and the definitions of the main concepts are collected on the knowledge map detail page (in Japanese). Data: JSON-LD / Turtle

2. The Approach This Time

The premises are these three:

  1. Write commands with their full names
  2. Center the work on read-only commands
  3. Always pair change commands with target verification and -WhatIf

For example, rather than learning the file deletion command straight away, it is safer to first internalize this flow:

Look at the targets
  -> filter by condition
  -> count them
  -> save to CSV if needed
  -> confirm the planned changes
  -> execute

This article focuses on investigation, aggregation, comparison, and output rather than strong operations like deletion and stopping.

3. A Map of the Frequently Used Commands

The main commands we will use this time. The right-hand column gives the chapter that covers each one in detail.

What you want to do Command Chapter
Check counts and totals Measure-Object 4, 5
Extract only the values you need Select-Object -ExpandProperty 6
Clean up duplicates Sort-Object -Unique 7
Aggregate by category Group-Object 8
Output only the columns you need, add computed columns Select-Object 9
Sort Sort-Object 10
Search strings Select-String 12
Compare two sets of results Compare-Object 14
Compute hash values Get-FileHash 15
Use the clipboard Set-Clipboard / Get-Clipboard 16
Output to both screen and file Tee-Object 17
Keep a work log Start-Transcript 18
Check network reachability Test-Connection / Test-NetConnection 26
Measure processing time Measure-Command 28
See error details Get-Error 31
Process each element ForEach-Object Throughout

There is no need to learn all of them at once. We recommend learning them paired with a goal: “to count, Measure-Object,” “to group, Group-Object,” “to compare, Compare-Object.”

A Map of the Chapters by Purpose

Chapters 4 through 36 fall into the following clusters. Read whichever one you need.

Category Chapters What it covers
Aggregation and shaping 4-11 Counts, totals, maximums and averages, extracting values, deduplication, aggregation by category, computed columns, sorting, where the Format commands belong
Text and log investigation 12-14 String search with surrounding lines, replacement, comparing two listings
Inside files and data 15, 19, 20 Checking hash values, filtering CSV, JSON configuration values
Output and audit trails 16-18 Sending to the clipboard, writing to screen and file at once, work logs
System investigation 21-25, 31 Environment variables, profiles, processes, services, event logs, error details
Network 26, 27 Reachability checks, web responses
How to run things 28-30 Measuring execution time, background execution, command history
Syntax reference 32 Comparison operators
Recipes you can use as-is 33, 34 Eleven practical recipes, plus what to watch for when combining them with change commands
Quick reference 35 A cheat sheet for recalling commands from a goal

If you are in the state of “I know what I want to look into, but I cannot remember which command,” the fastest route in is the quick reference in chapter 35.

4. Counting — Measure-Object

The first frequent operation is counting.

Count the files in the current folder.

Get-ChildItem . -File | Measure-Object

To include subfolders:

Get-ChildItem . -File -Recurse | Measure-Object

The Count in the Measure-Object result is the number of items.

To see the total file size, aggregate Length.

Get-ChildItem . -File -Recurse |
  Measure-Object -Property Length -Sum

As is, however, this is in bytes and a bit hard for humans to read, so convert it to GB.

$size = Get-ChildItem . -File -Recurse |
  Measure-Object -Property Length -Sum

[math]::Round($size.Sum / 1GB, 2)

This is handy when you want a rough view of the total size of a folder.

5. Checking Maximum and Average Too

Measure-Object can produce not just sums but also maximums and averages.

Get-ChildItem . -File |
  Measure-Object -Property Length -Sum -Average -Maximum -Minimum

This is useful for seeing the spread of file sizes.

You can also sum the cumulative CPU time of processes. The CPU from Get-Process is not a utilization percentage but the number of seconds of CPU time used since the process started.

Get-Process |
  Measure-Object -Property CPU -Sum

Note, however, that this value is cumulative rather than an instantaneous CPU utilization, so it varies with the timing of the run and how long the processes have been up.

It is sufficient for investigative purposes, but for rigorous monitoring you also need dedicated monitoring tools and log design.

6. Extracting Just the Values — Select-Object -ExpandProperty

Select-Object is commonly used to choose columns.

Get-Process |
  Select-Object Name, Id, CPU

This outputs “objects with the columns Name, Id, and CPU.”

When you want just the values, on the other hand, use -ExpandProperty.

Get-Process -Name notepad -ErrorAction SilentlyContinue |
  Select-Object -ExpandProperty Id

The same applies when extracting just file names.

Get-ChildItem . -File |
  Select-Object -ExpandProperty Name

When you want to pass a command’s results to another command or a text file, -ExpandProperty is convenient.

For example, save just the list of file names.

Get-ChildItem . -File |
  Select-Object -ExpandProperty Name |
  Set-Content .\file-names.txt -Encoding UTF8

Select-Object Name and Select-Object -ExpandProperty Name look similar, but the shape of the result differs.

Select-Object Name
  -> a table with a Name column

Select-Object -ExpandProperty Name
  -> the Name values themselves

If you want CSV output, keeping it as a column is better; if you want to feed it into further string processing, the bare values are easier to handle.

7. Removing Duplicates — Sort-Object -Unique

Say you want a list of just the file extensions.

Get-ChildItem . -File |
  Select-Object -ExpandProperty Extension |
  Sort-Object -Unique

Running this shows only the deduplicated extensions, such as .txt, .log, and .csv.

You can also see just the distinct process names.

Get-Process |
  Select-Object -ExpandProperty ProcessName |
  Sort-Object -Unique

An example of extracting just the list of department names from a user list CSV:

Import-Csv .\users.csv |
  Select-Object -ExpandProperty Department |
  Sort-Object -Unique

When “the list is cluttered with lots of identical values,” remembering Sort-Object -Unique first is handy.

8. Aggregating by Category — Group-Object

When you want not just deduplication but “how many of each,” use Group-Object.

Count files per extension.

Get-ChildItem . -File |
  Group-Object -Property Extension -NoElement |
  Sort-Object -Property Count -Descending

The output takes this shape. The counts and extensions depend on what is in the folder, but the column layout is the same.

Count Name
----- ----
   42 .log
   18 .csv
    7 .txt
    3

The row with an empty Name is for the files that have no extension.

Drop -NoElement and a Group column appears, showing the actual elements that fell into each group.

Count Name  Group
----- ----  -----
   42 .log  {app-20260601.log, app-20260602.log, app-20260603.log...}
   18 .csv  {export-01.csv, export-02.csv, export-03.csv...}

When you only want the counts, the Group column fills the screen, so add -NoElement. Conversely, leave it off when you want to see what ended up in each group.

Count by service status.

Get-Service |
  Group-Object -Property Status -NoElement

Count by event log level.

Get-WinEvent -LogName System -MaxEvents 500 |
  Group-Object -Property LevelDisplayName -NoElement

Group-Object is well suited to grasping the big picture at the start of an investigation.

Which extensions are most common
Which service statuses are most common
Which error levels are most common
Which department has the most data

Use it when you want to see the distribution before drilling into details.

9. Creating the Columns You Need — Adding Computed Columns

File size is available via Length, but the unit is bytes. Looking at byte counts every time is painful, so it is convenient to create columns converted to MB or KB.

Get-ChildItem . -File |
  Select-Object Name, @{Name="SizeKB"; Expression={ [math]::Round($_.Length / 1KB, 1) }}, LastWriteTime

An example that includes subfolders and lists the largest files first:

Get-ChildItem . -File -Recurse |
  Sort-Object -Property Length -Descending |
  Select-Object -First 20 FullName, @{Name="SizeMB"; Expression={ [math]::Round($_.Length / 1MB, 2) }}, LastWriteTime

@{Name="..."; Expression={ ... }} is the syntax for creating a computed column. It looks a little long at first, but it is used constantly in real work.

For example, you can display process memory usage in MB.

Get-Process |
  Sort-Object -Property WorkingSet -Descending |
  Select-Object -First 10 Name, Id, @{Name="MemoryMB"; Expression={ [math]::Round($_.WorkingSet / 1MB, 1) }}

For reports, a column like SizeMB is far more readable than the raw Length.

10. Viewing Newest, Oldest, and Largest First

In PowerShell, sorting is done with Sort-Object.

See the most recently modified files.

Get-ChildItem . -File |
  Sort-Object -Property LastWriteTime -Descending |
  Select-Object -First 10 Name, LastWriteTime

See the oldest files.

Get-ChildItem . -File |
  Sort-Object -Property LastWriteTime |
  Select-Object -First 10 Name, LastWriteTime

See the largest files.

Get-ChildItem . -File -Recurse |
  Sort-Object -Property Length -Descending |
  Select-Object -First 10 FullName, Length

These three come up all the time.

Newest first -> LastWriteTime -Descending
Oldest first -> LastWriteTime
Largest first -> Length -Descending

They are frequent companions in log investigation, cleaning out unneeded files, and disk space investigation.

11. Use the Format Commands Only at the End, for Display

To make things easier to read on screen, use Format-Table and Format-List.

Get-Process |
  Sort-Object -Property WorkingSet -Descending |
  Select-Object -First 10 Name, Id, WorkingSet |
  Format-Table -AutoSize

For viewing the details of a single item, Format-List is handy.

Get-Process -Id $PID |
  Format-List *

But Format-Table and Format-List are “for display.”

It is safer not to use them before outputting to CSV or before processing with subsequent commands.

# Avoid
Get-Process |
  Format-Table Name, Id |
  Export-Csv .\process.csv -NoTypeInformation

For CSV output, choose the columns with Select-Object.

# Correct
Get-Process |
  Select-Object Name, Id |
  Export-Csv .\process.csv -NoTypeInformation -Encoding UTF8

When in doubt, think of it like this:

Only viewing on screen -> Format-Table / Format-List
Using it later         -> Select-Object

12. Making Log Searches a Bit More Convenient — Select-String

For string searching, use Select-String.

Select-String -Path .\logs\*.log -Pattern "ERROR"

Multiple patterns can be specified.

Select-String -Path .\logs\*.log -Pattern "ERROR", "WARN", "FATAL"

To see the surrounding lines, use -Context.

Select-String -Path .\logs\*.log -Pattern "ERROR" -Context 2

This also displays the two lines before and after each matching line, which is helpful when you want to see the context around an error.

To search for a literal string, use -SimpleMatch.

Select-String -Path .\logs\*.log -Pattern "[ERROR]" -SimpleMatch

The -Pattern of Select-String is normally treated as a regular expression. When you want to search for symbols like [ or . literally, adding -SimpleMatch keeps things clear.

For case-sensitive searching:

Select-String -Path .\logs\*.log -Pattern "Error" -CaseSensitive

Search results can also be kept in a CSV.

Select-String -Path .\logs\*.log -Pattern "ERROR" |
  Select-Object Path, LineNumber, Line |
  Export-Csv .\error-lines.csv -NoTypeInformation -Encoding UTF8

13. Replacing Strings

To replace part of a string, use -replace.

"server01.example.com" -replace "\.example\.com$", ""

When replacing the contents of a file, it is safer not to overwrite the original right away. Output to a separate file first.

(Get-Content .\appsettings.json -Raw) -replace "localhost", "db01" |
  Set-Content .\appsettings.preview.json -Encoding UTF8

Review it, and apply it to the original file only if needed.

With configuration files and bulk replacements, hitting far more matches than you intended is how things break. It is safer to check the targets with Select-String first and then replace.

Select-String -Path .\appsettings.json -Pattern "localhost" -SimpleMatch

14. Comparing Two Sets of Results — Compare-Object

Compare-Object is the command for seeing the differences between two lists.

When to use it: when you want to confirm that a configuration has not changed across a piece of work, when you are matching up settings or installed software between two PCs, or when you are comparing service configurations between production and staging. It shines whenever the goal is to confirm that something you expect to be unchanged really is unchanged.

For example, compare file name listings before and after a change.

Compare-Object `
  -ReferenceObject (Get-Content .\before.txt) `
  -DifferenceObject (Get-Content .\after.txt)

If before.txt contains a.log, b.log, and c.log, and after.txt contains b.log, c.log, and d.log, the output looks like this.

InputObject SideIndicator
----------- -------------
d.log       =>
a.log       <=

b.log and c.log, which exist on both sides, are not shown by default. Only the differences appear.

The meanings of SideIndicator are as follows.

Shown Meaning
<= Exists only on the left side, i.e., in ReferenceObject
=> Exists only on the right side, i.e., in DifferenceObject
== Exists in both. Shown when -IncludeEqual is specified

The easiest way to remember the arrow direction is to read it as which side the item leans toward, ReferenceObject (left) or DifferenceObject (right). In the example above, a.log exists only on the left (before), so it is a file that disappeared, and d.log exists only on the right (after), so it is a file that was added.

An example of saving and comparing the file name listings of folders:

Get-ChildItem .\before -File |
  Select-Object -ExpandProperty Name |
  Set-Content .\before-list.txt -Encoding UTF8

Get-ChildItem .\after -File |
  Select-Object -ExpandProperty Name |
  Set-Content .\after-list.txt -Encoding UTF8

Compare-Object `
  -ReferenceObject (Get-Content .\before-list.txt) `
  -DifferenceObject (Get-Content .\after-list.txt)

You can also look at changes in the service list.

Get-Service |
  Select-Object Name, Status |
  Export-Csv .\services-before.csv -NoTypeInformation -Encoding UTF8

After the change, export again.

Get-Service |
  Select-Object Name, Status |
  Export-Csv .\services-after.csv -NoTypeInformation -Encoding UTF8

Compare.

Compare-Object `
  -ReferenceObject (Import-Csv .\services-before.csv) `
  -DifferenceObject (Import-Csv .\services-after.csv) `
  -Property Name, Status

Keeping before/after diffs of your work makes investigation and reporting easier.

15. Checking File Hashes — Get-FileHash

When you want to confirm that a downloaded file or a distributed file is identical, use hash values.

Get-FileHash .\installer.exe -Algorithm SHA256

To check all files in a folder at once:

Get-ChildItem .\downloads -File |
  ForEach-Object { Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256 }

These can also be kept in a CSV.

Get-ChildItem .\downloads -File |
  ForEach-Object { Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256 } |
  Select-Object Path, Hash |
  Export-Csv .\hashes.csv -NoTypeInformation -Encoding UTF8

Files that look identical by name alone can differ in content. For distributed artifacts, backups, and migrations, keeping hash values makes verification much easier.

16. Sending to the Clipboard — Set-Clipboard

When to use it: when you want to paste investigation results straight into a reply to an inquiry or an incident report, or into Excel as a table. It removes the whole create-a-file, open-it, select, copy routine.

When you want to paste command results into email or chat, sending them to the clipboard is convenient.

Get-Service |
  Select-Object Name, Status |
  ConvertTo-Csv -NoTypeInformation |
  Set-Clipboard

To view the contents of the clipboard:

Get-Clipboard

To make it tab-delimited for pasting into Excel, set the delimiter of ConvertTo-Csv to a tab.

Get-Process |
  Select-Object Name, Id, CPU |
  ConvertTo-Csv -NoTypeInformation -Delimiter "`t" |
  Set-Clipboard

If you just want to share results temporarily, this is easier than creating a file each time. But if you want to keep it as an audit trail, save it to a file with Export-Csv.

17. Showing on Screen While Saving to a File — Tee-Object

When you want to view results on screen while also saving them to a file, use Tee-Object.

Get-Process |
  Sort-Object -Property WorkingSet -Descending |
  Select-Object -First 10 Name, Id, WorkingSet |
  Tee-Object -FilePath .\top-process.txt

Tee-Object sends the pipeline results in two directions.

Show on screen
Save to a file as well

It suits “watch while recording” during an investigation. If you want to open the data in Excel later, however, Export-Csv is easier to work with.

Get-Process |
  Sort-Object -Property WorkingSet -Descending |
  Select-Object -First 10 Name, Id, WorkingSet |
  Export-Csv .\top-process.csv -NoTypeInformation -Encoding UTF8

Think of it as Tee-Object for the screen and Export-Csv for keeping tabular data, and it stays clear.

18. Keeping a Work Log — Start-Transcript

When to use it: when you are working in a production environment, and in any situation where you will later have to report what you ran and in what order. It is something you fire off once before you begin, as the audit trail for incident response or maintenance work.

To record a sequence of operations, use Start-Transcript.

Start-Transcript -Path .\work-log.txt

The commands you run after this, and the screen output, are recorded.

End it.

Stop-Transcript

In investigative work, you will later want to check things like:

Which commands were run
Which folder the work happened in
What the results were
Whether errors occurred

Using Start-Transcript acts as insurance for the times you forget to take working notes. Be careful, however, when the work puts information on screen that must not be recorded, such as passwords or tokens.

19. Reading a CSV and Viewing by Condition

CSV comes up constantly in business data.

For example, suppose you have the following users.csv.

Name,Department,Enabled
Suzuki,Sales,true
Tanaka,Accounting,false
Sato,Sales,true

Read it.

Import-Csv .\users.csv

See only the people whose department is Sales.

Import-Csv .\users.csv |
  Where-Object { $_.Department -eq "Sales" }

Output only the enabled users to a separate CSV.

Import-Csv .\users.csv |
  Where-Object { $_.Enabled -eq "true" } |
  Export-Csv .\enabled-users.csv -NoTypeInformation -Encoding UTF8

CSV values are usually treated as strings, so when comparing true / false or numbers, consider type conversion as needed.

For example, to see only rows with a score of 80 or higher:

Import-Csv .\scores.csv |
  Where-Object { [int]$_.Score -ge 80 }

20. Viewing JSON Configuration Values

JSON is commonly used in configuration files and Web APIs.

$config = Get-Content .\settings.json -Raw | ConvertFrom-Json
$config

You can also look at just a specific value.

$config.Database.Host

An example of changing a value and saving to a separate file:

$config.Database.Host = "db01"
$config |
  ConvertTo-Json -Depth 10 |
  Set-Content .\settings.updated.json -Encoding UTF8

When working with JSON, Get-Content -Raw is the common choice. With -Raw, the whole file is read as a single string, which is clearer when handling multi-line JSON.

21. Viewing Environment Variables

Environment variables come up often for application settings and path checks.

View the list.

Get-ChildItem Env:

View the current user name.

$env:USERNAME

View the temp folder.

$env:TEMP

Split PATH for readability.

$env:PATH -split ";"

Set an environment variable for the current PowerShell session only.

$env:APP_MODE = "Development"

This setting is effective only inside the PowerShell session currently open.

To persist it as a user environment variable, do this:

[Environment]::SetEnvironmentVariable("APP_MODE", "Development", "User")

This is an operation that changes the environment, so on corporate PCs and production servers, confirm the scope of impact before doing it.

22. Checking Your Profile

PowerShell has a profile that is loaded at startup.

Check its location.

$PROFILE

Check whether it exists.

Test-Path $PROFILE

To create it:

New-Item -ItemType File -Path $PROFILE -Force

Open it in Notepad.

notepad $PROFILE

Putting frequently used settings and functions in your profile is convenient. However, writing too much complex processing there slows PowerShell startup and makes things hard to reproduce in other environments, so at first we recommend limiting it to frequently used paths and display settings.

23. Viewing Processes

In process investigation, the first move is usually to look at the largest memory consumers.

Get-Process |
  Sort-Object -Property WorkingSet -Descending |
  Select-Object -First 10 Name, Id, @{Name="MemoryMB"; Expression={ [math]::Round($_.WorkingSet / 1MB, 1) }}

Filter by a specific name.

Get-Process -Name notepad -ErrorAction SilentlyContinue

When stopping a process, do not run it straight away — confirm with -WhatIf.

Stop-Process -Name notepad -WhatIf

If everything looks right, execute.

Stop-Process -Name notepad

In production environments, stopping a process can affect business operations. Before stopping, confirm the target, the users, and the recovery method.

24. Viewing Services

View the service list.

Get-Service

To see only stopped services:

Get-Service |
  Where-Object { $_.Status -eq "Stopped" }

Find services that are set to automatic start but are not running.

Get-Service |
  Where-Object { $_.StartType -eq "Automatic" -and $_.Status -ne "Running" } |
  Select-Object Name, DisplayName, Status, StartType

When restarting a service, confirm the target first as well.

Get-Service -Name "Spooler"

Rehearse.

Restart-Service -Name "Spooler" -WhatIf

Service operations have a large impact, so confirm the maintenance window and recovery procedure before executing.

25. Viewing Event Logs

In Windows investigation, looking at event logs comes up frequently.

View the most recent 100 entries in the System log.

Get-WinEvent -LogName System -MaxEvents 100 |
  Select-Object TimeCreated, ProviderName, Id, LevelDisplayName, Message

Narrow to just errors and warnings.

Get-WinEvent -LogName System -MaxEvents 500 |
  Where-Object { $_.LevelDisplayName -in @("Error", "Warning") } |
  Select-Object TimeCreated, ProviderName, Id, LevelDisplayName, Message

See only the errors from the last 24 hours.

$start = (Get-Date).AddHours(-24)

Get-WinEvent -FilterHashtable @{ LogName = "System"; StartTime = $start } |
  Where-Object { $_.LevelDisplayName -eq "Error" } |
  Select-Object TimeCreated, ProviderName, Id, Message

To keep investigation results in a CSV:

Get-WinEvent -LogName System -MaxEvents 500 |
  Where-Object { $_.LevelDisplayName -in @("Error", "Warning") } |
  Select-Object TimeCreated, ProviderName, Id, LevelDisplayName, Message |
  Export-Csv .\system-events.csv -NoTypeInformation -Encoding UTF8

Event logs easily grow large, so it is important to narrow the range with -MaxEvents or a time window.

26. Checking the Network

For reachability checks, use Test-Connection.

Test-Connection example.com -Count 4

To check TCP port reachability on Windows, Test-NetConnection is convenient.

Test-NetConnection example.com -Port 443

To check DNS resolution:

Resolve-DnsName example.com

In network investigation, isolate the problem like this:

Can the name be resolved
Can the IP be reached
Is the target port open
Is there interference from a proxy or firewall

Rather than judging everything with PowerShell alone, it works well as the entry point of the isolation process.

27. Looking at Web Results

For fetching web pages, use Invoke-WebRequest.

Invoke-WebRequest https://example.com |
  Select-Object StatusCode, StatusDescription

For handling JSON API results, Invoke-RestMethod is convenient.

Invoke-RestMethod https://api.github.com/repos/PowerShell/PowerShell |
  Select-Object full_name, stargazers_count, forks_count

API results can usually be handled as PowerShell objects from the start, which makes them easy to combine with Select-Object and Where-Object.

When investigating internal APIs too, start with read-only GET requests.

When handling credentials or tokens, be careful about screen display and what gets recorded in the transcript.

28. Measuring Execution Time — Measure-Command

When to use it: when deciding whether a piece of processing is safe to put into an overnight batch, or when comparing whether a rewrite actually made things faster. It lets you decide with numbers instead of gut feel.

When you want to know how long processing takes, use Measure-Command.

Measure-Command {
  Get-ChildItem C:\Windows -File -Recurse -ErrorAction SilentlyContinue |
    Measure-Object
}

The result includes TotalSeconds and TotalMilliseconds.

$result = Measure-Command {
  Get-ChildItem . -File -Recurse | Measure-Object
}

$result.TotalSeconds

When you want to speed up a script, measuring time rather than going by feel is important. However, results vary with file counts, disk state, and network state, so do not judge from a single run — check several times.

29. Running Slow Work in the Background — Start-Job

When to use it: when you want to kick off something that takes minutes — a full scan of a large folder, or hash computation — and keep working on another investigation while it runs. Conversely, if the work finishes in seconds, turning it into a job costs more in startup overhead than it saves.

To run a time-consuming investigation as a separate job, use Start-Job.

$job = Start-Job -ScriptBlock {
  Get-ChildItem C:\Windows -File -Recurse -ErrorAction SilentlyContinue |
    Measure-Object
}

View the job status.

Get-Job

Receive the results.

Receive-Job $job

Remove jobs that are no longer needed.

Remove-Job $job

Start-Job is convenient, but there is no need to use it heavily from the start. Run things normally first, and turn only the slow processing into jobs — that keeps things clear.

30. Using Command History

When to use it: when you have built up a command by trial and error and, once it works, need to transcribe it into a runbook or a script.

To see previously executed commands, use Get-History.

Get-History

Make just the command lines easy to read.

Get-History |
  Select-Object Id, CommandLine

Re-run a specific history entry.

Invoke-History 12

History is convenient, but it can also re-run dangerous commands. When re-running change commands like Remove-Item or Stop-Process from history, check the content before executing.

31. Viewing Error Details — Get-Error

In PowerShell 7, Get-Error is available for looking at recent errors in detail.

Get-Error

To see only the most recent error message:

$Error[0].Exception.Message

When an error occurs, do not just look at the red text on screen — check the following:

Which command failed
Which path or target it failed on
Whether it is a permissions problem
Whether the file does not exist
Whether it is a network or lock problem

To keep investigation results, combine with Start-Transcript and Export-Csv.

32. Frequently Used Comparison Operators

Comparison operators come up constantly with Where-Object.

Operator Meaning Example
-eq Equal $_.Status -eq "Running"
-ne Not equal $_.Status -ne "Running"
-gt Greater than $_.Length -gt 10MB
-ge Greater than or equal $_.Length -ge 10MB
-lt Less than $_.Length -lt 10MB
-le Less than or equal $_.Length -le 10MB
-like Wildcard match $_.Name -like "*.log"
-match Regular expression match $_.Name -match "^app"
-in The value is in a list $_.Status -in @("Running", "Paused")
-contains The list contains the value @("a", "b") -contains "a"

Examples:

Get-ChildItem . -File |
  Where-Object { $_.Length -gt 10MB }
Get-Service |
  Where-Object { $_.Status -in @("Running", "Paused") }
Get-ChildItem . -File |
  Where-Object { $_.Name -match "\.log$" }

At first, -eq, -ne, -gt, -lt, and -like alone are enough. Once you are comfortable, -match and -in make conditions easier to write.

33. Small Practical Recipes

From here, these are examples in a form you can use directly in real work.

See file counts per extension

Get-ChildItem C:\Work -File -Recurse |
  Group-Object -Property Extension -NoElement |
  Sort-Object -Property Count -Descending

Use this when you want a rough grasp of a folder’s contents.

See the top 20 largest files

Get-ChildItem C:\Work -File -Recurse |
  Sort-Object -Property Length -Descending |
  Select-Object -First 20 FullName, @{Name="SizeMB"; Expression={ [math]::Round($_.Length / 1MB, 2) }}

Commonly used in disk space investigation.

See files modified in the last 24 hours

$since = (Get-Date).AddHours(-24)

Get-ChildItem C:\Work -File -Recurse |
  Where-Object { $_.LastWriteTime -ge $since } |
  Select-Object FullName, Length, LastWriteTime

Useful after an incident for seeing which files were modified.

Turn log lines containing ERROR into a CSV

Select-String -Path C:\App\Logs\*.log -Pattern "ERROR" |
  Select-Object Path, LineNumber, Line |
  Export-Csv .\error-lines.csv -NoTypeInformation -Encoding UTF8

Makes investigation results easier to share.

See 2 lines before and after each ERROR

Select-String -Path C:\App\Logs\*.log -Pattern "ERROR" -Context 2

Use this when you want the context around the cause.

See the processes using the most memory

Get-Process |
  Sort-Object -Property WorkingSet -Descending |
  Select-Object -First 10 Name, Id, @{Name="MemoryMB"; Expression={ [math]::Round($_.WorkingSet / 1MB, 1) }}

See services set to automatic start but stopped

Get-Service |
  Where-Object { $_.StartType -eq "Automatic" -and $_.Status -ne "Running" } |
  Select-Object Name, DisplayName, Status, StartType

Turn System log errors into a CSV

Get-WinEvent -LogName System -MaxEvents 500 |
  Where-Object { $_.LevelDisplayName -eq "Error" } |
  Select-Object TimeCreated, ProviderName, Id, Message |
  Export-Csv .\system-errors.csv -NoTypeInformation -Encoding UTF8

Send command results to the clipboard

Get-Service |
  Select-Object Name, DisplayName, Status |
  ConvertTo-Csv -NoTypeInformation |
  Set-Clipboard

Convenient for pasting into email or chat.

Compare file listings before and after work

Get-ChildItem C:\Work -File |
  Select-Object -ExpandProperty Name |
  Set-Content .\before.txt -Encoding UTF8

Take it again after the work.

Get-ChildItem C:\Work -File |
  Select-Object -ExpandProperty Name |
  Set-Content .\after.txt -Encoding UTF8

Compare.

Compare-Object (Get-Content .\before.txt) (Get-Content .\after.txt)

See a folder’s total size in GB

$size = Get-ChildItem C:\Work -File -Recurse |
  Measure-Object -Property Length -Sum

[math]::Round($size.Sum / 1GB, 2)

34. Cautions When Combining with Change Commands

Most of what we have covered is read-only, and with those alone you can experiment fairly freely. These, on the other hand, are change commands.

Operation Command
Copy Copy-Item
Move Move-Item
Delete Remove-Item
Rename Rename-Item
Stop a process Stop-Process
Restart a service Restart-Service
Persist an environment variable [Environment]::SetEnvironmentVariable()

Use change commands in this order:

1. Look at the targets with Get-* commands
2. Filter with Where-Object
3. Review the target list with Select-Object
4. Count them with Measure-Object
5. Keep a record with Export-Csv if needed
6. Rehearse with -WhatIf where available
7. Execute

For example, deleting old .tmp files.

First, look at the targets.

$limit = (Get-Date).AddDays(-30)

$targets = Get-ChildItem C:\Temp -Filter *.tmp -File -Recurse |
  Where-Object { $_.LastWriteTime -lt $limit }

$targets |
  Select-Object FullName, Length, LastWriteTime

Count them.

$targets | Measure-Object

Keep them in a CSV.

$targets |
  Select-Object FullName, Length, LastWriteTime |
  Export-Csv .\delete-targets.csv -NoTypeInformation -Encoding UTF8

Confirm what would be deleted.

$targets | Remove-Item -WhatIf

If everything looks right, execute.

$targets | Remove-Item

Just sticking to this flow cuts down substantially on how much can go wrong.

35. How to Remember Them

Trying to memorize commands individually is hard, so it is easier to remember them by goal.

Goal Command to recall
Count Measure-Object
Sum Measure-Object -Sum
View by category Group-Object
Remove duplicates Sort-Object -Unique
Get just the values Select-Object -ExpandProperty
Shape the columns Select-Object
View largest first Sort-Object -Descending
Search for strings Select-String
See the surrounding lines Select-String -Context
Compare two things Compare-Object
Keep an audit trail Export-Csv / Start-Transcript
Output to screen and file Tee-Object
Send to the clipboard Set-Clipboard
Measure time Measure-Command
See error details Get-Error

A One-Page Cheat Sheet

A quick reference that includes the actual syntax, for printing out and keeping at hand. This alone is enough to assemble most everyday work.

Goal Ready-to-use form
Count files Get-ChildItem . -File \| Measure-Object
See total size in GB [math]::Round((Get-ChildItem . -File -Recurse \| Measure-Object -Property Length -Sum).Sum / 1GB, 2)
Counts per extension Get-ChildItem . -File \| Group-Object -Property Extension -NoElement \| Sort-Object -Property Count -Descending
Top 20 largest files Get-ChildItem . -File -Recurse \| Sort-Object Length -Descending \| Select-Object -First 20 FullName, Length
Modified in the last 24 hours Get-ChildItem . -File -Recurse \| Where-Object { $_.LastWriteTime -gt (Get-Date).AddDays(-1) }
Extract just the values Get-Service \| Select-Object -ExpandProperty Name
Remove duplicates Get-Content .\list.txt \| Sort-Object -Unique
Find ERROR in logs Select-String -Path .\logs\*.log -Pattern "ERROR"
See 2 lines around each ERROR Select-String -Path .\logs\*.log -Pattern "ERROR" -Context 2
Compare two listings Compare-Object -ReferenceObject (Get-Content .\before.txt) -DifferenceObject (Get-Content .\after.txt)
Check a hash value Get-FileHash .\installer.exe -Algorithm SHA256
Send results to the clipboard Get-Service \| Select-Object Name, Status \| ConvertTo-Csv -NoTypeInformation \| Set-Clipboard
Output to screen and file Get-Process \| Tee-Object -FilePath .\out.txt
Start recording a work log Start-Transcript -Path .\work.log
Filter a CSV by condition Import-Csv .\data.csv \| Where-Object { $_.Status -eq "NG" }
Save to CSV ... \| Export-Csv .\out.csv -NoTypeInformation -Encoding UTF8
Processes using the most memory Get-Process \| Sort-Object WorkingSet -Descending \| Select-Object -First 10 Name, Id, WorkingSet
Stopped automatic-start services Get-Service \| Where-Object { $_.StartType -eq "Automatic" -and $_.Status -ne "Running" }
Errors in the event log Get-WinEvent -LogName System -MaxEvents 200 \| Where-Object { $_.LevelDisplayName -eq "Error" }
Check reachability Test-NetConnection -ComputerName example.local -Port 443
Measure execution time Measure-Command { ... }
Inspect the last error in detail Get-Error

The order you must not forget

Look -> Count -> Shape -> Record -> Rehearse -> Execute

Format-Table and Format-List belong at the very end of this flow, only when you are looking at the screen. Slip one in before Export-Csv or before passing data to the next command, and everything downstream breaks.

36. Conclusion

PowerShell is powerful once you can write scripts. In real work, however, what you use most is, perhaps surprisingly, combinations of small commands.

Count
Sort
Group
Compare
Search
Extract
Save
Send to the clipboard
Keep a work log

Being able to do just these makes daily work considerably easier. There is no need to aim for difficult automation from the start. First become able to see the situation accurately with read-only commands, and on top of that, use change commands like Copy-Item, Move-Item, Remove-Item, and Restart-Service only when needed.

The trick to using PowerShell safely is this order:

Look -> Count -> Shape -> Record -> Rehearse -> Execute

Internalize this pattern, and PowerShell stops being “a black screen where you type commands” and becomes a practical tool supporting your daily investigation, organization, and reporting.

Recent articles sharing the same tags. Deepen your understanding with closely related topics.

These topic pages place the article in a broader service and decision context.

This article connects naturally to the following service pages.

Frequently Asked Questions

Common questions about the topic of this article.

How do I find the number of files and their total size in PowerShell?
Pipe the output of Get-ChildItem into Measure-Object. The Count property of the result gives you the number of items, and adding -Recurse includes subfolders. For total size, aggregate with Measure-Object -Property Length -Sum; a raw byte count is hard to read, so converting it to GB with something like [math]::Round($size.Sum / 1GB, 2) makes it much easier to scan. Adding -Average or -Maximum gives you the average and the largest value in the same pass.
How is Select-Object -ExpandProperty different from the normal form?
Select-Object Name returns a table (an object) that has a Name column, whereas Select-Object -ExpandProperty Name returns the Name values themselves. When you want CSV output, the normal form that keeps the data as a column is the better fit; when you want to hand just the values to another command, to string processing, or to a text file, -ExpandProperty is easier to work with. Choose based on what you are doing with the result.
Why should Format-Table not be used before Export-Csv?
Format-Table and Format-List are display commands: they turn the output into formatting objects, so the CSV export or further processing that follows can no longer work correctly. When you are exporting to CSV or processing the data with a later command, choose the columns with Select-Object and pass those to Export-Csv. Remember it as 'Format-* when you are only looking at the screen, Select-Object when you will use the data later' and you will not get it wrong.
Is there a safe procedure for change commands such as Remove-Item?
Do not run them straight away. First look at the targets with a Get-* command, narrow them down with Where-Object, review the target list with Select-Object, count them with Measure-Object, and keep a record with Export-Csv if you need one. Then, if the command supports -WhatIf, rehearse to confirm what would be deleted or moved, and execute only once that looks right. Just following this order dramatically reduces how much can go wrong.

Author Profile

Profile page for the article author.

Go Komura

Representative of KomuraSoft LLC

Focused on Windows software development, technical consulting, and investigations into failures that are difficult to reproduce.

Back to the Blog