PowerShell Tutorial: Learn Automation from Scratch (2026)
My journey with PowerShell started out of sheer necessity. I was managing fifty Windows servers, and every repetitive task — creating users, checking disk space, deploying updates — was a manual RDP session away. After one particularly painful weekend of patching servers one by one, I committed to learning PowerShell properly. It was the best career decision I have made. PowerShell transformed me from a click-ops admin into someone who could automate entire infrastructure workflows with a single script.
PowerShell is both a shell and a scripting language built on the .NET runtime. Unlike traditional Unix shells that pass text around, PowerShell passes objects. This fundamental difference means you can pipe the output of Get-Process directly into Where-Object or Export-Csv without parsing text. This tutorial starts with the basics and moves to practical automation patterns that I use daily in production.
Cmdlets, the Pipeline, and Object Orientation
Cmdlets (pronounced command-lets) are the building blocks of PowerShell. They follow a Verb-Noun naming convention — Get-Service, Set-ExecutionPolicy, New-Item — making them self-documenting. The pipeline, denoted by the pipe character, passes .NET objects from one cmdlet to the next. This is radically different from text-based shells. When you run Get-Process | Where-Object WorkingSet -gt 100MB, PowerShell filters processes with more than 100 MB working set using actual numeric comparison, not string parsing.
Understanding the pipeline is the key to PowerShell mastery. You can chain dozens of cmdlets, each transforming, filtering, or formatting the objects flowing through. The pipeline is also memory-efficient — objects stream one at a time rather than buffering the entire collection. This makes a huge difference when processing thousands of log files or registry keys.
Get-Service | Where-Object Status -eq 'Running' | Select-Object Name, DisplayName, StartType
Get-ChildItem C:\Logs -Filter *.log | Where-Object Length -gt 10MB | Remove-Item -WhatIf
Get-Process | Sort-Object CPU -Descending | Select-Object -First 5
Variables, Data Types, and Operators
PowerShell variables are prefixed with $ and can hold any .NET type. You do not need to declare types explicitly — the runtime infers them — but you can specify types for safety: [int]$count = 42. PowerShell supports arrays, hashtables, custom objects, and even strong types from .NET assemblies. Hashtables (dictionaries) are everywhere in PowerShell, used for splatting parameters, building configuration objects, and collecting key-value data.
Operators in PowerShell go beyond comparison and arithmetic. The -match and -replace operators use regular expressions. -like and -notlike use wildcard patterns. The containment operators -in, -notin, -contains, and -notcontains are intuitive and readable. The ternary operator introduced in PowerShell 7 (condition ? true : false) and the pipeline chain operators && and || make for cleaner conditional logic in scripts.
$servers = @('WEB01', 'WEB02', 'DB01')
$config = @{
AppPool = 'MyApp'
Port = 8080
UseSSL = $true
}
if ($env:COMPUTERNAME -like 'WEB*') {
Write-Host "Web server detected" -ForegroundColor Green
}
$results = Get-Service | Where-Object { $_.Status -eq 'Running' }
Functions, Scripts, and Modules
Functions in PowerShell use the function keyword and support named, positional, and dynamic parameters. Advanced functions use the [CmdletBinding()] attribute to gain access to common parameters like -Verbose, -Debug, and -WhatIf. A well-written function should support ShouldProcess for destructive operations, letting users dry-run with -WhatIf before making changes.
Scripts are .ps1 files that bundle related functions. Modules (.psm1) take it further by packaging functions into reusable units with module manifests (.psd1) that specify dependencies, versioning, and author information. I publish internal modules to a NuGet feed so my team can install them with Install-Module. Writing reusable, well-documented functions is the foundation of efficient PowerShell automation.
function Get-ServerHealth {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
[string[]]$ComputerName,
[switch]$IncludeDisk
)
process {
foreach ($comp in $ComputerName) {
$cpu = Get-CimInstance -ComputerName $comp Win32_Processor | Measure-Object -Property LoadPercentage -Average
[PSCustomObject]@{
Computer = $comp
CPUPercent = $cpu.Average
Timestamp = Get-Date
}
}
}
}
Remoting and CIM for Remote Administration
PowerShell Remoting (WinRM) lets you execute commands on remote machines as if they were local. Enter-PSSession creates an interactive session, while Invoke-Command runs scripts in parallel across multiple computers. Remoting requires WinRM to be enabled on target machines, which is the default on Windows Server but must be enabled on client OS with Enable-PSRemoting.
CIM (Common Information Model) provides an alternative to WMI with a standardized interface for querying system information. Get-CimInstance is preferred over the legacy Get-WmiObject because it works over WS-Management and supports modern authentication. For Linux servers, PowerShell 7 supports SSH-based remoting, unifying management across your entire infrastructure regardless of the operating system.
Enable-PSRemoting -Force
Invoke-Command -ComputerName WEB01, WEB02 -ScriptBlock {
Get-Service W3SVC | Restart-Service
Get-EventLog -LogName System -Newest 10
} -Credential (Get-Credential)
Get-CimInstance -ComputerName SRV001 -ClassName Win32_LogicalDisk -Filter "DriveType=3" | Select-Object DeviceID, Size, FreeSpace
Error Handling and Debugging
PowerShell has two types of errors: terminating and non-terminating. Non-terminating errors (like a failed file copy in a loop) do not stop execution by default. Use $ErrorActionPreference = 'Stop' or the -ErrorAction Stop parameter to treat all errors as terminating. Try/Catch/Finally blocks handle terminating errors, and the automatic variable $_ contains the current error in a catch block.
Debugging PowerShell scripts has improved dramatically. Set-PSBreakpoint comands let you set breakpoints on lines, variables, or commands. The built-in debugger supports stepping through code with s, v, o, and q commands. Write-Debug and Write-Verbose messages, combined with -Debug and -Verbose switches, let you instrument scripts without removing diagnostic output in production.
$ErrorActionPreference = 'Stop'
try {
$content = Get-Content -Path "C:\Config\appsettings.json" -ErrorAction Stop
$json = $content | ConvertFrom-Json
Write-Verbose "Loaded configuration for $($json.AppName)"
} catch [System.IO.FileNotFoundException] {
Write-Error "Configuration file not found: $($_.Exception.Message)"
} catch {
Write-Error "Unexpected error: $_"
} finally {
Write-Debug "Cleanup completed"
}
Desired State Configuration and Automation
PowerShell Desired State Configuration (DSC) is a configuration management platform that ensures machines remain in a declared state. You define a configuration block specifying Windows features, registry keys, files, and services that should be present. DSC applies the configuration and continuously monitors for drift, correcting deviations automatically. While DSC is powerful, the community has largely shifted to Terraform and Ansible for cross-platform IaC, but DSC remains excellent for pure Windows environments.
For task automation, the PowerShell Task Scheduler module lets you create scheduled jobs from scripts. Azure Automation extends this to the cloud with runbooks executed on schedules or triggered by events. GitHub Actions and Azure DevOps pipelines now include PowerShell tasks natively, making it easy to integrate automation into CI/CD workflows.
Configuration WebServerConfig {
Node $AllNodes.NodeName {
WindowsFeature IIS {
Ensure = 'Present'
Name = 'Web-Server'
}
File WebRoot {
DestinationPath = 'C:\inetpub\wwwroot'
Type = 'Directory'
Ensure = 'Present'
}
}
}
WebServerConfig -ConfigurationData $configData
Start-DscConfiguration -Path .\WebServerConfig -Wait -Verbose
Frequently Asked Questions
Is PowerShell only for Windows?
Not anymore. PowerShell 7 is cross-platform and runs on Windows, Linux, and macOS. It is built on .NET Core and supports SSH remoting for Linux targets. Many cmdlets are Windows-specific, but the core language and modules work everywhere.
What is the difference between PowerShell and CMD?
CMD is a legacy command processor with text-based I/O. PowerShell is a modern scripting language built on .NET that passes objects through the pipeline, supports advanced functions, and integrates with the full .NET ecosystem. PowerShell can run CMD commands but not vice versa.
How should I handle errors in my PowerShell scripts?
Set $ErrorActionPreference to 'Stop' at the top of scripts, use Try/Catch blocks for recovery, and add -ErrorAction Stop to critical cmdlets. Use Write-Verbose and Write-Debug for instrumentation and -WhatIf for dry runs.
What is the PowerShell Gallery and how do I use it?
The PowerShell Gallery (PSGallery) is a public repository of community and Microsoft modules. Use Find-Module to search, Install-Module to download, and Update-Module to upgrade. Always review module code from untrusted authors before installing.
Originally published on Ayodhyyya. Last updated June 1, 2026.