Practical PowerShell Command Recipes — Growing the Small Tools You Use Every Day
· Updated: · Go Komura · 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:
- Write commands with their full names
- Center the work on read-only commands
- 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.
Related Articles
Recent articles sharing the same tags. Deepen your understanding with closely related topics.
Parameter Design and Modularization for PowerShell Scripts — From a Script That Works to a Script You Can Hand Over
A step-by-step procedure for raising a PowerShell script to a quality you can hand to someone else. Covers the param block and [CmdletBin...
Calling COM and .NET from PowerShell — Widening What Your Scripts Can Reach
A practical guide to calling .NET classes from PowerShell, embedding C# and Win32 APIs with Add-Type, driving COM, cleaning up leftover E...
Automating Excel and CSV Work with PowerShell — Practical Recipes for Aggregation, Reconciliation, and Report Output
Practical recipes for automating CSV aggregation, reconciliation, and Excel report output with PowerShell. Covers the default encodings o...
PowerShell Command Basics — The Operations to Learn First and How to Use Them Safely
A guide for PowerShell beginners who need to get real work done: how to find cmdlets, the pipeline, file operations, CSV processing, exec...
Automating PC Provisioning With winget + PowerShell — Making the Runbook Executable
How to make new-hire PC setup reproducible. Covers installing applications with winget and export/import, declarative configuration with ...
Related Topics
These topic pages place the article in a broader service and decision context.
Windows Technical Topics
Topic hub for KomuraSoft LLC's Windows development, investigation, and legacy-asset articles.
Where This Topic Connects
This article connects naturally to the following service pages.
Windows App Development
We support Windows desktop applications that involve resident processing, device integration, operational logging, and maintainable structure.
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.