cv.cm

Coding · Programming languages

PowerShell tutorial for beginners

7 min read

Short answer

PowerShell is Microsoft's cross-platform shell and scripting language for automation. Its commands, called cmdlets, pass .NET objects through the pipeline instead of plain text, so you can filter, sort and export data without parsing. This page covers installing PowerShell 7, running scripts, cmdlets, variables, operators, control flow, functions, collections and error handling.

Open Regex tester

What PowerShell is and what it is used for

PowerShell is a cross-platform shell and scripting language from Microsoft, built on .NET. Unlike traditional shells, which pass plain text between commands, PowerShell passes .NET objects with properties and methods, so you can filter and sort data without parsing text.

System administrators and DevOps engineers use PowerShell to automate Windows, manage Active Directory, Microsoft 365 and Azure, and write build and deployment scripts. There are two editions: Windows PowerShell 5.1, which is built into Windows, and PowerShell 7 and later (command name pwsh), which is open source and runs on Windows, Linux and macOS. This tutorial targets PowerShell 7.

Install PowerShell and run Hello, world

On Windows, install PowerShell 7 with winget install --id Microsoft.PowerShell --source winget; on macOS and Linux, use Homebrew or the package manager described in Microsoft's install guide. Scripts are text files with the .ps1 extension.

# hello.ps1
Write-Output "Hello, world!"
pwsh ./hello.ps1
# Hello, world!

On Windows, the execution policy may block scripts. Set-ExecutionPolicy -Scope CurrentUser RemoteSigned allows your own local scripts while still requiring downloaded scripts to be signed. Visual Studio Code with the PowerShell extension is the recommended editor.

Cmdlets and the pipeline

Commands are called cmdlets and follow a Verb-Noun naming pattern, such as Get-Process, Set-Location or New-Item. Three cmdlets help you explore everything else: Get-Command, Get-Help and Get-Member.

Get-Command -Noun Process          # list commands that work with processes
Get-Help Get-ChildItem -Examples   # show usage examples

# five processes using the most memory
Get-Process |
    Sort-Object -Property WorkingSet64 -Descending |
    Select-Object -First 5 -Property Name, Id, WorkingSet64

# files larger than 1 MB in the current folder
Get-ChildItem -File | Where-Object { $_.Length -gt 1MB } |
    ForEach-Object { "{0}  {1:N0} KB" -f $_.Name, ($_.Length / 1KB) }

Get-Date | Get-Member -MemberType Property   # inspect an object

Inside a script block, $_ (or $PSItem) is the current object in the pipeline. Because objects keep their types, Sort-Object sorts numbers numerically and dates chronologically without extra work.

Variables and types

Variables start with $. They hold any .NET object, and you can add a type constraint in square brackets. Double-quoted strings expand variables and $( ) expressions; single-quoted strings are literal.

$name = "Ada"
[int]$count = 3
$price = 19.99                      # System.Double
$isAdmin = $false
$today = Get-Date

"Hello, $name"                      # Hello, Ada
"Next: $($count + 1)"               # Next: 4
'No $expansion here'                # No $expansion here
$price.GetType().Name               # Double
$today.ToString("yyyy-MM-dd")       # for example 2026-10-31
[int]"42" + 1                       # 43 (explicit conversion)
TypeExampleNotes
[int]4232-bit integer
[double]3.14floating point; [decimal] for money
[string]"text"case-insensitive comparisons by default
[bool]$true, $falseautomatic variables, not keywords
[datetime]Get-Datefull .NET DateTime object
[array], [hashtable]@(1, 2), @{ a = 1 }collections, covered below

Operators and control flow

Comparison operators are words, not symbols: -eq, -ne, -gt, -lt, -ge, -le, -like (wildcards), -match (regular expressions) and -contains. String comparisons are case-insensitive unless you use the c versions such as -ceq.

$temp = 22
if ($temp -gt 30) {
    "hot"
} elseif ($temp -ge 15) {
    "mild"                           # mild
} else {
    "cold"
}

switch ("report.csv") {
    { $_ -like "*.csv" } { "CSV file" }    # CSV file
    { $_ -like "*.txt" } { "text file" }
    default { "other" }
}

foreach ($n in 1..3) { "n = $n" }          # n = 1, n = 2, n = 3
for ($i = 0; $i -lt 2; $i++) { "i = $i" }  # i = 0, i = 1
$k = 3
while ($k -gt 0) { $k-- }
"Hello" -eq "hello"                        # True

Functions and parameters

Functions use param() blocks to declare typed, named parameters. Adding [CmdletBinding()] gives a function the same common parameters as built-in cmdlets, such as -Verbose. Any output that is not captured becomes the function's return value.

function Get-Greeting {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Name,
        [string]$Greeting = "Hello"
    )
    Write-Verbose "Building greeting for $Name"
    "$Greeting, $Name!"
}

Get-Greeting -Name "Ann"                  # Hello, Ann!
Get-Greeting -Name "Bo" -Greeting "Hi"    # Hi, Bo!
Get-Greeting "Cy" -Verbose                # also prints the verbose message

Call functions like cmdlets, with spaces between arguments: Get-Greeting -Name Ann. Writing Get-Greeting("Ann", "Hi") passes a single array argument, a frequent surprise for people coming from other languages.

Arrays, hashtables and custom objects

Arrays are created with @( ) or a comma-separated list. Hashtables, written @{ }, store key-value pairs. [pscustomobject] turns a hashtable into an object that works well with Sort-Object, Format-Table and Export-Csv.

$langs = @("PowerShell", "Python")
$langs += "Go"                       # creates a new, longer array
$langs.Count                         # 3
$langs[0]                            # PowerShell
$langs -contains "Go"                # True

$ports = @{ http = 80; https = 443 }
$ports["ssh"] = 22
$ports.https                         # 443
$ports.Keys.Count                    # 3

$user = [pscustomobject]@{ Name = "Ann"; Age = 31 }
$user.Name                           # Ann
$people = @(
    [pscustomobject]@{ Name = "Bo"; Age = 25 }
    $user
)
$people | Sort-Object Age | Format-Table

Adding to an array with += copies the whole array each time. For large loops, collect the loop output directly ($result = foreach (...) { ... }) or use a [System.Collections.Generic.List[object]].

Error handling

PowerShell has terminating errors, which stop execution, and non-terminating errors, which print a message and continue. try/catch/finally only catches terminating errors, so add -ErrorAction Stop to a cmdlet when you want to catch its failures.

try {
    $content = Get-Content -Path "missing.txt" -ErrorAction Stop
}
catch [System.Management.Automation.ItemNotFoundException] {
    Write-Warning "File not found: $($_.Exception.Message)"
}
catch {
    Write-Error "Unexpected error: $_"
}
finally {
    "cleanup done"
}

$ErrorActionPreference = "Stop"   # make all errors terminating in this script
throw "Something went wrong"      # raise your own terminating error

Inside catch, $_ is the error record. Setting $ErrorActionPreference = "Stop" at the top of a script is a common way to fail fast.

A small complete script: disk usage report

This script lists the largest files under a folder, groups them by extension and exports a CSV report. It combines parameters, the pipeline, calculated properties and error handling.

# report.ps1
param(
    [string]$Path = ".",
    [int]$Top = 5
)
$ErrorActionPreference = "Stop"

$files = Get-ChildItem -Path $Path -File -Recurse

"Largest $Top files:"
$files | Sort-Object Length -Descending | Select-Object -First $Top |
    Format-Table Name, @{ Name = "SizeKB"; Expression = { [math]::Round($_.Length / 1KB, 1) } }

$byExt = $files | Group-Object Extension | ForEach-Object {
    [pscustomobject]@{
        Extension = if ($_.Name) { $_.Name } else { "(none)" }
        Files     = $_.Count
        TotalMB   = [math]::Round(($_.Group | Measure-Object Length -Sum).Sum / 1MB, 2)
    }
} | Sort-Object TotalMB -Descending

$byExt | Format-Table
$byExt | Export-Csv -Path "usage.csv" -NoTypeInformation
"Report saved to usage.csv"
pwsh ./report.ps1 -Path ~/Documents -Top 3

Where to go next

  • PowerShell 101 and the PowerShell documentation on Microsoft Learn.
  • Get-Help about_* in the shell, for example Get-Help about_Operators; run Update-Help first.
  • The PowerShell Gallery, the official repository of modules, used with Install-Module.
  • PSScriptAnalyzer, a linter that checks scripts against recommended practices.

FAQ

What is the difference between PowerShell and Windows PowerShell?

Windows PowerShell 5.1 is the older edition built into Windows and based on .NET Framework. PowerShell 7 and later is the cross-platform, open-source edition based on modern .NET, started with the pwsh command.

Is PowerShell the same as Command Prompt?

No. Command Prompt (cmd.exe) runs batch commands and passes text. PowerShell is a full scripting language that passes objects, although it can still run most command-line programs.

Can PowerShell run on Linux and macOS?

Yes. PowerShell 7 and later runs on Windows, Linux and macOS, although some Windows-specific modules are only available on Windows.

Why can't I run my .ps1 script?

The execution policy on Windows may block it. Run Set-ExecutionPolicy -Scope CurrentUser RemoteSigned, and start scripts with a path, such as ./script.ps1.

What does $_ mean in PowerShell?

$_ (also $PSItem) is the current object in the pipeline, used inside script blocks for cmdlets such as Where-Object and ForEach-Object, and the current error inside catch.

All coding tutorials